什么是动态代理?JDK动态代理与CGLIB动态代理有什么区别?

动态代理是一种在运行时生成代理对象的技术,允许在不修改原始类代码的情况下增强其方法的功能。它广泛应用于日志记录、事务管理、安全检查等场景。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高级编程的基石之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小九没绝活

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值