本文是對近期學習知識的一個總結,附帶源碼注釋及流程圖,如有不足之處,還望評論區批評指正。
此處感謝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:可以看作前面四種通知的綜合。
四、切入點表達式
上面提到:連接點增加切入規則就相當於定義了切入點,當然切入點表達式分為很多種,這里主要學習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。
參考資料