本文是對近期學習知識的一個總結,附帶原始碼註釋及流程圖,如有不足之處,還望評論區批評指正。
此處感謝javadoop的原始碼解析,收益匪淺:https://javadoop.com/post/spring-aop-intro
一、AOP、SpringAOP、AspectJ的區別
AOP為Aspect Oriented Programming
的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期間動態代理實現程式功能的統一維護的一種技術。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
文縐縐的,沒用過確實很懵,但是用過之後,不說清晰,起碼有內意思了。
關於SpringAOP和AspectJ,參考Javadoop老師的解釋:https://javadoop.com/post/spring-aop-intro
Spring AOP:
- 它基於動態代理來實現。預設地,如果使用介面的,用 JDK 提供的動態代理實現,如果沒有介面,使用 CGLIB 實現。大家一定要明白背後的意思,包括什麼時候會不用 JDK 提供的動態代理,而用 CGLIB 實現。
- Spring 3.2 以後,spring-core 直接就把 CGLIB 和 ASM 的原始碼包括進來了,這也是為什麼我們不需要顯式引入這兩個依賴
- Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依賴於 IOC 容器來管理。
- 如果你是 web 開發者,有些時候,你可能需要的是一個 Filter 或一個 Interceptor,而不一定是 AOP。
- Spring AOP 只能作用於 Spring 容器中的 Bean,它是使用純粹的 Java 程式碼實現的,只能作用於 bean 的方法。
- Spring 提供了 AspectJ 的支援,後面我們會單獨介紹怎麼使用,一般來說我們用純的 Spring AOP 就夠了。
- 很多人會對比 Spring AOP 和 AspectJ 的效能,Spring AOP 是基於代理實現的,在容器啟動的時候需要生成代理例項,在方法呼叫上也會增加棧的深度,使得 Spring AOP 的效能不如 AspectJ 那麼好。
AspectJ:
AspectJ 出身也是名門,來自於 Eclipse 基金會,link:https://www.eclipse.org/aspectj
屬於靜態織入,它是通過修改程式碼來實現的,它的織入時機可以是:
- Compile-time weaving:編譯期織入,如類 A 使用 AspectJ 新增了一個屬性,類 B 引用了它,這個場景就需要編譯期的時候就進行織入,否則沒法編譯類 B。
- Post-compile weaving:也就是已經生成了 .class 檔案,或已經打成 jar 包了,這種情況我們需要增強處理的話,就要用到編譯後織入。
- Load-time weaving:指的是在載入類的時候進行織入,要實現這個時期的織入,有幾種常見的方法。1、自定義類載入器來幹這個,這個應該是最容易想到的辦法,在被織入類載入到 JVM 前去對它進行載入,這樣就可以在載入的時候定義行為了。2、在 JVM 啟動的時候指定 AspectJ 提供的 agent:
-javaagent:xxx/xxx/aspectjweaver.jar
。AspectJ 能幹很多 Spring AOP 幹不了的事情,它是 AOP 程式設計的完全解決方案。Spring AOP 致力於解決的是企業級開發中最普遍的 AOP 需求(方法織入),而不是力求成為一個像 AspectJ 一樣的 AOP 程式設計完全解決方案。
因為 AspectJ 在實際程式碼執行前完成了織入,所以大家會說它生成的類是沒有額外執行時開銷的。
二、AOP關鍵術語
-
切面(Aspect):也就是我們定義的專注於提供輔助功能的模組,比如安全管理,日誌資訊等。
-
連線點(JoinPoint):切面程式碼可以通過連線點切入到正常業務之中,圖中每個方法的每個點都是連線點。
-
切入點(PointCut):一個切面不需要通知所有的連線點,而在連線點的基礎之上增加切入的規則,選擇需要增強的點,最終真正通知的點就是切入點。
-
通知方法(Advice):就是切面需要執行的工作,主要有五種通知:before,after,afterReturning,afterThrowing,around。
-
織入(Weaving):將切面應用到目標物件並建立代理物件的過程,SpringAOP選擇再目標物件的執行期動態建立代理對
-
引入(introduction):在不修改程式碼的前提下,引入可以在執行期為類動態地新增方法或欄位。
三、通知的五種型別
- 前置通知Before:目標方法呼叫之前執行的通知。
- 後置通知After:目標方法完成之後,無論如何都會執行的通知。
- 返回通知AfterReturning:目標方法成功之後呼叫的通知。
- 異常通知AfterThrowing:目標方法丟擲異常之後呼叫的通知。
- 環繞通知Around:可以看作前面四種通知的綜合。
四、切入點表示式
上面提到:連線點增加切入規則就相當於定義了切入點,當然切入點表示式分為兩種:within和execution,這裡主要學習execution表示式。
- 寫法:execution(訪問修飾符 返回值 包名.包名……類名.方法名(引數列表))
- 例:
execution(public void com.smday.service.impl.AccountServiceImpl.saveAccount())
- 訪問修飾符可以省略,返回值可以使用萬用字元*匹配。
- 包名也可以使用
*
匹配,數量代表包的層級,當前包可以使用..
標識,例如* *..AccountServiceImpl.saveAccount()
- 類名和方法名也都可以使用
*
匹配:* *..*.*()
- 引數列表使用
..
可以標識有無引數均可,且引數可為任意型別。
全通配寫法:
* *…*.*(…)
通常情況下,切入點應當設定再業務層實現類下的所有方法:* com.smday.service.impl.*.*(..)
。
五、AOP應用場景
- 記錄日誌
- 監控效能
- 許可權控制
- 事務管理
六、AOP原始碼分析
SpringBean的生命週期
寫了好多篇文章,每次都要來回顧一下SpringBean的生命週期,可見它真的十分重要。
Spring的Aop又是在哪完成的對目標物件的代理呢?我們大概也能夠想到,其實就是在執行回撥的時候。按照慣例,先複習一下,從getBean開始到返回Bean經歷了什麼:
回顧完SpringBean的建立流程之後,我們以註解方式@EnableAspectJAutoProxy
配置Aop開啟@Aspectj為例,進行一波AOP的流程總結:
AOP的流程總結
通過原始碼可以發現,其實是通過@EnableAspectJAutoProxy
註解注入了一個AnnotationAwareAspectJAutoProxyCreator
,但這個類中其實並沒有重寫postProcessAfterInitialization()
,最終實現其實是在AbstractAutoProxyCreator
中。
具體乾的事情,我已經通過一張圖總結出來了,如果想要了解更加具體的資訊,不妨開啟原始碼,可以看的更加清晰一些。
AnnotationAwareAspectJAutoProxyCreator的註冊
首先是對AnnotationAwareAspectJAutoProxyCreator
的註冊環節:【在此不作贅述】
class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
@Override
@Nullable
// 1. 註冊proxy creator
public BeanDefinition parse(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(element, parserContext);
return null;
}
}
applyBeanPostProcessorsAfterInitialization入口
AbstractAutowireCapableBeanFactory.java
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
//如果bean實現了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware介面, 回撥
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
//aop在init-method之前並沒有進行操作, 目前還是原來那個物件
if (mbd == null || !mbd.isSynthetic()) {
//BeanPostProcessor 的 postProcessBeforeInitialization 回撥
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
//處理bean中定義的init-method或 bean實現了InitializingBean ,呼叫afterPropertiesSet() 方法
invokeInitMethods(beanName, wrappedBean, mbd);
if (mbd == null || !mbd.isSynthetic()) {
//BeanPostProcessor 的 postProcessAfterInitialization 回撥 注意這裡!
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
//AnnotationAwareAspectJAutoProxyCreator
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;//返回[可能代理後的]結果
}
AbstractAutoProxyCreator的主要方法
//SpringAop在IOC容器建立bean例項的最後對bean進行處理,進行代理增強, AbstractAutoProxyCreato
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);//這個方法將返回代理類
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//返回匹配當前bean 的所有的advisor, advice, interceptor
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;
}
createProxy過程
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
//建立ProxyFactory例項
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
//在schema-based配置方式裡,可以將 proxy-target-class="true",這樣不管有沒有介面都使用cglib
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
//返回當前bean的advisors陣列
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors); //設定advisors陣列
proxyFactory.setTargetSource(targetSource);//targetSource 攜帶了真實實現的資訊
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());//getProxy(getProxyClassLoader())這一步建立代理
}
JDK動態代理和CGLIB動態代理何時使用
這一步產生分歧的地方在ProxyFactory的getProxy方法,在getProxy之前,首先需要執行createAopProxy,而createAopProxy方法又被這個AopProxyFactory呼叫:
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
//建立AopProxy之前,需要一個AopProxyFactory
return getAopProxyFactory().createAopProxy(this);
}
// ProxyCreatorSupport
//這個aopProxyFactory用於建立aopProxy, 之後可以用aopProxy.getProxy(classLoader)建立代理
public ProxyCreatorSupport() {
this.aopProxyFactory = new DefaultAopProxyFactory();
}
也就是最後會走到DefaultAopProxyFactory中
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!IN_NATIVE_IMAGE &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException();
}
//如果要代理的類本身就是介面,使用JDK動態代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
//jdk動態代理基於介面,只有介面中的方法才會被增強, cglib基於類繼承,如果方法使用了final或者private修飾,也不能增強
return new ObjenesisCglibAopProxy(config);
}
else {
// 如果有介面,會跑到這個分支
return new JdkDynamicAopProxy(config);
}
}
總結:
-
如果被代理的目標實現了一個或多個自定義的介面,那麼就會使用JDK動態代理。
-
如果沒有實現任何介面,則使用CGLIB實現代理。
-
如果設定
proxy-target-class=true
或<property name="proxyTargetClass" value="true"/>
則不管有沒有實現介面都會使用CGLIB。
七、JDK動態代理的實現
最終的最終,都會走到真正建立代理物件的流程上:
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
//獲取代理介面
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
//查詢代理目標的介面是否定義equals和hashcode方法
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
//使用jdk動態代理建立代理物件
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
第一個引數:classLoader。
第二個引數:實現的介面。
第三個引數:InvocationHandler例項。
而本身JdkDynamicAopProxy本就實現了InvocationHandler,因此傳入this。至此,當呼叫被代理類的方法的時候,都會最終呼叫代理類實現的invoke方法,在這個方法中定義橫切的邏輯。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
- proxy:代理物件的引用。
- method:當前執行的方法。
- args:當前執行方法所需的引數。
- return:和被代理物件有相同的返回值。
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //當生成的代理類對外提供服務的時候,都會匯入到這個invoke方法中
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// 對equals方法的代理
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
//對hashCode()方法的代理
return hashCode();
}
//...
Object retVal;
//如果設定了exposeProxy,將proxy放入ThreadLocal中
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 獲取目標方法的攔截鏈,包含所有要執行的 advice
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 檢查一下這個鏈上是不是有advice,如果沒有的話,可以跳過建立MethodInvocation
if (chain.isEmpty()) { //chain如果是空的,表示不需要被增強,直接呼叫目標方法
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 如果chain裡有advice 執行方法,得到返回值
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
//沿著攔截器鏈,執行通知
retVal = invocation.proceed();
}
// 對返回值的處理
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException();
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// 釋放目標物件
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// 儲存代理物件
AopContext.setCurrentProxy(oldProxy);
}
}
}
八、總結
以@AspectJ註解方式為例
- 首先,依據
<aop:aspectj-autoproxy>
或@EnableAspectJAutoProxy
,Spring會在容器啟動的時候註冊名叫internalAutoProxyCreator
的AnnotationAwareAspectJAutoProxyCreator
。 - 在bean例項化完成,屬性裝配完成之後,開始執行回撥方法,這時取出所有的BeanPostProcessor,執行其postProcessAfterInitialization方法,準備開始對目標物件代理的建立。
- 首先建立一個代理工廠ProxyFactory,設定一系列的屬性,如所有的通知方法,增強器,攔截器和目標類等注入代理工廠,再呼叫ProxyFactory.getProxy(classLoader)獲取代理。
- 通過判斷是用JDK動態代理還是CGLIB建立不同的AopProxy,最後獲取getProxy。
參考資料