u07-反射机制

174 阅读6分钟

1. 入门概念

概念: 之前我们都是先设计一个类,然后通过这个类的实例来获取属性和方法,而其实我们还可以先得到这个类的Class对象,然后再通过Class对象获取属性和方法,这样的好处是可以获取所有属性和方法,即使是private修饰的。

反射是java动态性的一种体现。

1.1 获取Class对象

概念: 获取Class对象有三种方式:

  • instance.getClass():要求必须得先有实例。
  • 类名.class:很便捷,但是属于硬编码,对于JDK中自带的类型,推荐使用这种。
  • Class.forName(qualifiedName):不是硬编码,对于自定义的类型,推荐使用这种。

源码: /javase-oop/

  • src: c.y.reflect.GetClassTest
/**
 * @author yap
 */
public class GetClassTest {
    @Test
    public void getClassMethod() throws ClassNotFoundException {
        GetClassTest instance = new GetClassTest();
        Class<?> classA = instance.getClass();

        Class<?> classB = GetClassTest.class;

        String qualifiedName = "com.yap.reflect.GetClassTest";
        Class<?> classC = Class.forName(qualifiedName);

        System.out.println(classA.hashCode());
        System.out.println(classB.hashCode());
        System.out.println(classC.hashCode());
    }
}

1.2 数组的Class对象

概念: 判断数组的Class对象是否相同的时候,只比较它们的类型和维度。

源码: /javase-oop/

  • src: c.y.reflect.ArrayClassTest
/**
 * @author yap
 */
public class ArrayClassTest {
    @Test
    public void arrayTypeTest() {
        Class<?> classA = int[].class;
        Class<?> classB = int[][][].class;
        Class<?> classC = double[].class;
        System.out.println(classA.hashCode());
        System.out.println(classB.hashCode());
        System.out.println(classC.hashCode());
    }
}

2. 反射构造方法

2.1 获取构造方法

概念: 通过Class获取构造方法的相关API方法:

  • Constructor<?>[] getConstructors():获取类的所有public修饰的构造方法。
  • Constructor<?>[] getDeclaredConstructors():获取类的所有构造方法。
  • Constructor<T> getConstructor():获取指定的一个public构造方法。
  • Constructor<T> getDeclaredConstructor():获取指定的一个构造方法。

源码: /javase-oop/

  • src: c.y.reflect.ReflectConstructorTest.reflectConstructor()
/**
 * @author yap
 */
public class ReflectConstructorTest {

    static class Demo {

        public Demo(int num, String str) {
            System.out.println("public constructor...");
        }

        private Demo() {
            System.out.println("private constructor...");
        }

        public void sayHello() {
            System.out.println("hello!");
        }
    }

    private Class<?> klass = Demo.class;

    @Test
    public void reflectConstructor() throws NoSuchMethodException {
        System.out.println(Arrays.toString(klass.getConstructors()));
        System.out.println(Arrays.toString(klass.getDeclaredConstructors()));
        System.out.println(klass.getConstructor(int.class, String.class));
        System.out.println(klass.getDeclaredConstructor());
    }
}

2.2 使用构造方法

概念: Constructor类的常用API:

  • newInstance():等同于使用new来调用构造器的效果。
  • setAccessible(true);:开启私有访问权限。

源码: /javase-oop/

  • src: c.y.reflect.ReflectConstructorTest.usePublicConstructor()
/**
 * @author yap
 */
public class ReflectConstructorTest {

    static class Demo {

        public Demo(int num, String str) {
            System.out.println("public constructor...");
        }

        private Demo() {
            System.out.println("private constructor...");
        }

        public void sayHello() {
            System.out.println("hello!");
        }
    }

    private Class<?> klass = Demo.class;

    @Test
    public void usePublicConstructor() throws Exception {
        Constructor<?> constructor = klass.getConstructor(int.class, String.class);
        // Demo demo = new Demo(10, "hello");
        Demo demo = (Demo) constructor.newInstance(10, "hello");
        demo.sayHello();
    }
}
  • src: c.y.reflect.ReflectConstructorTest.usePrivateConstructor()
/**
 * @author yap
 */
public class ReflectConstructorTest {

    static class Demo {

        public Demo(int num, String str) {
            System.out.println("public constructor...");
        }

        private Demo() {
            System.out.println("private constructor...");
        }

        public void sayHello() {
            System.out.println("hello!");
        }
    }

    private Class<?> klass = Demo.class;

    @Test
    public void usePrivateConstructor() throws Exception {
        Constructor<?> constructor = klass.getDeclaredConstructor();
        constructor.setAccessible(true);
        Demo demo = (Demo) constructor.newInstance();
        demo.sayHello();
    }
}

3. 反射成员属性

3.1 获取成员属性

概念: 通过Class获取成员属性的相关API方法:

  • Field[] getFields():获取类的所有public修饰的属性。
  • Field[] getDeclaredFields():获取类的所有属性。
  • Field getField("属性名"):获取指定的一个public属性。
  • Field getDeclaredField():获取指定的一个属性。

源码: /javase-oop/

  • src: c.y.reflect.ReflectFieldTest.reflectField()
/**
 * @author yap
 */
public class ReflectFieldTest {
    private Class<?> klass = Demo.class;

    static class Demo {
        public String name;
        private int gender = 0;
    }

    @Test
    public void reflectField() throws NoSuchFieldException {
        System.out.println(Arrays.toString(klass.getFields()));
        System.out.println(Arrays.toString(klass.getDeclaredFields()));
        System.out.println(klass.getField("name"));
        System.out.println(klass.getDeclaredField("gender"));
    }
}

3.2 使用成员属性

概念: Field类的常用API:

  • set(实例, 值):往哪个实例的对应属性中设置值。
  • get(实例):从哪个实例中取出对应属性的值。
  • 对应的还有 setInt()/getInt(),setDouble()/getDouble() 等方法。

源码: /javase-oop/

  • src: c.y.reflect.ReflectFieldTest.usePublicField()
/**
 * @author yap
 */
public class ReflectFieldTest {
    private Class<?> klass = Demo.class;

    static class Demo {
        public String name;
        private int gender = 0;
    }

    @Test
    public void usePublicField() throws Exception {
        // FieldTest instanceA = new FieldTest();
        // FieldTest instanceB = new FieldTest();
        Object instanceA = klass.getDeclaredConstructor().newInstance();
        Object instanceB = klass.getDeclaredConstructor().newInstance();

        Field nameField = klass.getField("name");

        // instanceA.name = "赵四"
        // instanceB.name = "刘能"
        nameField.set(instanceA, "赵四");
        nameField.set(instanceB, "刘能");

        // instanceA.name
        // instanceB.name
        System.out.println(nameField.get(instanceA));
        System.out.println(nameField.get(instanceB));
    }
}
  • src: c.y.reflect.ReflectFieldTest.usePrivateField()
/**
 * @author yap
 */
public class ReflectFieldTest {
    private Class<?> klass = Demo.class;

    static class Demo {
        public String name;
        private int gender = 0;
    }

    @Test
    public void usePrivateField() throws Exception {
        Object instance = klass.getDeclaredConstructor().newInstance();
        Field ageField = klass.getDeclaredField("gender");
        ageField.setAccessible(true);
        ageField.setInt(instance, 0);
        System.out.println(ageField.get(instance));
    }
}

4. 反射成员方法

4.1 获取成员方法

概念: 通过Class获取成员方法的相关API方法:

  • Method[] getMethods():获取类的所有public修饰的方法,包括继承下来的public方法。
  • Method[] getDeclaredMethods():获取类的所有方法,不包括继承下来的方法。
  • Method getMethod():获取指定的一个public方法。
  • Method getDeclaredMethod():获取指定的一个方法。

源码: /javase-oop/

  • src: c.y.reflect.ReflectMethodTest.reflectMethods()
/**
 * @author yap
 */
public class ReflectMethodTest {
    private Class<?> klass = Demo.class;

    static class Demo {
        public static void methodA(String str, int num) {
            System.out.println("I am methodA..." + str + num);
        }

        private static void methodB() {
            System.out.println("I am methodB...");
        }
    }

    @Test
    public void reflectMethods() throws NoSuchMethodException {
        System.out.println(Arrays.toString(klass.getMethods()));
        System.out.println(Arrays.toString(klass.getDeclaredMethods()));
        System.out.println(klass.getMethod("methodA", String.class, int.class));
        System.out.println(klass.getDeclaredMethod("methodB"));
    }
}

4.2 使用成员方法

概念: Method回调的API为 方法对象.invoke(实例, 参数)

  • p1:调用哪个实例中的这个方法,并传入对应参数,如果是静态方法,传入null即可。
  • p2:调用方法时传入的参数列表对应的Class对象。

源码: /javase-oop/

  • src: c.y.reflect.ReflectMethodTest.usePublicMethod()
/**
 * @author yap
 */
public class ReflectMethodTest {
    private Class<?> klass = Demo.class;

    static class Demo {
        public static void methodA(String str, int num) {
            System.out.println("I am methodA..." + str + num);
        }

        private static void methodB() {
            System.out.println("I am methodB...");
        }
    }

    @Test
    public void usePublicMethod() throws Exception {
        Method methodA = klass.getMethod("methodA", String.class, int.class);
        methodA.invoke(klass.getDeclaredConstructor().newInstance(), "赵四", 58);
    }
}
  • src: c.y.reflect.ReflectMethodTest.usePrivateMethod()
/**
 * @author yap
 */
public class ReflectMethodTest {
    private Class<?> klass = Demo.class;

    static class Demo {
        public static void methodA(String str, int num) {
            System.out.println("I am methodA..." + str + num);
        }

        private static void methodB() {
            System.out.println("I am methodB...");
        }
    }
    @Test
    public void usePrivateMethod() throws Exception {
        Method methodB = klass.getDeclaredMethod("methodB");
        methodB.setAccessible(true);
        methodB.invoke(klass.getDeclaredConstructor().newInstance());
    }
}

5. 反射注解

概念: 注解信息可以从类,属性或方法上获取,常用API方法如下:

  • getAnnotations():获取类,属性或方法上的所有的注解,包括继承来的。
  • getDeclaredAnnotations():获取类,属性或方法上的注解,不包括继承来的。
  • getAnnotation(注解名.class):获取类,属性或方法上的某个注解,包括继承来的。
  • getDeclaredAnnotation(注解名.class):获取类,属性或方法上的某个注解,不包括继承来的。

源码: /javase-oop/

  • src: c.y.reflect.ReflectAnnotationTest
/**
 * @author yap
 */
public class ReflectAnnotationTest {

    @Documented
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        String name();

        int age() default 18;

        String[] course() default {"语文", "数学"};
    }

    @MyAnnotation(name = "赵四")
    static class Student {

        @MyAnnotation(name = "刘能")
        public void method() {
        }
    }

    @Test
    public void reflectAnnotation() {
        Class<?> klass = Student.class;
        MyAnnotation annotation = klass.getDeclaredAnnotation(MyAnnotation.class);
        System.out.println(annotation.age());
        System.out.println(annotation.name());
        System.out.println(Arrays.toString(annotation.course()));
    }
}

获取方法或属性的注解,需要先获取到方法和属性对象,然后再调用获取注解的方法。

6. 动态操作

概念: 之前我们编译和运行java代码,都是通过IDE操作来完成的,如果我有一个需求,是从客户端传递到服务器中一个java文件,或者一段java代码,你接受到之后,利用代码来编译运行它,这个过程就是动态操作(JDK1.6引入):

  • 动态编译需要使用 javax.tools.ToolProvider
    • static JavaCompiler getSystemJavaCompiler():获取系统的javac。
    • int run(InputStream in, OutputStream out, OutputStream err, String... arguments):执行javac
      • p1:inputStream输入流,传递给javac的数据,如果为null,表示使用 System.in 标准输入。
      • p2:outputStream输出流,javac返回的数据,如果为null,表示使用 System.out 标准输出。
      • p3:outputStream输出流,javac返回的错误信息,如果为null,表示使用 System.err 错误输出。
      • p4:String类型的不定长数组,可以传递一个或者多个java文件
      • return:0代表编译成功,其他数代表编译失败
  • 动态运行需要使用 java.net.URL:负责指向一个文件所在的目录,格式以 file: 开头
  • 动态运行需要使用 java.net.URLClassLoader:把URL指向的class加载到内存中
    • URLClassLoader(URL[] urls):构造的时候需要指定一个URL数组,规定URLClassLoader的工作范围。
    • Class<?> loadClass(String name):通过类名来指定加载哪个class到内存中。
    • void close():所有的类加载器在使用完毕之后都需要关闭。

源码: /javase-oop/

  • src: c.y.reflect.DynamicOperaTest
/**
 * 先在D盘创建一个HelloWorld.java文件
 *
 * @author yap
 */
public class DynamicOperaTest {

    @Test
    public void dynamicCompile() {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        String filePath = "D:" + File.separator + "HelloWorld.java";
        int result = compiler.run(null, null, null, filePath);
        System.out.println(result == 0 ? "compile success" : "compile fail");
    }

    @Test
    public void dynamicRun() throws Exception {
        URL url = new URL("file:" + File.separator + "D:" + File.separator);
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
        Class<?> klass = urlClassLoader.loadClass("HelloWorld");
        Method method = klass.getMethod("main", String[].class);
        /*
            Use `Object` instead of `String[]`
            Because `new String[]{"a", "b"}` will be split into two parameters: "a" and "b"
            The two parameters are wrong for main()
        */
        method.invoke(null, (Object) new String[]{"a", "b"});
        urlClassLoader.close();
    }
}

如果客户端传递过来的不是一个java文件,而是一段java代码,则可以先用IO流技能将这段java代码输出到一个临时的java文件中,然后在动态编译和运行,得到结果后,别忘了销毁那个临时文件。