首页 > 其他分享 >Spring Boot 中的 AOP,到底是 JDK 动态代理还是 Cglib 动态代理?

Spring Boot 中的 AOP,到底是 JDK 动态代理还是 Cglib 动态代理?

时间:2023-03-23 16:25:24浏览次数:30  
标签:JDK spring 代理 Spring 动态 class

好啦,开始今天的正文。

大家都知道,AOP 底层是动态代理,而 Java 中的动态代理有两种实现方式:

  • 基于 JDK 的动态代理
  • 基于 Cglib 的动态代理

这两者最大的区别在于基于 JDK 的动态代理需要被代理的对象有接口,而基于 Cglib 的动态代理并不需要被代理对象有接口。

那么小伙伴们不禁要问,Spring 中的 AOP 是怎么实现的?是基于 JDK 的动态代理还是基于 Cglib 的动态代理?

1. Spring

先来说结论,Spring 中的动态代理,具体用哪种,分情况:

如果代理对象有接口,就用 JDK 动态代理,否则就是 Cglib 动态代理。

如果代理对象没有接口,那么就直接是 Cglib 动态代理。

来看看这段来自官方文档的说辞:

 

 

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

2. Spring Boot

Spring Boot 和 Spring 一脉相承,那么在动态代理这个问题上是否也是相同的策略呢?抱歉,这个还真不一样。

Spring Boot 中对这个问题的处理,以 Spring Boot2.0 为节点,前后不一样。

在 Spring Boot2.0 之前,关于 Aop 的自动化配置代码是这样的(Spring Boot 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 配置文件中的 spring.aop.proxy-target-class 属性的值。

具体起作用的是 @ConditionalOnProperty 注解,关于这个注解中的几个属性,松哥也来稍微说下:

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

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

  • 如果开发者设置了spring.aop.proxy-target-class 为 false,则使用 JDK 代理。
  • 如果开发者设置了spring.aop.proxy-target-class 为 true,则使用 Cglib 代理。
  • 如果开发者一开始就没配置spring.aop.proxy-target-class 属性,则使用 JDK 代理。
  • 这是 Spring Boot 2.0 之前的情况。

再来看看 Spring Boot 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 {

 }

}

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

3. 实践

最后我们写一个简单的例子验证一下我们的想法。

首先创建一个 Spring Boot 项目(本案例使用最新版 Spring Boot,即默认使用 Cglib 代理),加入三个依赖即可,如下:

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

 

接下来我们创建一个 IUserService 接口,如下:

public interface IUserService {
    void hello();
}

 

然后我们再来创建一个该接口的实现类:

@Service
public class UserServiceImpl implements IUserService {
    @Override
    public void hello() {

    }
}

方法不用实现。

再来一个简单的切面:

@EnableAspectJAutoProxy
@Aspect
@Component
public class LogAspect {
    @Before("execution(* org.javaboy.demo.UserServiceImpl.*(..))")
    public void before(JoinPoint jp) {
        System.out.println("jp.getSignature().getName() = " + jp.getSignature().getName());
    }
}

最后再来一个简单的测试方法,注入 IUserService 实例:

@RestController
public class HelloController {
    @Autowired
    IUserService iUserService;
    @GetMapping("/hello")
    public void hello() {
        iUserService.hello();
    }
}

DBUEG 运行一下,就可以看到 IUserService 是通过 Cglib 来代理的。

图片

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

spring.aop.proxy-target-class=false

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

 

 

可以看到,已经使用了 JDK 动态代理了。

如果用的是 Spring Boot 1.5.22.RELEASE 这个版本,那么即使不在 application.properties 中添加配置,默认也是 JDK 代理,这个我就不测试了,小伙伴们可以自己来试试。

4. 小结

总结一下:

  • Spring 中的 AOP,有接口就用 JDK 动态代理,没有接口就用 Cglib 动态代理。
  • Spring Boot 中的 AOP,2.0 之前和 Spring 一样;2.0 之后首选 Cglib 动态代理,如果用户想要使用 JDK 动态代理,需要自己手动配置。

标签:JDK,spring,代理,Spring,动态,class
From: https://www.cnblogs.com/csnjava/p/17247884.html

相关文章

  • 代理模式
    代理模式通过代理对象来访问真实对象,相当于对真实对象进行了封装操作。Subject:真实对象和代理对象的共同接口;Proxy:代理对象,实现了与真实对象相同的接口,所以在任何时刻都......
  • 【动态规划】【矩阵快速幂优化】【XR-1】分块
    【XR-1】分块题目描述有一个长度为\(n\)的序列,xht37现在想分块维护它。PinkRabbit要求他只准将序列分成\(PR\)种长度的块。NaCly_Fish要求他只准将序列分成\(N......
  • Java数组的动态初始化与静态初始化和常见问题
    一、动态初始化数组的格式:数据类型[]数组名=new数据类型[数组长度];在创建的时候,由我们自己指定数组的长度,由虚拟机给出默认的初始化值。数组默认的初始值规律:1、......
  • 【leetcode-动态规划】斐波那契数
    题目:斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:F(0)=0,  F(1) =1F(N)=......
  • vue3中如何通过遍历传入组件名称动态创建多个component 组件
    背景在vue3中,如果使用component,可以动态加载一个组件,例如<!--直接创建--><component:is="Image"/>这样会将已经定义好并导入的比如Image组件加载出来,但......
  • 01背包问题(动态规划)
    【说明】有n个物品,第i个物品价值为v(i),重量为w(i),其中v(i)和w(i)均为非负数,背包的容量为W,W为非负数。现需要考虑如何选择装入背包的物品,使装入背包的物品总价值最大。......
  • Delphi动态创建组件,并释放内存
    unitUnit1;interfaceusesWinapi.Windows,Winapi.Messages,System.SysUtils,System.Variants,System.Classes,Vcl.Graphics,Vcl.Controls,Vcl.Forms,......
  • JDK20正式发布了GA版本,短期维护支持,以及JDK21预览
    JDK20正式发布了GA版本,短期维护支持,以及JDK21预览最近,Oracle发布了JDK20,相比对于Java开发者来说,JDK的发版是比较收关注的事情了,小简也来和大家一起了解了解JDK20发生了什......
  • Vue进阶(二十五):<component>实现动态组件
    一、前言<component>元素是vue里面的内置组件。在<component>里面使用:is,可以实现动态组件的效果。二、示例解析下面例子创建一个包含多子组件的vue实例。vue......
  • jsp 静态引入<%@ include %> 动态引入<jsp:include> 区别
    1.首先先介绍下,jsp机制: servlet容器,先将jsp转化成servlet,然后编译成.class文件,放置容器缓冲区【tomcat的work目录下】。每次调用jsp时,服务器会读取编译好的servler.class,......