給冰冰看的SpringAOP面試題
Spring之前已經(jīng)跟學弟具體聊很詳細的IOC已經(jīng)循環(huán)依賴問題,接下來要接著為跟學妹們聊另外的一個模塊了,那就是AOP,這也是面試官比較喜歡問的一個模塊點。
什么是AOP?
- AOP通常叫面向切面編程(Aspect-oriented Programming,簡稱AOP),它是一種編程范式,通過預編譯的方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術。
- 通常用來對隔離不同業(yè)務邏輯,比如常見的事務管理、日志管理等。同時實現(xiàn)AOP的方式也有兩種:cglib 以及 jdk兩種方式來實現(xiàn)。
為什么要有AOP?
假設現(xiàn)在有幾個實現(xiàn)方法,需要做日志處理,正常來說我們只需要手動添加一下日志就可以了,我們都知道在真正的業(yè)務代碼中,代碼行數(shù),以及方法數(shù)那是一個天文數(shù)字,如果都要手動添加那工作量不現(xiàn)實。
本著作為程序員因該想著怎么合理的偷懶的習慣,所以應該想辦法提高效率。
AOP因此就產(chǎn)生了,說白了AOP就是通過某種匹配規(guī)則去匹配方法,然后再添加對應的日志處理。而AOP本身的實現(xiàn)方式就是通過ASM字節(jié)碼框架動態(tài)生成技術,在程序運行的時候,根據(jù)需求(添加文件)動態(tài)創(chuàng)建字節(jié)碼文件,之前講的設計模式中動態(tài)代理模式中也有講到,大家可以再去復習一下。
AOP的核心概念
切面(Aspect):似于 Java 中的類聲明,常用于應用中配置事務或者日志管理。一般使用 @Aspect 注解或者
連接點(Join Point):程序執(zhí)行中的特定點,比如方法執(zhí)行、處理一個異常等
切點(Pointcut):通過一種規(guī)則匹配的正則表達式,當有連接點可以匹配到切點時,就會觸發(fā)改切點相關聯(lián)的指定通知。
通知(Advice):在切面中某個連接點采取的動作,通知方式也有5種
- around(環(huán)繞通知):前后都加
- before(前置通知)
- after(后置通知)
- exception(異常通知)
- return(返回通知)
織入(Weaving):鏈接切面和目標對象創(chuàng)建一個通知對象的過程。
AOP其實就是一種編程思想,而這上面的這個點就是編程的具體實現(xiàn)規(guī)范。
一個應用中可以有多種通知方式所以在AOP中引入一種設計模式責任鏈模式通過這這種模式來順序執(zhí)行每一個通知當然也可以使用@Order注解,配置數(shù)字越小,越先執(zhí)行。關于責任鏈模式的大家也可以去看看我之前寫的設計模式復習一下。
AOP的執(zhí)行過程
之前跟大家聊IOC的時候跟大家聊過它的啟動過程,同樣的AOP也有指定的執(zhí)行流程,但是需要IOC作為基礎。
- IOC容器啟動,用來存放對象
- 進行對象的實例化和初始化操作,將生成的完成的對象存放到容器中(容器運行中的一些對象比如BeanFactoryProcesser、methodInterceptore等還有其他的很多對象)
- 從創(chuàng)建好的容器中獲取需要對象
- 調(diào)用具體的方法開始調(diào)用
說了這么多理論知識,要想知道里面的具體執(zhí)行流程,還是老樣子,一步一步debug進入源碼查看流程了
首先還是需要先準備配置一個切面
- @Aspect
- @Component
- public class LogUtil {
- @Pointcut("execution(public * com.ao.bing.demo.spring.aop..*.*(..))")
- public void pctMethod() {
- }
- @Around(value = "pctMethod()")
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
- Object ret = pjp.proceed();
- System.out.println("Around advice");
- return ret;
- }
- @Before("pctMethod()")
- public void before() {
- System.out.println("Before advice");
- }
- @After(value = "pctMethod()")
- public void after() {
- System.out.println("After advice");
- }
- @AfterReturning(value = "pctMethod()")
- public void afterReturning() {
- System.out.println("AfterReturning advice");
- }
- @AfterThrowing(value = "pctMethod()")
- public void afterThrowing() {
- System.out.println("AfterThrowing advice");
- }
- // mian方法測試demo
- public static void main(String[] args) {
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- AopTestDemo aopTestDemo = applicationContext.getBean(AopTestDemo.class);
- aopTestDemo.method("測試AOP");
- }
- }
這里配置一個LogUtil的切面demo,五種通知都寫了一遍,同時也有一個main方法測試。準備工作做完了就正式開始debug代碼了
首先在aopTestDemo里面我們打上斷點,此時我們的過去的bean對象aopTestDemo已經(jīng)是通過動態(tài)代理生成的對象了,其中這里面有很多的CALLBACK方法屬性。那寫方法屬性是什么呢?
其實這里面就又跟Spring的攔截器有關,其實就是一種設計模式觀察者模式說白了就是對對象的一種行為的監(jiān)聽。通過回調(diào)機制來實現(xiàn)通知的功能。
那既然是回調(diào)方法,那就先進DynamicAdvisedInterceptor方法中
- private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
- private final AdvisedSupport advised;
- public DynamicAdvisedInterceptor(AdvisedSupport advised) {
- this.advised = advised;
- }
- @Nullable
- public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
- Object oldProxy = null;
- boolean setProxyContext = false;
- Object target = null;
- TargetSource targetSource = this.advised.getTargetSource();
- Object var16;
- try {
- if (this.advised.exposeProxy) {
- oldProxy = AopContext.setCurrentProxy(proxy);
- setProxyContext = true;
- }
- target = targetSource.getTarget();
- Class<?> targetClass = target != null ? target.getClass() : null;
- // 從advised中 獲取配置好的AOP的通知方法 -重點
- List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
- Object retVal;
- // 如果沒有配置通知方法,則直接調(diào)用target對象的調(diào)用方法
- if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
- Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
- retVal = methodProxy.invoke(target, argsToUse);
- } else {
- // 通過CglibMethodInvocation來啟動advice通知 - 重點
- retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
- }
- retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
- var16 = retVal;
- } finally {
- if (target != null && !targetSource.isStatic()) {
- targetSource.releaseTarget(target);
- }
- if (setProxyContext) {
- AopContext.setCurrentProxy(oldProxy);
- }
- }
- return var16;
- }
在DynamicAdvisedInterceptor中有一個List chain這里獲取到我們配置的通知方法
從上面的截圖可以看到Spring先是把所有的通知獲取到,放在一個list集合對象中,因為此時list不為空所以就又會走到 通過CglibMethodInvocation來啟動advice通知這一步的流程中
- public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
- protected final Object proxy;
- @Nullable
- protected final Object target;
- protected final Method method;
- protected Object[] arguments = new Object[0];
- @Nullable
- private final Class<?> targetClass;
- /**
- * Lazily initialized map of user-specific attributes for this invocation.
- */
- @Nullable
- private Map<String, Object> userAttributes;
- /**
- * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
- * that need dynamic checks.
- */
- protected final List<?> interceptorsAndDynamicMethodMatchers;
- /**
- * Index from 0 of the current interceptor we're invoking.
- * -1 until we invoke: then the current interceptor.
- */
- private int currentInterceptorIndex = -1;
- // 省略其他的方法
- @Override
- @Nullable
- public Object proceed() throws Throwable {
- // We start with an index of -1 and increment early.
- // 從索引為-1的攔截器還是調(diào)用,并且按順序遞增,如果整個List chain中調(diào)用完畢,則開始調(diào)用target的函數(shù)
- // 具體的實現(xiàn)方式在AOPUtils.invokeJoinpointUsingRefection方法中,說白了就是通過反射實現(xiàn)目標方法
- if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
- return invokeJoinpoint();
- }
- // 獲取下一個需要執(zhí)行的攔截器,沿著定義好的interceptorOrInterceptionAdvice的鏈進行處理
- Object interceptorOrInterceptionAdvice =
- this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
- if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
- // Evaluate dynamic method matcher here: static part will already have
- // been evaluated and found to match.
- // 對攔截器進行動態(tài)匹配,如果和定義的pointcut匹配 則就會執(zhí)行當前的這個advice
- InterceptorAndDynamicMethodMatcher dm =
- (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
- Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
- if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
- return dm.interceptor.invoke(this);
- }
- else {
- // Dynamic matching failed.
- // Skip this interceptor and invoke the next in the chain.
- return proceed();
- }
- }
- else {
- // It's an interceptor, so we just invoke it: The pointcut will have
- // been evaluated statically before this object was constructed.
- // 普通攔截器,直接調(diào)用攔截器,將當前的this(CglibMethodInvocation)作為參數(shù)保證當前實例中調(diào)用鏈的執(zhí)行
- return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
- }
- }
- // 省略其他的方法
- }
這里需要注意的一點 從上面的源碼中currentInterceptorIndex默認值定義為-1,相當于是作為判斷當前通知鏈式是執(zhí)行完成,執(zhí)行完那就是直接通過反射調(diào)用目標方法,否者向下接著執(zhí)行。
- ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this)
interceptorOrInterceptionAdvice這個方法是關鍵點,即具體的調(diào)用攔截器去執(zhí)行具體的方法
- public final class ExposeInvocationInterceptor implements MethodInterceptor, PriorityOrdered, Serializable {
- @Override
- public Object invoke(MethodInvocation mi) throws Throwable {
- MethodInvocation oldInvocation = invocation.get();
- invocation.set(mi);
- try {
- // 具體執(zhí)行
- return mi.proceed();
- }
- finally {
- invocation.set(oldInvocation);
- }
- }
- }
不知道大家發(fā)現(xiàn)了問題沒有,在剛上面截圖的時候List chain中有6個通知方法,而在配置的util中只配置了5個方法,第一個這就是ExposeInvocationInterceptor這個方法,那這個到底是啥呢?
- 其實這里是Spring引入的又一個設計模式責任鏈設計模式引入這個設計模式原因就是在于,配置的通知方法這么多,那spring怎么知道去執(zhí)行那一個advice呢?所以通過ExposeInvocationInterceptor(暴露調(diào)用器的攔截器)作為第一個通知方法,來保證所以的通知方法按按這中連式方式執(zhí)行下去。
至此SpringAOP的通知連式結構調(diào)用流程就開始了,重復開始循環(huán)調(diào)用。一直到List chain整個鏈全部執(zhí)行完畢
當前這里面還有一些其他的邏輯需我就沒有具體細說了
比如整個鏈有沒有一種順序執(zhí)行?還是說根據(jù)代碼編寫的先后順序執(zhí)行?
- org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#sortAdvisors
在獲取到整個責任鏈之后會經(jīng)過一次排序執(zhí)行,這個排序默認是使用拓撲排序方法,大家可以具體的去看看sortAdvisors這個方法
然后在執(zhí)行每一種通知方法時,都會有對應的切面通知方法
- org.springframework.aop.aspectj.AspectJAfterAdvice
- org.springframework.aop.aspectj.AspectJAfterReturningAdvice
- org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
- org.springframework.aop.aspectj.AspectJAroundAdvice
- org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
我們配置的五種通知對應著上面的五種處理邏輯
所以整個責任鏈來說通知調(diào)用鏈路可以理解為一張圖結構:
以上就是Spring通知整個執(zhí)行的過程,總結一下
- Spring中的五種通知,首先是通過具Spring容器的啟動過程獲取到具體的通知,在調(diào)用對象時,通過動態(tài)代理ASM技術,把需要執(zhí)行的advice先全部放在一個chain對象的集合中,為了保證整個鏈的調(diào)用默認會先調(diào)用ExposeInvocationInterceptor去觸發(fā)整個鏈式執(zhí)行,在執(zhí)行完每一個advice時后都會再次回到super的proceed方法中,執(zhí)行下一個advice,在執(zhí)行不通的advice時后有對應的切面通知方法,當所有的advice執(zhí)行完畢再通過反射調(diào)用目標方法
- 整個過程大家可以自己dubug試一下,也不是特別的麻煩!!!
AOP事務功能
AOP處理常見的配置切面處理日志等業(yè)務,還有大家也比較熟悉的那就是事務。
其實AOP的事務也是通過配置的advice的方式來執(zhí)行的
通知對advice的拆解來實現(xiàn)事務的功能。
- 在看源碼的時候能給我們帶來很多的思考,對我們
在Spring中大家可以通過@Transactional注解來現(xiàn)實事務功能,看過源碼的同學肯定看過TransactionAspectSupport這個類
- public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
- // 省略其他代碼。。。。
- @Nullable
- protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
- final InvocationCallback invocation) throws Throwable {
- // If the transaction attribute is null, the method is non-transactional.
- TransactionAttributeSource tas = getTransactionAttributeSource();
- final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
- final PlatformTransactionManager tm = determineTransactionManager(txAttr);
- final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
- if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
- // Standard transaction demarcation with getTransaction and commit/rollback calls.
- TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
- Object retVal = null;
- try {
- // This is an around advice: Invoke the next interceptor in the chain.
- // This will normally result in a target object being invoked.
- retVal = invocation.proceedWithInvocation();
- }
- catch (Throwable ex) {
- // target invocation exception
- // 異?;貪L 重點
- completeTransactionAfterThrowing(txInfo, ex);
- throw ex;
- }
- finally {
- cleanupTransactionInfo(txInfo);
- }
- // 成功后提交 重點
- commitTransactionAfterReturning(txInfo);
- return retVal;
- }
- // 省略其他的代碼。。。。
- }
因此SpringAOP的聲明事務也是通過advice實現(xiàn)。
AOP的事務整體來說比較簡單,說白了就是通過advice的從新組合來完成事務功能,當然也是Spring的強大之處,擴展性是真的高。
總結
SpringAop就跟學妹們聊完了,需要理解的就是advice的通知整個流程,但是這個過程需要大家不斷的去看這個框架的源碼,看源碼的過程能給我們很多思考,怎么才能寫出高質量代碼?
為了加強理解,還是有兩個比較常見的面試題
advice的通知執(zhí)行流程?
- 看完整個流程如果還是不理解我覺得可以自己debug走一遍加深自己的理解,文中我也做了總結。但是要自己真的理解才能不會被面試官問倒
AOP中的Transactional事務是怎么實現(xiàn)的?
- 這個問題如果理解advice的調(diào)用流程那么也就能很簡單的回答了。
【編輯推薦】