Spring源码情操陶冶-AOP之Advice通知类解析与使用

阅读本文请先稍微浏览下上篇文章Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器,本文则对aop模式的通知类作简单的分析

入口

根据前文讲解,我们知道通知类的解析主要建立在aop:aspect节点的解析上。废话少说我们直接观察ConfigBeanDefinitionParser#parseAdvice()方法

ConfigBeanDefinitionParser#parseAdvice()-解析通知类并注册到bean工厂

先奉上源码

	/**
	 * Parses one of '{@code before}', '{@code after}', '{@code after-returning}',
	 * '{@code after-throwing}' or '{@code around}' and registers the resulting
	 * BeanDefinition with the supplied BeanDefinitionRegistry.
	 * @return the generated advice RootBeanDefinition
	 */
	 /**
	 ** 这稍微对入参作下备注
	 ** @param aspectName  待绑定的切面名
	 ** @param order 排序号
	 ** @param aspectElement <aop:aspect>节点
	 ** @param adviceElement <aop:advice>节点
	 ** @param parserContext 解析节点的上下文对象
	 ** @param beanDefinitions 与aspect相关的所有bean对象集合
	 ** @param beanReferences  与aspect相关的所有bean引用对象集合
	 **/
	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)));

			// create the method factory bean
			// 解析advice节点中的"method"属性,并包装为MethodLocatingFactoryBean对象
			RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
			methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
			methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
			methodDefinition.setSynthetic(true);

			// create instance factory definition
			// 关联aspectName,包装为SimpleBeanFactoryAwareAspectInstanceFactory对象
			RootBeanDefinition aspectFactoryDef =
					new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
			aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
			aspectFactoryDef.setSynthetic(true);

			// register the pointcut
			// 涉及point-cut属性的解析,并结合上述的两个bean最终包装为AbstractAspectJAdvice通知对象
			AbstractBeanDefinition adviceDef = createAdviceDefinition(
					adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
					beanDefinitions, beanReferences);

			// configure the advisor,最终包装为AspectJPointcutAdvisor对象
			RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
			advisorDefinition.setSource(parserContext.extractSource(adviceElement));
			advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
			if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
				advisorDefinition.getPropertyValues().add(
						ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
			}

			// register the final advisor
			parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

			return advisorDefinition;
		}
		finally {
			this.parseState.pop();
		}
	}

由代码可知,最终解析得到的bean对象为AspectJPointcutAdvisor.class类型的,其内部拥有Advice的接口对象属性,而具体的解析则需要查看ConfigBeanDefinitionParser#createAdviceDefinition()方法。

ConfigBeanDefinitionParser#createAdviceDefinition()-具体解析通知类

源码奉上

	/**
	 * Creates the RootBeanDefinition for a POJO advice bean. Also causes pointcut
	 * parsing to occur so that the pointcut may be associate with the advice bean.
	 * This same pointcut is also configured as the pointcut for the enclosing
	 * Advisor definition using the supplied MutablePropertyValues.
	 */
	private AbstractBeanDefinition createAdviceDefinition(
			Element adviceElement, ParserContext parserContext, String aspectName, int order,
			RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
			List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
		// 首先根据adviceElement节点分析出是什么类型的Advice。
		RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
		adviceDefinition.setSource(parserContext.extractSource(adviceElement));
		// 设置aspectName属性和declarationOrder属性
		adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
		adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);
		// 解析节点是否含有`returning`/`throwing`/`arg-names`,有则设置
		if (adviceElement.hasAttribute(RETURNING)) {
			adviceDefinition.getPropertyValues().add(
					RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));
		}
		if (adviceElement.hasAttribute(THROWING)) {
			adviceDefinition.getPropertyValues().add(
					THROWING_PROPERTY, adviceElement.getAttribute(THROWING));
		}
		if (adviceElement.hasAttribute(ARG_NAMES)) {
			adviceDefinition.getPropertyValues().add(
					ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));
		}
		// 设置构造函数的入参变量
		// Method/AspectJExpressionPointcut/AspectInstanceFactory三个入参
		ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
		cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
		// 解析point-cut属性
		Object pointcut = parsePointcutProperty(adviceElement, parserContext);
		if (pointcut instanceof BeanDefinition) {
			cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
			beanDefinitions.add((BeanDefinition) pointcut);
		}
		else if (pointcut instanceof String) {
			RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
			cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
			beanReferences.add(pointcutRef);
		}

		cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);

		return adviceDefinition;
	}
  1. Advice接口类与节点对应关系如下,其均是AbstractAspectJAdvice.class的子类
  • aop:before对应AspectJMethodBeforeAdvice.class
  • aop:after对应AspectJAfterAdvice.class
  • aop:after-returning对应AspectJAfterReturningAdvice.class
  • aop:after-throwing对应AspectJAfterThrowingAdvice.class
  • aop:around对应AspectJAroundAdvice.class
  1. 通知类生成的bean对象,其会设置aspectName切面名、declarationOrder序列等属性;且对其公共构造函数三个入参Method/AspectJExpressionPointcut/AspectInstanceFactory都会进行设置

  2. parseAdvice()最主要的目的是使aspect对象中的方法与通知类结合起来,从而起到多样化的作用,下面的简单实例就是如此

例子结尾

public class TestAdvice {  
  
    /** 
     * 在核心业务执行前执行,不能阻止核心业务的调用。 
     * @param joinPoint 
     */  
    private void doBefore(JoinPoint joinPoint) {  
        System.out.println("-----doBefore().invoke-----");  
        System.out.println(" 此处意在执行核心业务逻辑前,做一些安全性的判断等等");  
        System.out.println(" 可通过joinPoint来获取所需要的内容");  
        System.out.println("-----End of doBefore()------");  
    }  
      
    /** 
     * 手动控制调用核心业务逻辑,以及调用前和调用后的处理, 
     *  
     * 注意:当核心业务抛异常后,立即退出,转向After Advice 
     * 执行完毕After Advice,再转到Throwing Advice 
     * @param pjp 
     * @return 
     * @throws Throwable 
     */  
    private Object doAround(ProceedingJoinPoint pjp) throws Throwable {  
        System.out.println("-----doAround().invoke-----");  
        System.out.println(" 此处可以做类似于Before Advice的事情");  
          
        //调用核心逻辑  
        Object retVal = pjp.proceed();  
          
        System.out.println(" 此处可以做类似于After Advice的事情");  
        System.out.println("-----End of doAround()------");  
        return retVal;  
    }  
  
    /** 
     * 核心业务逻辑退出后(包括正常执行结束和异常退出),执行此Advice 
     * @param joinPoint 
     */  
    private void doAfter(JoinPoint joinPoint) {  
        System.out.println("-----doAfter().invoke-----");  
        System.out.println(" 此处意在执行核心业务逻辑之后,做一些日志记录操作等等");  
        System.out.println(" 可通过joinPoint来获取所需要的内容");  
        System.out.println("-----End of doAfter()------");  
    }  
      
    /** 
     * 核心业务逻辑调用正常退出后,不管是否有返回值,正常退出后,均执行此Advice 
     * @param joinPoint 
     */  
    private void doReturn(JoinPoint joinPoint) {  
        System.out.println("-----doReturn().invoke-----");  
        System.out.println(" 此处可以对返回值做进一步处理");  
        System.out.println(" 可通过joinPoint来获取所需要的内容");  
        System.out.println("-----End of doReturn()------");  
    }  
      
    /** 
     * 核心业务逻辑调用异常退出后,执行此Advice,处理错误信息 
     * @param joinPoint 
     * @param ex 
     */  
    private void doThrowing(JoinPoint joinPoint,Throwable ex) {  
        System.out.println("-----doThrowing().invoke-----");  
        System.out.println(" 错误信息:"+ex.getMessage());  
        System.out.println(" 此处意在执行核心业务逻辑出错时,捕获异常,并可做一些日志记录操作等等");  
        System.out.println(" 可通过joinPoint来获取所需要的内容");  
        System.out.println("-----End of doThrowing()------");  
    }  
}  

对应的spring配置如下

<bean id="xmlHandler" class="com.jing.aop.TestAdvice" />  
    <aop:config>  
        <aop:aspect id="aspect" ref="xmlHandler">  
            <aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>  
              
            <aop:before method="doBefore"  pointcut-ref="pointUserMgr"/>  
            <aop:after method="doAfter"  pointcut-ref="pointUserMgr"/>  
            <aop:around method="doAround"  pointcut-ref="pointUserMgr"/>  
            <aop:after-returning method="doReturn"  pointcut-ref="pointUserMgr"/>  
            <aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>  
              
        </aop:aspect>  
    </aop:config> 

具体的如何触发相应的Advice我们放在后续的篇章讲解,敬请期待

posted @ 2017-10-25 20:34  南柯问天  阅读(816)  评论(0编辑  收藏  举报