动态代理和反射

由于这两个东西很重要经常用,所以我专门用一个文章来讲解,忘了就看着一个就够了

反射

反射机制就是在 运行的时候 获取类的结构信息,比如方法,字段,构造函数并操作对象的一种机制。

反射操作相对于静态调用有比较高的性能开销,因为他涉及到动态解析和方法调用。所以在性能敏感的场景中,尽量避免使用反射。可以通过 缓存反射结果 避免反射性能问题。

反射主要有个Class类,通过Class类的实例可以获取类的各种信息。

反射的主要主要功能:

  • 创建对象:通过Class.newInstance 或者Constructor.newInstance()创建对象实例
  • 访问字段:通过Field类访问和修改对象的字段。
  • 调用方法:使用Method类调用对象的方法
  • 获取类对象:获取类的名称、父类、接口等信息。

类的元数据存在方法区 / 元空间,Class 实例存在堆中,反射通过堆中的 Class 实例,访问方法区的元数据,进而操作对象

动态代理

首先了解静态代理

image-20251013042305051

一个租房子问题,中介就是这个原理。

缺点代理需要手动创建,简单来说就是房主和中介都要实现租房接口,这个租房接口规定了租房的规范,然后消费者只用调用中介的方法就能实现中介的很多方法并且最终实际租房的时候还是走的房东的租房方法,只是多加了很多中介的额外方法。

但是每次想要租房都得自己手动new一个中介出来。

动态代理

动态代理就是在静态代理的基础上利用反射实现动态代理.

java的动态代理分为基于jdk和cglib的,这里先说jdk的。

这里举个例子就是给用户服务添加服务日志的代理效果的动态代理。

1、定义目标接口,共同契约

1
2
3
4
5
6
public interface UserService {
// 新增用户(无返回值)
void addUser(String username, Integer age);
// 查询用户(有返回值)
String getUser(String username);
}

2、实现目标类,被代理类,用于实现核心业务的

1
2
3
4
5
6
7
8
9
10
11
12
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username, Integer age) {
System.out.println("【核心业务】数据库新增用户:用户名=" + username + ",年龄=" + age);
}

@Override
public String getUser(String username
System.out.println("【核心业务】数据库查询用户:用户名=" + username);
return "用户信息:username=" + username + ",等级=VIP"; // 返回查询结果
}
}

3、实现InvocationHandler 这个是用来当拦截器 + 增强逻辑的

这里的作用就是把上面的核心业务包装一下,然后用InvocationHandler拦截器的作用,动态生成被代理对象本身,然后在方法前或者后进行增强。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class LogInvocationHandler implements InvocationHandler {
// 持有目标对象(被代理的真实业务对象,比如 UserServiceImpl 实例)
private final Object target;

// 构造器注入目标对象(让拦截器知道要代理哪个业务对象)
public LogInvocationHandler(Object target) {
this.target = target;
}

/**
* 每次调用代理对象的方法,都会触发这个方法(拦截逻辑的核心)
* @param proxy 动态生成的代理对象本身(一般不用,避免循环调用)
* @param method 被调用的「目标方法」的反射对象(比如 addUser、getUser)
* @param args 目标方法的参数数组(比如 addUser 的 "张三"、20)
* @return 目标方法的返回值(如果有)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// -------------------------- 增强逻辑(方法调用前)--------------------------
long startTime = System.currentTimeMillis(); // 记录开始时间
System.out.println("\n===== 日志增强:开始调用方法 -> " + method.getName());
System.out.println("===== 日志增强:方法参数 -> " + Arrays.toString(args)); // 打印参数

// -------------------------- 反射调用目标方法(核心业务)--------------------------
Object result = null;
try {
// 用反射调用目标对象的真实方法(这是和反射的核心关联!)
result = method.invoke(target, args);
} catch (Exception e) {
// 增强逻辑:异常处理(可选)
System.out.println("===== 日志增强:方法调用异常 -> " + e.getMessage());
throw e; // 异常向上抛出,不破坏原有业务的异常机制
}

// -------------------------- 增强逻辑(方法调用后)--------------------------
long costTime = System.currentTimeMillis() - startTime; // 计算耗时
System.out.println("===== 日志增强:方法调用结束 -> " + method.getName());
System.out.println("===== 日志增强:方法返回值 -> " + result);
System.out.println("===== 日志增强:方法耗时 -> " + costTime + "ms");

return result; // 返回目标方法的结果(给代理对象,再返回给调用者)
}
}

4、创建代理工厂,简化代理对象生成。

这里就是编写一个工具类,动态生成上面的代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyFactory {
/**
* 生成目标对象的代理对象
* @param target 目标对象(被代理的业务对象)
* @return 代理对象(实现了目标接口)
*/
public static <T> T getProxy(Object target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器(和目标对象一致)
target.getClass().getInterfaces(), // 目标对象实现的所有接口(代理类要实现这些接口)
new LogInvocationHandler(target) // 拦截器(增强逻辑)
);
}
}

这个工具类利用了Proxy类,的newInstance方法来生成代理对象,只用传入被代理对象的类加载器,被代理对象的接口,然后new拦截器,就能生成代理对象。

5、调用代理对象测试

首先创建目标对象,然后通过代理工厂生成代理对象,然后调用代理对象的方法就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JdkProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象(真实业务对象)
UserService userService = new UserServiceImpl();

// 2. 通过代理工厂生成代理对象(核心:运行时动态生成)
UserService userServiceProxy = ProxyFactory.getProxy(userService);

// 3. 调用代理对象的方法(不是直接调用目标对象!)
System.out.println("=== 测试新增用户 ===");
userServiceProxy.addUser("张三", 25); // 调用无返回值方法

System.out.println("\n=== 测试查询用户 ===");
String userInfo = userServiceProxy.getUser("李四"); // 调用有返回值方法
System.out.println("\n调用者获取到的结果:" + userInfo);
}
}

总结

组件 代码中的实现 核心作用
目标接口 UserService 定义方法契约,让代理类和目标类方法一致
目标类 UserServiceImpl 实现核心业务逻辑(被代理的对象)
InvocationHandler LogInvocationHandler 拦截方法调用,植入增强逻辑(日志、耗时)
Proxy 类 Proxy.newProxyInstance() 运行时动态生成代理类字节码并创建代理对象
代理工厂 ProxyFactory 封装代理对象生成逻辑,简化调用
对比维度 静态代理 动态代理
代理类生成时机 编译期手动编写(写死) 运行时动态生成(自动)
代码冗余度 一个目标类对应一个代理类,冗余高 一个处理器适配所有目标类,无冗余
灵活性 新增方法 / 目标类需修改代理类 新增方法 / 目标类无需修改处理器
依赖 需共同接口(和你说的一致) JDK 需接口,CGLIB 无需接口

总结

jdk动态代理流程就是首先规定一个共同的接口,然后实现一个实现类实现接口写好核心业务,然后写自定义的MyInvocationHandler代理类实现InvocationHandler接口,然后在里面通过invoke方法然后在里面调用核心业务并且在核心业务前或后写增强代码,然后在写个代理生成工厂,用于动态生成刚刚的动态代理,然后这个代理工厂就用Proxy的newInstance方法生成代理类,然后传入代理类的类加载器和实现的接口还有拦截器就是刚刚的InvocationHandler就可以动态生成代理类了。然后使用的时候就新建目标对象,然后用代理工厂创建目标对象的代理对象,然后调用的时候用代理对象的方法就能实现增强方法了。

上面是jdk的动态代理,但是框架中常用的是cglib的动态代理

cglib是继承目标类而不是实现共同接口的

CGLIB 通过字节码技术,在运行时动态生成目标类的子类(subclass),并重写其中的非 final 方法,在方法调用前后插入自定义逻辑(如日志、事务等)。

也就是说:

  • 不需要目标类实现任何接口;
  • 代理对象是目标类的一个“增强子类”;
  • 所有对代理对象的方法调用,都会进入你定义的 MethodInterceptor(拦截器)。