spring AOP 淺析

說到spring的Transactional,必須先了解spring AOP的原理,先看個簡單的例子

//一個普通的類
public class CyclicA {
   
    public void printlnMethod(){
        System.out.println("i am method");
    }
}

/**
 * 切面,包含了一個Pointcut和多個Advice
 */
@Aspect
public class aspectjTest {

	//切點
    @Pointcut("execution(* *.printlnMethod(..))")
    public void test(){

    }

	//前置通知
    @Before("test()")
    public void before(JoinPoint joinPoint){
        System.out.println("傳入參數:"+ Arrays.asList(joinPoint.getArgs()));
        System.out.println("before");
    }

	//後置通知
    @After("test()")
    public void after(){
        System.out.println("after");
    }

	//環繞通知
    @Around("test()")
    public Object  around(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("aroundBefore");
        Object o = null;
        try {
            //執行攔截器方法鏈,如果是最後一個攔截器,這裏執行原對象方法
            proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("aroundAfter");
        return o;
    }

}

<!--spring-core.xml--> 
<bean class="spring.aspectjTest"></bean>
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>

//測試方法
public static void main(String[] args){
        ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext("spring-core.xml");
        CyclicA cyclicA = (CyclicA) factory.getBean("cyclicA");
        cyclicA.printlnMethod();
    }

打印結果

aroundBefore
傳入參數:[]
before
i am method
aroundAfter
after

爲了更好的理解,先介紹一下AOP相關的術語和對應的接口類

1、JoinPoint(連接點)

public interface JoinPoint {
	 //執行方法鏈,該方法鏈會被攔截器攔截
    Object proceed() throws Throwable;

    Object getThis();

    AccessibleObject getStaticPart();

}

簡單的理解,連接點就是目標對象可以被代理的部分,SpringAOP代理是方法級,所以目標對象的每個方法都可以認爲是一個連接點.
上面只是列舉了JoinPoint接口的常用方法,在前置通知中,可以通過JoinPoint獲取參數進行預處理或者記錄日誌等

//前置通知
    @Before("test()")
    public void before(JoinPoint joinPoint){
        System.out.println("傳入參數:"+ Arrays.asList(joinPoint.getArgs()));
        System.out.println("before");
    }

2、Pointcut(切點)

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();
}

實際運用中,並不是每一個連接點都需要去代理,那麼如何去選擇需要的連接點,這就用到了切點Pointcut,從接口方法的名稱可以看出來,切點主要的工作就是根據制定的規則匹配連接點,例如上面的例子

//切點
    @Pointcut("execution(* *.printlnMethod(..))")
    public void test(){

    }

3、Advice(通知)

public interface Advice {
}

//前置通知
public interface MethodBeforeAdvice extends BeforeAdvice {
	void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}

//後置通知
public interface AfterReturningAdvice extends AfterAdvice {
	void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;

}

匹配到指定的連接點,我們就可以做一些邏輯處理了,而這些邏輯處理就是通知,分爲

前置通知(Before advice)- 在目標方法調用前執行通知
後置通知(After advice)- 在目標方法完成後執行通知
返回通知(After returning advice)- 在目標方法執行成功後,調用通知(異常則不調用)
異常通知(After throwing advice)- 在目標方法拋出異常後,執行通知
環繞通知(Around advice)- 在目標方法調用前後均可執行自定義邏輯

4、顧問(Advisor)
可以認爲是Advice的裝配器,讓advice能正確的匹配到目標方法

public interface Advisor {
	Advice getAdvice();
}

public interface PointcutAdvisor extends Advisor {
	Pointcut getPointcut();
}

5、攔截器(Interceptor)
通常一個目標方法可能存在多個通知乃至多個切面,並且通知的時機也不相同,例如前置通知、後置通知. springAop通過JoinPoint.proceed()執行方法鏈,通過攔截器對方法鏈進行攔截,在此過程中決定advice執行的時機,當所有攔截器執行完畢,將通過反射執行目標方法

public interface Interceptor extends Advice {
}

public interface MethodInterceptor extends Interceptor {
	//核心方法,通過該方法執行攔截器的邏輯	
	Object invoke(MethodInvocation invocation) throws Throwable;
}

//看一個例子,前置通知攔截器
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

	//攔截器包裝了前置通知
	private final MethodBeforeAdvice advice;


	/**
	 * Create a new MethodBeforeAdviceInterceptor for the given advice.
	 * @param advice the MethodBeforeAdvice to wrap
	 */
	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}

	//對方法鏈進行了攔截,在下一個方法執行之前執行前置通知邏輯
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}

}

6、織入(Weaving)
把切面(aspect)連接到其它的應用程序類型或者對象上,並創建一個被通知(advised)的對象,這樣的行爲叫做織入,上面Advice、Advisor、Interceptor的邏輯就是織入的具體實現

下面通過一個簡單的時序圖來說明下spring AOP的一個大概實現過程
在這裏插入圖片描述
1、通過beanFactory.getBean(String beanName)獲取bean的時候,代理流程就開始了

2、AbstractAutoProxyCreator實現了BeanPostProcessor接口,所以可以通過postProcessAfterInitialization方法在bean初始化之後進行處理

3、獲取匹配目標bean的所有通知Advice 並且包裝成Advisor列表

4、對步驟3的分解,查找所有類型是Advisor的bean, 子類AnnotationAwareAspectJAutoProxyCreator對該方法進行了擴展,可以查找所有加了@Aspect註解的bean,並且將其中的PointCut 和 Advice包裝成Advisor

5、對步驟3的分解,對Advisor列表進行分析,尋找匹配目標bean的Advisor

8、如果有匹配的Advisor,開始針對目標bean創建代理類,這個實現最終交給DefaultAopProxyFactory

9、默認如果目標對象實現了接口,那麼交給JdkDynamicAopProxy實現JDK動態代理
否則交給ObjenesisCglibAopProxy使用Cglib創建代理

至此一個代理類就順利創建了,那麼接下來看看這個代理類執行方法的時候springAop是如何實現織入的

以一開始的例子,假設只注入了前置通知和後置通知,通過JDK動態代理

在這裏插入圖片描述

2、假設是JDK動態代理,執行方法的時候,調用invoke方法進行攔截

3、如果配置了exposeProxy屬性爲true,則將當前代理緩存到ThreadLocal中

4、通過Advisor包裝的advice創建相應的攔截器,加入到攔截器數組中,例如前面提到的MethodBeforeAdviceInterceptor以及後置通知AspectJAfterAdvice,這裏的AspectJAfterAdvice自身就實現了MethodInterceptor,所以他既是Advice又是Interceptor

//在下一個方法執行之前執行前置通知邏輯  MethodBeforeAdviceInterceptor
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}

//在方法鏈調用結束後再執行後置通知邏輯 AspectJAfterAdvice
@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}
		finally {
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		}
	}

7、通過ReflectiveMethodInvocation類調用方法鏈proceed,主要邏輯就是從攔截器數組中按順序取出攔截器執行invoke,並且將自身也當作參數傳入, 由攔截器決定下一個proceed和advice的調用順序,以此正確的織入前置通知、後置通知. 當所有的攔截器全部調用完畢,則通過反射執行目標方法返回

看一下這部分代碼

public Object proceed() throws Throwable {
		//	當所有當攔截器都調用完畢,最後通過反射執行目標方法
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		//獲得下一個攔截器
		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.
			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.
			//攔截器調用
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

Spring AOP還是相對比較簡單的,代理的級別也只是方法級,但是對於絕大多數應用已經足夠了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章