zoukankan      html  css  js  c++  java
  • 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我们放在后续的篇章讲解,敬请期待

  • 相关阅读:
    连载:面向对象葵花宝典:思想、技巧与实践(2)
    关于虚拟化一些思考——不应该盲目使用
    Zimbra8.x邮件服务器安装及配置
    CodeForces 371D. Vessels
    【建模】UML类关系分析
    公式提取软件mathpix
    ROS多线程编程
    ROS节点的初始化及退出详解(ros::init、SIGINT、ros::ok、ros::NodeHandle
    ROS 日志消息(C++)
    Python 中的 if __name__ == '__main__' 该如何理解
  • 原文地址:https://www.cnblogs.com/question-sky/p/7732214.html
Copyright © 2011-2022 走看看