Spring AOP學習筆記02:如何開啟AOP
上文簡要總結了一些AOP的基本概念,并在此基礎上叙述了Spring AOP的基本原理,并且輔以一個簡單例子幫助了解。從本文開始,我們要開始深入到源碼層面來一探Spring AOP魔法的原理了。
要使用Spring AOP,第一步是要将這一功能開啟,一般有兩種方式:
通過xml配置檔案的方式;
通過注解的方式;
-
配置檔案開啟AOP功能
我們先來看一下配置檔案的方式,這個上文也提到過,在xml檔案中加上對應的标簽,而且别忘了加上對應的名稱空間(即下面的xmlns:aop。。。):
<?xml version="1.0" encoding="UTF-8"?>
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop = "http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<aop:aspectj-autoproxy/>
這裡是通過标簽來完成開啟AOP功能,這是一個自定義标簽,需要自定義其解析,而這些spring都已經實作好了,前面專門寫過一篇文章講述spring是如何解析自定義xml标簽的,我們這裡大緻回顧一下解析流程:
定義一個XML檔案來描述你的自定義标簽元素;
建立一個Handler,擴充自NamespaceHandlerSupport,用于注冊下面的parser;
建立若幹個BeanDefinitionParser的實作,用來解析XML檔案中的定義;
将上述檔案注冊到Spring中,這裡其實是做一下配置;
我們就不照着這個步驟來了,我們直接參考spring對這個自定義标簽的解析過程,上面的4個步驟隻是作為參考,在整個解析過程中都會涉及到。
前面講解析自定義xml标簽時候提到過,解析的流程大緻如下:
首先會去擷取自定義标簽對應的名稱空間;
然後根據名稱空間找到對應的NamespaceHandler;
調用自定義的NamespaceHandler進行解析;
1.1 擷取名稱空間
這裡對應的名稱空間是什麼呢?在上面的開啟aop的配置檔案裡面名稱空間那裡給出了一些線索,其實就是下面這個:
http://www.springframework.org/schema/aop至于名稱空間的擷取,也無甚好說的,其實就是直接調用org.w3c.dom.Node提供的相應方法來完成名稱空間的提取。
1.2 擷取handler
然後又是如何根據名稱空間找到對應的NamespaceHandler呢?之前也說到過,在找對應的NamespaceHandler時會去META-INF/spring.handlers這個目錄下加載資源檔案,我們來找一下spring.handlers這個檔案看看(需要去spring-aop對應的jar報下找):
http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
看到沒,這裡是以key-value的形式維護着名稱空間和對應handler的關系的,是以對應的handler就是這個AopNamespaceHandler。spring根據名稱空間找到這個handler之後,會通過反射的方式将這個類加載,并緩存起來。
1.3 解析标簽
上面的handler隻有一個自定義的方法:
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());
}
這是一個初始化方法,在加載的時候會執行,主要作用就是注冊一些解析器,這裡我們主要關注AspectJAutoProxyBeanDefinitionParser,這就是我們要找的,它的作用就是解析标簽的。主要流程就是,spring會調用上一步拿到的AopNamespaceHandler的parse()方法,在這個方法裡面,會将解析的工作委托給AspectJAutoProxyBeanDefinitionParser來完成具體解析工作,我們就來看一下具體幹了啥吧。
開始解析的工作從這裡開始:
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
此時我們拿到的handler其實是我們自定義的AopNamespaceHandler了,但是它并沒有實作parse()方法,是以這裡這個應該是調用的父類(NamespaceHandlerSupport)中的parse()方法:
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 尋找解析器并進行解析操作
return findParserForElement(element, parserContext).parse(element, parserContext);
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 擷取元素名稱,也就是<aop:aspectj-autoproxy/>中的aspectj-autoproxy
String localName = parserContext.getDelegate().getLocalName(element);
// 根據aspectj-autoproxy找到對應的解析器,也就是在registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
// 注冊的解析器
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
首先是尋找元素對應的解析器,然後調用其parse()方法。結合我們前面的示例,其實就是首先擷取在AopNamespaceHandler類中的init()方法中注冊對應的AspectJAutoProxyBeanDefinitionParser執行個體,并調用其parse()方法進行進一步解析:
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(element, parserContext);
return null;
// 下面的代碼在AopConfigUtils中
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, parserContext);
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
上面這一堆代碼最核心的部分就在後兩個方法中,就是完成了對AnnotationAwareAspectJAutoProxyCreator類的注冊,到這裡對自定義标簽的解析也就完成了,可以看到其最核心的部分就是完成了對AnnotationAwareAspectJAutoProxyCreator類的注冊,那為什麼注冊了這個類就開啟了aop功能呢?這裡先賣個關子,後面詳細說。
這裡再回過頭來看一下上面說到的spring對自定義标簽解析的4個步驟,其實第一步的schema對應的是在org.springframework.aop.config路徑下的spring-aop-3.0.xsd檔案,其映射關系是維護在META-INF/spring.schemas檔案中的,而spring-aop-3.0.xsd的主要作用就是描述自定義标簽。
當通過META-INF/spring.handlers找到對應的AopNamespaceHandler,并通過在其加載後執行init()方法過程中完成了AspectJAutoProxyBeanDefinitionParser的注冊,有這個parser再來完成對自定義标簽的解析工作,這對應上面4個步驟中的第二步和第三部。至于第四步的配置工作,無非就是将spring.schemas和spring.handlers這兩個配置檔案放在META-INF/目錄下罷了。
關于這部分解析過程,寫得不是非常詳細,如果有不明白,可以參考之前一篇文章,講spring是如何解析自定義xml标簽。
-
注解方式開啟aop
另一種開啟spring aop的方式是通過注解的方式,使用的注解是@EnableAspectJAutoProxy,可以通過配置類的方式完成注冊:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
也可以在啟動類上直接加上這個注解,這在springboot中比較常見,其實質也是上面的方式。通過這種方式配置之後,就開啟了aop功能,那具體又是如何實作的呢?我們看一下這個注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
這裡我們的關注點是其通過@Import(AspectJAutoProxyRegistrar.class)引入了AspectJAutoProxyRegistrar,那這又是什麼?
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
看到這裡,是不是有點眼熟了呢?是的,其實它也是和上面說的xml配置使用的方式一樣,通過AopConfigUtils來完成AnnotationAwareAspectJAutoProxyCreator類的注冊。是不是比xml配置檔案的方式友善許多呢。
-
開啟aop的魔法
通過前面的學習我們了解了可以通過Spring自定義配置完成對AnnotationAwareAspectJAutoProxyCreator類型的自動注冊,而這個類到底是做了什麼工作來實作AOP的操作呢?這裡還是先來看一下AnnotationAwareAspectJAutoProxyCreator的類層次結構:
這裡有一個很重要的點,就是AnnotationAwareAspectJAutoProxyCreator實作了BeanPostProcessor接口。在IOC部分的文章中有詳細說過,Spring在加載Bean的過程中會在執行個體化bean前後調用BeanPostProcessor的相關方法(相關邏輯是在initializeBean方法中,調用postProcessBeforeInitialization、postProcessAfterInitialization方法),而AOP的魔法就是從這裡開始的。
每次看到這裡,我内心對spring的軟體架構設計都是湧現出無比的佩服,通過後處理器的方式來做擴充,對原有子產品是沒有任何改動,也不會産生耦合,spring親自踐行着對修改關閉,對擴充開放的原則。
-
總結
本文我們學習了spring是如何開啟aop功能的,無論是通過xml配置檔案方式,還是通過Java config這種注解的方式,其最終都是完成了将AnnotationAwareAspectJAutoProxyCreator這個類注冊到spring容器當中,那這個類又有什麼魔法,可以達到将其注冊到容器即達到開啟aop的功效,其實其繼承自BeanPostProcessor接口,通過後處理器的方式擴充出了開啟spring aop的功能。
原文位址
https://www.cnblogs.com/volcano-liu/p/12990888.html