Java 的反射是指在运行期可以拿到一个对象的所有信息。反射支持在运行期间对某个实例一无所知的情况下,调用其方法。
Class 类
动态加载
JVM 在运行 Java 程序是,不是一次性把所有用到的 class 全部加载到内存中,JVM 只有在第一次读取到某一种 class 类型时将其加载进内存,每加载一种 class,JVM 就为其创建一个相关联的 Class 类型的实例。
以 String 类为例,当 JVM 加载 String 类时,它首先读取 String.class 文件到内存,然后为 String 类创建一个 Class 实例并与之相关联
Class class = new Class(String);
上述 Class 实例是 JVM 内部创建的,在 JDK 源码中 Class 类的构造方法是 private,只有 JVM 能创建 Class 实例。这个实例中保存了该 class 的所有信息,包括类名、包名、实现的接口、所有方法、字段等。因此,只要获取到某个 Class 实例就可以获取到该实例对应的 class 的所有信息。通过 Class 实例获取 class 信息的方法就称之为反射 Reflection
三种获取 Class 实例的方法
(推荐使用)方法一:直接通过一个 class 的静态变量 class 获取
Class class = String.class
方法二:如果持有一个实例变量, 可以通过该实例变量提供的 getClass() 方法获取
String str = "Hello World";Class class = str.getClass();
方法三;如果知道一个 class 的完整类名,通过 Class.forName() 方法获取
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 }
<a name="5PhDy"></a>### 读写字段值通过 `Field` 实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用 `setAccessible(true)` 来访问非 `public` 字段。`Field.get(Object)` 获取指定实例的指定字段的值,`Field.set(Object, Object)` 设置指定实例的指定字段的值```javaclass Person {private String name;public Person(String name) {this.name = name;}}public static void main(String[] args) throws Exception {Object p = new Person("Xiao Ming");Class c = p.getClass();Field f = c.getDeclaredField("name");f.setAccessible(true);Object value = f.get(p);System.out.println(value); // "Xiao Ming"f.set(p, "Xiao Hong");System.out.println(value); // "Xiao Hong"}
读写类中方法
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 {
} }Class stdClass = Student.class;// 获取public方法getScore,参数为String:System.out.println(stdClass.getMethod("getScore", String.class));// 获取继承的public方法getName,无参数:System.out.println(stdClass.getMethod("getName"));// 获取private方法getGrade,参数为int: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”; } }
一个 `Method` 对象包含一个方法的所有信息:- `getName()`:返回方法名称,例如:`"getScore"`;- `getReturnType()`:返回方法返回值类型,也是一个 `Class` 实例,例如:`String.class`;- `getParameterTypes()`:返回方法的参数类型,是一个 `Class` 数组,例如:`{String.class, int.class}`;- `getModifiers()`:返回方法的修饰符,它是一个`int`,不同的 `bit` 表示不同的含义。<a name="0ff3q"></a>### 调用方法对 `Method` 实例调用 `invoke` 方法就相当于调用方法。`invoke` 的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错```javaimport java.lang.reflect.Method;public class Main {public static void main(String[] args) throws Exception {// String对象:String s = "Hello world";// 获取String substring(int)方法,参数为int:Method m = String.class.getMethod("substring", int.class);// 在s对象上调用该方法并获取结果:String r = (String) m.invoke(s, 6);// 打印调用结果:System.out.println(r);}}
如果获取到的
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 核心库的安全。
多态
使用反射调用方法时,仍然遵循多态原则:即总是调用实例类型的覆写方法(如果存在)
import java.lang.reflect.Method;public class Main {public static void main(String[] args) throws Exception {// 获取Person的hello方法:Method h = Person.class.getMethod("hello");// 对Student实例调用hello方法:h.invoke(new Student());}}class Person {public void hello() {System.out.println("Person:hello");}}class Student extends Person {public void hello() {System.out.println("Student:hello");}}
调用构造方法创建对象
通常情况下,创建对象新的实例时使用 new 操作符;但在反射中,可以调用 Class 提供的 newInstance() 方法创建对象。
Person p = Person.class.newInstance();
指定构造方法创建对象
Class.newInstance() 这种方式实际调用的是该类的 public 无参数构造方法。因此,如果构造方法带有参数或者不是 public,那么就无法使用这种方式创建对象。
有些使用我们需要通过调用指定构造方法来创建对象。因此,Java 的反射 API 提供了 Constructor 对象,它包含一个构造方法的所有信息,每个 Constructor 都对应一个构造方法。因此,可以通过获取对象的指定 Constructor 来实现指定构造方法创建对象。
public class Main {public static void main(String[] args) throws Exception {// 获取构造方法Integer(int):Constructor cons1 = Integer.class.getConstructor(int.class);// 调用构造方法:Integer n1 = (Integer) cons1.newInstance(123);System.out.println(n1);// 获取构造方法Integer(String)Constructor cons2 = Integer.class.getConstructor(String.class);Integer n2 = (Integer) cons2.newInstance("456");System.out.println(n2);}}
Spring 框架的 IoC 核心代码就是利用反射实现创建对象。使用 Spring 框架时先按照约定在配置文件中配置对象,然后程序在启动读取配置文件来创建并配置对象
instanceof 和 isAssignableFrom()
当我们判断一个实例是否是某个类型时,正常情况下,使用 instanceof 操作符:
Object n = Integer.valueOf(123);boolean isDouble = n instanceof Double; // falseboolean isInteger = n instanceof Integer; // trueboolean isNumber = n instanceof Number; // trueboolean isSerializable = n instanceof java.io.Serializable; // true
如果是两个 Class 实例,要判断一个向上转型是否成立,可以调用 isAssignableFrom():
// Integer i = ?Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer// Number n = ?Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number// Object o = ?Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object// Integer i = ?Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
动态代理 Dynamic Proxy
Java 标准库提供来一种动态代理的机制,可以在运行期动态创建某个 interface 的实例。通过 JDK 提供的 Proxy 类和 InvocationHandler 接口实现动态代理,在运行期动态创建一个 interface 实例的方法如下:
- 定义一个 InvocationHandler 实例,它负责实现接口的方法调用
- 通过 Proxy.newProxyInstance() 创建 interface 实例,它需要 3 个参数:
- 使用的 ClassLoader,通常就是接口类的 ClassLoader;
- 需要实现的接口数组,至少需要传入一个接口;
- 用来处理接口方法调用的 InvocationHandler 实例;
- 将返回的 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) {
} }InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(method);if (method.getName().equals("morning")) {System.out.println("Good morning, " + args[0]);}return null;}};Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), // 传入ClassLoadernew Class[] { Hello.class }, // 传入要实现的接口handler); // 传入处理调用方法的InvocationHandlerhello.morning("Bob");
interface Hello { void morning(String name); }
<a name="WWnwa"></a>### 动态代理和 AOP借助 `Proxy` 和 `InvocationHandler` 可以实现 AOP 代理,在方向执行前后增加额外的操作。如方法执行耗时,方法参数校验等操作。简单示例如下:1. 接口 `IPerson` 定义 `sayHello(String name)` 方法```javapublic interface IPerson {void sayHello(String name);}
- 简单实现类
Student```java import lombok.extern.slf4j.Slf4j;
@Slf4j public class Student implements IPerson { @Override public void sayHello(String name) { log.debug(“Student.sayHello: {}”, name); } }
3. `InvocationHandler` 简单实现类 `MyInvocationHandler`,记录调用方法、传入形参、方法执行耗时```javaimport cn.hutool.core.date.DateUtil;import cn.hutool.core.date.TimeInterval;import lombok.Setter;import lombok.extern.slf4j.Slf4j;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Objects;@Slf4jpublic class MyInvocationHandler implements InvocationHandler {@Setterprivate Object target;@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Objects.requireNonNull(target,"MyInvocationHandler:target can't be null");log.debug("开始执行方法:{}", method.getName());if (ArrayUtil.isNotEmpty(args)) {for (Object arg : args) {log.debug("参数类型:{},参数值:{}", arg.getClass(), arg);}} else {log.debug("未传入参数");}final TimeInterval timer = DateUtil.timer();timer.start();final Object result = method.invoke(target, args);final long interval = timer.interval();log.debug("方法执行结束,用时:{} ms", interval);return result;}}
Proxy工厂类 MyProxyFactory 提供getProxy方法为target对象创意动态代理对象 ```java import java.lang.reflect.Proxy; import java.util.Objects;
public class MyProxyFactory {
public static Object getProxy(Object target) {Objects.requireNonNull(target);MyInvocationHandler handler = new MyInvocationHandler();handler.setTarget(target);return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);}
}
5. 调用示例```javapublic class Main {public static void main(String[] args) {final Student student = new Student();final IPerson proxy = (IPerson)MyProxyFactory.getProxy(student);proxy.sayHello("Hello World");}}
- 打印输出
23:21:44.778 [main] DEBUG com.dev.aop.MyInvocationHandler - 开始执行方法:sayHello23:21:44.781 [main] DEBUG com.dev.aop.MyInvocationHandler - 参数类型:class java.lang.String,参数值:Hello World23:21:44.784 [main] DEBUG com.dev.aop.Student - Student.sayHello: Hello World23:21:44.784 [main] DEBUG com.dev.aop.MyInvocationHandler - 方法执行结束,用时:0 ms
