参考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容器处理过了。
既然如此,那无非就两个地方处理:
- 加载Bean定义的时候应该有过特殊的处理
- 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);
|