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代理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM