Java反射简析

包括部分modifier的介绍

Posted by Haiming on December 19, 2019

参考:

https://juejin.im/post/598ea9116fb9a03c335a99a4

概述

Java反射机制,指的是在程序 运行 的时候,对于任意一个类, 可以知道这个的所有属性和方法, 对于任意一个对象,可以调用这个对象的所有属性和方法。 这种动态的获取信息和动态的调用对象的方法的功能被称为Java的反射机制。

反射机制最重要的就是“运行时”,其使得我们可以使用在程序运行时加载和使用编译期间完全未知的.class 文件。 换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后知道整个类的完整构造,并且对其做操作,例如生成对象,对某些字段赋值,或者调用其本身具有的方法。

Modifier简介

本文之中只是对Modifier 的一些简介。Modifier这个类主要是用来得到当前的对象的某些字段的属性,例如public,private 等等。其实现方式是通过按位与运算,下面是代码(以PUBLIC为例):

/**
     * Return {@code true} if the integer argument includes the
     * {@code public} modifier, {@code false} otherwise.
     *
     * @param   mod a set of modifiers
     * @return {@code true} if {@code mod} includes the
     * {@code public} modifier; {@code false} otherwise.
     */
    public static boolean isPublic(int mod) {
        return (mod & PUBLIC) != 0;
    } 


/**
     * The {@code int} value representing the {@code public}
     * modifier.
     */
    public static final int PUBLIC           = 0x00000001;

    /**
     * The {@code int} value representing the {@code private}
     * modifier.
     */
    public static final int PRIVATE          = 0x00000002;

    /**
     * The {@code int} value representing the {@code protected}
     * modifier.
     */
    public static final int PROTECTED        = 0x00000004;

    /**
     * The {@code int} value representing the {@code static}
     * modifier.
     */
    public static final int STATIC           = 0x00000008;

    /**
     * The {@code int} value representing the {@code final}
     * modifier.
     */
    public static final int FINAL            = 0x00000010;

    /**
     * The {@code int} value representing the {@code synchronized}
     * modifier.
     */
    public static final int SYNCHRONIZED     = 0x00000020;

    /**
     * The {@code int} value representing the {@code volatile}
     * modifier.
     */
    public static final int VOLATILE         = 0x00000040;

    /**
     * The {@code int} value representing the {@code transient}
     * modifier.
     */
    public static final int TRANSIENT        = 0x00000080;

    /**
     * The {@code int} value representing the {@code native}
     * modifier.
     */
    public static final int NATIVE           = 0x00000100;

    /**
     * The {@code int} value representing the {@code interface}
     * modifier.
     */
    public static final int INTERFACE        = 0x00000200;

    /**
     * The {@code int} value representing the {@code abstract}
     * modifier.
     */
    public static final int ABSTRACT         = 0x00000400;

    /**
     * The {@code int} value representing the {@code strictfp}
     * modifier.
     */
    public static final int STRICT           = 0x00000800;

通过反射获取类的信息

获取类的所有变量信息

Class和Modifier继承关系

此处里面是对于Class的继承关系,下面我会放上三段代码,给大家仔细讲一下不同。

Parent.class

class ParentClass {
    String fatherName
    int fatherAge
    int justtest

    void printFatherName(){

    }
}

Child.class

class ChildClass extends ParentClass {
    String childName;
    int childAge;
    String childBirthday;

    void printChildMsg() {
        System.out.println("Child attributes are: childAge: " + childAge + " childName " + childName);
    }
}

ReflectionStudt.class

class ReflectionStudy {
    public static void main(String[] args) {
        Class tClass = ChildClass.class;
        System.out.println("Class name is " + tClass.getName());

        Field[] fields = tClass.getFields();

        for (Field field : fields) {
            int modifiers = field.getModifiers();
            System.out.println("Modifier int is " + modifiers);
            System.out.print("Modifier toString is " + Modifier.toString(modifiers) + "    ");
            System.out.println(field.getType().getName() + " " + field.getName());
        }
        System.out.println("***************************************************");

        fields = tClass.getDeclaredFields();
        for (Field field : fields) {
            int modifiers = field.getModifiers();
            System.out.println("Modifier int is " + modifiers);
            System.out.print("Modifier toString is " + Modifier.toString(modifiers));
            System.out.println(field.getType().getName() + " " + field.getName());
        }
    }

}

本处的代码是使用 childclass 继承 parentclass。在 ReflectionStudy 之中使用了两个方法,一个是class.getFields(), 一个是class.getDeclearedFields()。 二者之间的区别是:

class.getFields() 可以获得所有public权限的变量,包括自己声明的和从别处继承的。

class.getDeclearedFields() 可以获得所有本类所有权限的变量。

结果:

调用 getFields() 方法,输出 SonClass 类以及其所继承的父类( 包括 FatherClassObject ) 的 public 方法。注:Object 类中没有成员变量,所以没有输出。

类的名称:obj.SonClass
public java.lang.String mSonBirthday
public java.lang.String mFatherName
public int mFatherAge
复制代码

调用 getDeclaredFields() , 输出 SonClass 类的所有成员变量,不问访问权限。

类的名称:obj.SonClass
private java.lang.String mSonName
protected int mSonAge
public java.lang.String mSonBirthday

注意:在此处,groovy和java 不同。因为 groovy 是将源码生成java代码,因此会在这个过程之中“夹带私货”。导致实际使用groovy得到的结果是:

Class name is ReflectionStudy.ChildClass
Modifier int is 4233
Modifier toString is public static transient    boolean __$stMC
Modifier int is 4233
Modifier toString is public static transient    boolean __$stMC
***************************************************
Modifier int is 2
Modifier toString is privatejava.lang.String childName
Modifier int is 2
Modifier toString is privateint childAge
Modifier int is 2
Modifier toString is privatejava.lang.String childBirthday
Modifier int is 4106
Modifier toString is private staticorg.codehaus.groovy.reflection.ClassInfo $staticClassInfo
Modifier int is 4233
Modifier toString is public static transientboolean __$stMC
Modifier int is 4106
Modifier toString is private staticorg.codehaus.groovy.reflection.ClassInfo $staticClassInfo$
Modifier int is 4106
Modifier toString is private staticjava.lang.ref.SoftReference $callSiteArray

Process finished with exit code 0

这种groovy和java代码不同的地方,我会在之后单独写文章梳理说明(感觉又是一个大坑)

获取类的所有方法信息

这里面讲一下java 的 literalliteral 是在jvm之中的实现,在源码之中不可以通过平常的 Ctrl+点击 来获得。比如:

Class mclass = ChildClass.class

这一句里面的 .class 就是一个 literal 层面的东西,没法通过 jdk 源码获得,要深入到 jvm 层面才能看到其实现。

获取类的所有方法的信息,其实和获取类的所有变量信息是一模一样的。只是类相对而言多了参数,还有其可以抛出的 Exception,所以多加了两步。

ReflectionStudyMethod.java

package ReflectionStudy;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;

class ReflectionStudyMethod {
    public static void main(String[] args) {
        Class mclass = ChildClass.class;
        System.out.println("Class name is " + mclass.getName());
        /*
        1. get all methodss which permission is "public" from its own and parent class
         */
        Method[] methods = mclass.getMethods();
        /*
        2. get all methods with all permissions, but only the class own
         */
//        Method[] methods = mclass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.print(Modifier.toString(method.getModifiers()) + " " + method.getReturnType() + " " + method.getName() + "(");
            Parameter[] parameters = method.getParameters();
            for (Parameter parameter : parameters) {
                System.out.print(parameter.getType().getName() + " " + parameter.getName() + ",");
            }
            Class[] exceptions = method.getExceptionTypes();
            if (exceptions.length == 0) {
                System.out.println(")");
            } else {
                for (Class c : exceptions) {
                    System.out.println(") throws " + c.getName());
                }
            }
        }


    }
}

结果是:

Class name is ReflectionStudy.ChildClass
public final native class java.lang.Class getClass()
public void printChildMsg()
public final void wait() throws java.lang.InterruptedException
public final void wait(long arg0,) throws java.lang.InterruptedException
public final native void wait(long arg0,int arg1,) throws java.lang.InterruptedException
public int hashCode()
public boolean equals(java.lang.Object arg0,)
public final native void notifyAll()
public class java.lang.String toString()
public void printFatherName()
public final native void notify()

Process finished with exit code 0

访问或操作类的私有变量和方法

我们都知道,类的私有变量和方法是不能被其他的类直接调用的,但是我们其实可以通过反射的方式得到其私有变量和方法并且使用。

下面的代码之中,代码的写法不建议在实际生产环境之中使用。

Private.java

package ReflectionStudy;

public class PrivateClass {
    private String MSG = "Original";

    private void output(String para1, int para2) {
        System.out.println(para1 + " " + para2);
    }

    public String getMSG() {
        return MSG;
    }
}
 

UsePrivateThingOfOthers.java

package ReflectionStudy;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class UsePrivateThingOfOthers {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        /*
        Get class object
         */
        PrivateClass privateClass = new PrivateClass();
        Class pClass = privateClass.getClass();
        System.out.println("Class name is " + pClass.getName());
        /*
        Get private method of privateClass, and then use pass types of parameters into the `getDeclearedMethod`
         */
        Method method = pClass.getDeclaredMethod("output", String.class, int.class);

        if (method != null) {
            /*
            Here has to set the Accessible to true, or will throws Exception.
             */
            method.setAccessible(true);
          /*
          Notice here for the invoke method, the first parameter is the class of the private method, and then is 2 parameters of the method.
          */
            method.invoke(privateClass, "output reflect ", 666);
        }

        Field privateField = pClass.getDeclaredField("MSG");
        System.out.println("Before Modify: MSG = " + privateClass.getMSG());
        /*
        Same reason as above.
         */
        privateField.setAccessible(true);
      /*
      Here is same as above.
      */
        privateField.set(privateClass, "MSGModified");
        System.out.println("After modify: MSG = " + privateClass.getMSG());

    }
}

比较重要的部分都已经写在了注释之中,上面提到了,如果不使用 setAccessible(true) ,那么所报的错误是:

Exception in thread "main" java.lang.IllegalAccessException: Class ReflectionStudy.UsePrivateThingOfOthers can not access a member of class ReflectionStudy.PrivateClass with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)

原因是因为其中的类的类型是private

输出结果是:

Class name is ReflectionStudy.PrivateClass
output reflect  666
Before Modify: MSG = Original
After modify: MSG = MSGModified

修改私有常量

先上代码:

FinalFieldClass.java

package ReflectionStudy;

public class FinalFieldClass {
    private final String FINAL = "FINAL FIELD";

    public String getFINAL() {
        return FINAL;
    }


}

modifyFinalField.java

package ReflectionStudy;

import java.lang.reflect.Field;

public class modifyFinalField {
    public static void main(String[] args) throws Exception {
        FinalFieldClass finalFieldClass = new FinalFieldClass();
        Class fClass = finalFieldClass.getClass();

        System.out.println("Class name is " + fClass.getName());

        Field field = fClass.getDeclaredField("FINAL");

        field.setAccessible(true);

        System.out.println("Before modification is " + field.get(finalFieldClass));

        field.set(finalFieldClass, "MODIFIED");
        /*
        Here and before, for the `get`,`set` and `invoke` methods, parameter inside the branket is
         */
        System.out.println("After modification is " + field.get(finalFieldClass));

        System.out.println("Actual value is " + finalFieldClass.getFINAL());
    }
    
}

输出结果为:

Class name is ReflectionStudy.FinalFieldClass
Before modification is FINAL FIELD
After modification is MODIFIED
Actual value is FINAL FIELD

可见modification之中虽然其值变了,但是实际上面在最后使用的过程之中其又回到了原样,其最后还是初始值。

原因为何?

程序运行时是根据编译之后的 .class 来执行的。

其原因就是在JVM编译的过程之中,实际上已经对于常量进行了替换。下面是编译之后的结果:

FinalFieldClass.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package ReflectionStudy;

public class FinalFieldClass {
    private final String FINAL = "FINAL FIELD";

    public FinalFieldClass() {
    }

    public String getFINAL() {
        return "FINAL FIELD";
    }
}

但是实际上面并不是对于所有类型的变量都优化,比如 int,long,boolean 这些基本类型会进行优化,但是对于包装类型,比如 Integer,Long 等等不会优化。

总结而言,就是对基本类型的静态常量,JVM会在编译阶段就将用这个常量的代码替换成相应的值。

如何绕过私有常量的修改限制?

首先要说的是,不管直接对于常量进行赋值,构造函数赋值或者通过三目运算符进行赋值(下面会讲)的方法,实际上我们都可以通过反射来成功修改常量的值,也就是在程序运行的阶段,我们肯定可以通过反射来修改常量值。但是我们所想要达到的结果是,在执行实际优化之后的 .class 文件的时候,修改之后的值真的起作用了吗?

因为如果程序在编译阶段就已经将常量的值替换成了具体的值,那么在程序运行的过程之中,我们无论怎么替换常量的值,都没法再去”真正“的修改这个常量的值,因为这种情况之下怎么修改都不会影响到最后的结果了。

那么按照这个思路,我们想要”绕过“私有常量”的修改限制,那么我们就要尽力避免在编译的过程之中就已经将常量替换为具体的量。有两种方法:

方法1:

其实 Java 允许在声明常量的时候不赋值,这种情况下,就必须要在constructor 之中对于常量进行赋值,下面就是具体代码:

FinalFieldClassWithConstructor.java

package ReflectionStudy;

public class FinalFieldClassWithConstructor {
    private final String FINAL;

    public FinalFieldClassWithConstructor(){
        this.FINAL="FINAL VALUE";
    }

    public String getFINAL() {
        return FINAL;
    }
}

modifyFinalField.java

package ReflectionStudy;

import java.lang.reflect.Field;

public class modifyFinalField {
    public static void main(String[] args) throws Exception {
        FinalFieldClass finalFieldClass = new FinalFieldClass();
        Class fClass = finalFieldClass.getClass();

        System.out.println("Class name is " + fClass.getName());

        Field field = fClass.getDeclaredField("FINAL");

        field.setAccessible(true);

        System.out.println("Before modification is " + field.get(finalFieldClass));

        field.set(finalFieldClass, "MODIFIED");
        /*
        Here and before, for the `get`,`set` and `invoke` methods, parameter inside the branket is
         */
        System.out.println("After modification is " + field.get(finalFieldClass));

        System.out.println("Actual value is " + finalFieldClass.getFINAL());

        System.out.println("********************Class With Constructor*********************");

        FinalFieldClassWithConstructor finalFieldClassWithConstructor = new FinalFieldClassWithConstructor();

        Class fClassWithCons = finalFieldClassWithConstructor.getClass();

        System.out.println("Class name is " + fClassWithCons.getName());

        field = fClassWithCons.getDeclaredField("FINAL");

        field.setAccessible(true);

        System.out.println("Before modification is " + field.get(finalFieldClassWithConstructor));

        field.set(finalFieldClassWithConstructor, "MODIFIED");
        /*
        Here and before, for the `get`,`set` and `invoke` methods, parameter inside the branket is
         */
        System.out.println("After modification is " + field.get(finalFieldClassWithConstructor));

        System.out.println("Actual value is " + finalFieldClassWithConstructor.getFINAL());
    }
}

输出为:

Class name is ReflectionStudy.FinalFieldClass
Before modification is FINAL FIELD
After modification is MODIFIED
Actual value is FINAL FIELD
********************Class With Constructor*********************
Class name is ReflectionStudy.FinalFieldClassWithConstructor
Before modification is FINAL VALUE
After modification is MODIFIED
Actual value is MODIFIED

Process finished with exit code 0

原因为何?直接上编译之后的源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package ReflectionStudy;

public class FinalFieldClassWithConstructor {
    private final String FINAL = "FINAL VALUE";

    public FinalFieldClassWithConstructor() {
    }

    public String getFINAL() {
        return this.FINAL;
    }
}

可见,编译过程将 Constructor 之中的变量赋值部分放到了一开始的变量声明部分,但是并没有将 getFINAL() 之中的值替换掉,这也就是我们可以将其修改的原因:在运行阶段修改常量的值就有了意义。

方法2:

实际上原理和上面一样,只是这次使用了三目运算符。

去掉构造函数,将声明常量的语句改为使用三目表达式赋值:

private final String FINAL_VALUE
        = null == null ? "FINAL" : null;

其实,上述代码等价于直接为 FINAL_VALUE 赋值 “FINAL”,但是他就是可以!至于为什么,你这么想:null == null ? "FINAL" : null 是在运行时刻计算的,在编译时刻不会计算,也就不会被优化,所以修改可以生效。

总而言之,都是避免在编译时刻被优化,这样通过反射来修改变量才有意义。