zoukankan      html  css  js  c++  java
  • Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器

    • aop-Aspect Oriented Programming,面向切面编程。根据百度百科的解释,其通过预编译方式和运行期动态代理实现程序功能的一种技术。主要目的是为了程序间的解耦,常用于日志记录、事务管理等方面。
    • spring中常用<aop-config>来配置aop代理

    AOP概念梳理

    1. 切面(Aspect)
    2. 连接点(Joinpoint)
    3. 通知(Advice)
    4. 切入点(PointCut)
    5. 目标对象
    6. AOP代理

    具体的AOP解释以及用法可详情参考专业人士写的博客>>>Spring Aop详尽教程。总的来说AOP是基于java设计模式中的代理模式来实现的。

    AOP简单例子

    配置一发与springboot结合的代码例子,简洁又易懂

    package com.jing.springboot.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    /**
     * 请求拦截器,使用aop来处理
     * 
     * @author jtj
     *
     */
    @Aspect
    @Component
    public class RequestAopInterceptor {
    	private static final Logger REQ_LOGGER = LoggerFactory.getLogger(RequestAopInterceptor.class);
    
    	// 切入点,代表其关注哪些方法行为
    	// 匹配语法为:注解 修饰符 返回值类型 类名 方法名(参数列表) 异常列表 具体可查看
    	// http://blog.csdn.net/wangpeng047/article/details/8556800
    	@Pointcut("execution(* com.jing.springboot.controller..*(..)) and @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    	public void interceptor() {
    
    	}
    
    	// 指定声明的pointcut,前置通知
    	@Before("interceptor()")
    	public void before() {
    		REQ_LOGGER.info("before the requestMapping");
    	}
    
    	// 后置通知
    	@After("interceptor()")
    	public void after() {
    		REQ_LOGGER.info("requestMapping is over");
    	}
    	
    	// 返回通知
    	@AfterReturning(pointcut = "interceptor()", returning = "result")
    	public void doAfterReturing(String result) {
    		REQ_LOGGER.info("doAfterReturing-->请求返回的信息为: " + result);
    	}
    
    	// 环绕通知,此处的joinPoint代表了连接点
    	@Around("interceptor()")
    	public Object doRound(ProceedingJoinPoint joinPoint) {
    		Object result = null;
    		REQ_LOGGER.info("doRound before");
    		try {
    			result = joinPoint.proceed();
    		} catch (Throwable e) {
    			e.printStackTrace();
    		}
    		REQ_LOGGER.info("doRound after");
    
    		return result;
    	}
    }
    
    

    上述是拦截controller层的所有方法,此处博主举例HelloWorldCtrl.class的某个方法以作测试

    @RequestMapping(value = "/", method = RequestMethod.GET)
    	public String hello() {
    		return "hello world";
    	}
    

    最后请求得到的控制台打印结果为如下

    2017-10-18 20:01:31.821  INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor          : doRound before
    2017-10-18 20:01:31.822  INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor          : before the requestMapping
    2017-10-18 20:01:31.831  INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor          : doRound after
    2017-10-18 20:01:31.832  INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor          : requestMapping is over
    2017-10-18 20:01:31.832  INFO 21445 --- [nio-9901-exec-1] c.j.s.aop.RequestAopInterceptor          : doAfterReturing-->请求返回的信息为: hello world
    

    由上得出规律:

    • 执行顺序为JointPoint之前-->前置通知-->JointPoint-->JoinPoint之后-->后置通知-->后置返回通知
    • 通知依赖于切入点,即Advice依赖于PointCut。PointCut是切面必须指定的

    ConfigBeanDefinitionParser-切面aop在Spring的解析器

    我们直接看下其主要的parse()方法源码

    @Override
    	public BeanDefinition parse(Element element, ParserContext parserContext) {
    		CompositeComponentDefinition compositeDef =
    				new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    		parserContext.pushContainingComponent(compositeDef);
    		// 注册自动代理模式创建器,其作用于<aop:config>
    		configureAutoProxyCreator(parserContext, element);
    		// 解析其aop:config子节点下的aop:pointcut/aop:advisor/aop:aspect
    		List<Element> childElts = DomUtils.getChildElements(element);
    		for (Element elt: childElts) {
    			String localName = parserContext.getDelegate().getLocalName(elt);
    			if (POINTCUT.equals(localName)) {
    				parsePointcut(elt, parserContext);
    			}
    			else if (ADVISOR.equals(localName)) {
    				parseAdvisor(elt, parserContext);
    			}
    			else if (ASPECT.equals(localName)) {
    				parseAspect(elt, parserContext);
    			}
    		}
    
    		parserContext.popAndRegisterContainingComponent();
    		return null;
    	}
    

    由以上源码可以得知aop:config节点的解析器主要做的工作如下:

    • 注册自动代理模式创建器
    • 解析子节点aop:pointcut/aop:advisor/aop:aspect

    那我们接下来按上述步骤解读下

    ConfigBeanDefinitionParser#configureAutoProxyCreator()-注册自动代理创建器

    直接上源码

    /**
    	 * Configures the auto proxy creator needed to support the {@link BeanDefinition BeanDefinitions}
    	 * created by the '{@code <aop:config/>}' tag. Will force class proxying if the
    	 * '{@code proxy-target-class}' attribute is set to '{@code true}'.
    	 * @see AopNamespaceUtils
    	 */
    	private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
    		AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
    	}
    

    直接通过AopNamespaceUtils工具类完成自动代理注册工作,并且提到aop:config如果指定proxy-target-classtrue,则将采用类代理。有点稀里糊涂的,我们还是继续看AopNamespaceUtils#registerAspectJAutoProxyCreatorIfNecessary()的方法源码

    public static void registerAspectJAutoProxyCreatorIfNecessary(
    			ParserContext parserContext, Element sourceElement) {
    		// 注册名为org.springframework.aop.config.internalAutoProxyCreator的beanDefinition,其中的class类为`AspectJAwareAdvisorAutoProxyCreator`,其也会被注册到bean工厂中
    		BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
    				parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    		// 如果指定proxy-target-class=true,则使用CGLIB代理,否则使用JDK代理
    		// 其实其为AspectJAwareAdvisorAutoProxyCreator类的proxyTargetClass属性
    		useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    		// 注册到spring的bean工厂中,再次校验是否已注册
    		registerComponentIfNecessary(beanDefinition, parserContext);
    	}
    
    1. 此处最需要关注的是AspectJAwareAdvisorAutoProxyCreator代理创建类,其会对Advisor类做如何的代理操作,详情可参阅>>>SpringAop源码情操陶冶-AspectJAwareAdvisorAutoProxyCreator

    2. aop:config的属性proxy-target-class含义

    • true。表示针对目标对象为接口类,则采取JDK代理;对于其他的类,则采取CGLIB代理
    • false。表示进行JDK代理

    ConfigBeanDefinitionParser#parsePointcut()-解析切入点

    我们先简单的看下源码

    	/**
    	 * Parses the supplied {@code &lt;pointcut&gt;} and registers the resulting
    	 * Pointcut with the BeanDefinitionRegistry.
    	 */
    	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));
    			// 创建切入点bean对象
    			// beanClass为AspectJExpressionPointcut.class。并且设置属性expression到该beanClass中
    			pointcutDefinition = createPointcutDefinition(expression);
    			pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
    
    			String pointcutBeanName = id;
    			if (StringUtils.hasText(pointcutBeanName)) {
    				// 注册bean对象
    				parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
    			}
    			else {
    				pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
    			}
    
    			parserContext.registerComponent(
    					new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
    		}
    		finally {
    			// 创建后移除
    			this.parseState.pop();
    		}
    
    		return pointcutDefinition;
    	}
    

    aop:point-cut对应的beanClass为AspectJExpressionPointcut。内部也含有expression属性

    ConfigBeanDefinitionParser#parseAspect()-解析切面

    切面内可以有多个切入点(pointcut)和对应的多个通知(advice)。下面直接查看源码

    private void parseAspect(Element aspectElement, ParserContext parserContext) {
    		// <aop:aspect> id属性
    		String aspectId = aspectElement.getAttribute(ID);
    		// aop ref属性,必须配置。代表切面
    		String aspectName = aspectElement.getAttribute(REF);
    
    		try {
    			this.parseState.push(new AspectEntry(aspectId, aspectName));
    			List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
    			List<BeanReference> beanReferences = new ArrayList<BeanReference>();
    			// 解析<aop:aspect>下的declare-parents节点
    			// 采用的是DeclareParentsAdvisor作为beanClass加载
    			List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
    			for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
    				Element declareParentsElement = declareParents.get(i);
    				beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
    			}
    
    			// We have to parse "advice" and all the advice kinds in one loop, to get the
    			// ordering semantics right.
    			// 解析其下的advice节点
    			NodeList nodeList = aspectElement.getChildNodes();
    			boolean adviceFoundAlready = false;
    			for (int i = 0; i < nodeList.getLength(); i++) {
    				Node node = nodeList.item(i);
    				// 是否为advice:before/advice:after/advice:after-returning/advice:after-throwing/advice:around节点
    				if (isAdviceNode(node, parserContext)) {
    					// 校验aop:aspect必须有ref属性,否则无法对切入点进行观察操作
    					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));
    					}
    					// 解析advice节点并注册到bean工厂中
    					AbstractBeanDefinition advisorDefinition = parseAdvice(
    							aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
    					beanDefinitions.add(advisorDefinition);
    				}
    			}
    
    			AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
    					aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
    			parserContext.pushContainingComponent(aspectComponentDefinition);
    			
    			// 解析aop:point-cut节点并注册到bean工厂
    			List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
    			for (Element pointcutElement : pointcuts) {
    				parsePointcut(pointcutElement, parserContext);
    			}
    
    			parserContext.popAndRegisterContainingComponent();
    		}
    		finally {
    			this.parseState.pop();
    		}
    	}
    
    1. aop:aspect切面配置,必须有ref属性,其可以是普通的class类,可定义很多的方法,用于aop:before/aop:after等节点引用来绑定切入点

    2. aop:aspect内部的节点aop:ponit-cut是必须配置的,而且其可以配置更多的切入点供aop:before/aop:after等节点选择使用

    3. aop:before/aop:after节点的解析可见>>>ConfigBeanDefinitionParser#parseAdvice()-解析Advice通知类

    4. 对通知类的解析最终都会保存到AspectJPointcutAdvisor.class,其内部属性包含AdvicePonitcut信息。与下面的ConfigBeanDefinitionParser#parseAdvisor()是类似的

    ConfigBeanDefinitionParser#parseAdvisor()-解析Advisor顾问类

    Advisor的功能其实与Aspect的功能类似,相当于是特殊的Aspect。
    用法如下

    <tx:advice id="txAdvice" transaction-manager="transactionManager">  
            <tx:attributes>  
                <tx:method name="save*" propagation="REQUIRED"/>  
                <tx:method name="delete*" propagation="REQUIRED"/>  
                <tx:method name="get*" read-only="true"/>  
                <tx:method name="find*" read-only="true"/>  
            </tx:attributes>  
    </tx:advice> 
    <aop:config>
    	<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Dao.*(..))" />
    	<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Service.*(..))" />
    	<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.XXX..*.*Controller.*(..))" />
    <aop:config>
    

    接着我们直接看下其中的源码

    private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
    		// 解析<aop:advisor>节点,最终创建的beanClass为`DefaultBeanFactoryPointcutAdvisor`
    		// 另外advice-ref属性必须定义,其与内部属性adviceBeanName对应
    		AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
    		String id = advisorElement.getAttribute(ID);
    
    		try {
    			// 注册到bean工厂
    			this.parseState.push(new AdvisorEntry(id));
    			String advisorBeanName = id;
    			if (StringUtils.hasText(advisorBeanName)) {
    				parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
    			}
    			else {
    				advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
    			}
    			// 解析point-cut属性并赋值到DefaultBeanFactoryPointcutAdvisor#pointcut内部属性
    			Object pointcut = parsePointcutProperty(advisorElement, parserContext);
    			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();
    		}
    	}
    
    • 可以看到aop:advisor的解析最终包装为beanClass为DefaultBeanFactoryPointcutAdvisor的beanDefinition对象,其内部的属性advisorBeanNamepointcut是必须被赋值的,对应的节点信息为<aop:advisor advice-ref="" pointcut="">

    • aop:aspectaop:advisor最终解析成的beanClass均为org.springframework.aop.PointcutAdvisor接口类的实现类。内部都包含两个属性类org.springframework.aop.Pointcut接口实现类和org.aopalliance.aop.Advice接口实现类

    • aop:advisor基本类似于aop:aspect,只是其没有后者分的那么细的通知即aop:before/aop:after等节点。

    小结

    1.aop相关节点解析后对应的beanClass作下汇总

    • aop:point-cut对应的beanClass为org.springframework.aop.aspectj.AspectJExpressionPointcut
    • aop:before/aop:after等对应的beanClass为org.springframework.aop.aspectj.AbstractAspectJAdvice的子类
    • aop:advisor对应的beanClass为org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
    • aop:aspect对应的beanClass为org.springframework.aop.aspectj.AspectJPointcutAdvisor

    2.aop最终会对point-cut表达式提及的类以及方法进行代理操作,默认采取JDK代理,如果指定proxy-target-class属性为true,则也会采用CGLIB代理

  • 相关阅读:
    第二次上机
    第二次作业
    第一次上机
    第一次作业
    第3次上机作业
    第四周作业
    第二次上机作业(第四周)
    第三周作业
    第一次上机作业
    第一次JAVA作业
  • 原文地址:https://www.cnblogs.com/question-sky/p/7688863.html
Copyright © 2011-2022 走看看