动态代理示例解析
动态代理
文章目录
- 动态代理
- 先来看一个示例
- 定义接口
- 实现接口
- 创建动态代理类
- 测试动态代理
- 输出结果
- 关键点解析
- CGLIB 动态代理
- 定义目标类(无需实现接口)
- 创建 CGLIB 动态代理
- 测试代码
- JDK 动态代理 vs CGLIB 动态代理
- 为什么需要使用代理对象?
- 动态代理适合的场景
- 动态代理的优势
- 什么时候不适合使用动态代理?
- Spring 中动态代理的主要应用场景包括:
- AOP(面向切面编程)
- 事务管理
- Spring 中动态代理的主要应用场景包括:
- AOP(面向切面编程)
- 事务管理
先来看一个示例
定义接口
// 定义一个业务接口
public interface UserService {
void addUser(String name);
void deleteUser(String name);
}
实现接口
// 实现接口的业务逻辑
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public void deleteUser(String name) {
System.out.println("删除用户: " + name);
}
}
创建动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理处理器
public class UserServiceProxyHandler implements InvocationHandler {
private Object target; // 被代理的目标对象
public UserServiceProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前增强逻辑
System.out.println("代理前: 准备执行方法 " + method.getName());
// 执行目标方法
Object result = method.invoke(target, args);
// 方法调用后增强逻辑
System.out.println("代理后: 方法 " + method.getName() + " 执行完成");
return result;
}
// 获取代理对象
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
this // 当前InvocationHandler实例
);
}
}
测试动态代理
public class DynamicProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 创建代理处理器并获取代理对象
UserService proxyService = (UserService) new UserServiceProxyHandler(userService).getProxy();
// 调用代理对象的方法
proxyService.addUser("Alice");
proxyService.deleteUser("Bob");
}
}
输出结果
代理前: 准备执行方法 addUser
添加用户: Alice
代理后: 方法 addUser 执行完成
代理前: 准备执行方法 deleteUser
删除用户: Bob
代理后: 方法 deleteUser 执行完成
关键点解析
1.InvocationHandler 接口
- proxy: 代理对象本身(通常不直接使用)
- method: 被调用的目标方法
- args: 方法参数
2.Proxy.newProxyInstance 方法
- ClassLoader: 目标对象的类加载器
- Class<?>[]: 目标对象实现的接口列表
- InvocationHandler: 处理代理逻辑的处理器
3.适用场景
- JDK 动态代理要求目标对象必须实现接口。如果目标对象没有实现接口,可以使用 CGLIB 动态代理。
4.扩展性
- 可以在 invoke 方法中加入更多功能,如日志记录、性能监控、事务管理等。
CGLIB 动态代理
CGLIB 动态代理不要求目标对象实现接口,代理通过字节码生成子类的方式实现。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
定义目标类(无需实现接口)
public class UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
public void deleteUser(String name) {
System.out.println("删除用户: " + name);
}
}
创建 CGLIB 动态代理
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 CglibProxyHandler implements MethodInterceptor {
private Object target; // 被代理的目标对象
public CglibProxyHandler(Object target) {
this.target = target;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类为目标类
enhancer.setCallback(this); // 设置回调
return enhancer.create(); // 创建代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB代理前: 准备执行方法 " + method.getName());
Object result = method.invoke(target, args); // 执行目标方法
System.out.println("CGLIB代理后: 方法 " + method.getName() + " 执行完成");
return result;
}
}
测试代码
public class CglibProxyTest {
public static void main(String[] args) {
UserService userService = new UserService();
UserService proxyService = (UserService) new CglibProxyHandler(userService).getProxy();
proxyService.addUser("Alice");
proxyService.deleteUser("Bob");
}
}
JDK 动态代理 vs CGLIB 动态代理
特性 | JDKL动态代理 | CGLB动态代理 |
---|---|---|
实现方式 | 基于接口 | 基于继承(生成子类) |
目标对象要求 | 必须实现接口 | 不需要实现接口 |
性能 | 接口调用效率较高 | 子类生成效率较低,但运行时性能接近 |
适用场景 | 目标对象有接口 | 目标对象无接口或需要代理类本身的方法 |
局限性 | 无法代理类本身的非接口方法 | 无法代理 final 类或 final 方法 |
为什么需要使用代理对象?
即使你已经通过 new 创建了具体的对象,直接使用它也能完成业务逻辑,但这种方式存在以下局限性:
1. 代码侵入性强
如果你希望在方法调用前后添加一些通用逻辑(如日志记录、性能监控、事务管理等),直接修改原始类的代码会破坏单一职责原则,并且会导致代码冗余。
2.功能扩展困难
假设你需要为某些方法添加权限校验或缓存支持,直接修改原始类会让代码变得复杂且容易出错。而代理可以在不修改原始类的情况下,动态地添加这些功能。
3. 解耦与复用
通过代理模式,可以将核心业务逻辑与非核心逻辑(如日志、事务)分离,提升代码的可读性和复用性。
动态代理适合的场景
动态代理的主要目的是在运行时动态地为目标对象添加额外的行为,而不改变其原有代码。以下是动态代理的典型应用场景:
1. 日志记录
在方法调用前后记录日志,便于调试和审计。
System.out.println("代理前: 准备执行方法 " + method.getName());
Object result = method.invoke(target, args);
System.out.println("代理后: 方法 " + method.getName() + " 执行完成");
2.性能监控
统计方法的执行时间,用于性能优化。
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + "ms");
3. 事务管理
在方法调用前后开启和提交事务,确保数据一致性。
transactionManager.beginTransaction();
try {
Object result = method.invoke(target, args);
transactionManager.commit();
return result;
} catch (Exception e) {
transactionManager.rollback();
throw e;
}
4. 权限校验
在方法调用前检查用户是否有权限执行该操作。
if (!securityManager.hasPermission(user, method)) {
throw new SecurityException("无权访问");
}
return method.invoke(target, args);
5. 缓存支持
在方法调用前检查缓存中是否存在结果,避免重复计算。
String cacheKey = generateCacheKey(method, args);
if (cache.contains(cacheKey)) {
return cache.get(cacheKey);
}
Object result = method.invoke(target, args);
cache.put(cacheKey, result);
return result;
6. 远程调用(RPC)
在分布式系统中,动态代理可以隐藏远程调用的细节,让开发者像调用本地方法一样调用远程服务。
// 动态代理实现 RPC 调用
Object result = rpcClient.invokeRemoteService(target, method, args);
动态代理的优势
- 透明性:动态代理对调用者是透明的,调用者无需关心代理的存在,只需像操作普通对象一样调用方法。
- 灵活性:代理逻辑可以在运行时动态生成,适用于多种场景(如日志、事务、权限等),而无需修改原始类。
- 解耦:将核心业务逻辑与非核心逻辑分离,符合单一职责原则和开闭原则。
什么时候不适合使用动态代理?
尽管动态代理非常强大,但它也有一些局限性,以下场景可能不适合使用动态代理:
1. 性能敏感场景
动态代理会引入一定的性能开销(尤其是 CGLIB),对于高频调用的场景可能需要权衡。
2. 简单任务
如果只是简单的功能调用,没有额外的需求(如日志、事务等),直接使用原始对象即可。
3. 静态需求
如果增强逻辑是固定的,且不需要动态生成,可以通过继承或装饰器模式实现。
Spring 中动态代理的主要应用场景包括:
AOP(面向切面编程)
- 用于增强方法调用(如日志记录、性能监控、权限校验等)。
- 示例:通过 @Aspect 注解定义切面逻辑。
事务管理
- 用于声明式事务控制。
- 示例:通过 @Transactional 注解实现事务管理。
定的性能开销(尤其是 CGLIB),对于高频调用的场景可能需要权衡。
2. 简单任务
如果只是简单的功能调用,没有额外的需求(如日志、事务等),直接使用原始对象即可。
3. 静态需求
如果增强逻辑是固定的,且不需要动态生成,可以通过继承或装饰器模式实现。
Spring 中动态代理的主要应用场景包括:
AOP(面向切面编程)
- 用于增强方法调用(如日志记录、性能监控、权限校验等)。
- 示例:通过 @Aspect 注解定义切面逻辑。
事务管理
- 用于声明式事务控制。
- 示例:通过 @Transactional 注解实现事务管理。