前面的章節(jié)已經(jīng)介紹了AOP的相關(guān)概念和一些知識點,接下來我們就要開始分析SpringAOP的源碼了,接下來的分析都基于@AspectJ
注解。
1.aspectj-autoproxy標簽簡介
使用注解方式應(yīng)用aop需要在配置文件中配置<aop:aspectj-autoproxy/>
, 而該標簽有兩個屬性,proxy-target-class
和 expose-proxy
钝域。
- proxy-target-class
如果被代理的目標對象至少實現(xiàn)了一個接口,則會使用JDK動態(tài)代理锭魔,所有實現(xiàn)該目標類實現(xiàn)的接口都將被代理例证;如果該目標對象沒有實現(xiàn)任何接口,則創(chuàng)建CGLIB動態(tài)代理迷捧。但是可以通過proxy-target-class屬性強制指定使用CGLIB代理织咧。如果指定了proxy-target-class="true"
則將強制開啟CGLIB動態(tài)代理。 - expose-proxy
解決目標對象內(nèi)部的自我調(diào)用無法實施切面增強的問題漠秋。
2.aspectj-autoproxy標簽解析
在第十七小節(jié)笙蒙,講解了Spring對默認標簽的解析過程,但是沒有對默認標簽解析進行分析庆锦,今天就來分析一下Spring是如何解析自定義標簽aspectj-autoproxy的捅位。順便通過對該標簽的解析,補充十七節(jié)欠缺的分析搂抒。
- 自定義標簽解析入口
打開DefaultBeanDefinitionDocumentReader類的parseBeanDefinitions方法
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 1艇搀、解析默認命名空間
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);
}
}
}
}
// 2、解析自定義命名空間
else {
delegate.parseCustomElement(root);
}
}
通過parseCustomElement解析自定義標簽
- 自定義標簽解析過程
/**
* 解析自定義標簽
* @param ele
* @param containingBd
* @return
*/
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 1求晶、獲取namespaceUri
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 2中符、根據(jù)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;
}
// 2、使用命名空間解析器解析自定義標簽
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
對自定義標簽的解析交給了NamespaceHandler接口誉帅,如果我們想在Spring中實現(xiàn)自己的自定義標簽,那么就需要實現(xiàn)NamespaceHandler接口,并通過重寫其中的方法蚜锨,來完成對自定義標簽的解析档插。
- 獲取NamespaceHandler
public NamespaceHandler resolve(String namespaceUri) {
// 1.獲取所有的namespaceUri,NamespaceHandler鍵值對map集合并得到
// 當(dāng)前namespaceUri對應(yīng)的NamespaceHandler類
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
// 2亚再、通過BeanUtils實例化NamespaceHandler并調(diào)用其init方法進行初始化操作
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
該方法非常簡單郭膛,首先根據(jù)namespaceUri從所有的Map<namespaceUri,NamespaceHandler>中獲取對應(yīng)的NamespaceHandler實現(xiàn)類,然后通過BeanUtils.instantiateClass實例化該類氛悬,最后調(diào)用NamespaceHandler實例的init方法则剃。
- AopNamespaceHandler的init方法
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
在該方法中我們已經(jīng)看到了aspectj-autoproxy標簽,該方法也很簡單如捅,不做過多的分析棍现。接下來分析aspectj-autoproxy解析的過程。
3.通過NamespaceHandler的parse方法解析aspectj-autoproxy標簽
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
具體的轉(zhuǎn)換工作委托給了BeanDefinitionParser實例:
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 1镜遣、注冊AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 2己肮、處理子標簽<aop:include/>, 指定@Aspect類悲关,支持正則表達式谎僻,符合該表達式的切面類才會被應(yīng)用
extendBeanDefinition(element, parserContext);
return null;
}
該轉(zhuǎn)換過程主要完成兩個功能,注冊AnnotationAwareAspectJAutoProxyCreator和解析標簽<aop:aspectj-autoproxy/>
寓辱,并在第二步解析了<aop:include/>
子標簽艘绍,我們逐步分析一下:
3.1 注冊AnnotationAwareAspectJAutoProxyCreator
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
// 1、注冊AnnotationAwareAspectJAutoProxyCreator
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(),
parserContext.extractSource(sourceElement));
// 2秫筏、解析子標簽 proxy-target-class 和 expose-proxy
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// 3诱鞠、注冊組件并發(fā)送組件注冊事件
registerComponentIfNecessary(beanDefinition, parserContext);
}
- 注冊AnnotationAwareAspectJAutoProxyCreator
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
@Nullable Object source) {
// cls --> org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
// AUTO_PROXY_CREATOR_BEAN_NAME --> org.springframework.aop.config.internalAutoProxyCreator
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
// 如果registry已經(jīng)包含了internalAutoProxyCreator,
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
// 如果已經(jīng)注冊的internalAutoProxyCreator不是AnnotationAwareAspectJAutoProxyCreator跳昼,則需要判斷優(yōu)先級并決定使用哪個
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
// 如果已經(jīng)注冊的internalAutoProxyCreator是AnnotationAwareAspectJAutoProxyCreator般甲,則無需特殊處理
return null;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
// HIGHEST_PRECEDENCE --> 指定最高優(yōu)先級
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
// ROLE_INFRASTRUCTURE --> 表示Spring的內(nèi)部bean
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注冊BeanDefinition
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
- 解析子標簽 proxy-target-class 和 expose-proxy
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
if (sourceElement != null) {
// PROXY_TARGET_CLASS_ATTRIBUTE --> proxy-target-class
boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
// EXPOSE_PROXY_ATTRIBUTE --> expose-proxy
boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
該過程最核心的作用就是注冊AnnotationAwareAspectJAutoProxyCreator,關(guān)于該類的作用鹅颊,我們會在下面的章節(jié)分析敷存。
3.2 處理子標簽<aop:include/>
<aop:include/>
標簽的作用:例如有下面的配置文件,并指定了<aop:include name="aspectJTest"></aop:include>
堪伍,則aspectJTest切面類會被應(yīng)用锚烦,且name支持正則表達式,如果配置文件中的切面類不符合name的表達式帝雇,則切面類不會被應(yīng)用涮俄。
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
1、proxy-target-class
如果被代理的目標對象至少實現(xiàn)了一個接口尸闸,則會使用JDK動態(tài)代理彻亲,所有實現(xiàn)該目標類實現(xiàn)的接口都將被代理
如果該目標對象沒有實現(xiàn)任何接口孕锄,則創(chuàng)建CGLIB動態(tài)代理。
但是可以通過proxy-target-class屬性強制指定使用CGLIB代理苞尝,
2畸肆、expose-proxy
解決目標對象內(nèi)部的自我調(diào)用無法實施切面增強的問題
-->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true">
<!-- 指定@Aspect類,支持正則表達式宙址,符合該表達式的切面類才會被應(yīng)用-->
<aop:include name="aspectJTest"></aop:include>
</aop:aspectj-autoproxy>
<bean id="aopTestBean" class="com.lyc.cn.v2.day04.AopTestBean"></bean>
<bean name="aspectJTest" class="com.lyc.cn.v2.day04.AspectJTest"></bean>
</beans>
了解了標簽的作用之后轴脐,我們來看其解析過程,比較簡單就不附加注釋了:
private void extendBeanDefinition(Element element, ParserContext parserContext) {
BeanDefinition beanDef = parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
if (element.hasChildNodes()) {
addIncludePatterns(element, parserContext, beanDef);
}
}
private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
ManagedList<TypedStringValue> includePatterns = new ManagedList<>();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof Element) {
Element includeElement = (Element) node;
TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
valueHolder.setSource(parserContext.extractSource(includeElement));
includePatterns.add(valueHolder);
}
}
if (!includePatterns.isEmpty()) {
includePatterns.setSource(parserContext.extractSource(element));
beanDef.getPropertyValues().add("includePatterns", includePatterns);
}
}
那么到這里<aop:aspectj-autoproxy/>
標簽的解析工作也就完成了抡砂。
4.總結(jié)
本節(jié)主要分析了<aop:aspectj-autoproxy/>
標簽的解析大咱,而該標簽也是Spring的自定義標簽,所以注益,如果大家有自己的自定義標簽時碴巾,可以參考借鑒Spring的源碼,寫出自己的標簽解析過程聊浅。通過繼承NamespaceHandler實現(xiàn)自己的命名空間解析器即可餐抢。
在解析<aop:aspectj-autoproxy/>
的過程中,除了對標簽的解析工作之外低匙,還注冊了AnnotationAwareAspectJAutoProxyCreator旷痕,該類的作用是什么呢,我們留在下節(jié)分析顽冶。