Spirng和SpringBoot中的Aop优先使用的是JDK动态代理还是Cglib

一、Spring Aop 的实现

如果代理对象有接口,就用 JDK 动态代理。JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法时,会先调用实现了 InvokeHandler 接口的 invoke() 方法,来实现业务增强

如果代理对象没有接口,那么就直接使用 Cglib 动态代理。Cglib 动态代理是利用 asm 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理

来自 Spring 官方文档的说辞

可以看到,即使在最新版的 Spring 中,依然是如上策略不变。即能用 JDK 做动态代理就用 JDK,不能用 JDK 做动态代理就用 Cglib,即首选 JDK 做动态代理

Spring 中代理对象产生的源码

DefaultAopProxyFactory 类的 createAopProxy() 方法,具体源码可以参考

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {

    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        // 获得被代理对象的类型,以确定代理的方式
        Class<?> targetClass = config.getTargetClass();
        // 如果对象类型是接口,或者是JAVA的动态代理类,那么就调用JDK的动态代理方法生成代理对象
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        // 否则使用CGLIB生成代理对象
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

二、SpringBoot 中 Aop 的实现

SpringBoot 和 Spring 一脉相承,那么在动态代理这个问题上是否也是相同的策略呢?抱歉,这个还真不一样,SpringBoot 中对这个问题的处理,以 SpringBoot 2.0 为节点,前后不一样

2.1、SpringBoot 2.0 之前

关于 Aop 的自动化配置代码是这样的(SpringBoot 1.5.22.RELEASE)

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",matchIfMissing = true)
    public static class JdkDynamicAutoProxyConfiguration {

    }

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
    public static class CglibAutoProxyConfiguration {

    }
}

可以看到,这个自动化配置主要是在论述 application.properties/yml 配置文件中的 spring.aop.proxy-target-class 属性的值

具体起作用的是 @ConditionalOnProperty 注解,关于这个注解中的几个属性

  • prefix:配置文件的前缀
  • name:配置文件的名字,和 prefix 共同组成配置的 key
  • having:期待配置的值,如果实际的 spring.aop.proxy-target-class 配置和 having 的值相同,则这个配置就会生效,否则不生效
  • matchIfMissing:如果开发者没有在 application.properties/yml 中进行配置,那么这个配置类是否生效

基于如上介绍,我们很容易看出

  • 如果开发者设置了 spring.aop.proxy-target-class 为 false,则使用 JDK 代理
  • 如果开发者设置了 spring.aop.proxy-target-class 为 true,则使用 Cglib 代理
  • 如果开发者一开始就没有配置 spring.aop.proxy-target-class 属性,则使用 JDK 代理

这是 SpringBoot 2.0 之前的情况

2.2、SpringBoot 2.0 之后

再来看看 SpringBoot 2.0(含)之后的情况(Spring Boot 2.0.0.RELEASE)

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
    public static class JdkDynamicAutoProxyConfiguration {

    }

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
    public static class CglibAutoProxyConfiguration {

    }
}

AopAutoConfiguration 这个类是干嘛的?

AopAutoConfiguration 这个类就是一个配置类,SpringBoot 会去扫描这种配置类,并将其加载进 Spring 容器中,但是加载进 Spring 容器中是有条件的,这就使用到了 @Conditional 系列的条件注解

@ConditionalOnClass 注解:当类路径下存在 EnableAspectJAutoProxy 或 Aspect 或 Advice 或 AnnotatedElement 任意一个类时,Spring 容器才会去加载实例化 AopAutoConfiguration 类

对于 EnableAspectJAutoProxy 这个类它存在于 spring-context 的 jar 包中,即无需引入 aop 的依赖;而对于 Aspect 和 Advice 类就必须引入以下依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

可以看到,大部分配置都是一样的,有一个地方不太相同,那就是 matchIfMissing 属性的值。可以看到,从 SpringBoot 2.0 开始,如果用户什么都没有配置,那么默认情况下使用的是 Cglib 代理

2.3、2.0之后的测试

创建一个 SpringBoot 项目(版本 2.0.9.RELEASE),进行测试

Maven 主要依赖

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

下面使用的是 Spring 的事务进行测试,是不需要引入 aop 依赖的,在上面已经阐述了原因,AopAutoConfiguration 这个配置类依然可以被 Spring 加载进容器中

如果使用了 @Aspect 注解进行了切面增强,那么此时必须引入 aop 的依赖

Service 层

简单的进行插入数据操作,添加 @Transactional 注解

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    @Override
    public int addUser(List<User> list) {
        list.forEach(item -> {
            log.info("item的值为:" + item.toString());
        });
        return userMapper.batchAddUser(list);
    }
}

Controller 层

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping(path = "/batchInsertUser")
    @ResponseBody
    public int batchInsertUser() {
        List<User> list = new ArrayList<>();
        User user1 = new User("李四", "123456", "李四");
        User user2 = new User("张三", "123123", "张三");
        list.add(user1);
        list.add(user2);
        return userService.addUser(list);
    }
}

Debug 测试

默认配置测试

测试结论:从 SpringBoot 2.0 开始,如果用户什么都没有配置,那么默认情况下优先使用的是 Cglib 代理

使用 JDK 进行代理

如果想用 JDK 来代理,那么只需要在 application.properties 中添加如下配置即可

spring.aop.proxy-target-class=false

添加完成后,重新 DEBUG,如下图

 

三、小结

Spring 中的 AOP,有接口就用 JDK 动态代理,没有接口就用 Cglib 动态代理;并优先使用 JDK 动态代理

SpringBoot 中的 AOP,在 2.0 版本之前和 Spring 一样

SpringBoot 中的 AOP,在 2.0 版本之后首选 Cglib 动态代理,如果用户想要使用 JDK 动态代理,需要自己手动配置

 

摘自:https://blog.csdn.net/weixin_38192427/article/details/121621872

posted @ 2022-03-15 14:25  Arbitrary233  阅读(520)  评论(0编辑  收藏  举报