在进行源码阅读之前建议先看一下这篇文章: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-classexpose-proxy,如果有配置,则为 beanDefinition 添加 exposeProxyproxyTargetClass 属性,这两个配置标签后面还会遇到,这里先简单介绍下作用。

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();}}

主要流程如下:

  1. 获取 <aop:aspect > 的 id 与 ref 属性,封装成 AspectEntry 添加到 parseState 集合中,流程处理完后,出栈
  2. 获取 <aop:declare-parents /> 子标签,如果有配置则封装成 beanDefinition
  3. 解析 <aop:aspect > 下所有子标签,循环处理通知配置
  4. 解析切点配置
   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 配置信息相关推荐

  1. Spring读源码系列之AOP--03---aop底层基础类学习

    Spring读源码系列之AOP--03---aop底层基础类学习 引子 Spring AOP常用类解释 AopInfrastructureBean---免被AOP代理的标记接口 ProxyConfig ...

  2. Spring IoC 源码系列(四)bean创建流程与循环依赖问题分析

    创建单例 bean 的代码细节在 org.springframework.beans.factory.support.AbstractBeanFactory#getBean 中,getBean 顾名思 ...

  3. Spring IoC 源码系列(一)BeanDefinition 初始化与注册

    一.BeanDefinition 1.1 什么是 BeanDefinition 在一般的 Spring 项目中,主要通过 XML 的方式配置 bean,而 BeanDefinition 就是 XML ...

  4. Spring IoC 源码系列(五)getBean 流程分析

    一.FactoryBean 用法讲解 在分析源码流程之前,我们先来看一下 FactoryBean,乍一看这家伙和 BeanFactory 很像,它们都可以用来获取 bean 对象,简单来说 Facto ...

  5. Spring IoC 源码系列(三)Spring 事件发布机制原理分析

    在 IoC 容器启动流程中有一个 finishRefresh 方法,具体实现如下: protected void finishRefresh() {clearResourceCaches();init ...

  6. JVM源码系列:ThreadMXBean 打出堆栈信息原理分析

    我们通常会使用工具jstack 去跟踪线程信息,其如何实现使用attach 的方式还是ptrace 的方式,这些可以去参考本人的博客的其他文章. 但这些方式都是外部使用的方式,如何直接使用java代码 ...

  7. Spring AOP源码(1)—<aop:config/>AOP配置标签解析【一万字】

      基于最新Spring 5.x,对Spring AOP中的<aop:config/>标签的解析源码进行了详细分析,这是Spring AOP源码的入口!   此前我们已经详细学习了Spri ...

  8. Spring AOP源码(2)—AspectJAwareAdvisorAutoProxyCreator创建代理对象【两万字】

      基于最新Spring 5.x,介绍了Spring AOP中的AspectJAwareAdvisorAutoProxyCreator自动代理创建者的工作流程,对于创建代理对象的源码进行了深度分析! ...

  9. Spring 事务源码(2)—<tx:annotation-driven/>事务标签源码解析

      基于最新Spring 5.x,详细介绍了Spring 事务源码,包括< tx:annotation-driven/>标签源码解析.   此前我们已经学习了Spring的<tx:a ...

最新文章

  1. 大快网站:如何选择正确的hadoop版本
  2. Tensorflow载入模型详解,方法一(基础版):针对测试模型性能 和 使用模型。
  3. Mendix:云原生应用是软件的未来
  4. Open Infrastructure开启开放协作新时代
  5. cookie保存用户的登陆状态-草案
  6. SAP开源Java SCA工具,提供静态代码安全性测试功能
  7. Know more about Enqueue Deadlock Detection
  8. 图的深度优先搜索(DFS)
  9. 【javascript】简单原型链、借用构造函数
  10. SAS® Model Manager功能调研
  11. win7和win8双系统的问题
  12. iOS开发多线程篇---线程间的通信
  13. Web学习篇之---html基础知识(一)
  14. linux有线程的概念,Linux线程相关概念
  15. 下载MDK5 Software Packs导入Keil5
  16. android soundpool 封装,Android中使用SoundPool来播放音频
  17. 类ResourceBundle详解
  18. Robomaster视觉教程(二)Win10+VS201x+Opencv3.4.x环境搭建
  19. Redis文档--详解redis
  20. 求点到线段的最短距离(QT)

热门文章

  1. Web Security——英语写作与教学评价系统(iWrite)解决写作时禁止复制粘贴问题解决方案
  2. BugKuCTF 杂项 白哥的鸽子
  3. 哈尔滨工程大学ACM预热赛
  4. jquery+原生js模拟淘宝输入框下拉提示
  5. 【练习】c++分别用链队列和普通队列输出杨辉三角
  6. 【笔记】opencv图像轮廓 获得平均灰度值在原图上画轮廓 观察灰度图的分解
  7. hadoop 2.4.1 集群安装二
  8. FreeMarker四种变量的用法
  9. 给定一组查找关键字(19,14,23,1,65,20,84,27,55,11,10,79) 哈希函数为:H(key)=key % 13, 哈希表长为m=15,设每个记录的查找概率相等。【MOOC】
  10. MySql 常用命令总结