Spring AOP 源码系列(一)解析 AOP 配置信息
在进行源码阅读之前建议先看一下这篇文章:Spring AOP 源码分析系列文章导读 by 田小波,写的非常好,推荐阅读。
关于 AOP 中常用的一些术语这里就不解释了,如果不清楚的建议先看一遍上面推荐的文章。
一、AOP 配置入口
在分析源码之前,我们先来看一下 XML 中关于 AOP 的配置:
<bean id="userServiceImpl" class="com.jas.mess.aop.UserServiceImpl"/><bean id="ownerAspect" class="com.jas.mess.aop.spring.OwnerAspect"/><aop:config><aop:aspect ref="ownerAspect"><aop:pointcut id="ownerPointcut" expression="execution(* com.jas.mess.aop.*.*(..))"/><aop:before method="recordLog" pointcut-ref="ownerPointcut"/></aop:aspect></aop:config>
在以前的文章里有总结过,XML 文件的配置信息会被转化成 BeanDefinition
,其中 AOP 相关的配置也不例外,从上面的例子中我们能看出一个明显的区别:普通的 bean 标签与 AOP 配置标签不一样,一个是 <bean />
一个是 <aop />
,除此之外你要想配置 AOP,还需要在 XMl 中引入 xmlns:aop="http://www.springframework.org/schema/aop"
命名空间。
普通的 bean 标签与 AOP 标签处理逻辑通过命名空间区分,下面我们来看源码。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 命名空间检查,默认的命名空间是 "http://www.springframework.org/schema/beans"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;// <bean /> 标签处理入口if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {// <aop /> 标签处理入口delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}
上面我们说过,AOP 的命名空间是 http://www.springframework.org/schema/aop
,因此会执行到 delegate.parseCustomElement(ele)
。
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间获取 handlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}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);}
上面的流程比较简单,根据命名空间 URI 获取命名空间 handler,这里对应的是 AopNamespaceHandler
,通过 handler 去解析配置。handler 内部会根据标签配置信息得到 ConfigBeanDefinitionParser
,最终调用 parse
方法。
public BeanDefinition parse(Element element, ParserContext parserContext) {CompositeComponentDefinition compositeDef =new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));parserContext.pushContainingComponent(compositeDef);// 注册 AspectJAwareAdvisorAutoProxyCreator 类型的beanDefinition,继承自 BeanPostProcessorconfigureAutoProxyCreator(parserContext, element);// 获取所有子标签List<Element> childElts = DomUtils.getChildElements(element);for (Element elt: childElts) {String localName = parserContext.getDelegate().getLocalName(elt);// 解析 <aop:poincut />if (POINTCUT.equals(localName)) {parsePointcut(elt, parserContext);}// 解析 <aop:>advisor />else if (ADVISOR.equals(localName)) {parseAdvisor(elt, parserContext);}// 解析 <aop:aspect />else if (ASPECT.equals(localName)) {parseAspect(elt, parserContext);}}parserContext.popAndRegisterContainingComponent();return null;}
这里涉及到一个 <aop:advisor />
标签,大家可能不太清楚它的作用,advisor 与 advice(<aop:before />
,<aop:after />
…)功能类似,都可以用来增强目标方法,具体用法可以参考这篇文章:Spring Aop(八)——advisor标签 by 234390216
parse
方法会处理 <aop:config />
标签下的子标签,具体的处理逻辑后面会分析到。在解析标签之前先调用了 configureAutoProxyCreator
方法,这个方法比较重要。
二、注册 AspectJAwareAdvisorAutoProxyCreator beanDefinition
private void configureAutoProxyCreator(ParserContext parserContext, Element element) {AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);}public static void registerAspectJAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);registerComponentIfNecessary(beanDefinition, parserContext);}
registerAspectJAutoProxyCreatorIfNecessary
方法中有三个处理逻辑,下面一一来看下。
@Nullablepublic static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {// 注意这里传的是 AspectJAwareAdvisorAutoProxyCreator 类型return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);}private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");// AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator"if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);if (!cls.getName().equals(apcDefinition.getBeanClassName())) {int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());int requiredPriority = findPriorityForClass(cls);if (currentPriority < requiredPriority) {apcDefinition.setBeanClassName(cls.getName());}}return null;}RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);beanDefinition.setSource(source);beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 注册一个类型为 AspectJAwareAdvisorAutoProxyCreator 的 beanDefinitionregistry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);return beanDefinition;}
注册一个类型为 AspectJAwareAdvisorAutoProxyCreator
的 beanDefinition,注意此时还没有创建 bean,AspectJAwareAdvisorAutoProxyCreator
其实是一个 BeanPostProcessor
,后面在对方法增强的时候会用到,具体细节会在后面的文章里总结。
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {if (sourceElement != null) {boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));if (proxyTargetClass) {AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));if (exposeProxy) {AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);}}}
检查 <aop:config />
是否配置了 proxy-target-class
与 expose-proxy
,如果有配置,则为 beanDefinition 添加 exposeProxy
与 proxyTargetClass
属性,这两个配置标签后面还会遇到,这里先简单介绍下作用。
proxy-target-class
:默认为 false,如果设置为 true,会使用 CGLIB 创建代理对象
expose-proxy
:默认为 false,作用:假设 A,B 方法都被增强,A 方法调用 B 方法,A 方法会被切面拦截,A 里的 B 方法是否会被切面拦截增强,默认不会
private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {if (beanDefinition != null) {parserContext.registerComponent(new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));}}
注册组件。
三、解析 <aop:aspect ref="" id="" />
<aop:aspect />
标签下可以配置多种类型的子标签,比如 <aop:before />
、<aop:after
/>、<aop:pointcut />
、<aop:declare-parents />
等,不同类型的标签处理逻辑也是不一样的。
private void parseAspect(Element aspectElement, ParserContext parserContext) {String aspectId = aspectElement.getAttribute(ID);// 切面 beanNameString aspectName = aspectElement.getAttribute(REF);try {// 添加进 LinkedListthis.parseState.push(new AspectEntry(aspectId, aspectName));List<BeanDefinition> beanDefinitions = new ArrayList<>();List<BeanReference> beanReferences = new ArrayList<>();// 获取 <aop:declare-parents /> 子标签List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);for (Element declareParentsElement : declareParents) {beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));}NodeList nodeList = aspectElement.getChildNodes();boolean adviceFoundAlready = false;for (int i = 0; i < nodeList.getLength(); i++) {Node node = nodeList.item(i);// 处理通知节点 <aop:before,after, around ... />if (isAdviceNode(node, parserContext)) {if (!adviceFoundAlready) {adviceFoundAlready = true;if (!StringUtils.hasText(aspectName)) {parserContext.getReaderContext().error("<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",aspectElement, this.parseState.snapshot());return;}beanReferences.add(new RuntimeBeanReference(aspectName));}// 处理通知AbstractBeanDefinition advisorDefinition = parseAdvice(aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);beanDefinitions.add(advisorDefinition);}}// 创建 AspectComponentDefinitionAspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);parserContext.pushContainingComponent(aspectComponentDefinition);// 获取所有切点 elementList<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);for (Element pointcutElement : pointcuts) {parsePointcut(pointcutElement, parserContext);}parserContext.popAndRegisterContainingComponent();}finally {this.parseState.pop();}}
主要流程如下:
- 获取
<aop:aspect >
的 id 与 ref 属性,封装成AspectEntry
添加到parseState
集合中,流程处理完后,出栈 - 获取
<aop:declare-parents />
子标签,如果有配置则封装成 beanDefinition - 解析
<aop:aspect >
下所有子标签,循环处理通知配置 - 解析切点配置
private AbstractBeanDefinition parseAdvice(String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {try {this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);// 切面 bean(类)methodDefinition.getPropertyValues().add("targetBeanName", aspectName);// 设置切面方法methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));methodDefinition.setSynthetic(true);// 创建切面 BeanDefinitionRootBeanDefinition aspectFactoryDef =new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);aspectFactoryDef.setSynthetic(true);// 创建通知 BeanDefinition,此时已明确通知类型AbstractBeanDefinition adviceDef = createAdviceDefinition(adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,beanDefinitions, beanReferences);RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);advisorDefinition.setSource(parserContext.extractSource(adviceElement));// 将上面创建的 adviceDef 注入到 advisorDefinition 中advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);// <aop:aspect ref="" order="" />,控制切面方法优先级if (aspectElement.hasAttribute(ORDER_PROPERTY)) {advisorDefinition.getPropertyValues().add(ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));}// 注册通知 BeanDefinitionparserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);return advisorDefinition;}finally {this.parseState.pop();}}
上面逻辑主要是创建出 advisorDefinition
,并注册到 beanFactory 中,由此可见,我们配置的每一个通知,最终都会被转化成 beanDefinition 并注册到 IoC 容器中。
上面这个方法深追下去知识点还挺多的,比如 createAdviceDefinition
方法,注册 beanDefinition 时 beanName 的生成规则等,有兴趣的自己可以研究下,这里就不介绍了,看不懂的地方可以通过 DEBUG 的方式帮助理解。
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {String id = pointcutElement.getAttribute(ID);// 获取切点表达式String expression = pointcutElement.getAttribute(EXPRESSION);AbstractBeanDefinition pointcutDefinition = null;try {this.parseState.push(new PointcutEntry(id));// 创建切点 beanDefinition,类型为 ConfigurableBeanFactory.SCOPE_PROTOTYPEpointcutDefinition = createPointcutDefinition(expression);pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));String pointcutBeanName = id;// 注册切点 BeanDefinition 到 beanFactory 中if (StringUtils.hasText(pointcutBeanName)) {parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);}else {// <aop:pointcut id="" expression=""/> 没有配置 id 先生成 beanName 在注册到 beanFactory 中pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);}parserContext.registerComponent(new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));}finally {this.parseState.pop();}return pointcutDefinition;}protected AbstractBeanDefinition createPointcutDefinition(String expression) {RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);// 切点作用域原型beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);beanDefinition.setSynthetic(true);beanDefinition.getPropertyValues().add(EXPRESSION, expression);return beanDefinition;}
处理切点的逻辑与通知类似,注意切点 beanDefinition 的作用域并非单例而是原型,初始化 beanDefinition 后注册到 IoC 容器中。
参考
【Spring源码分析】AOP源码解析(上篇) by 五月的仓颉
Spring AOP 源码系列(一)解析 AOP 配置信息相关推荐
- Spring读源码系列之AOP--03---aop底层基础类学习
Spring读源码系列之AOP--03---aop底层基础类学习 引子 Spring AOP常用类解释 AopInfrastructureBean---免被AOP代理的标记接口 ProxyConfig ...
- Spring IoC 源码系列(四)bean创建流程与循环依赖问题分析
创建单例 bean 的代码细节在 org.springframework.beans.factory.support.AbstractBeanFactory#getBean 中,getBean 顾名思 ...
- Spring IoC 源码系列(一)BeanDefinition 初始化与注册
一.BeanDefinition 1.1 什么是 BeanDefinition 在一般的 Spring 项目中,主要通过 XML 的方式配置 bean,而 BeanDefinition 就是 XML ...
- Spring IoC 源码系列(五)getBean 流程分析
一.FactoryBean 用法讲解 在分析源码流程之前,我们先来看一下 FactoryBean,乍一看这家伙和 BeanFactory 很像,它们都可以用来获取 bean 对象,简单来说 Facto ...
- Spring IoC 源码系列(三)Spring 事件发布机制原理分析
在 IoC 容器启动流程中有一个 finishRefresh 方法,具体实现如下: protected void finishRefresh() {clearResourceCaches();init ...
- JVM源码系列:ThreadMXBean 打出堆栈信息原理分析
我们通常会使用工具jstack 去跟踪线程信息,其如何实现使用attach 的方式还是ptrace 的方式,这些可以去参考本人的博客的其他文章. 但这些方式都是外部使用的方式,如何直接使用java代码 ...
- Spring AOP源码(1)—<aop:config/>AOP配置标签解析【一万字】
基于最新Spring 5.x,对Spring AOP中的<aop:config/>标签的解析源码进行了详细分析,这是Spring AOP源码的入口! 此前我们已经详细学习了Spri ...
- Spring AOP源码(2)—AspectJAwareAdvisorAutoProxyCreator创建代理对象【两万字】
基于最新Spring 5.x,介绍了Spring AOP中的AspectJAwareAdvisorAutoProxyCreator自动代理创建者的工作流程,对于创建代理对象的源码进行了深度分析! ...
- Spring 事务源码(2)—<tx:annotation-driven/>事务标签源码解析
基于最新Spring 5.x,详细介绍了Spring 事务源码,包括< tx:annotation-driven/>标签源码解析. 此前我们已经学习了Spring的<tx:a ...
最新文章
- 大快网站:如何选择正确的hadoop版本
- Tensorflow载入模型详解,方法一(基础版):针对测试模型性能 和 使用模型。
- Mendix:云原生应用是软件的未来
- Open Infrastructure开启开放协作新时代
- cookie保存用户的登陆状态-草案
- SAP开源Java SCA工具,提供静态代码安全性测试功能
- Know more about Enqueue Deadlock Detection
- 图的深度优先搜索(DFS)
- 【javascript】简单原型链、借用构造函数
- SAS® Model Manager功能调研
- win7和win8双系统的问题
- iOS开发多线程篇---线程间的通信
- Web学习篇之---html基础知识(一)
- linux有线程的概念,Linux线程相关概念
- 下载MDK5 Software Packs导入Keil5
- android soundpool 封装,Android中使用SoundPool来播放音频
- 类ResourceBundle详解
- Robomaster视觉教程(二)Win10+VS201x+Opencv3.4.x环境搭建
- Redis文档--详解redis
- 求点到线段的最短距离(QT)
热门文章
- Web Security——英语写作与教学评价系统(iWrite)解决写作时禁止复制粘贴问题解决方案
- BugKuCTF 杂项 白哥的鸽子
- 哈尔滨工程大学ACM预热赛
- jquery+原生js模拟淘宝输入框下拉提示
- 【练习】c++分别用链队列和普通队列输出杨辉三角
- 【笔记】opencv图像轮廓 获得平均灰度值在原图上画轮廓 观察灰度图的分解
- hadoop 2.4.1 集群安装二
- FreeMarker四种变量的用法
- 给定一组查找关键字(19,14,23,1,65,20,84,27,55,11,10,79) 哈希函数为:H(key)=key % 13, 哈希表长为m=15,设每个记录的查找概率相等。【MOOC】
- MySql 常用命令总结