别再死记硬背了!来扒一扒Java动态代理与CGLIB

嘿,各位Javaer!关于动态代理。

你可能每天都在用它,但又不完全知道它是什么。比如,当你潇洒地在Service方法上写下 @Transactional 时,有没有想过,这个注解是如何像魔法一样,自动帮你开启和提交事务的?这背后的大功臣,就是我们今天要聊的动态代理。

在Java世界里,实现动态代理主要有两大门派:JDK动态代理CGLIB。它们就像是武林中的“南拳”和“北腿”,各有千秋,共同撑起了AOP(面向切面编程)的半壁江山。

啥是代理?为啥要用它?

在说“动态代理”之前,我们先得明白“代理”是干嘛的。

想象一下,你想请一位大明星(目标对象)来演出,但你不能直接联系到他。你得通过他的经纪人(代理对象)。这个经纪人除了会帮你安排演出(调用目标方法),还可能会在演出前后做一些额外的事情,比如:谈合同、安排安保、演出后收款(增强逻辑)。

在程序世界里,代理模式就是这么个意思。我们不想直接访问某个对象,而是通过一个代理对象来间接访问。这样做的好处是,我们可以在不修改原对象代码的情况下,对它的功能进行增强或控制。这就是所谓的“开闭原则”(对扩展开放,对修改关闭)。

而“动态代理”的“动态”二字,指的是代理类是在程序运行时动态生成的,而不是我们手动写好的。这给了我们极大的灵活性。

一:JDK官方亲儿子 —— JDK动态代理

JDK动态代理是Java官方提供的、根正苗红的实现方式。它的核心思想是:面向接口代理

核心原理

JDK动态代理有一个死规定:被代理的目标类必须实现至少一个接口。它在运行时,会动态地创建一个新的代理类(比如 $Proxy0),这个代理类会实现目标类所实现的所有接口,并继承 java.lang.reflect.Proxy 类。

当你调用代理对象的任何一个接口方法时,这个调用都会被转发到一个叫做 InvocationHandler 的处理器上。你所有的增强逻辑,比如日志、权限校验、事务管理,都写在这个处理器的 invoke 方法里。

架构图解

图解说明:

  1. 客户端(Client) 不直接与 目标对象(Real Object) 交互,而是与 代理对象(Proxy Object) 交互。

  2. 代理对象和目标对象都实现了同一个 接口(Some Interface)

  3. 当客户端调用代理对象的方法时,这个调用会被分派到 InvocationHandlerinvoke 方法中。

  4. InvocationHandler 可以在调用真实方法前后,执行我们自定义的增强逻辑。

  5. 最后,通过Java的反射机制InvocationHandler 调用目标对象的真实方法,完成整个流程。

实战代码

1. 定义一个用户服务接口

// 1. The interface that defines the business logic
public interface IUserService {
    void createUser(String username);
}

2. 创建一个实现类

// 2. The concrete implementation of the service
public class UserServiceImpl implements IUserService {
    @Override
    public void createUser(String username) {
        // Core business logic
        System.out.println("Executing: Creating user '" + username + "' in the database.");
    }
}

3. 创建我们的“经纪人”—— InvocationHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 3. The invocation handler, which contains the enhancement logic
public class LoggingInvocationHandler implements InvocationHandler {

    private final Object target; // The real object to be proxied

    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("===> [JDK Proxy] Method " + method.getName() + " is about to be executed.");
        
        // Execute the target method via reflection
        Object result = method.invoke(target, args);
        
        System.out.println("===> [JDK Proxy] Method " + method.getName() + " has been executed.");
        
        return result;
    }
}

4. 客户端调用

import java.lang.reflect.Proxy;

public class JdkProxyDemo {
    public static void main(String[] args) {
        // 1. Create the real target object
        IUserService realService = new UserServiceImpl();

        // 2. Create the invocation handler
        InvocationHandler handler = new LoggingInvocationHandler(realService);

        // 3. Create the proxy instance
        IUserService proxyService = (IUserService) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(), // ClassLoader
                realService.getClass().getInterfaces(),  // Interfaces implemented by the target
                handler                                  // The invocation handler
        );

        // 4. Call the method on the proxy object
        proxyService.createUser("Alice");
    }
}

运行结果:

===> [JDK Proxy] Method createUser is about to be executed.
Executing: Creating user 'Alice' in the database.
===> [JDK Proxy] Method createUser has been executed.

看,我们成功地在不修改 UserServiceImpl 的情况下,给 createUser 方法加上了日志功能!

二:能力更强的外援 —— CGLIB

有时候,我们的目标类很“任性”,它就是没有实现任何接口。这时候JDK动态代理就束手无策了。别急,CGLIB(Code Generation Library)闪亮登场!

核心原理

CGLIB的实现方式更加激进:继承父类,创建子类

它不要求目标类实现接口。在运行时,CGLIB会动态地创建一个目标类的子类作为代理对象,并重写父类中所有非 final 的方法。

当你调用代理对象的任何方法时,这个调用会被一个叫做 MethodInterceptor 的拦截器捕获。你所有的增强逻辑,都写在这个拦截器的 intercept 方法里。

架构图解

图解说明:

  1. CGLIB代理对象目标类 的一个子类。

  2. 当客户端调用代理对象的方法时,这个调用会被 MethodInterceptor 拦截。

  3. MethodInterceptor 执行完增强逻辑后,通过调用 proxy.invokeSuper(obj, args) 来调用父类(也就是目标类)的原始方法。

实战代码

假设我们有一个支付服务,它没有接口。

1. 创建一个普通的类

// 1. A plain class without any interface
public class PaymentService {
    public void pay(double amount) {
        // Core business logic
        System.out.println("Executing: Processing payment of $" + amount);
    }

    // A final method cannot be proxied by CGLIB
    public final void closeTransaction() {
        System.out.println("Transaction closed.");
    }
}

2. 创建我们的“拦截器”—— MethodInterceptor

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 2. The method interceptor for CGLIB
public class TimingMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("===> [CGLIB] Method " + method.getName() + " is about to be executed.");
        long start = System.nanoTime();

        // Execute the superclass method
        Object result = proxy.invokeSuper(obj, args);

        long end = System.nanoTime();
        System.out.println("===> [CGLIB] Method " + method.getName() + " executed in " + (end - start) + " ns.");
        
        return result;
    }
}

注意:你需要添加CGLIB的依赖,例如在Maven中:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

3. 客户端调用

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyDemo {
    public static void main(String[] args) {
        // 1. Create an Enhancer instance
        Enhancer enhancer = new Enhancer();
        
        // 2. Set the superclass (the class to be proxied)
        enhancer.setSuperclass(PaymentService.class);
        
        // 3. Set the callback (the interceptor)
        enhancer.setCallback(new TimingMethodInterceptor());
        
        // 4. Create the proxy object
        PaymentService proxyService = (PaymentService) enhancer.create();
        
        // 5. Call the method on the proxy object
        proxyService.pay(100.50);

        // This call will not be intercepted because the method is final
        proxyService.closeTransaction();
    }
}

运行结果:

===> [CGLIB] Method pay is about to be executed.
Executing: Processing payment of $100.5
===> [CGLIB] Method pay executed in 12345 ns.
Transaction closed.

完美!我们成功代理了一个没有接口的类。

注意,对 final 方法 closeTransaction 的调用没有被拦截,这印证了CGLIB的原理——它无法重写 final 方法。

JDK动态代理 vs CGLIB

特性

JDK 动态代理

CGLIB

核心原理

基于接口实现,运行时生成接口的实现类

基于继承,运行时生成目标类的子类

依赖

JDK自带,无需额外依赖

需要引入第三方cglib

代理对象

必须有接口

无需接口,但不能是final

代理方法

只能代理接口中定义的方法

可以代理类中所有非final、非private的方法

性能

早期版本反射调用较慢,但JDK 8后性能大幅优化

通过字节码操作,早期性能优于JDK,现在两者差距很小

限制

目标类必须实现接口

目标类不能是final,目标方法不能是finalstatic

一句话总结:

  • JDK代理:轻量、官方、稳定,但有“接口洁癖”。

  • CGLIB:强大、灵活,能代理普通类,但有点“恐高”(怕final)。

Spring Boot 3 中的代理栗子

Spring AOP的实现,底层就是靠JDK动态代理和CGLIB。Spring会非常智能地为你选择使用哪一种。

Spring的代理选择策略
  1. 老规矩 (Spring 2.0 之前):如果一个Bean实现了接口,Spring就默认使用JDK动态代理。如果没实现接口,就用CGLIB。

  2. 新风尚 (Spring Boot 2.0 及以后)默认统一使用CGLIB

为什么Spring Boot要改变默认策略,更偏爱CGLIB呢?

  • 统一行为:不管你有没有实现接口,代理的行为都一样,避免了一些因代理方式不同而产生的怪异问题。

  • 解决类型转换问题:使用JDK代理时,从Spring容器中获取的代理对象不能被强制转换为它的原始实现类类型,只能转换为接口类型。而CGLIB是子类,不存在这个问题。

    // 如果用JDK代理,这行会抛出 ClassCastException
    // UserServiceImpl service = (UserServiceImpl) context.getBean("userService"); 
    
    // 只能这样
    IUserService service = (IUserService) context.getBean("userService");
    
    

    使用CGLIB则没有这个烦恼,这让开发体验更加顺滑。

在Spring Boot 3中,这个默认使用CGLIB的策略被延续了下来。你可以在 application.properties 中手动修改它,但通常没有必要。

# 如果你非要用JDK代理(不推荐),可以这样设置
# spring.aop.proxy-target-class=false 

Spring AOP代码示例

让我们看一个最经典的 @Transactional 例子。

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Transactional // <--- Spring AOP 在这里施展魔法
    public void createOrder(Order order) {
        // 1. 在这个方法执行前,Spring会创建一个代理
        // 2. 代理对象会开启一个数据库事务
        
        orderRepository.save(order);
        
        if (order.isInvalid()) {
            throw new RuntimeException("Invalid order!"); // 如果这里抛异常
        }
        
        // 3. 如果方法正常结束,代理对象会提交事务
        // 4. 如果方法抛出运行时异常,代理对象会回滚事务
    }
}

当你调用 orderService.createOrder(order) 时,你实际上调用的不是原始的 OrderService 对象,而是Spring用CGLIB(默认)为它创建的一个代理子类。这个代理子类在调用真实的 createOrder 方法前后,插入了事务管理(开启、提交、回滚)的逻辑。

这一切都悄无声息地发生了,让我们能专注于业务逻辑,这就是AOP和动态代理的魅力所在。

总结

  • 代理模式 是一种在不修改原代码的情况下增强对象功能的设计模式。

  • JDK动态代理 是Java官方的、基于接口的实现。它要求目标类必须实现接口,通过 InvocationHandler 来织入逻辑。

  • CGLIB 是一个强大的第三方库,基于继承实现。它可以代理没有实现接口的普通类,通过 MethodInterceptor 来织入逻辑,但无法代理final类或方法。

  • Spring Boot 3 默认使用 CGLIB 来实现AOP,因为它更强大、更一致。像 @Transactional, @Cacheable 等注解的背后,都是动态代理在默默付出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nextera-void

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

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

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

打赏作者

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

抵扣说明:

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

余额充值