动态代理是一种在运行时生成代理对象的技术,允许在不修改原始类代码的情况下增强其方法的功能。它广泛应用于日志记录、事务管理、安全检查等场景。JDK动态代理和CGLIB动态代理是两种主流实现,主要区别如下:
核心区别
- 实现机制:
- JDK动态代理:基于接口,使用反射Proxy和InvocationHandler实现
- CGLIB动态代理:基于继承,通过ASM字节码框架生成目标类的子类
- 依赖条件:
- JDK代理要求目标类必须实现接口
- CGLIB代理适用于无接口的类,但无法处理final类或方法
- 性能差异:
- JDK生成代理较快,调用方法时反射略慢
- CGLIB生成代理较慢,但方法调用更快(直接调用)
根据项目需求和环境选择合适的方式:若目标类有接口且追求轻量,优先使用JDK动态代理;若无接口且需要高性能,选择CGLIB。注意Spring AOP默认根据目标类是否实现接口自动选择这两种方式。
代码示例
1. JDK动态代理示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
interface UserService {
void addUser(String username);
}
// 实现类
class UserServiceImpl implements UserService {
public void addUser(String username) {
System.out.println("添加用户: " + username);
}
}
// InvocationHandler实现
class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(Object target) {
this.target = target;
}
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代理] 方法调用后");
return result;
}
}
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{UserService.class},
new LogHandler(target)
);
proxy.addUser("张三");
}
}
2. CGLIB动态代理示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 目标类(无需接口)
class UserServiceWithoutInterface {
public void addUser(String username) {
System.out.println("添加用户: " + username);
}
}
// MethodInterceptor实现
class LogInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB代理] 方法调用前: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("[CGLIB代理] 方法调用后");
return result;
}
}
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceWithoutInterface.class);
enhancer.setCallback(new LogInterceptor());
UserServiceWithoutInterface proxy = (UserServiceWithoutInterface) enhancer.create();
proxy.addUser("李四");
}
}
执行结果
[JDK代理] 方法调用前: addUser
添加用户: 张三
[JDK代理] 方法调用后
[CGLIB代理] 方法调用前: addUser
添加用户: 李四
[CGLIB代理] 方法调用后
动态代理项目示例
在Java项目中,动态代理主要用于在运行时动态拦截方法调用,并在方法执行前后插入额外逻辑,实现解耦横切关注点(如日志、事务、权限等)与核心业务逻辑。以下是常见场景及实际项目示例:
1.日志记录(Logging)
场景:记录方法的调用参数、执行时间、结果或异常。
示例:在Web应用中,通过动态代理拦截Service层方法,记录接口调用的详细信息(如用户ID、参数、耗时)。
public class LoggingHandler implements InvocationHandler {
private final 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() + ", 参数: " + Arrays.toString(args));
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long duration = System.currentTimeMillis() - start;
System.out.println("方法执行耗时: " + duration + "ms");
return result;
}
}
// 使用动态代理
UserService userService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LoggingHandler(new UserServiceImpl())
);
2.事务管理(Transaction Management)
场景:在数据库操作前后自动开启、提交或回滚事务。
示例:Spring Framework:通过@Transactional注解实现声明式事务。Spring使用动态代理(JDK或CGLIB)拦截被注解的方法,在方法执行前开启事务,执行后提交,异常时回滚。
@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
from.debit(amount);
to.credit(amount);
}
3. 权限校验(Security)
场景:在方法执行前检查用户权限。
示例:拦截Controller层方法,检查用户是否有权限访问特定API。
public class SecurityProxy implements InvocationHandler {
private Object target;
private User currentUser;
public SecurityProxy(Object target, User user) {
this.target = target;
this.currentUser = user;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isAnnotationPresent(RequiresRole.class)) {
RequiresRole annotation = method.getAnnotation(RequiresRole.class);
if (!currentUser.hasRole(annotation.value())) {
throw new SecurityException("权限不足!");
}
}
return method.invoke(target, args);
}
}
4. 缓存(Caching)
场景:在方法调用前查询缓存,命中则直接返回,未命中则执行方法并缓存结果。
示例:拦截数据访问层(DAO)的查询方法,自动缓存结果。
public class CacheHandler implements InvocationHandler {
private final Object target;
private final Map<String, Object> cache = new HashMap<>();
public CacheHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String key = method.getName() + Arrays.hashCode(args);
if (cache.containsKey(key)) {
return cache.get(key);
}
Object result = method.invoke(target, args);
cache.put(key, result);
return result;
}
}
5. 远程方法调用(RPC)
场景:透明化远程服务调用,客户端像调用本地方法一样调用远程服务。
示例:Dubbo、gRPC:通过动态代理生成服务接口的代理类,底层将方法调用转换为网络请求。
// Dubbo消费者示例
@Reference
private UserService userService; // 实际是动态代理对象
// 调用时,代理会将方法名和参数序列化后发送到远程服务端
User user = userService.getUserById(1);
6. MyBatis Mapper接口实现
场景:MyBatis通过动态代理将Mapper接口的方法调用转换为SQL执行。
// MyBatis的Mapper接口无需实现类
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(int id);
}
// 实际调用时,MyBatis通过动态代理生成实现类,解析注解中的SQL并执行。
总结
特性 | JDK动态代理 | CGLIB动态代理 |
---|---|---|
实现方式 | 基于接口反射 | 基于继承,字节码操作 |
依赖条件 | 目标类必须实现接口 | 无法代理final类或方法 |
生成速度 | 快 | 较慢(需生成字节码) |
方法调用速度 | 较慢(反射调用) | 快(直接调用) |
适用场景 | 有接口的类 | 无接口的普通类 |
第三方依赖 | 无需(JDK内置) | 需要引入CGLIB库 |
动态代理的核心价值在于解耦和复用,通过将横切逻辑(如日志、事务)与业务代码分离,提升可维护性。在主流框架(Spring、MyBatis、Dubbo)中广泛应用,是Java高级编程的基石之一。