经典总结
动态代理 是 Java 中的一种技术,它让程序在运行时能够自动创建代理对象,然后通过代理对象来拦截和增强你原本的方法。
1. 怎么实现
- JDK 动态代理:它用的是 Java 反射,所以被代理的类必须实现接口。
- CGLIB 动态代理:它通过 字节码操作,即使类没有实现接口也能代理。
2. 主要用途
- 增强功能:你可以在方法运行前后加点额外的操作,比如记录日志、检查权限或者管理事务。
- 解耦设计:把一些通用功能(比如日志、权限检查)和核心业务逻辑分开,让代码更清晰、更容易维护。
3. 应用场景
- Spring AOP:动态代理用于实现一些功能,比如记录日志和事务管理,和核心代码分开。
- MyBatis:通过动态代理自动生成接口的实现类,省去了手动编写代码的麻烦。
详情内容
1. 动态代理的核心原理
1.1 什么是代理?
在编程中,代理模式 是一种结构型设计模式。代理对象是对目标对象的替代或包装,它可以:
- 在调用目标对象的方法前后执行自定义逻辑。
- 控制对目标对象的访问。
静态代理 是预先定义好的代理类,而 动态代理 则是在运行时动态生成代理类。
1.2 动态代理的两种实现
(1) JDK 动态代理
- 实现基础:基于反射(
java.lang.reflect
)。 - 核心类:
Proxy
:动态生成代理类。InvocationHandler
:定义方法拦截逻辑。
- 适用场景:被代理对象实现了接口。
示例代码:JDK 动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义一个接口
interface UserService {
void addUser(String name);
}
// 接口的具体实现类
class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
// 动态代理处理器
class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(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 class JDKProxyExample {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LoggingHandler(userService)
);
// 调用代理方法
proxy.addUser("张三");
}
}
输出结果:
日志:调用方法 addUser
添加用户:张三
日志:方法 addUser 执行完毕
解析
Proxy.newProxyInstance
动态生成代理对象。LoggingHandler
是方法拦截器,定义了在方法调用前后要执行的逻辑。
(2) CGLIB 动态代理
-
实现基础:基于字节码操作(ASM 框架)。
-
核心类:
Enhancer
:生成代理类。MethodInterceptor
:定义方法拦截逻辑。
-
适用场景:目标类没有实现接口。
示例代码:CGLIB 动态代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 目标类
class ProductService {
public void addProduct(String name) {
System.out.println("添加产品:" + name);
}
}
// 动态代理拦截器
class CglibLoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("日志:调用方法 " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用实际方法
System.out.println("日志:方法 " + method.getName() + " 执行完毕");
return result;
}
}
public class CGLIBProxyExample {
public static void main(String[] args) {
// 创建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ProductService.class);
enhancer.setCallback(new CglibLoggingInterceptor());
ProductService proxy = (ProductService) enhancer.create();
proxy.addProduct("手机");
}
}
输出结果:
日志:调用方法 addProduct
添加产品:手机
日志:方法 addProduct 执行完毕
解析
Enhancer
动态生成目标类的子类。CglibLoggingInterceptor
实现方法的拦截逻辑。
2. JDK 动态代理与 CGLIB 动态代理的对比
对比维度 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
实现方式 | 基于反射机制 | 基于字节码操作 |
代理对象要求 | 目标类必须实现接口 | 目标类无需实现接口 |
性能 | 性能较低,反射开销较大 | 性能更高,但生成字节码开销略大 |
典型应用 | Spring AOP 中对接口的增强 | Spring AOP 中对类的增强 |
限制 | 无法代理没有接口的类 | 无法代理被 final 修饰的类或方法 |
3. 动态代理的实际应用场景
(1) Spring AOP
动态代理是 Spring AOP 的核心技术,用于对方法调用进行增强。通过代理对象,Spring 可以在方法执行前后执行日志记录、事务管理等逻辑。
示例:Spring 中的事务管理
@Transactional
public void processOrder() {
// 核心业务逻辑
}
Spring 使用动态代理在方法执行前后自动开启和提交事务。
(2) MyBatis Mapper 动态代理
在 MyBatis 中,Mapper 接口的实现类是在运行时通过动态代理生成的。
示例代码
// Mapper 接口
public interface UserMapper {
User findUserById(int id);
}
// 动态代理生成的代理类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(1);
解析:
getMapper
方法动态生成代理类。- 代理类负责执行 SQL 并将结果映射为实体对象。
知识拓展
1. ASM 框架与字节码操作
CGLIB 的底层实现依赖于 ASM 框架,通过直接操作字节码生成代理类。
- 优势:性能优于反射。
- 缺点:实现复杂,代码难以维护。
2. 动态代理的性能优化
性能瓶颈
- JDK 动态代理 使用反射调用方法,性能略低。
- CGLIB 动态代理 虽然性能更高,但生成字节码有一定开销。
优化方案
- JDK 11+ 引入了更高效的代理实现机制。
- 使用 ByteBuddy 替代 CGLIB,支持更复杂的字节码操作场景。
总结
- 动态代理的核心:
- 运行时生成代理类,为方法调用添加增强逻辑。
- JDK 动态代理基于反射,适用于接口。
- CGLIB 动态代理基于字节码生成,适用于没有接口的类。
- 动态代理的优势:
- 高度灵活,可动态增强方法。
- 广泛应用于框架(Spring、MyBatis)。
- 扩展建议:
- 深入研究字节码操作(如 ASM 和 ByteBuddy)。
- 在实际项目中,根据场景选择合适的代理技术。
通过以上总结的内容,你是不是对 Java 动态代理有了更全面的理解?
如果还有疑问,欢迎评论区进一步交流!