jdk源码分析书籍 pdf_什么?Spring5 AOP 默认使用Cglib?从现象到源码深度分析

3c84230ab4fbeb5e82915eaeb9c51e98.png

推荐阅读:

阿里工作十年拿下P8,多亏了这些PDF陪我成长(Spring全家桶+源码解析+Redis实战等)​zhuanlan.zhihu.com
8db4acc27925fd86c947149037233c23.png
从入门到熟悉,一步一步带你了解 MySQL 中的「索引」和「锁」​zhuanlan.zhihu.com
f0e14057d15887cfd98da477be4b0a6d.png

29d84d64f277b882cd6592aa2f823e11.png

Spring5 AOP 默认使用 Cglib 了?我第一次听到这个说法是在一个微信群里:

589da8970934ed3bad65531938d99e97.png

真的假的?查阅文档

刚看到这个说法的时候,我是保持怀疑态度的。

大家都知道 Spring5 之前的版本 AOP 在默认情况下是使用 JDK 动态代理的,那是不是 Spring5 版本真的做了修改呢?于是我打开 Spring Framework 5.x 文档,再次确认了一下:

文档地址:http://docs.spring.io/spring/docs…

bbad4da8795947194782791175b18801.png

简单翻译一下。Spring AOP 默认使用 JDK 动态代理,如果对象没有实现接口,则使用 CGLIB 代理。当然,也可以强制使用 CGLIB 代理。

什么?文档写错了?!

当我把官方文档发到群里之后,又收到了这位同学的回复:

6797c6139d9ad6f84c80d554ee03c03d.png

SpringBoot 2.x 代码示例

为了证明文档写错了,这位同学还写了一个 DEMO。下面,就由我来重现一下这个 DEMO 程序:

运行环境:SpringBoot 2.2.0.RELEASE 版本,内置 Spring Framework 版本为 5.2.0.RELEASE 版本。同时添加 spring-boot-starter-aop 依赖,自动装配 Spring AOP。
public interface UserService {void work();
}
@Service
public class UserServiceImpl implements UserService {@Overridepublic void work() {System.out.println("开始干活...coding...");}
}
@Component
@Aspect
public class UserServiceAspect {@Before("execution(* com.me.aop.UserService.work(..))")public void logBefore(JoinPoint joinPoint) {System.out.println("UserServiceAspect.....()");}
}

1b3606bf526dabb554a65aae96ea829e.png

UserServiceImpl实现了UserService接口,同时使用UserServiceAspect对UserService#work方法进行前置增强拦截。

从运行结果来看,这里的确使用了 CGLIB 代理而不是 JDK 动态代理。

难道真的是文档写错了?!

@EnableAspectJAutoProxy 源码注释

在 Spring Framework 中,是使用@EnableAspectJAutoProxy注解来开启 Spring AOP 相关功能的。

Spring Framework 5.2.0.RELEASE 版本@EnableAspectJAutoProxy注解源码如下:

b577138579e2c46aad6c4173ab2ea101.png

通过源码注释我们可以了解到:在 Spring Framework 5.2.0.RELEASE 版本中,proxyTargetClass的默认取值依旧是false,默认还是使用 JDK 动态代理。

难道文档和源码注释都写错了?!

@EnableAspectJAutoProxy 的 proxyTargetClass 无效了?

接下来,我尝试使用@EnableAspectJAutoProxy来强制使用 JDK 动态代理。

运行环境:SpringBoot 2.2.0.RELEASE 版本,内置 Spring Framework 版本为 5.2.0.RELEASE 版本。

816e05c79b6010a614886d3afbbe1a65.png

通过运行发现,还是使用了 CGLIB 代理。难道@EnableAspectJAutoProxy 的 proxyTargetClass设置无效了?

Spring Framework 5.x

整理一下思路

  1. 有人说 Spring5 开始 AOP 默认使用 CGLIB 了
  2. Spring Framework 5.x 文档和 @EnableAspectJAutoProxy源码注释都说了默认是使用 JDK 动态代理
  3. 程序运行结果说明,即使继承了接口,设置proxyTargetClass为false,程序依旧使用 CGLIB 代理

等一下,我们是不是遗漏了什么?

示例程序是使用 SpringBoot 来运行的,那如果不用 SpringBoot,只用 Spring Framework 会怎么样呢?

运行环境:Spring Framework 5.2.0.RELEASE 版本。 UserServiceImpl 和 UserServiceAspect 类和上文一样,这里不在赘述。

eb4114c0121e54d59856b2569a98085f.png

49879d561f4f8926a492bba71a50498d.png

运行结果表明: 在 Spring Framework 5.x 版本中,如果类实现了接口,AOP 默认还是使用 JDK 动态代理。

再整理思路

  1. Spring5 AOP 默认依旧使用 JDK 动态代理,官方文档和源码注释没有错。
  2. SpringBoot 2.x 版本中,AOP 默认使用 cglib,且无法通过proxyTargetClass进行修改。
  3. 那是不是 SpringBoot 2.x 版本做了一些改动呢?

再探 SpringBoot 2.x

结果上面的分析,很有可能是 SpringBoot2.x 版本中,修改了 Spring AOP 的相关配置。那就来一波源码分析,看一下内部到底做了什么。

源码分析

源码分析,找对入口很重要。那这次的入口在哪里呢?

@SpringBootApplication是一个组合注解,该注解中使用@EnableAutoConfiguration实现了大量的自动装配。

EnableAutoConfiguration也是一个组合注解,在该注解上被标志了@Import。关于@Import注解的详细用法,可以参看笔者之前的文章:http://mp.weixin.qq.com/s/7arh4sVH1…

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

AutoConfigurationImportSelector实现了DeferredImportSelector接口。

在 Spring Framework 4.x 版本中,这是一个空接口,它仅仅是继承了ImportSelector接口而已。而在 5.x 版本中拓展了DeferredImportSelector接口,增加了一个getImportGroup方法:

3356f24a6edd5830cf48affcdd220fe1.png

在这个方法中返回了AutoConfigurationGroup类。这是AutoConfigurationImportSelector中的一个内部类,他实现了DeferredImportSelector.Group接口。

在 SpringBoot 2.x 版本中,就是通过AutoConfigurationImportSelector.AutoConfigurationGroup#process方法来导入自动配置类的。

9239171a46ab46efe470914f260de163.png

通过断点调试可以看到,和 AOP 相关的自动配置是通过org.springframework.boot.autoconfigure.aop.AopAutoConfiguration来进行配置的。

52fee74f89b3623415f02df554644d42.png

真相大白

看到这里,可以说是真相大白了。在 SpringBoot2.x 版本中,通过AopAutoConfiguration来自动装配 AOP。

默认情况下,是肯定没有spring.aop.proxy-target-class这个配置项的。而此时,在 SpringBoot 2.x 版本中会默认使用 Cglib 来实现。

SpringBoot 2.x 中如何修改 AOP 实现

通过源码我们也就可以知道,在 SpringBoot 2.x 中如果需要修改 AOP 的实现,需要通过spring.aop.proxy-target-class这个配置项来修改。

#在application.properties文件中通过spring.aop.proxy-target-class来配置
spring.aop.proxy-target-class=false

08de55e97ae0bba07a4f9d91c8afaa2b.png

这里也提一下spring-configuration-metadata.json文件的作用:在使用application.properties或application.yml文件时,IDEA 就是通过读取这些文件信息来提供代码提示的,SpringBoot 框架自己是不会来读取这个配置文件的。

SringBoot 1.5.x 又是怎么样的

ad8f7bf99db43d9b14c1610c3d83cbf4.png

可以看到,在 SpringBoot 1.5.x 版本中,默认还是使用 JDK 动态代理的。

SpringBoot 2.x 为何默认使用 Cglib

SpringBoot 2.x 版本为什么要默认使用 Cglib 来实现 AOP 呢?这么做的好处又是什么呢?笔者从网上找到了一些资料,先来看一个 issue。

Spring Boot issue #5423

Use @EnableTransactionManagement(proxyTargetClass = true) #http://5423github.com/spring-proj…

在这个 issue 中,抛出了这样一个问题:

625cb5a24cdc8c54ed94c5ca383295b4.png
翻译一下:我们应该使用@EnableTransactionManagement(proxyTargetClass = true)来防止人们不使用接口时出现讨厌的代理问题。

这个"不使用接口时出现讨厌的代理问题"是什么呢?思考一分钟。

讨厌的代理问题

假设,我们有一个UserServiceImpl和UserService类,此时需要在UserContoller中使用UserService。在 Spring 中通常都习惯这样写代码:

@Autowired
UserService userService;

在这种情况下,无论是使用 JDK 动态代理,还是 CGLIB 都不会出现问题。

但是,如果你的代码是这样的呢:

@Autowired
UserServiceImpl userService;

这个时候,如果我们是使用 JDK 动态代理,那在启动时就会报错:

1b572b0dd7d8accfc28881f3400f7e53.png

因为 JDK 动态代理是基于接口的,代理生成的对象只能赋值给接口变量。

而 CGLIB 就不存在这个问题。因为 CGLIB 是通过生成子类来实现的,代理对象无论是赋值给接口还是实现类这两者都是代理对象的父类。

SpringBoot 正是出于这种考虑,于是在 2.x 版本中,将 AOP 默认实现改为了 CGLIB。

更多的细节信息,读者可以自己查阅上述 issue。

总结

  1. Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。
  2. SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。
  3. 在 SpringBoot 2.x 中,如果需要默认使用 JDK 动态代理可以通过配置项spring.aop.proxy-target-class=false来进行修改,proxyTargetClass配置已无效。
转载自作者:程序员小黑
链接:https://juejin.im/post/5db7870a518825647178f16c

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288939.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx monitor

为什么80%的码农都做不了架构师?>>> 最近在初步研究了一下nginx的监控,主要是想监控一些和业务相关的信息,发现能用的方案不多,主要有如下: 1 监控解析nginx log:ngxtop 官方的解释是可以…

ASP.NET 6 中间件系列 - 执行顺序

这篇文章是 ASP.NET 6 中间件系列文章的第 3 部分,你还可以阅读第1部分和第2部分。我们通过中间件创建的管道是有执行顺序的,执行顺序与中间件的添加顺序是相同的,接下来我们讨论一下为什么要有执行顺序,以及它的重要性。示例项目…

OSChina 周四乱弹 ——程序员怎么撩外国妹子攻略

2019独角兽企业重金招聘Python工程师标准>>> Osc乱弹歌单(2017)请戳(这里) 【今日歌曲】 冬天之雪 :听歌听到苏菲玛索和刘欢演唱《玫瑰人生》。有网友评论:法语专业的刘欢老师等的就是这一刻。…

【C语言简单说】七:自定义函数(3)

前一节说了返回值的内容,那么这一节就说一下参数的内容 手打码了几章内容了。。。。困。^( ̄) ̄)《( ̄) ̄)^困.困.困. 相比看过前一节的知道我说自定义函数如果你要做一个运…

WireShare抓包在ssl协议里面提示(Level: Fatal, Description: Protocol Version)

1 问题 在后台访问部分链接的时候抓包,客户端发了client hello包,但是没有收到Server hello包, 提示:Alert(Level: Fatal, Description: Handshake Failure ) 包文如下 2 解决办法 是因为客户端的ssl协议版本和链接地址环境ss…

对于 APM 用户的一次真实调查分析(下)

一.前言 对 APM 用户的一次真实调查分析(上)中,我们主要聊到了现阶段国外 APM 行业对各个企业的渗透率、大部分使用 APM 工具的企业规模以及 APM 工具在用户心中的地位等问题,有兴趣的朋友可以点击链接观看。 我们本次继续顺着这个…

linux cpu核数查看_Linux日常必备的 8 个小技能

身为一个码农,日常工作中与我们打交道次数较多的操作系统除了Windows和Mac OS 之外,还有一个就是 Linux。今天偶尔有空翻越了之前码代码时期汇总的一些小技巧发现挺实用的,故分享给大家,希望能对大伙有一定的帮助。1. 如何查看系统…

NotificationManagerService使用详解与原理分析(一)

概况 Android在4.3的版本中(即API 18)加入了NotificationListenerService,根据SDK的描述(AndroidDeveloper)可以知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法。同时在Android 4.4 中新增了Notifica…

【C语言简单说】八:分支结构之if(1)

今天貌似更了很多章了,现在感觉累觉不爱。。。 ┐(—__—)┌ 你说我有啥米办法咧~(要不叫别人替我更一下?) 继续更。。。 这一节我们来说一下if语句;这个东西可是很常用的呀;在此之前我们来举个例子&…

ASP.NET 6 中间件系列 - 自定义中间件类

这篇文章是 ASP.NET 6 中间件系列文章的第2部分,点击这里可以阅读第1部分。在上一篇文章中,我们讨论了什么是中间件,它的作用是什么,以及在 ASP.NET 6 应用管道中添加中间件的简单方法。在这篇文章中,我们将在这些基础…

如何在IE浏览器里面定位到关键字的位置(页面代码)和这个关键字位置模块的请求

1 问题 比如用IE浏览器,打开一个页面,如何定位到关键字的具体位置,以及这个位置请求是什么?可能这个请求不是主页面的请求,因为我们知道页面html里面可以嵌套很多Frame(框架),把页面分割成很多块,然而每个Frame(框架)里面可以再嵌套一个url,有时候我们需要找到这个请求…

Java并发编程-原子性变量

image.png1. 原子性布尔 AtomicBoolean AtomicBoolean 类为我们提供了一个可以用原子方式进行读和写的布尔值,它还拥有一些先进的原子性操作,比如 compareAndSet()。AtomicBoolean 类位于 java.util.concurrent.atomic 包,完整类名是为 java.…

【C语言简单说】八:分支结构之if...else...(2)

上一节我们说了if的基本用法,这一小节我们来说明if…else…的用法 首先惯例举例子: 你今天早上饿了,打算去吃包子,可是没有包子了,你打算去吃米粉。 你昨天早上下雨了,带伞出门,结果没找到&a…

Java集合之LinkedList

上一篇写的是ArrayList,这一篇写一下LinkedList. 开宗明义,因为Vector已经被废弃了,所以list家族只剩下ArrayList和LinkedList两兄弟了,这里直接对比一下二位: ArrayList基于动态数组的实现,它长于随机访问…

由于开发者通过接口修改了菜单配置_Android SDK开发艺术探索(四)个性化配置...

一、前言本篇是Android SDK开发艺术探索系列的第四篇文章。介绍了通过流式API设计思想优雅地实现SDK的自定义选项配置需求。目录概览:一、前言 二、SDK自定义配置2.1、什么是自定义配置2.2、设计一个配置方法 三、结语系列文章:Android SDK开发艺术探索&…

C#中切片语法糖的使用

例子首先我们看这样一个例子,有这样一个数组string [] lst new string[] { "1", "2", "3", "4", "5", "6", "7" };我们怎么获取它的最后一个值, 传统方法是这样写的&#xff0c…

JavaScript 语言基础知识点总结(思维导图)

1.JavaScript数组 2.JavaScript 函数基础 3.Javascript 运算符 4.JavaScript 流程控制 5.JavaScript 正则表达式 6.JavaScript 变量 7.JavaScript 字符串函数 8.DOM 基本操作 制作工具:Mindjet MindManager 文章摘自:http://m.oschina.net/blog/175426转…

linux之一些比较新但是常用的命令(expr ag tree cloc stat tmux axel)

1 expr命令 介绍:这个命令用来匹配正则表达式,这个命令linux系统自带,不信你自己试下 使用:expr 正则表达式 输出结果 expr http:\/\/www\.baidu\.com http//www.baidu.com 用了这个命令,我们就不需要在网上去搞在线正则表达式匹配 2 tree命令 这个命令需要安装 sudo…

基于tiny4412的Linux内核移植 -- MMA7660驱动移植(九)

作者信息 作者: 彭东林 邮箱:pengdonglin137163.com QQ:405728433 平台简介 开发板:tiny4412ADK S700 4GB Flash 要移植的内核版本:Linux-4.4.0 (支持device tree) u-boot版本:友善之臂自带的…

【C语言简单说】八:分支结构之if...else if()...else...(3)

既然前面几种情况大家都了解了话&#xff0c;这一节的话我就不举例子了。。。 直接上代码&#xff1a; #include<stdio.h> #include<stdlib.h> int main() {int a1;if(a1){printf("a的值等于1\n");}else if(a2){printf("a的值等于2\n"); …