学好路更宽,钱多少加班。 ——小马哥

简介

大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring AOP 编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。

Spring AOP XML 标签基本使用

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 开启 AspectJ 注解解析 --><aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/><!-- 基于 AspectJ 表达式语法的 AOP 配置 --><aop:config expose-proxy="true" proxy-target-class="false"><!-- 配置切点,只支持 AspectJ 表达式语法 --><aop:pointcut id="outerAnyPublishMethod" expression="execution(public * *(..))"/><aop:advisor advice-ref="echoServiceMethodInterceptor" order="11" pointcut-ref="outerAnyPublishMethod"/><aop:aspect id="AspectXml" ref="aspectXml" order="20"><!-- Introduction --><aop:declare-parents types-matching="com.wenhai.spring.aop.features.*"implement-interface="com.wenhai.spring.aop.features.service.EchoService"delegate-ref="echoService"/><!-- 配置切点,只支持 AspectJ 表达式语法 --><aop:pointcut id="innerAnyPublishMethod" expression="execution(public * *(..))"/><!-- 前置通知 --><aop:before method="beforeAnyPublicMethod" pointcut-ref="outerAnyPublishMethod"/><!-- 后置通知 --><aop:after-returning method="afterReturningAnyPublicMethod" pointcut-ref="innerAnyPublishMethod"/><!-- 异常通知 --><aop:after-throwing method="afterThrowingAnyPublicMethod" pointcut-ref="innerAnyPublishMethod"/><!-- 最终通知 --><aop:after method="afterAnyPublicMethod" pointcut-ref="innerAnyPublishMethod"/><!-- 环绕通知 --><aop:around method="aroundAnyPublicMethod" pointcut-ref="outerAnyPublishMethod"/><!-- 环绕通知 --><aop:around method="aroundAnyPublicMethod" pointcut="execution(public * *(..))"/></aop:aspect></aop:config><!-- 切面 bean --><bean name="aspectXml" class="com.wenhai.spring.aop.features.aspect.AspectXmlConfiguration"/>
<bean name="echoService" class="com.wenhai.spring.aop.features.service.DefaultEchoServiceImpl"/><bean name="echoServiceMethodInterceptor" class="com.wenhai.spring.aop.features.interceptor.EchoServiceMethodInterceptor"/><bean name="echoProxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="targetName" value="echoService"/><property name="interceptorNames" value="echoServiceMethodInterceptor"/></bean>
</beans>

自定义 Spring XML 配置文件标签步骤

除了 Spring IoC XML 配置文件提供的 <import/>、<alias />、<beans/>、<description/>标签之外的标签都是属性自定义标签

  1. 自定义 xsd 约束文件,并在 spring.schemas(参见 Spring Jar 包下面的 META-INF 目录下的 spring.schemas 配置文件)中配置 xsd 文件的位置。
  2. 继承 NamespaceHandlerSupport 并实现 init() 方法,注册标签解析器(XML 元素命名空间处理器 )
  3. spring.handlers(参见 Spring Jar 包下面的 META-INF 目录下的 spring.handlers 配置文件)中配置命名空间对应的处理器

解析 Spring AOP XML 配置文件的标签

打开 Maven 中 spring-aop Jar 包,可以在 META-INF 目录下找到对应的 spring.handlersAopNamespaceHandler 这个类,就是处理标签的主要入口。

@Override
public void init() {// 处理 <aop:config/> 标签registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());// 处理 <aop:aspectj-autoproxy/> 标签registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());// 处理 <aop:scoped-proxy/> 标签registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());// 2.0 XSD 中的标签,在 2.5 版本已经移除registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

解析 <aop:aspectj-autoproxy/> 标签

由 AspectJAutoProxyBeanDefinitionParser#parse 方法进行解析

public BeanDefinition parse(Element element, ParserContext parserContext) {// 通过 BeanDefinitionRegistry 注册一个 // bean 名称为 org.springframework.aop.config.internalAutoProxyCreator // bean 类为 AnnotationAwareAspectJAutoProxyCreator // BeanDefinition 类型的  RootBeanDefinition,// 并设置 order 为最高优先级, BeanDefinition role 为 INFRASTRUCTURE// 如果标签设置了 proxy-target-class 和  expose-proxy 则通过 PropertyValues 设置AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);extendBeanDefinition(element, parserContext);return null;
}

具体流程如图:参数是 AnnotationAwareAspectJAutoProxyCreator

解析 <aop:config /> 标签

ConfigBeanDefinitionParser#parse() 方法进行解析

public BeanDefinition parse(Element element, ParserContext parserContext) {// 根据标签名 aop:config 新建一个 CompositeComponentDefinition 实例,然后放入双端队列头部CompositeComponentDefinition compositeDef =new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));parserContext.pushContainingComponent(compositeDef);// 通过 BeanDefinitionRegistry 注册一个 // bean 名称为 org.springframework.aop.config.internalAutoProxyCreator // bean 类为 AspectJAwareAdvisorAutoProxyCreator// BeanDefinition 类型的 BeanComponentDefinition 的 RootBeanDefinition,// 并设置 order 为最高优先级, BeanDefinition role 为 INFRASTRUCTURE// 如果标签设置了 proxy-target-class 和  expose-proxy 则通过 PropertyValues 设置 configureAutoProxyCreator(parserContext, element);List<Element> childElts = DomUtils.getChildElements(element);for (Element elt: childElts) {String localName = parserContext.getDelegate().getLocalName(elt);if (POINTCUT.equals(localName)) {// 解析子标签 <aop:pointcut/>parsePointcut(elt, parserContext);}else if (ADVISOR.equals(localName)) {// 解析子标签 <aop:advisor/> parseAdvisor(elt, parserContext);}else if (ASPECT.equals(localName)) {// 解析子标签 <aop:aspect/>parseAspect(elt, parserContext);}}parserContext.popAndRegisterContainingComponent();return null;
}

注册 <aop:config /> 标签流程如下图,参数为 AspectJAwareAdvisorAutoProxyCreator

解析 <aop:config /> 子标签

解析 <aop:pointcut/> 标签

private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {// 获取 pointcut 标签里面的 id 元素属性值值String id = pointcutElement.getAttribute(ID);// 获取 pointcut 标签里面的 expression 元素属性值值String expression = pointcutElement.getAttribute(EXPRESSION);AbstractBeanDefinition pointcutDefinition = null;try {// 入栈(在 config 标签里面,解析时候入栈,解析完出栈)this.parseState.push(new PointcutEntry(id));// 定义一个 RootBeanDefinition,beanClass 为 AspectJExpressionPointcut// bean 作用域为原型,是合成 bean,添加 expression 属性。pointcutDefinition = createPointcutDefinition(expression);// 设置来源pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));// 注册 beanDefinition,如果 pointcut 标签设置了 id 属性,beanName 就是指定 id 的值,// 否则由 BeanNameGenerator 生成String pointcutBeanName = id;if (StringUtils.hasText(pointcutBeanName)) {parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);}else {pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);}// 嵌套设置(config 标签是父标签)parserContext.registerComponent(new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));}finally {// 出栈this.parseState.pop();}return pointcutDefinition;
}
总结

解析 pointcut 标签就是往 BeanDefinitonRegistry 注册一个 RootBeanDefinitiion(不需要再经过合并阶段),并设置了 beanDefinition 中的 beanName 为 pointcut 标签 id 属性值、beanClass 为AspectJExpressionPointcut、 scope 为 prototype、synthetic 为 true,beanDefinition 中添加了 expression 属性。

解析 <aop:advisor/> 标签

private void parseAdvisor(Element advisorElement, ParserContext parserContext) {// 定义一个 RootBeanDefinition,beanClass 为 DefaultBeanFactoryPointcutAdvisor// 添加属性名 adviceBeanName 属性值为 RuntimeBeanNameReference( 标签里面的 advice-ref 属性值   <aop:advisor advice-ref=/>)// 如果 <aop:advisor/> 设置了 order 属性,则 添加属性名 order 属性值为 order 属性值AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);String id = advisorElement.getAttribute(ID);try {this.parseState.push(new AdvisorEntry(id));String advisorBeanName = id;// 注册 beanDefinition,如果 advisor 标签设置了 id 属性,beanName 就是指定 id 的值,// 否则由 BeanNameGenerator 生成if (StringUtils.hasText(advisorBeanName)) {parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);}else {advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);}// 解析关联的 pointcut,如果标签 advisor 里面关联的 pointcut // 则会 定义一个 RootBeanDefinition,beanClass 为 AspectJExpressionPointcut// bean 作用域为原型,是合成 bean,添加 expression 属性。// 如果关联的是 pointcut-ref 获取里面的属性值Object pointcut = parsePointcutProperty(advisorElement, parserContext);// 添加属性,属性名为: pointcut,属性值根据 advisor 里面的标签值不同// 如果是 pointcut 则添加属性值类型是 BeanDefinition // 如果是 pointcut-ref 则添加属性值类型是 RuntimeBeanReferenceif (pointcut instanceof BeanDefinition) {advisorDef.getPropertyValues().add(POINTCUT, pointcut);parserContext.registerComponent(new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));}else if (pointcut instanceof String) {advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));parserContext.registerComponent(new AdvisorComponentDefinition(advisorBeanName, advisorDef));}}finally {this.parseState.pop();}
}
总结

解析 advisor 标签就是往 BeanDefinitonRegistry 注册一个 RootBeanDefinitiion(不需要再经过合并阶段),并设置了 beanDefinition 中的 beanName 为 advisor 标签 id 属性值(或根据 BeanNameGenerator 自动生成)、beanClass 为 DefaultBeanFactoryPointcutAdvisor,beanDefinition 中添加了 adviceBeanName 或者 order 属性和 pointcut 属性。

解析 <aop:aspect/> 标签

<aop:aspect id="AspectXml" ref="aspectXml" order="20"><!-- Introduction --><aop:declare-parents types-matching="com.wenhai.spring.aop.features.*"implement-interface="com.wenhai.spring.aop.features.service.EchoService"delegate-ref="echoService"/><!-- 配置切点,只支持 AspectJ 表达式语法 --><aop:pointcut id="innerAnyPublishMethod" expression="execution(public * *(..))"/><!-- 前置通知 --><aop:before method="beforeAnyPublicMethod" pointcut-ref="outerAnyPublishMethod"/><!-- 后置通知 --><aop:after-returning method="afterReturningAnyPublicMethod" pointcut-ref="innerAnyPublishMethod"/><!-- 异常通知 --><aop:after-throwing method="afterThrowingAnyPublicMethod" pointcut-ref="innerAnyPublishMethod"/><!-- 最终通知 --><aop:after method="afterAnyPublicMethod" pointcut-ref="innerAnyPublishMethod"/><!-- 环绕通知 --><aop:around method="aroundAnyPublicMethod" pointcut-ref="outerAnyPublishMethod"/><!-- 环绕通知 --><aop:around method="aroundAnyPublicMethod" pointcut="execution(public * *(..))"/></aop:aspect>
private void parseAspect(Element aspectElement, ParserContext parserContext) {// 对着上面 xml 配置// 获取 aspect 标签 id 属性值String aspectId = aspectElement.getAttribute(ID);// 获取 aspect 标签 ref 属性值(切面类)String aspectName = aspectElement.getAttribute(REF);try {this.parseState.push(new AspectEntry(aspectId, aspectName));List<BeanDefinition> beanDefinitions = new ArrayList<>();List<BeanReference> beanReferences = new ArrayList<>();// aspect 子标签 declare-parentsList<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);for (int i = METHOD_INDEX; i < declareParents.size(); i++) {Element declareParentsElement = declareParents.get(i);// 根据 declare-parents 标签中的 types-matching、implement-interface、default-impl 或者 delegate-ref// 构建一个 DeclareParentsAdvisor类的 RootBeanDefitionbeanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));}// 解析 advice 子标签NodeList nodeList = aspectElement.getChildNodes();// 控制 aspect-ref 属性。boolean adviceFoundAlready = false;for (int i = 0; i < nodeList.getLength(); i++) {Node node = nodeList.item(i);// 判断子标签是不是 advice 类型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;}// 添加 apsect-ref 中引用的 aspect 类的 RuntimeBeanReference beanReferences.add(new RuntimeBeanReference(aspectName));}// 构建 AspectJPointcutAdvisor 类的 RootBeanDefinition(详情见下流程图)// AspectJPointcutAdvisor 构造器是 AspectJPointcutAdvisor 类// 所以解析 advice 标签就是构造 AspectJPointcutAdvisor 类的 BeanDefinitionAbstractBeanDefinition advisorDefinition = parseAdvice(aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);beanDefinitions.add(advisorDefinition);}}// 构建一个 AspectComponentDefinition 对象然后入队AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);parserContext.pushContainingComponent(aspectComponentDefinition);List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);for (Element pointcutElement : pointcuts) {// 解析 pointcut 子标签,与上述 pointcut 标签解析过程一样parsePointcut(pointcutElement, parserContext);}parserContext.popAndRegisterContainingComponent();}finally {this.parseState.pop();}
}

总结

解析 aspect 标签就是往 BeanDefinitonRegistry 注册各类 RootBeanDefinitiion(不需要再经过合并阶段)。根据 adivce 标签的类型以及标签属性不同转换不同的 AbstractAspectJAdvice 子类,然后进行属性填充。最后封装成 AspectJPointcutAdvisor 类型的 RootBeanDefinition 注册到容器里。

跟着小马哥学系列之 Spring AOP(基于 XML 定义 Advice 源码解析)相关推荐

  1. 跟着小马哥学系列之 Spring AOP(Advisor 详解)

    学好路更宽,钱多少加班. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  2. 跟着小马哥学系列之 Spring AOP(Pointcut 组件详解)

    学好路更宽,钱多少加班. --小马哥 版本修订 2021.5.19:去除目录 2021.5.21:引用 Spring 官方 Pointcut 概念,修改 Pointcut 功能表述 简介 大家好,我是 ...

  3. 跟着小马哥学系列之 Spring AOP(AbstractAutoProxyCreator 详解)

    学成路更宽,吊打面试官. --小马哥 版本修订 2021.5.19:去除目录 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了 ...

  4. 跟着小马哥学系列之 Spring IoC(源码篇:Bean 生命周期)

    跟着小马哥学系列之 Spring IoC(源码篇:Bean 生命周期) 简介 Bean 元信息来源 Bean 元信息解析成 BeanDefinition 并注册 BeanDefinition 转变成 ...

  5. 跟着小马哥学系列之 Spring IoC(源码篇:@Import)

    跟着小马哥学系列之 Spring IoC(源码篇:@Import) 简介 @ Import 简介 元信息 元注解 属性 @Import 注解 value 属性取值范围 ImportSelector I ...

  6. 跟着小马哥学系列之 Spring IoC(进阶篇:Environment)

    学成路更宽,吊打面试官. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  7. 跟着小马哥学系列之 Spring IoC(进阶篇:类型转换)

    学成路更宽,吊打面试官. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  8. spring MVC cors跨域实现源码解析

    spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就是跨域. sp ...

  9. 基于postCss的TaiWindCss源码解析

    基于postCss的TaiWindCss源码解析 前言 了解 postCss 什么 是 postCss? postCss的核心原理/工作流 TaiWindCss 源码解析 TaiWindCss 是什么 ...

最新文章

  1. 深度学习:梯度下降算法改进
  2. Android存储方式之SQLite
  3. simulink仿真实例_MATLAB机器人运动学仿真入门
  4. 如何根据原理图画封装_如何根据业务封装自己的功能组件
  5. Python基础教程:高阶函数和函数嵌套
  6. CSS基础(part4)--CSS的层叠性继承性优先级
  7. Android之实现RTL的ViewPager
  8. linux pap认证,配置PPP PAP 认证
  9. Docker - 导出导入容器
  10. c语言中cnthe普通变量,不得不说,关于 *(unsigned long *) 和 (unsigned long)
  11. 参加软件测试工程师面试前,这些内容你一定要准备
  12. CVE-2017-4901 VMware虚拟机逃逸漏洞分析【Frida Windows实例】
  13. ensp(华为VRRP配置)
  14. 网络安全运营能力建设
  15. CMP SUB 区别
  16. iTween 用法总结
  17. 单片机c语言1ms 2ms 4ms方波,定时器使用:利用单片机内部定时器0通过P1.0端口输出一定周期的方波信号。 - 试题答案网问答...
  18. Java获取IPv4/IPv6地理位置-IP地址库
  19. 晨风机器人卡片/文字双切配置
  20. 课后作业——Day11

热门文章

  1. php RSA非对称加密秘钥生成
  2. itextpdf将html转成pdf,包含中文字体以及中文换行
  3. 必读的10本有关Java的书籍
  4. 字节跳动架构师讲解Java开发!美的java开发面试
  5. 安卓app中国际化任何资源的方法
  6. Kotlin使用Jectpack的Compose组件--基础环境构建
  7. Metrics,入门到应用
  8. Novell开放工作组套件(转)
  9. HTML form 垂直水平居中
  10. 小东吖 之 java 运算符