Java 的反射是指在运行期可以拿到一个对象的所有信息。反射支持在运行期间对某个实例一无所知的情况下,调用其方法。

Class 类

动态加载

JVM 在运行 Java 程序是,不是一次性把所有用到的 class 全部加载到内存中,JVM 只有在第一次读取到某一种 class 类型时将其加载进内存,每加载一种 class,JVM 就为其创建一个相关联的 Class 类型的实例。
以 String 类为例,当 JVM 加载 String 类时,它首先读取 String.class 文件到内存,然后为 String 类创建一个 Class 实例并与之相关联

  1. Class class = new Class(String);

上述 Class 实例是 JVM 内部创建的,在 JDK 源码中 Class 类的构造方法是 private,只有 JVM 能创建 Class 实例。这个实例中保存了该 class 的所有信息,包括类名、包名、实现的接口、所有方法、字段等。因此,只要获取到某个 Class 实例就可以获取到该实例对应的 class 的所有信息。通过 Class 实例获取 class 信息的方法就称之为反射 Reflection

三种获取 Class 实例的方法

(推荐使用)方法一:直接通过一个 class 的静态变量 class 获取

  1. Class class = String.class

方法二:如果持有一个实例变量, 可以通过该实例变量提供的 getClass() 方法获取

  1. String str = "Hello World";
  2. Class class = str.getClass();

方法三;如果知道一个 class 的完整类名,通过 Class.forName() 方法获取

  1. Class class = Class.forName("java.lang.String");

读写类中字段

Class 类提供获取字段的方法

  • Field getField(name) : 根据字段名获取某个 public 的 field(包括父类)
  • Field getDeclareField(name) : 根据字段名获取当前类的某个 field(不包括父类、不受 public 限制)
  • Field[] getFields() : 获取所有 public 的 field(包括父类)
  • Field[] getDeclareFields() : 获取当前类的所有 field (不包括父类、不受 public 限制) ```java public final class String { private final byte[] value; }

public static void main(String[] args) throws Exception { Field f = String.class.getDeclaredField(“value”); f.getName(); // “value” f.getType(); // class [B 表示byte[]类型 int m = f.getModifiers(); Modifier.isFinal(m); // true Modifier.isPublic(m); // false Modifier.isProtected(m); // false Modifier.isPrivate(m); // true Modifier.isStatic(m); // false }

  1. <a name="5PhDy"></a>
  2. ### 读写字段值
  3. 通过 `Field` 实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用 `setAccessible(true)` 来访问非 `public` 字段。`Field.get(Object)` 获取指定实例的指定字段的值,`Field.set(Object, Object)` 设置指定实例的指定字段的值
  4. ```java
  5. class Person {
  6. private String name;
  7. public Person(String name) {
  8. this.name = name;
  9. }
  10. }
  11. public static void main(String[] args) throws Exception {
  12. Object p = new Person("Xiao Ming");
  13. Class c = p.getClass();
  14. Field f = c.getDeclaredField("name");
  15. f.setAccessible(true);
  16. Object value = f.get(p);
  17. System.out.println(value); // "Xiao Ming"
  18. f.set(p, "Xiao Hong");
  19. System.out.println(value); // "Xiao Hong"
  20. }

读写类中方法

Class 类提供获取 Method 的方法

  • Method getMethod(name, Class...): 获取某个 public 的 Method(包括父类)
  • Method getDeclaredMethod(name, Class...): 获取当前类的某个 Method(不包括父类)
  • Method[] getMethods(): 获取所有 public 的 Method(包括父类)
  • Method[] getDeclaredMethods(): 获取当前类的所有 Method(不包括父类) ```java public class Main { public static void main(String[] args) throws Exception {
    1. Class stdClass = Student.class;
    2. // 获取public方法getScore,参数为String:
    3. System.out.println(stdClass.getMethod("getScore", String.class));
    4. // 获取继承的public方法getName,无参数:
    5. System.out.println(stdClass.getMethod("getName"));
    6. // 获取private方法getGrade,参数为int:
    7. System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    } }

class Student extends Person { public int getScore(String type) { return 99; } private int getGrade(int year) { return 1; } }

class Person { public String getName() { return “Person”; } }

  1. 一个 `Method` 对象包含一个方法的所有信息:
  2. - `getName()`:返回方法名称,例如:`"getScore"`;
  3. - `getReturnType()`:返回方法返回值类型,也是一个 `Class` 实例,例如:`String.class`;
  4. - `getParameterTypes()`:返回方法的参数类型,是一个 `Class` 数组,例如:`{String.class, int.class}`;
  5. - `getModifiers()`:返回方法的修饰符,它是一个`int`,不同的 `bit` 表示不同的含义。
  6. <a name="0ff3q"></a>
  7. ### 调用方法
  8. 对 `Method` 实例调用 `invoke` 方法就相当于调用方法。`invoke` 的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错
  9. ```java
  10. import java.lang.reflect.Method;
  11. public class Main {
  12. public static void main(String[] args) throws Exception {
  13. // String对象:
  14. String s = "Hello world";
  15. // 获取String substring(int)方法,参数为int:
  16. Method m = String.class.getMethod("substring", int.class);
  17. // 在s对象上调用该方法并获取结果:
  18. String r = (String) m.invoke(s, 6);
  19. // 打印调用结果:
  20. System.out.println(r);
  21. }
  22. }

如果获取到的 Method 表示一个静态方法,调用静态方法时,由于无须指定实例对象,所以 invoke 方法传入的第一个参数永远是 null 如果需要调用非 public 方法,处理手段和 Field 相似。获取 Method 实例后,通过 Method.setAccessible(true) 允许其调用,如果在 setAccessible(true) 方法前调用则会抛出 IllegalAccessException

setAccessible(true) 并不是“银弹”

setAccessible(true) 可能会失败。如果 JVM 运行期存在 SecurityManager,那么它会根据规则进行检查,有可能阻止 setAccessible(true)。例如,某个 SecurityManager 可能不允许对 java 和 javax 开头的 package 的类调用 setAccessible(true),这样可以保证 JVM 核心库的安全。

多态

使用反射调用方法时,仍然遵循多态原则:即总是调用实例类型的覆写方法(如果存在)

  1. import java.lang.reflect.Method;
  2. public class Main {
  3. public static void main(String[] args) throws Exception {
  4. // 获取Person的hello方法:
  5. Method h = Person.class.getMethod("hello");
  6. // 对Student实例调用hello方法:
  7. h.invoke(new Student());
  8. }
  9. }
  10. class Person {
  11. public void hello() {
  12. System.out.println("Person:hello");
  13. }
  14. }
  15. class Student extends Person {
  16. public void hello() {
  17. System.out.println("Student:hello");
  18. }
  19. }

调用构造方法创建对象

通常情况下,创建对象新的实例时使用 new 操作符;但在反射中,可以调用 Class 提供的 newInstance() 方法创建对象。

  1. Person p = Person.class.newInstance();

指定构造方法创建对象

Class.newInstance() 这种方式实际调用的是该类的 public 无参数构造方法。因此,如果构造方法带有参数或者不是 public,那么就无法使用这种方式创建对象。
有些使用我们需要通过调用指定构造方法来创建对象。因此,Java 的反射 API 提供了 Constructor 对象,它包含一个构造方法的所有信息,每个 Constructor 都对应一个构造方法。因此,可以通过获取对象的指定 Constructor 来实现指定构造方法创建对象。

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. // 获取构造方法Integer(int):
  4. Constructor cons1 = Integer.class.getConstructor(int.class);
  5. // 调用构造方法:
  6. Integer n1 = (Integer) cons1.newInstance(123);
  7. System.out.println(n1);
  8. // 获取构造方法Integer(String)
  9. Constructor cons2 = Integer.class.getConstructor(String.class);
  10. Integer n2 = (Integer) cons2.newInstance("456");
  11. System.out.println(n2);
  12. }
  13. }

Spring 框架的 IoC 核心代码就是利用反射实现创建对象。使用 Spring 框架时先按照约定在配置文件中配置对象,然后程序在启动读取配置文件来创建并配置对象

instanceof 和 isAssignableFrom()

当我们判断一个实例是否是某个类型时,正常情况下,使用 instanceof 操作符:

  1. Object n = Integer.valueOf(123);
  2. boolean isDouble = n instanceof Double; // false
  3. boolean isInteger = n instanceof Integer; // true
  4. boolean isNumber = n instanceof Number; // true
  5. boolean isSerializable = n instanceof java.io.Serializable; // true

如果是两个 Class 实例,要判断一个向上转型是否成立,可以调用 isAssignableFrom():

  1. // Integer i = ?
  2. Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
  3. // Number n = ?
  4. Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
  5. // Object o = ?
  6. Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
  7. // Integer i = ?
  8. Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

动态代理 Dynamic Proxy

Java 标准库提供来一种动态代理的机制,可以在运行期动态创建某个 interface 的实例。通过 JDK 提供的 Proxy 类和 InvocationHandler 接口实现动态代理,在运行期动态创建一个 interface 实例的方法如下:

  1. 定义一个 InvocationHandler 实例,它负责实现接口的方法调用
  2. 通过 Proxy.newProxyInstance() 创建 interface 实例,它需要 3 个参数:
    1. 使用的 ClassLoader,通常就是接口类的 ClassLoader;
    2. 需要实现的接口数组,至少需要传入一个接口;
    3. 用来处理接口方法调用的 InvocationHandler 实例;
  3. 将返回的 Object 强制转型为接口 ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) {
    1. InvocationHandler handler = new InvocationHandler() {
    2. @Override
    3. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    4. System.out.println(method);
    5. if (method.getName().equals("morning")) {
    6. System.out.println("Good morning, " + args[0]);
    7. }
    8. return null;
    9. }
    10. };
    11. Hello hello = (Hello) Proxy.newProxyInstance(
    12. Hello.class.getClassLoader(), // 传入ClassLoader
    13. new Class[] { Hello.class }, // 传入要实现的接口
    14. handler); // 传入处理调用方法的InvocationHandler
    15. hello.morning("Bob");
    } }

interface Hello { void morning(String name); }

  1. <a name="WWnwa"></a>
  2. ### 动态代理和 AOP
  3. 借助 `Proxy` 和 `InvocationHandler` 可以实现 AOP 代理,在方向执行前后增加额外的操作。如方法执行耗时,方法参数校验等操作。简单示例如下:
  4. 1. 接口 `IPerson` 定义 `sayHello(String name)` 方法
  5. ```java
  6. public interface IPerson {
  7. void sayHello(String name);
  8. }
  1. 简单实现类 Student ```java import lombok.extern.slf4j.Slf4j;

@Slf4j public class Student implements IPerson { @Override public void sayHello(String name) { log.debug(“Student.sayHello: {}”, name); } }

  1. 3. `InvocationHandler` 简单实现类 `MyInvocationHandler`,记录调用方法、传入形参、方法执行耗时
  2. ```java
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.date.TimeInterval;
  5. import lombok.Setter;
  6. import lombok.extern.slf4j.Slf4j;
  7. import java.lang.reflect.InvocationHandler;
  8. import java.lang.reflect.Method;
  9. import java.util.Objects;
  10. @Slf4j
  11. public class MyInvocationHandler implements InvocationHandler {
  12. @Setter
  13. private Object target;
  14. @Override
  15. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  16. Objects.requireNonNull(target,"MyInvocationHandler:target can't be null");
  17. log.debug("开始执行方法:{}", method.getName());
  18. if (ArrayUtil.isNotEmpty(args)) {
  19. for (Object arg : args) {
  20. log.debug("参数类型:{},参数值:{}", arg.getClass(), arg);
  21. }
  22. } else {
  23. log.debug("未传入参数");
  24. }
  25. final TimeInterval timer = DateUtil.timer();
  26. timer.start();
  27. final Object result = method.invoke(target, args);
  28. final long interval = timer.interval();
  29. log.debug("方法执行结束,用时:{} ms", interval);
  30. return result;
  31. }
  32. }
  1. Proxy 工厂类 MyProxyFactory 提供 getProxy 方法为 target 对象创意动态代理对象 ```java import java.lang.reflect.Proxy; import java.util.Objects;

public class MyProxyFactory {

  1. public static Object getProxy(Object target) {
  2. Objects.requireNonNull(target);
  3. MyInvocationHandler handler = new MyInvocationHandler();
  4. handler.setTarget(target);
  5. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
  6. }

}

  1. 5. 调用示例
  2. ```java
  3. public class Main {
  4. public static void main(String[] args) {
  5. final Student student = new Student();
  6. final IPerson proxy = (IPerson)MyProxyFactory.getProxy(student);
  7. proxy.sayHello("Hello World");
  8. }
  9. }
  1. 打印输出
    1. 23:21:44.778 [main] DEBUG com.dev.aop.MyInvocationHandler - 开始执行方法:sayHello
    2. 23:21:44.781 [main] DEBUG com.dev.aop.MyInvocationHandler - 参数类型:class java.lang.String,参数值:Hello World
    3. 23:21:44.784 [main] DEBUG com.dev.aop.Student - Student.sayHello: Hello World
    4. 23:21:44.784 [main] DEBUG com.dev.aop.MyInvocationHandler - 方法执行结束,用时:0 ms