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

Class 类

动态加载

JVM 在运行 Java 程序是,不是一次性把所有用到的 class 全部加载到内存中,JVM 只有在第一次读取到某一种 class 类型时将其加载进内存,每加载一种 classJVM 就为其创建一个相关联的 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) : 根据字段名获取某个 publicfield(包括父类)
  • Field getDeclareField(name) : 根据字段名获取当前类的某个 field(不包括父类、不受 public 限制)
  • Field[] getFields() : 获取所有 publicfield(包括父类)
  • 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...): 获取某个 publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...): 获取当前类的某个 Method(不包括父类)
  • Method[] getMethods(): 获取所有 publicMethod(包括父类)
  • 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 可能不允许对 javajavax 开头的 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