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

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

简介

大家好,我是小马哥成千上万粉丝中的一员!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/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://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 则添加属性值类型是 RuntimeBeanReference
		if (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-parents
		List<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类的 RootBeanDefition
			beanDefinitions.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 类的 BeanDefinition
				AbstractBeanDefinition 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 注册到容器里。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿大叔文海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值