【Spring AOP】【二】Spring AOP源码解析-XML方式加载解析过程
1 前言
这篇我们看一下,我们的AOP代码是怎么被Spring加载的进去的,那么分两种一种是XML配置的,一种就是我们常用的注解,我们从源码先看下XML方式的都是怎么被加载解析的。
2 代码准备
<context:component-scan base-package="com.virtuous.demo.spring.cycle"/>
<bean id="A" class="com.virtuous.demo.spring.cycle.A" /> <!--通知--> <bean id="logAspect" class="com.virtuous.demo.spring.LogAspect"/> <!--aop配置--> <aop:config proxy-target-class="true"> <aop:aspect ref="logAspect" order="1"> <aop:pointcut id="logAspectPointcut" expression="execution( * com.virtuous.demo.spring.cycle.A.*(..))"/> <aop:before method="before" pointcut-ref="logAspectPointcut"/> <aop:after method="after" pointcut-ref="logAspectPointcut"/> <aop:around method="around" pointcut-ref="logAspectPointcut"/> <aop:after-returning method="afterReturning" pointcut-ref="logAspectPointcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="logAspectPointcut"/> </aop:aspect> </aop:config>
/** * @author kuku */ public class LogAspect { public void before(JoinPoint point) { System.out.println("before"); } public void after(JoinPoint point) { System.out.println("after"); } public void afterReturning(JoinPoint point) { System.out.println("afterReturning"); } public void afterThrowing(JoinPoint point) { System.out.println("afterThrowing"); } public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("around before"); joinPoint.proceed(); System.out.println("around after"); } }
@Component public class A { public void say() { System.out.println("111"); } }
// 测试类 public class ApplicationContextTest { @Test public void testFactoryBean() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring/spring-alias.xml"); A a = applicationContext.getBean("a", A.class); a.say(); } }
3 源码分析
(1)Spring对于AOP在XML的配置采用的是自定义标签解析的形式,入口在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中。每种自定义标签都有自己的Handler来处理。
// 解析XML
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 解析默认的名称空间 比如bean if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { // 解析自定义的名称空间 比如AOP的解析 delegate.parseCustomElement(root); } }
// 解析自定义标签 public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { // 1、获取namespaceUri String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // 2、根据namespaceUri得到命名空间解析器 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // 3、使用命名空间解析器解析自定义标签 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
(2)针对AOP的解析,对应的Handler为AopNamespaceHandler
public class AopNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // In 2.0 XSD as well as in 2.5+ XSDs registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace in 2.5+ registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); } }
(3)ConfigBeanDefinitionParser解析aop:config
public BeanDefinition parse(Element element, ParserContext parserContext) { CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); parserContext.pushContainingComponent(compositeDef); // 注册AspectJAutoProxyCreator configureAutoProxyCreator(parserContext, element); List<Element> childElts = DomUtils.getChildElements(element); for (Element elt: childElts) { String localName = parserContext.getDelegate().getLocalName(elt); // 解析aop:pointcut if (POINTCUT.equals(localName)) { parsePointcut(elt, parserContext); } // 解析aop:advisor else if (ADVISOR.equals(localName)) { parseAdvisor(elt, parserContext); } // 解析aop:config else if (ASPECT.equals(localName)) { parseAspect(elt, parserContext); } } parserContext.popAndRegisterContainingComponent(); return null; }
(4)passeAspect,并获取下边的通知,并进行一一创建
private void parseAspect(Element aspectElement, ParserContext parserContext) { // 解析 id String aspectId = aspectElement.getAttribute(ID); // 解析ref String aspectName = aspectElement.getAttribute(REF); try { this.parseState.push(new AspectEntry(aspectId, aspectName)); List<BeanDefinition> beanDefinitions = new ArrayList<>(); List<BeanReference> beanReferences = new ArrayList<>(); 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 // 解析子元素advice 比如 NodeList nodeList = aspectElement.getChildNodes(); boolean adviceFoundAlready = false; for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); // 判断是不是通知的标签 也就是是不是那5个通知 before after around after-returning after-throwing 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; } beanReferences.add(new RuntimeBeanReference(aspectName)); } // 给5个通知创建对应的BeanDefinition 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);
// 解析切点 也是作为BeanDefinition List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT); for (Element pointcutElement : pointcuts) { parsePointcut(pointcutElement, parserContext); } parserContext.popAndRegisterContainingComponent(); } finally { this.parseState.pop(); } }
// 创建通知的BeanDefinition private AbstractBeanDefinition createAdviceDefinition( Element adviceElement, ParserContext parserContext, String aspectName, int order, RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef, List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) { RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext)); adviceDefinition.setSource(parserContext.extractSource(adviceElement)); adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName); adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order); 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)); } ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues(); cav.addIndexedArgumentValue(METHOD_INDEX, methodDef); // !!!这点很关键 就是解析通知的切点pointCut 这样切点就和通知关联上了 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; }
切点的创建和通知差不多这里就不展示了,这样我们的AOP的配置就都生成了BeanDefinition,我们来总结一下执行流程:
- XML中的AOP相关的名称空间,由AopNamespaceHandler来负则解析
- config标签的内容由ConfigBeanDefinitionParser来解析内容
- 解析aspect标签的内容
- 遍历每个通知以及切点进行创建对应的BeanDefinition并注册到Bean工厂(有一点需要注意的就是创建通知的时候会解析标签上的pointCut并关联,这样切点和通知有了联系)
4 小结
我们本文讲解了XML方式配置的AOP是如何被加载进Spring中的,主要就是对我们的配置进行解析,封装成BeanDefinition放进Bean工厂,下文我们讲解注解方式的解析过程。