在 Java 开发中,动态代理是实现 AOP(面向切面编程)的核心技术,广泛应用于日志记录、事务管理、权限控制等场景。其中,JDK 动态代理和 CGlib 是两种最常用的动态代理实现方式。本文将从原理、区别、使用场景等方面深入解析这两种技术,帮助开发者更好地理解和选择。
一、动态代理的基本概念
动态代理是一种在运行时动态生成代理类的技术,无需手动编写代理类代码。其核心作用是:在不修改目标对象代码的前提下,对目标对象的方法进行增强(如在方法执行前后添加日志、性能监控等逻辑)。
简单来说,动态代理就像给目标对象 “套了一层壳”,所有对目标对象的调用都会先经过这层壳,从而实现增强逻辑的统一管理。
二、JDK 动态代理
1. 原理
JDK 动态代理是 Java 官方提供的代理技术,基于接口和反射机制实现:
- 要求目标类必须实现一个或多个接口。
- 运行时,JDK 会动态生成一个实现了目标类所有接口的代理类($ProxyXXX)。
- 代理类通过调用
InvocationHandler
接口的invoke
方法,将增强逻辑与目标方法的执行结合起来。
2. 核心接口:InvocationHandler
public interface InvocationHandler {// proxy:代理对象本身// method:目标方法// args:目标方法的参数Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
代理类的所有方法调用都会被转发到invoke
方法,开发者需在该方法中实现增强逻辑。
3. 实现示例
步骤 1:定义目标接口和实现类
// 目标接口
public interface UserDao {void add();
}// 目标类(实现接口)
public class UserDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("执行UserDao的add方法");}
}
步骤 2:实现 InvocationHandler
public class JdkProxyHandler implements InvocationHandler {// 目标对象(被代理的原始对象)private Object target;public JdkProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 增强逻辑:方法执行前System.out.println("JDK代理:方法执行前(日志记录)");// 执行目标方法Object result = method.invoke(target, args);// 增强逻辑:方法执行后System.out.println("JDK代理:方法执行后(日志记录)");return result;}
}
步骤 3:生成代理对象并使用
public class JdkProxyDemo {public static void main(String[] args) {// 目标对象UserDao target = new UserDaoImpl();// 生成代理对象(通过Proxy类的newProxyInstance方法)UserDao proxy = (UserDao) Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器target.getClass().getInterfaces(), // 目标类实现的接口new JdkProxyHandler(target) // 增强逻辑处理器);// 调用代理对象的方法(实际会执行增强逻辑+目标方法)proxy.add();}
}
输出结果
JDK代理:方法执行前(日志记录)
执行UserDao的add方法
JDK代理:方法执行后(日志记录)
三、CGlib 动态代理
1. 原理
CGlib(Code Generation Library)是一个第三方字节码生成库,基于继承实现动态代理:
- 不要求目标类实现接口,通过生成目标类的子类作为代理类。
- 运行时,CGlib 会动态生成目标类的子类,并重写目标类的非 final 方法。
- 代理逻辑通过
MethodInterceptor
接口的intercept
方法实现。
2. 核心接口:MethodInterceptor
public interface MethodInterceptor {// obj:代理对象(目标类的子类)// method:目标方法// args:目标方法的参数// proxy:方法代理对象(用于调用目标方法)Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
3. 实现示例
步骤 1:添加 CGlib 依赖(Maven)
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
步骤 2:定义目标类(无需实现接口)
public class UserService {public void update() {System.out.println("执行UserService的update方法");}
}
步骤 3:实现 MethodInterceptor
public class CglibInterceptor implements MethodInterceptor {// 目标对象private Object target;public CglibInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 增强逻辑:方法执行前System.out.println("CGlib代理:方法执行前(性能监控)");// 执行目标方法(通过代理对象调用父类方法)Object result = proxy.invokeSuper(obj, args);// 增强逻辑:方法执行后System.out.println("CGlib代理:方法执行后(性能监控结束)");return result;}
}
步骤 4:生成代理对象并使用
public class CglibDemo {public static void main(String[] args) {// 目标对象UserService target = new UserService();// CGlib增强器(用于生成代理类)Enhancer enhancer = new Enhancer();// 设置父类(目标类)enhancer.setSuperclass(UserService.class);// 设置回调(增强逻辑)enhancer.setCallback(new CglibInterceptor(target));// 生成代理对象(目标类的子类)UserService proxy = (UserService) enhancer.create();// 调用代理对象的方法proxy.update();}
}
输出结果
CGlib代理:方法执行前(性能监控)
执行UserService的update方法
CGlib代理:方法执行后(性能监控结束)
四、JDK 动态代理 vs CGlib 动态代理
对比维度 | JDK 动态代理 | CGlib 动态代理 |
---|---|---|
底层技术 | 基于接口 + 反射 | 基于继承 + 字节码生成(ASM 库) |
目标类要求 | 必须实现接口 | 可无接口(但不能是 final 类 / 方法) |
代理类生成 | 实现目标接口的代理类 | 继承目标类的子类 |
效率 | 反射调用,效率较低(JDK 8+后优化明显) | 直接调用子类方法,效率较高 |
灵活性 | 仅能代理接口方法 | 可代理类中所有非 final 方法 |
依赖 | JDK 内置,无需额外依赖 | 需要引入 CGlib 库 |
关键区别总结:
- 接口依赖:JDK 动态代理强制要求目标类实现接口,CGlib 无此限制。
- 性能:在多次调用场景下,CGlib 效率更高(因避免了反射开销)。
- 限制:CGlib 无法代理 final 类或 final 方法(子类无法重写)。
五、Spring 中的选择策略
Spring AOP 默认根据目标类是否实现接口自动选择代理方式:
- 若目标类实现了接口:默认使用 JDK 动态代理。
- 若目标类未实现接口:自动切换为 CGlib 代理。
若需强制使用 CGlib(即使目标类有接口),可通过配置开启:
- XML 配置:
<aop:aspectj-autoproxy proxy-target-class="true"/>
- 注解配置:
@EnableAspectJAutoProxy(proxyTargetClass = true)
六、使用场景建议
-
优先用 JDK 动态代理:
- 目标类已实现接口。
- 追求开发便捷性(无需额外依赖)。
- 方法调用频率不高的场景。
-
选择 CGlib:
- 目标类无接口,或需代理非接口方法。
- 对性能要求高(如高频调用的核心服务)。
- 可接受引入第三方依赖。
七、总结
JDK 动态代理和 CGlib 是动态代理的两大主流实现,各有优劣:
- JDK 动态代理基于接口,简单易用,是 Spring 的默认选择。
- CGlib 基于继承,性能更优,适合无接口或高性能需求的场景。
理解两者的原理和区别,有助于在实际开发中根据场景合理选择,也能更深入地理解 Spring AOP 等框架的底层实现。