Java 反射与动态代理 详细笔记
一、反射的核心思想
反射:在程序运行时,动态获取某个类的完整信息(类名、方法、字段、父类、接口等),并能动态创建对象、调用方法、访问字段。
- 不需要在编译时知道具体类名,可以在运行时根据字符串类名加载类。
- 常用于框架(Spring、MyBatis)、通用工具(IDE 自动补全)、动态代理等。
二、Class 类:一切反射的入口
2.1 每个类都有唯一的 Class 对象
- JVM 在第一次加载一个类时,会在内存中为该类创建一个
Class 实例(俗称“说明书”)。 - 这个
Class 实例保存了该类的全部信息(类名、包、方法、字段、父类、接口等)。 - 无论通过哪种方式获取,同一个类的
Class 实例是同一个对象(可用 == 比较)。
2.2 获取 Class 实例的三种方法
// 方法1:通过类名.class(最直接,编译时已知类)
Class<String> cls1 = String.class;
// 方法2:通过实例的 getClass() 方法
String s = "hello";
Class<?> cls2 = s.getClass(); // 实际返回 String.class
// 方法3:通过 Class.forName(完整类名)(运行时根据字符串加载)
Class<?> cls3 = Class.forName("java.lang.String");
三者获得的是同一个 Class 对象:cls1 == cls2 == cls3
三、通过反射访问字段(Field)
3.1 获取字段的方法(返回值是 java.lang.reflect.Field)
| 方法 | 说明 | 能否得到父类字段 |
|---|
getField(String name) | 获取 public 字段(包括父类) | ✅ 能 |
getDeclaredField(String name) | 获取当前类声明的任意字段(不限访问修饰符) | ❌ 不能,只看当前类 |
getFields() | 获取所有 public 字段(包括父类) | ✅ 能 |
getDeclaredFields() | 获取当前类声明的所有字段(不包括父类) | ❌ 不能 |
3.2 访问私有字段
- 通过
getDeclaredField 拿到私有 Field 对象后,调用 setAccessible(true) 打破封装。 - 然后可以
get(obj) / set(obj, value) 读写该私有字段。
class Person {
private String name;
}
Class<Person> cls = Person.class;
Field field = cls.getDeclaredField("name");
field.setAccessible(true); // 允许访问私有字段
Person p = new Person();
field.set(p, "Alice");
String name = (String) field.get(p); // "Alice"
四、通过反射调用方法(Method)
4.1 获取方法的方法(返回值是 java.lang.reflect.Method)
| 方法 | 说明 | 包括父类 |
|---|
getMethod(String name, Class<?>... parameterTypes) | 获取某个 public 方法 | ✅ 包括 |
getDeclaredMethod(...) | 获取当前类声明的某个方法(不限修饰符) | ❌ 不包括 |
getMethods() | 获取所有 public 方法 | ✅ 包括 |
getDeclaredMethods() | 获取当前类声明的所有方法 | ❌ 不包括 |
4.2 调用方法
- 拿到
Method 对象后,调用 invoke(Object obj, Object... args)。 - 如果方法是
static,第一个参数传 null。
// 示例:调用 String 的 length() 方法
String str = "hello";
Method m = String.class.getMethod("length");
int len = (int) m.invoke(str); // len = 5
五、通过反射调用构造器(Constructor)
5.1 获取构造器
| 方法 | 说明 |
|---|
getConstructor(Class<?>... parameterTypes) | 获取某个 public 构造器 |
getDeclaredConstructor(...) | 获取任意修饰符的构造器(包括 private) |
getConstructors() / getDeclaredConstructors() | 获取所有构造器 |
5.2 创建实例
- 通过
Constructor.newInstance(Object... initargs) 创建对象。 - 比
Class.newInstance() 更灵活(可以传参数,可以调用非 public 构造器)。
// 调用带参数的构造器
Constructor<String> cons = String.class.getConstructor(String.class);
String s = cons.newInstance("abc"); // 相当于 new String("abc")
// 调用私有构造器(单例模式等)
Constructor<MyClass> cons2 = MyClass.class.getDeclaredConstructor();
cons2.setAccessible(true);
MyClass obj = cons2.newInstance();
注意:Class.newInstance() 已过时(只能调用无参 public 构造器),推荐使用 Constructor.newInstance()。
六、获取继承关系
| 方法 | 作用 | 示例 |
|---|
Class.getSuperclass() | 返回父类的 Class 对象 | Integer.class.getSuperclass() → Number.class |
Class.getInterfaces() | 返回当前类直接实现的接口数组 | Integer.class.getInterfaces() → Comparable, Constable, ConstantDesc |
Class.isAssignableFrom(Class<?> cls) | 判断当前类是否可接收 cls 类型的对象(即 cls 能否向上转型为当前类) | Number.class.isAssignableFrom(Integer.class) → true |
6.1 关于接口的特殊说明
- 对接口调用
getSuperclass() → 返回 null。 - 获取接口的父接口必须用
getInterfaces()。 - 接口的
isAssignableFrom 用法与类一致:Serializable.class.isAssignableFrom(Integer.class) → true。
6.2 instanceof 与 isAssignableFrom 的区别
instanceof 需要对象实例:obj instanceof NumberisAssignableFrom 需要Class 对象:Number.class.isAssignableFrom(Integer.class)
七、动态代理(Dynamic Proxy)
7.1 解决的问题
不手动编写接口的实现类,就能在运行时动态创建实现了该接口的对象。
典型应用:AOP(面向切面编程)、RPC 框架、日志/事务拦截等。
7.2 限制条件
- 只能代理接口(不能代理普通类)。
- 如果需要代理普通类,可使用 CGLIB 或 ByteBuddy。
7.3 核心 API
java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler(函数式接口)
7.4 实现步骤
- 定义接口(必须,告知代理对象有哪些方法)。
- 实现
InvocationHandler:重写 invoke 方法,定义当接口方法被调用时实际执行的代码。 - 调用
Proxy.newProxyInstance 创建代理实例。 - 将代理实例转型为接口类型,然后正常调用方法。
7.5 示例代码(完整)
// 1. 接口
public interface Hello {
void morning(String name);
}
// 2. 使用动态代理(不写 HelloWorld 类)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
public static void main(String[] args) {
// 2.1 定义调用处理器
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("morning")) {
String name = (String) args[0];
System.out.println("Good morning, " + name);
}
return null; // 方法返回 void,所以返回 null
}
};
// 2.2 创建代理对象
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 类加载器
new Class[]{Hello.class}, // 要实现的接口列表
handler // 调用处理器
);
// 2.3 调用方法(实际执行 handler.invoke)
hello.morning("Bob"); // 输出:Good morning, Bob
}
}
7.6 动态代理内部原理
Proxy.newProxyInstance 会在运行时动态生成一个新的类(例如 $Proxy0)。这个类实现了指定的接口(Hello),并且每个方法的实现都是:
public void morning(String name) {
super.h.invoke(this, method_morning, new Object[]{name});
}
super.h 就是你传入的 InvocationHandler。- 然后通过
ClassLoader 加载这个类,并实例化返回。
换句话说:动态代理 = JVM 自动帮你写了一个实现类,并用反射调用你的 handler。
7.7 动态代理与反射的关系
- 动态代理生成的新类(
$Proxy0)本身也是一个类,JVM 会为它创建 Class 对象。 - 你可以对这个
Class 对象进行任何反射操作。 InvocationHandler.invoke 方法中收到的 Method 参数就是通过反射得到的。
所以动态代理是反射机制的一个典型应用,而不是替代品。
八、总结:什么时候用什么技术?
| 场景 | 推荐技术 |
|---|
| 已知类名,需要调用它的方法/访问字段 | 直接调用,无需反射 |
| 类名字符串在运行时才确定,需要创建实例 | Class.forName() + Constructor.newInstance() |
| 需要遍历一个对象的所有字段/方法(例如写序列化工具) | getDeclaredFields() / getDeclaredMethods() |
| 不写实现类,仅通过接口定义行为 | 动态代理 |
| 需要在方法调用前后统一添加逻辑(日志、事务) | 动态代理 + InvocationHandler |
最后记住一句话:
反射让你在运行时“看见”并“操作”一个类;动态代理让你在运行时“凭空造出”一个实现了接口的类,并把所有方法调用委托给你写的处理器。
评论