Spring AOP实现原理

参考http://www.linkedkeeper.com/detail/blog.action?bid=1048

前言

AOP的实现原理分析是个比较难的知识点。为了探究AOP实现原理,首先定义几个类,一个Dao接口:

1
2
3
4
public interface Dao {
     public void select();
     public void insert();
}

Dao接口的实现类DaoImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DaoImpl implements Dao {
 
     @Override
     public void select() {
         System.out.println( "Enter DaoImpl.select()" );
     }
 
     @Override
     public void insert() {
         System.out.println( "Enter DaoImpl.insert()" );
     }
 
}

定义一个TimeHandler,用于方法调用前后打印时间,在AOP中,这扮演的是横切关注点的角色:

1
2
3
4
5
6
7
public class TimeHandler {
 
     public void printTime() {
         System.out.println( "CurrentTime:" + System.currentTimeMillis());
     }
 
}

定义一个XML文件aop.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<? xml version = "1.0" encoding = "UTF-8" ?>
< beans xmlns = "http://www.springframework.org/schema/beans"
     xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
     xmlns:aop = "http://www.springframework.org/schema/aop"
     xmlns:tx = "http://www.springframework.org/schema/tx"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 
 
http://www.springframework.org/schema/aop
 
 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
 
     < bean id = "daoImpl" class = "org.xrq.action.aop.DaoImpl" />
     < bean id = "timeHandler" class = "org.xrq.action.aop.TimeHandler" />
 
     < aop:config proxy-target-class = "true" >
         < aop:aspect id = "time" ref = "timeHandler" >
             < aop:pointcut id = "addAllMethod" expression = "execution(* org.xrq.action.aop.Dao.*(..))" />
             < aop:before method = "printTime" pointcut-ref = "addAllMethod" />
             < aop:after method = "printTime" pointcut-ref = "addAllMethod" />
         </ aop:aspect >
     </ aop:config >
 
</ beans >

写一段测试代码TestAop.java:

1
2
3
4
5
6
7
8
9
10
11
public class TestAop {
 
     @Test
     public void testAop() {
         ApplicationContext ac = new ClassPathXmlApplicationContext( "spring/aop.xml" );
 
         Dao dao = (Dao)ac.getBean( "daoImpl" );
         dao.select();
     }
 
}

代码运行结果就不看了,有了以上的内容,我们就可以根据这些跟一下代码,看看Spring到底是如何实现AOP的。

AOP实现原理——找到Spring处理AOP的源头

有很多朋友不愿意去看AOP源码的一个很大原因是因为找不到AOP源码实现的入口在哪里,这个确实是。不过我们可以看一下上面的测试代码,就普通Bean也好、AOP也好,最终都是通过getBean方法获取到Bean并调用方法的,getBean之后的对象已经前后都打印了TimeHandler类printTime()方法里面的内容,可以想见它们已经是被Spring容器处理过了。

既然如此,那无非就两个地方处理:

  1. 加载Bean定义的时候应该有过特殊的处理
  2. getBean的时候应该有过特殊的处理

因此,本文围绕【1.加载Bean定义的时候应该有过特殊的处理】展开,先找一下到底是哪里Spring对AOP做了特殊的处理。代码直接定位到DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
     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 {
         delegate.parseCustomElement(root);
     }
}

正常来说,遇到<bean id=”daoImpl”…>、<bean id=”timeHandler”…>这两个标签的时候,都会执行第9行的代码,因为<bean>标签是默认的Namespace。但是在遇到后面的<aop:config>标签的时候就不一样了,<aop:config>并不是默认的Namespace,因此会执行第12行的代码,看一下:

1
2
3
4
5
6
7
8
9
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
     String namespaceUri = getNamespaceURI(ele);
     NamespaceHandler handler = this .readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
     if (handler == null ) {
         error( "Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]" , ele);
         return null ;
     }
     return handler.parse(ele, new ParserContext( this .readerContext, this , containingBd));
}

因为之前把整个XML解析为了org.w3c.dom.Document,org.w3c.dom.Document以树的形式表示整个XML,具体到每一个节点就是一个Node。

首先第2行从<aop:config>这个Node(参数Element是Node接口的子接口)中拿到Namespace=”http://www.springframework.org/schema/aop“,第3行的代码根据这个Namespace获取对应的NamespaceHandler即Namespace处理器,具体到aop这个Namespace的NamespaceHandler是org.springframework.aop.config.AopNamespaceHandler类,也就是第3行代码获取到的结果。具体到AopNamespaceHandler里面,有几个Parser,是用于具体标签转换的,分别为:

  • config–>ConfigBeanDefinitionParser
  • aspectj-autoproxy–>AspectJAutoProxyBeanDefinitionParser
  • scoped-proxy–>ScopedProxyBeanDefinitionDecorator
  • spring-configured–>SpringConfiguredBeanDefinitionParser

接着,就是第8行的代码,利用AopNamespaceHandler的parse方法,解析<aop:config>下的内容了。

AOP Bean定义加载——根据织入方式将<aop:before>、<aop:after>转换成名为adviceDef的RootBeanDefinition

上面经过分析,已经找到了Spring是通过AopNamespaceHandler处理的AOP,那么接着进入AopNamespaceHandler的parse方法源代码:

1
2
3
public BeanDefinition parse(Element element, ParserContext parserContext) {
     return findParserForElement(element, parserContext).parse(element, parserContext);
}

首先获取具体的Parser,因为当前节点是<aop:config>,上一部分最后有列,config是通过ConfigBeanDefinitionParser来处理的,因此findParserForElement(element, parserContext)这一部分代码获取到的是ConfigBeanDefinitionParser,接着看ConfigBeanDefinitionParser的parse方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public BeanDefinition parse(Element element, ParserContext parserContext) {
     CompositeComponentDefinition compositeDef =
             new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
     parserContext.pushContainingComponent(compositeDef);
 
     configureAutoProxyCreator(parserContext, element);
 
     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 ;
}

重点先提一下第6行的代码,该行代码的具体实现不跟了但它非常重要,configureAutoProxyCreator方法的作用我用几句话说一下:

  • 向Spring容器注册了一个BeanName为org.springframework.aop.config.internalAutoProxyCreator的Bean定义,可以自定义也可以使用Spring提供的(根据优先级来)
  • Spring默认提供的是org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator,这个类是AOP的核心类,留在下篇讲解
  • 在这个方法里面也会根据配置proxy-target-class和expose-proxy,设置是否使用CGLIB进行代理以及是否暴露最终的代理。

<aop:config>下的节点为<aop:aspect>,想见必然是执行第18行的代码parseAspect,跟进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
private void parseAspect(Element aspectElement, ParserContext parserContext) {
     String aspectId = aspectElement.getAttribute(ID);
     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>();
 
         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.
         NodeList nodeList = aspectElement.getChildNodes();
         boolean adviceFoundAlready = false ;
         for ( int i = 0 ; i < nodeList.getLength(); i++) {
             Node node = nodeList.item(i);
             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));
                 }
                 AbstractBeanDefinition advisorDefinition = parseAdvice(
                         aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值