前言
在Spring中AOP是我們使用的非常頻繁的一個特性。通過AOP我們可以補足一些物件導向程式設計中不足或難以實現的部分。
AOP
前置理論
首先在學習原始碼之前我們需要了解關於AOP的相關概念如切點切面等,以及如何使用AOP,這裡可以看我之前的文章:Spring系列之AOP的原理及手動實現
建立AOP相關物件
對於Java這種面嚮物件語言來說任何功能的實現都是依賴於物件,AOP也不例外。
首先我們先來準備好在配置檔案中配置好AOP相關的屬性。
spring.xml
<bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/>
<bean id="beforeAdvice" class="cn.javass.spring.chapter6.aop.BeforeAdviceImpl"/>
<aop:aspectj-autoproxy/>
<aop:config>
<aop:pointcut id="pointcutA" expression="execution(* cn.javass..*.sayAfterReturning(..))"></aop:pointcut>
<aop:advisor id="advisor" pointcut="execution(* cn.javass..*.sayAdvisorBefore(..))"
advice-ref="beforeAdvice"/>
<aop:aspect id="aspects" ref="aspect">
<aop:before pointcut="execution(* cn.javass..*.sayBefore(..)) and args(param)" method="beforeAdvice(java.lang.String)" arg-names="param"/>
<aop:after-returning pointcut-ref="pointcutA" method="afterReturningAdvice" arg-names="retVal" returning="retVal"/>
<aop:after-throwing pointcut="execution(* cn.javass..*.sayAfterThrowing(..))" method="afterThrowingAdvice" arg-names="exception" throwing="exception"/>
<aop:after pointcut="execution(* cn.javass..*.sayAfterFinally(..))" method="afterFinallyAdvice"/>
<aop:around pointcut="execution(* cn.javass..*.sayAround(..))" method="aroundAdvice"/>
</aop:aspect>
</aop:config>
在上面的配置中建立了幾種不同的advice。這些配置在spring啟動時會被相應的建立為物件。
AopNamespaceHandler
在前面IOC文章中我們有提到在Spring中通過spi的機制來確定解析配置檔案中不同標籤的解析類。
在aop包中找到spring.handlers檔案:
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
可以確定的是處理aop相關標籤的就是AopNamespaceHandler
這個類。
public void init() {
this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
在AopNamespaceHandler
中除了建構函式就只有上面的init方法,上面程式碼就是註冊解析不同標籤的解析器的BeanDefinition。我們需要關注的是aspectj-autoproxy
標籤的解析器類AspectJAutoProxyBeanDefinitionParser
。
#### AopConfigUtils#registerOrEscalateApcAsRequired
跟蹤```AspectJAutoProxyBeanDefinitionParser```的parse方法最終會進入到```AopConfigUtils#registerOrEscalateApcAsRequired```中。
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;
}
這裡的常量```AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator";```
首先要明白的是internalAutoProxyCreator並不是spring中的一個實際的類,AUTO_PROXY_CREATOR_BEAN_NAME是一個用於建立aop類的Beandefinition的名字。
在上面的程式碼邏輯中如果AUTO_PROXY_CREATOR_BEAN_NAME表示的Beandefinition已經存在則判斷新需要註冊的類其優先順序和已經存在的類定義進行比較,如果新需要註冊的優先順序較高則進行替換。
如果不存在已經註冊的Beandefinition則將其進行註冊。被註冊的Beandefinition表示的類為```AspectJAwareAdvisorAutoProxyCreator```。
#### 完成aop功能需要建立的物件
在前面IOC文章中分析過了在解析完配置檔案後需要建立的物件都會將其BeanDefinition註冊到IOC容器中,所以我們可以將斷點設定在配置檔案解析完成之後就可以看到需要建立那些物件了。
![需要建立的物件](https://user-gold-cdn.xitu.io/2019/7/5/16bc0e4a57f8a819?w=789&h=405&f=png&s=22442)
如上圖```helloWorldService```就是需要被增強的類。
public interface IHelloWorldService {
void sayHello();
void sayBefore(String param);
boolean sayAfterReturning();
void sayAfterThrowing();
boolean sayAfterFinally();
void sayAround(String param);
void sayAdvisorBefore(String param);
}
而```aspect,beforeAdvice,pointcutA,advisor```都是我們在配置檔案中配置過的,是切點,切面和處理方法的實現類。```org.springframework.aop.config.internalAutoProxyCreator```是上面分析過的用於建立aop代理的實現類。
而後面的以```org.springframework.aop.aspectj.AspectJPointcutAdvisor```開頭的幾個類實際上就是包含了切點和通知的一個切面的實現類,也就是它來決定哪些類需要被增強。
![增強實現類](https://user-gold-cdn.xitu.io/2019/7/5/16bc0e4a582a62c7?w=785&h=223&f=png&s=15964)
### 功能增強
#### 增強時機
如果看過前面手寫aop文章的同學應該知道當時我們分析aop增強時機時有說過aop的增強功能實際上是依賴於動態代理實現的。而動態代理如果要對一個物件進行增強那麼首先需要持有該物件才行。
所以我們在對物件進行增強的前提是該物件已經被建立完成之後。而且我們要清楚的是一個類物件被增強後我們所有需要使用該物件的地方都應該使用該物件,這樣就確定了類增強的時機一定是在類物件建立之後並且在完成注入之前。
#### AspectJAwareAdvisorAutoProxyCreator
前面有說過建立代理物件實際上是通過```AspectJAwareAdvisorAutoProxyCreator```來完成,先來了解一下該類,檢視該類的繼承體系。
![繼承體系](https://user-gold-cdn.xitu.io/2019/7/5/16bc0e4a5a031b8b?w=639&h=215&f=png&s=21661)
可以看到實際上該類本身還是一個BeanPostProcessor,那麼可以肯定的是我們只要找到執行BeanPostProcessor的地方並且是在例項化後執行的地方即可。經過除錯後定位到```AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization```方法。
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
這裡是對後置處理器進行遍歷,對於aop我們需要關注的是```AspectJAwareAdvisorAutoProxyCreator```這一個處理器。
##### wrapIfNecessary
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//more code
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
這裡去掉了前面一些程式碼,getAdvicesAndAdvisorsForBean方法是用來獲取和當前物件匹配的切面。這裡獲取相匹配的切面類是通過```AbstractAdvisorAutoProxyCreator```來實現。
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
//根據在配置檔案中配置的order屬性或者註解@order()進行從小到大的排序
//order的值越小其優先順序越高
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
首先獲取到所有的切面類,然後通過```AopUtils.findAdvisorsThatCanApply```方法來確定哪些類能夠匹配。
##### AopUtils.findAdvisorsThatCanApply
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
實現邏輯很簡單,遍歷所有的advisor呼叫canApply確定是否匹配。
切面是有切入點和通知組成,切入點用來確定哪些物件需要被增強,而通知決定如何進行增強。所以很明顯這裡確定類物件是否匹配是由切入點(pointCut)決定的。
我們先來看一下切入點是什麼。
public interface Pointcut {
//類過濾器 用於確定類物件是否匹配 只有當類物件匹配後才進行方法的匹配
ClassFilter getClassFilter();
//方法匹配器 用於確定具體哪一些方法需要被增強。
MethodMatcher getMethodMatcher();
//生成一個pointcut物件例項
Pointcut TRUE = TruePointcut.INSTANCE;
}
上面我們可以看到實際上就是通過ClassFilter和MethodMatcher相互配合來實現的,具體的實現過程會因為實現方式大同小異。其中實現方式包括比如正則匹配,AspectJ匹配等,在我們之前的手寫系列中就是通過正則來進行匹配的,這裡匹配的實現不深入探討。
通過上面的邏輯便可以確定好增強該類會用到哪些advisor。
#### createProxy
當確定好需要用到的advisor和其順序後就開始進行建立代理物件了。建立代理物件的方法由前面提到的```wrapIfNecessary```來呼叫```createProxy```方法實現。
protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
建立實際上市代理通過代理工廠類(ProxyFactory)實現的。
##### JDK代理還是cglib
在建立代理物件時需要確定使用JDK代理還是cglib代理,前面有提到過如果在配置檔案中配置了```proxy-target-class="true"```的話那麼就只會使用cglib進行代理。但是如果沒有配置的話則需要通過實際情況來決定是JDK代理還是cglib。
而除了```proxy-target-class```外,我們實際上還可以配置一個屬性```optimize```,該屬性預設值為false,如果我們將其置為true那麼就表示允許spring對代理生成策略進行優化,意思就是如果該類有介面,就代理介面(使用JDK代理);如果沒有介面,就代理類(使用CGLIB代理)。而不是像如果只配置proxyTargetClass=true時強制代理類,而不去考慮代理介面的方式。
綜上在spring中使用代理方式的策略如下:
- 如果沒有配置```optimize```和```proxy-target-class```並且該類實現了介面,那麼使用JDK動態代理。
- 如果沒有配置```optimize```和```proxy-target-class```並且該類沒有實現介面,那麼使用cglib動態代理。
- 如果配置了```optimize```和```proxy-target-class```並且該類實現了介面,那麼使用JDK動態代理。
- 如果配置了```optimize```和```proxy-target-class```並且該類沒有實現介面,那麼使用cglib動態代理。
實現程式碼如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
現在已經取到了建立代理物件的策略和目標物件,就可以直接建立代理物件了。如果對這放面有興趣的可以自行搜尋。
建立好代理物件之後使用代理物件替代之前建立好的物件,那麼在使用的時候就會呼叫增強後的方法完成功能。
#### 多個advisor如何確定順序
在實際開發過程中,可能會存在一個方法被多個advisor增強,可能有的在方法執行前增強有的在方法執行後進行增強。那麼在spring中如何確定每一個增強方法的呼叫時機保證不會出問題的呢?
在手寫aop系列中有講過這個問題,當時我們是通過[責任鏈模式](https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html)來解決這個問題。實際上spring中就是通過責任鏈模式來解決該問題的。
在```JdkDynamicAopProxy```的```invoke```方法中,會通過getInterceptorsAndDynamicInterceptionAdvice方法來獲取增強當前呼叫方法的所有advisor的chain,但是需要注意的是這個chain並不是根據實際應該的執行順序排列的。僅僅只是所有會被執行的增強方法的集合。
List