Spring从成神到升仙系列 三2023年再不会 AOP 源码,就要被淘汰了
Posted 爱敲代码的小黄
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring从成神到升仙系列 三2023年再不会 AOP 源码,就要被淘汰了相关的知识,希望对你有一定的参考价值。
- 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
- 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
- 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
- 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
- 📝联系方式:hls1793929520,和大家一起学习,一起进步👀
文章目录
Spring AOP源码解析
一、引言
对于Java开发者而言,关于 Spring
,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。
但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。
本期 Spring
源码解析系列文章,将带你领略 Spring
源码的奥秘
本期源码文章吸收了之前 Kafka
源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。
废话不多说,发车!
本文流程图可关注公众号:爱敲代码的小黄,回复:AOP 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用
二、适合人群
本文采用的 Spring
版本为:4.3.11.RELEASE
本文适合人群:使用过Spring AOP的功能,了解过切面编程
本文前置知识:
- Spring-IOC源码剖析(必看)
- Spring-代理源码剖析(必看)
如果你都已经满足,那么跟我一起了解一下抽象的 AOP
吧。
三、Spring AOP配置
首先创建我们的核心类:BeanA
public class BeanA
public void do1()
System.out.println("I am a do1, I start....");
我们日志配置类:LogUtil
public class LogUtil
private void before(JoinPoint joinPoint)
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取参数信息
Object[] args = joinPoint.getArgs();
System.out.println("log---" + signature.getName() + "I am before");
private void after(JoinPoint joinPoint)
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取参数信息
Object[] args = joinPoint.getArgs();
System.out.println("log---" + signature.getName() + "I am before");
我们 xml
配置:application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="beanA" class="com.hls.aop.BeanA"/>
<bean id="logUtil" class="com.hls.aop.LogUtil"/>
<aop:config>
<aop:aspect ref="logUtil">
<aop:pointcut id="myPoint" expression="execution(* com.hls.aop.BeanA.do*(..))"/>
<aop:before method="before" pointcut-ref="myPoint"/>
<aop:after method="after" pointcut-ref="myPoint"/>
</aop:aspect>
</aop:config>
</beans>
编写我们的测试类:AOPTest
public class AOPTest
public static void main(String[] args)
ApplicationContext context = new GenericXmlApplicationContext("application.xml");
BeanA beanA = context.getBean(BeanA.class);
beanA.do1();
运行输出结果:
log---do1I am before
I am a do1, I start....
log---do1I am before
四、Spring AOP 组件分析
AOP
的组件在源码中比较重要的有四个:
- Pointcut:定义切面的匹配点,主要是类和方法
- Advice:定义切面的行为,即在匹配点执行的操作。
- Advisor:将 Pointcut 和 Advice 组合成一个对象,表示一个完整的切面。
- Aspect:使用注解或
XML
配置方式定义切面,通常包含多个 Advisor
我们依次讲解这些组件是什么意思
1、Pointcut
一般情况下我们的 Pointcut
都是以这种形式出现:
<aop:pointcut id="myPoint" expression="execution(* com.mashibing.hls.aop.BeanA.do*(..))"/>
主要就是指明切入点的表达式,比如上述表达式:* com.hls.aop.BeanA.do*(..)
代表 com.hls.aop
包下的 BeanA
类以 do
开头的方法都可以进行切入
我们看下源码中的形式:
public interface Pointcut
/**
* 在类级别上限定joinpoint的匹配范围
*
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never @code null)
*/
ClassFilter getClassFilter();
/**
* 在方法级别上限定joinpoint的匹配范围
*
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never @code null)
*/
MethodMatcher getMethodMatcher();
/**
* 用于匹配上的一个实例,永远返回true
*
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
// Pointcut 的实现
public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware
// 类的匹配
public boolean matches(Class<?> targetClass)
// 方法的匹配
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions)
通过源码我们可以发现,这个类提供的功能主要是用来匹配类与方法。
2、Advice
一般情况下,我们的 Advice
以这种情况出现:
<aop:before/>
<aop:after/>
主要的目的就是定义我们切面的行为,比如:方法前切入、方法后切入、环绕切入等
我们看下源码的形式:
public interface Advice
// 前置切入实现
public interface BeforeAdvice extends Advice
// 后置切入实现
public interface AfterAdvice extends Advice
一般我们的 Advice
都与 Pointcut
相结合使用
3、Advisor
一般情况下,我们的 Advisor
以这种情况出现:
<aop:before method="before" pointcut-ref="myPoint"/>
<aop:after method="after" pointcut-ref="myPoint"/>
将 Advice
和 Pointcut
结合起来使用
我们看下源码的形式:
public interface Advisor
// 返回切面对应的通知
Advice getAdvice();
很明显可以看出,这个类主要包装了 Advice
,后续我们会继续讲到
4、Aspect
一般情况下,我们的 Aspect
以这种情况出现:
<aop:aspect ref="logUtil">
</aop:aspect>
定义一个日志配置类,其方法作为实际的切入业务逻辑
5、总结
从我们上述的描述得知,我们几个组件的关系如下:
大家这里心里有个大致的了解即可,我们后面会详细讲到。
五、Spring AOP 源码剖析
同样和我们的 IOC
类似,我们从入口开始:
ApplicationContext context = new GenericXmlApplicationContext("application.xml");
1、beanDefinition的注册
同样和我们 IOC
流程一样,先进行 xml
文件的解析
我们直接找到 DefaultBeanDefinitionDocumentReader
类的 parseBeanDefinitions
方法
这里从业务来说,做了两件事:
- 注册了
AspectJAwareAdvisorAutoProxyCreator
的类,方便之后代理的扩展 - 解析
xml
中<aop:config>
的配置,最终生成AspectJPointcutAdvisor
的BeanDefinition
注册至DefaultListableBeanFactory
的BeanDefinitionMap
中
1.1 AspectJAwareAdvisorAutoProxyCreator的注册
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
if (delegate.isDefaultNamespace(root))
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++)
Node node = nl.item(i);
if (node instanceof Element)
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele))
// 正常的标签,IOC中讲过
parseDefaultElement(ele, delegate);
else
// <AOP>、<dubbo> 类的标签
delegate.parseCustomElement(ele);
public BeanDefinition parseCustomElement(Element ele)
return parseCustomElement(ele, null);
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd)
// 获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
// 根据命名空间找到对应的NamespaceHandlerspring
// 因为我们这里处理的是 AOP 的标签,所以会有 AopNamespaceHandler 的执行
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
// 调用自定义的NamespaceHandler(AopNamespaceHandler)进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
public BeanDefinition parse(Element element, ParserContext parserContext)
// 获取元素的解析器
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
public BeanDefinition parse(Element element, ParserContext parserContext)
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 注册自动代理模式创建器,AspectjAwareAdvisorAutoProxyCreator
configureAutoProxyCreator(parserContext, element);
private void configureAutoProxyCreator(ParserContext parserContext, Element element)
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
public static void registerAspectJAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement)
// 注册名为org.springframework.aop.config.internalAutoProxyCreator的beanDefinition,其中的class类为`AspectJAwareAdvisorAutoProxyCreator`,其也会被注册到bean工厂中
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
// 如果指定proxy-target-class=true,则使用CGLIB代理,否则使用JDK代理
// 其实其为AspectJAwareAdvisorAutoProxyCreator类的proxyTargetClass属性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// 注册到spring的bean工厂中,再次校验是否已注册
registerComponentIfNecessary(beanDefinition, parserContext);
public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source)
return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source)
// cls = AspectJAwareAdvisorAutoProxyCreator
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// key = org.springframework.aop.config.internalAutoProxyCreator
// value = org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator
// 这里正式注册了 !!!
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
有读者可能疑惑,这里为什么要大费周折注册一个 AspectJAwareAdvisorAutoProxyCreator
这个类,他有什么特殊的地方嘛
这里先卖个关子,后续我们会讲到
1.2、AspectJPointcutAdvisor 的注册
由于这一段的注册逻辑写的很绕、很晦涩难懂,这里博主带大家大致的浏览下流程
不然,直接劝退了大部分人
我们跟着上面这部分源码继续往下看:
public BeanDefinition parse(Element element, ParserContext parserContext)
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 注册自动代理模式创建器,AspectjAwareAdvisorAutoProxyCreator
configureAutoProxyCreator(parserContext, element);
// 解析aop:config子节点下的aop:pointcut/aop:advice/aop:aspect
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts)
String localName = parserContext.getDelegate().getLocalName(elt);
if (POINTCUT.equals(localName))
parsePointcut(elt, parserContext);
else if (ADVISOR.equals(localName))
parseAdvisor(elt, parserContext);
// 因为我们上面是 <aop:aspect/> 的标签,会走这个业务逻辑
else if (ASPECT.equals(localName))
parseAspect(elt, parserContext);
parserContext.popAndRegisterContainingComponent();
return null;
private void parseAspect(Element aspectElement, ParserContext parserContext)
// <aop:aspect> id属性
String aspectId = aspectElement.getAttribute(ID);
// aop ref属性,必须配置。代表切面
String aspectName = aspectElement.getAttribute(REF);
try
List<BeanDefinition> beanDefinitions = new ArrayList<>();
List<BeanReference> beanReferences = new ArrayList<>();
// 解析其下的advice节点
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++)
Node node = nodeList.item(i);
// 是否为advice:before/advice:after/advice:after-returning/advice:after-throwing/advice:around节点
// 这个就是 advice 的种类,不明白的小伙伴可以百度一下
if (isAdviceNode(node, parserContext))
// 校验aop:aspect必须有ref属性,否则无法对切入点进行观察操作
if (!adviceFoundAlready)
adviceFoundAlready = true;
if (!StringUtils.hasText(aspectName))
return;
beanReferences.add(new RuntimeBeanReference(aspectName));
// 解析advice节点并注册到bean工厂中
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);
AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
parserContext.pushContainingComponent(aspectComponentDefinition);
// 解析aop:point-cut节点并注册到bean工厂
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts)
parsePointcut(pointcutElement, parserContext);
parserContext.popAndRegisterContainingComponent();
finally
this.parseState.pop();
到这里我们先停一下,从上述源码中我们得到了一些信息,这部分源码的注册基本就三个方面:
- 解析
Advice
节点并注册到bean
工厂中 - 解析
Advisor
节点注册到bean
工厂中 - 解析
Pointcut
节点并注册到bean
工厂
// 整体的注册方法
AbstractBeanDefinition advisorDefinition = parseAdvice(aspectName, i, aspectElement,
2019.11.15
好味廚
20:30-22:00
硬
幣
兌
換
亨利 亞當斯
人在香港,剛下飛機,還沒上公交車。
身上只有一張百萬英鎊支票
誰能幫我換下硬幣
一百萬,公交車司機實在找不開
在線等,急
59分钟前
馬克 吐溫, 平安香港, CSSAUG
CSSAUG :還等什麼~快來我們的硬幣兌換活動吧!
五阿哥
謝CSSAUG解決了我的大麻煩!
感恩!
30分钟前
皇上, 三阿哥, 公主, 駙馬爺, 羅天師, CSSAUG
CSSAUG : 感謝永琪老鐵的宣傳!我們11.15恭候您的到來!
CSSAUG
為滿足CSSAUG會員對不同種類錢幣的需求,方便會員手中錢幣的儲存及使用,我們為會員們搭建了一個硬紙幣(HKD)兌換的平臺--硬幣兌換活動~!
我們可以幫大家把紙幣兌換成硬幣,或將硬幣兌換成紙幣~
對於以下幾種情況,需要您點擊下方閱讀全文進行登記哦~
1. 硬幣兌換紙幣金額超過100HKD
2. 紙幣兌換硬幣超過20枚
3. 有需要特定面額硬幣的要求
※ 建議兌換其他金額的會員填寫預約表格(可選),我們會優先保證有預約會員的兌換。
活動時間:2019年11月15日(星期五)20:30~22:00
活動地點:學生宿舍區Hall Canteen 好味廚 (好妹子)
預約截止時間 :2019年11月10日23:59
26分钟前
五阿哥,小燕子,容嬤嬤, 亨利 亞當斯,馬克吐溫, CSSAUG
五阿哥:老鐵666沒毛病!
亨利 亞當斯 :現在我旁邊有一卡車硬幣,誰能來幫我搬一下,在線等,急。
CSSAUG 福利部 徐陳晨
大家好呀~!
我是此次硬幣兌換的pic徐陳晨,大家有什麼問題可以聯繫我呢~左邊是我的微信!
郵箱:chennchenn16@gmail.com
電話:46400710
右邊是福利部部長姬皓冉的微信哦~
大家有什麼疑問也可以問他呢!
郵箱:1565877096ji@gmail.com
電話:46405474
26分钟前
亨利 亞當斯
亨利 亞當斯:能不能來幫我搬下硬幣...
文案&排版:吳子揚
以上是关于Spring从成神到升仙系列 三2023年再不会 AOP 源码,就要被淘汰了的主要内容,如果未能解决你的问题,请参考以下文章
Spring从成神到升仙系列 四从源码分析 Spring 事务的来龙去脉
Netty 从成神到升仙系列 三Netty 凭什么成为国内最流行的网络通信框架?
Kafka从成神到升仙系列 四你真的了解 Kafka 的缓存池机制嘛