动态代理
动态代理是在不修改目标对象源代码的情况下,在程序运行时动态地生成一个代理对象。这个代理对象拥有和目标对象一样的接口,但它在调用目标对象方法之前或之后,可以插入我们自己的附加逻辑。
两种主要实现方式
1. JDK 动态代理(基于接口)
这是 Java 标准库自带的动态代理实现方式。
核心原理:JDK 动态代理只能为实现了接口的类创建代理。它会生成一个实现了目标对象所有接口的代理类,这个代理类和目标对象实现了相同的接口,因此可以被视为目标对象的替身。
关键组成:
java.lang.reflect.Proxy类:创建代理对象的工具类java.lang.reflect.InvoccationHandler接口:核心,需要在它的invoke()方法中编写代理逻辑。每当代理对象的方法被调用时,实际执行的就是InvocationHandler的invoke()方法。
工作流程:
- 定义一个接口(比如
UserService和他的实现类UserServiceImpl)。 - 创建一个
InvacationHandler的实现类,把UserServiceImpl对象作为参数传给它。在invoke()方法里,可以在调用UserServiceImpl的实际方法前后添加额外的逻辑。 - 使用
Proxy.newProxyInstance()方法,传入类加载器、目标对象实现的接口数组、以及你的InvocationHandler实例,来生成代理对象。 - 通过代理对象调用方法时,这个调用会被转发到
InvocationHandler的invoke()方法,在那里额外逻辑会执行,然后invoke()再通过反射调用目标对象的实际方法。
代码示例:
// 1. 定义接口
public interface UserService {
void addUser(String name);
String getUser(int id);
}
// 2. 实现类 (目标对象)
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("用户服务:正在添加用户:" + name);
// 模拟数据库操作
}
@Override
public String getUser(int id) {
System.out.println("用户服务:正在获取用户 ID:" + id);
return "用户" + id;
}
}
// 3. 动态代理处理器 (InvocationHandler)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler {
private Object target; // 目标对象
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标方法之前执行的逻辑(前置增强)
System.out.println("--- JDK代理:方法 " + method.getName() + " 开始执行 ---");
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 在调用目标方法之后执行的逻辑(后置增强)
System.out.println("--- JDK代理:方法 " + method.getName() + " 执行结束 ---");
return result; // 返回目标方法的执行结果
}
// 获取代理对象的方法
public Object getProxy() {
// 创建代理对象:
// 参数1:类加载器,通常和目标对象使用同一个类加载器
// 参数2:目标对象实现的接口数组,代理对象会实现这些接口
// 参数3:InvocationHandler 实例,负责处理代理对象的每一次方法调用
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
}
// 4. 测试
public class JdkProxyDemo {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 创建InvocationHandler
MyInvocationHandler handler = new MyInvocationHandler(userService);
// 获取代理对象
UserService proxy = (UserService) handler.getProxy();
// 通过代理对象调用方法
proxy.addUser("张三");
System.out.println("--------------------");
proxy.getUser(101);
}
}
2. CGLIB 动态代理(基于类)
JDK 动态代理的限制是它只能代理接口。如果一个类没有实现任何接口,那么 JDK 动态代理就无能为力了。
核心原理:
CGLIB 是一个强大的高性能字节码生成库。它能够在运行时子类化(继承)一个类,然后重写父类的方法来达到代理的目的。因此,CGLIB 代理不需要目标对象实现接口,它可以直接代理普通的类。
关键组成:
- net.sf.cglib.proxy.Enhancer 类: 这是创建代理对象的核心类。
- net.sf.cglib.proxy.MethodInterceptor 接口: 类似于 JDK 代理的 InvocationHandler,你需要在它的 intercept() 方法中编写代理逻辑。
工作流程:
- 你有一个普通的类 (比如 ProductService),可能没有实现任何接口。
- 你创建一个 MethodInterceptor 的实现类,并在 intercept() 方法中定义代理逻辑,包括调用目标对象的方法。
- 使用 Enhancer 类来设置父类 (目标类) 和回调函数 (MethodInterceptor)。
- 调用 enhancer.create() 生成代理对象。这个代理对象是目标类的子类。
- 当你通过 CGLIB 生成的代理对象调用方法时,这个调用会被转发到 MethodInterceptor 的 intercept() 方法,在那里你的附加逻辑会执行,然后 intercept() 再通过 MethodProxy 调用父类(目标类)的对应方法。
示例代码:
// 1. 普通类 (目标对象),无需实现接口
public class ProductService {
public void addProduct(String name) {
System.out.println("产品服务:正在添加产品:" + name);
}
public String getProduct(int id) {
System.out.println("产品服务:正在获取产品 ID:" + id);
return "产品" + id;
}
}
// 2. 动态代理处理器 (MethodInterceptor)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
private Object target; // 目标对象
public MyMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在调用目标方法之前执行的逻辑(前置增强)
System.out.println("--- CGLIB代理:方法 " + method.getName() + " 开始执行 ---");
// 调用目标对象的方法
// proxy.invokeSuper(obj, args) 是调用父类(即目标对象)的方法
Object result = proxy.invokeSuper(obj, args);
// 在调用目标方法之后执行的逻辑(后置增强)
System.out.println("--- CGLIB代理:方法 " + method.getName() + " 执行结束 ---");
return result; // 返回目标方法的执行结果
}
// 获取代理对象的方法
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类为目标对象类
enhancer.setCallback(this); // 设置回调为当前的 MethodInterceptor
return enhancer.create(); // 创建代理对象(目标类的子类)
}
}
// 3. 测试
public class CglibProxyDemo {
public static void main(String[] args) {
// 创建目标对象
ProductService productService = new ProductService();
// 创建MethodInterceptor
MyMethodInterceptor interceptor = new MyMethodInterceptor(productService);
// 获取代理对象
ProductService proxy = (ProductService) interceptor.getProxy();
// 通过代理对象调用方法
proxy.addProduct("电脑");
System.out.println("--------------------");
proxy.getProduct(202);
}
}
动态代理的优点与应用
优点
- 代码解耦: 将横切关注点(如日志、事务、安全)从核心业务逻辑中分离出来,提高了代码的内聚性。
- AOP 的基石: 动态代理是实现 AOP 的核心技术之一,Spring 框架的事务管理、日志记录等都是基于动态代理实现的。
- 不修改源代码: 可以在不改变原有类结构的基础上,增强其功能。
- 运行时增强: 功能的增强是在程序运行时动态完成的,非常灵活。
常见应用场景
- Spring AOP: Spring 的声明式事务
- @Transactional 就是通过动态代理实现的。
- RPC 框架: 远程方法调用,代理对象负责网络通信和序列化/反序列化。
- ORM 框架: 如 Hibernate,延迟加载(Lazy Loading)功能可能就用到了动态代理。
- 缓存: 在方法调用前后添加缓存逻辑。
- 权限校验: 在方法执行前检查用户权限。
- 日志记录: 统一记录方法的入参、出参和执行时间。