Spring中的两种代理 | CGLIB动态代 与JDK动态代理 优缺点及性能比较

代理的主要功能

  • 拦截对所有方法的调用,并将它们根据需要传递给AOP框架。

    Spring AOP 结合了动态代理技术和 AOP 切面逻辑的解析。以下是实现步骤:

    1. 创建代理

    2. 拦截方法调用

    3. 通知链执行

    通知链的执行通过 MethodInterceptorinvoke 方法实现

    • Spring 使用 ProxyFactory 创建代理对象。
    • 根据目标对象是否实现接口,决定使用 JDK 动态代理还是 CGLIB 代理。
    • 使用 AOP 切面配置,Spring 将切面的通知逻辑封装为 Advice 对象。
    • 通过 MethodInterceptor 实现通知链调用。
    • 代理拦截方法后,通知链按照顺序执行:
      • 前置通知(@Before)
      • 调用目标方法。
      • 后置通知(@After、@AfterReturning)
      • 异常通知(@AfterThrowing)
  • 通过 AopContext 获取当前代理对象(代理自我公开)。

    ℹ️ 通常情况下,当你在目标对象的方法中调用同一个类的另一个方法时,这种内部调用是直接调用目标对象的方法,而不会触发代理(也就是不会应用通知逻辑,比如 AOP 切面)。如果需要在目标对象内部调用时也触发 AOP 逻辑,必须通过代理对象来调用方法。

    举个例子:

    public class MyService {
        public void methodA() {
            System.out.println("Executing methodA");
            // 内部调用另一个方法
            methodB();
        }
    
        public void methodB() {
            System.out.println("Executing methodB");
        }
    }
    

    MyService 配置AOP切面:

    @Aspect
    @Component
    public class LoggingAspect {
        @Before("execution(* MyService.*(..))")
        public void logBefore(JoinPoint joinPoint) {
            System.out.println("Logging before: " + joinPoint.getSignature().getName());
        }
    }
    

    当调用methodA() 时,由于methodA() 在内部直接调用了methodB() ,因此methodB() 不会触发LoggingAspect

    如果需要methodB() 同样触发LoggingAspect ,则需要启用AopContextProxyFactory.setExposeProxy(true);),并通过AopContext 来获取当前对象。

    public void methodA() {
        System.out.println("Executing methodA");
        ((MyService) AopContext.currentProxy()).methodB(); // 使用代理调用方法B
    }
    
  • 通过 Advised 接口动态修改代理的通知链。
    ProxyFactory factory = new ProxyFactory(new MyService());
    factory.addAdvice(new LoggingAdvice());
    
    // 获取代理对象
    Object proxy = factory.getProxy();
    
    // 代理对象实现了 Advised 接口
    Advised advised = (Advised) proxy;
    
    // 动态添加新的通知
    advised.addAdvice(new PerformanceMonitoringAdvice());
    

        在代理对象创建后,你可以通过 Advised 接口获取或修改通知链(advice chain)。

        支持动态增强或移除 AOP 切面逻辑,而无需重新创建代理。


 

JDK动态代理

JDK代理只能生成接口的代理,因此,想要代理的对象必须至少实现一个接口,并且生成的代理将是实现该接口的对象,示意图如下:

⚠️使用JDK代理时,所有方法调用都会被JVM拦截并路由到代理的invoke() 方法,也就是说决定如何处理方法调用的决策会在运行时做出

⚠️可以通过使用setInterfaces() 指定要代理的接口列表,从而让ProxyFactory 使用JDK代理

CGLIB代理

CGLIB代理生成的代理类型是目标类的子类

ℹ️ 在首次创建CGLIB代理时,会询问Spring如何处理每个方法。这意味着每次调用JDK代理上的invoke()时执行的许多决策对于CGLIB来说只会执行一次

优缺点及适用场景

JDK 动态代理CGLIB 动态代理
优点

1. 实现简单,原生支持,依赖较少。

2. 适合轻量级、简单场景。

性能高,尤其适合频繁调用。
缺点性能在高频调用时明显不如 CGLIB

1. 使用更复杂,可能引入额外依赖。

2. 需要为目标类生成子类,因此无法代理 final 类或 final 方法。

附 jdk1.8 环境下性能对比 (ms)

  • loop = 50000

    CGLIB (STANDARD)CGLIB (FROZEN)JDK
    advised Method32619
    unadvised Method.11111
    equals() Method2116
    hashcode() Method446
    getTargetClass() Method436
  • loop = 500000

    CGLIB (STANDARD)CGLIB (FROZEN)JDK
    advised Method401240
    unadvised Method.35735
    equals() Method2638
    hashcode() Method5617
    getTargetClass() Method3714
  • loop = 5000000

    CGLIB (STANDARD)CGLIB (FROZEN)JDK
    advised Method12185193
    unadvised Method.9134201
    equals() Method32491
    hashcode() Method132038
    getTargetClass() Method31042

附 测试代码(来源 Spring5 高级编程 CH5.6)

package com.annie.study.proxy;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

/**
 * @className: ProxyPerTest
 * @Description: TODO
 * @author: annie
 * @date: 2024/11/25
 */
public class ProxyPerTest {
    public static void main(String[] args) {
        SimpleBean target = new DefaultSimpleBean();
        Advisor advisor = new DefaultPointcutAdvisor(new TestPointcut(), new NoOpBeforeAdvise());

        runCglibTests(advisor, target);
        runCglibFrozenTests(advisor, target);
        runjdkTests(advisor, target);
    }

    private static void runCglibTests(Advisor advisor, SimpleBean target){
        ProxyFactory factory = new ProxyFactory();
        factory.setProxyTargetClass(true);
        factory.setTarget(target);
        factory.addAdvisor(advisor);

        SimpleBean proxy = (SimpleBean) factory.getProxy();
        System.out.println("Running CGLIB (STANDARD) test");
        test(proxy);
    }

    private static void runCglibFrozenTests(Advisor advisor, SimpleBean target){
        ProxyFactory factory = new ProxyFactory();
        factory.setProxyTargetClass(true);
        factory.setTarget(target);
        factory.addAdvisor(advisor);
        factory.setFrozen(true);

        SimpleBean proxy = (SimpleBean) factory.getProxy();
        System.out.println("Running CGLIB (Frozen) test");
        test(proxy);
    }

    private static void runjdkTests(Advisor advisor, SimpleBean target){
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisor(advisor);
        factory.setInterfaces(SimpleBean.class);

        SimpleBean proxy = (SimpleBean) factory.getProxy();
        System.out.println("Running JDK test");
        test(proxy);
    }
    public static void test(SimpleBean bean){
        int loop = 50000;
        System.out.println("Loop equals " + loop);
        long before = 0;
        long after = 0;
        System.out.println("Testing advised Method...");
        before = System.currentTimeMillis();
        for (int x = 0; x < loop; x ++){
            bean.advised();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");

        System.out.println("Testing unadvised Method...");
        before = System.currentTimeMillis();
        for (int x = 0; x < loop; x ++){
            bean.unadvised();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");

        System.out.println("Testing equals() Method...");
        before = System.currentTimeMillis();
        for (int x = 0; x < loop; x ++){
            bean.equals(bean);
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");

        System.out.println("Testing hashcode() Method...");
        before = System.currentTimeMillis();
        for (int x = 0; x < loop; x ++){
            bean.hashCode();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");

        Advised advised = (Advised) bean;
        System.out.println("Testing getTargetClass() Method...");
        before = System.currentTimeMillis();
        for (int x = 0; x < loop; x ++){
            advised.getTargetClass();
        }
        after = System.currentTimeMillis();
        System.out.println("Took " + (after - before) + " ms");

        System.out.println(">>>>>>\n");
    }
}
public interface SimpleBean {
    void advised();
    void unadvised();
}

public class DefaultSimpleBean implements SimpleBean{
    private long dummy = 0;
    @Override
    public void advised() {
        dummy = System.currentTimeMillis();
    }

    @Override
    public void unadvised() {
        dummy = System.currentTimeMillis();
    }
}
public class TestPointcut extends StaticMethodMatcherPointcut {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return "advised".equals(method.getName());
    }
}

public class NoOpBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
//定义一个不执行任何操作的advice
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值