Spring學習筆記 —— AOP(面向切面程式設計) 之使用ProxyFactoryBean實現AOP
引言
到上一篇文章Spring學習筆記 —— Spring Context為止,我們已經基本瞭解Spring中的基本物件——Bean——的建立、相關屬性的注入以及獲取。其實在這不難發現,Spring的容器設計與Java的物件設計之間是有相似的地方的,比如BeanDefinition和Class物件,Bean和Object物件。
而Java的反射,則對應到了Spring中的AOP(面向切面程式設計),當然,AOP有著比反射更強大的功能以及更方便的配置。關於反射,我在前面已經有系列文章分析過了,有興趣的可以從這裡開始再閱讀一下。
Spring中的AOP分為兩種,一種是由Spring框架實現,基於ProxyFactoryBean
AspectJ
的AOP,我們首先介紹前者,在後續的文章也會介紹後者。
本文主要分成三部分,AOP概念的介紹,Spring AOP例項以及Spring AOP具體實現的分析。
AOP(Aspect Oriented Programming)簡介
關於什麼是AOP,我想通過一個《Spring in Action》中的一個例子來解釋。
我們每家每戶都會有電腦,電腦會耗電,而每家每戶都會有一個電錶來記錄用電量,每個月會有人來查電錶,這樣電力公司就知道該收取多少電費了。
但是,如果沒有電錶,也沒有人來檢視用電量,而是由戶主來聯絡電力公司並報告自己的用電量。雖然可能會有一些戶主會很詳細地記錄所有的用電量,但是肯定大多數人並不會這麼做。因為每個人每天都有很多事要處理,而監控電力使用情況會浪費他們大量時間,不交電費對他們只有好處而沒有壞處。
軟體系統的某些功能就像我們家裡的電錶,這些功能需要應用到系統的多個地方,但卻不適合在應用到的地方被顯式呼叫。
監控電量是一個很重要的功能,但卻並不是大多數家庭重點關注的問題。監控電量更像是一個被動事件。
在軟體中,有些行為對於大多數應用都是通用的。日誌、安全和事務管理的確很重要。但它們是否應用物件主動參與的行為呢?如果讓應用物件只關注於自己所針對的業務領域問題,而其他方面的問題由其他應用物件來處理,會不會更好呢?
在軟體開發中,分佈於應用中多處的功能被稱為橫切關注點(cross-cutting concerns)。通常,這些橫切關注點從概念上是與應用的業務邏輯相分離的(但是往往直接嵌入到應用的業務邏輯之中)。將這些橫切關注點與業務邏輯相分離正式面向切面程式設計(AOP)所要解決的。
而在AOP中,會涉及到一些新的概念,現在先對這些概念進行描述。
名稱 | 含義 |
---|---|
切面(Aspect) | 作為提供業務程式碼之外的功能物件,如例子中提到的電錶,記錄了每家每戶的用電量。程式中有可能會是日誌,輸出特定的日誌結果。 |
目標物件(target object) | 執行業務邏輯的物件,如例子中每個獨立的戶主。 |
織入(Weaving) | 就是將切面中的物件與目標物件糅合在一起的過程。 |
連結點(JoinPoint) | 需要插入切面的地方,通常指業務程式碼中具體某個物件的某個方法。而在例子中,就是需要裝電錶的家庭(有些地方可能不需要安裝電錶)。 |
通知(Advice) | 通知是指在連線點上發生的事情,比如說電錶會記錄用電量。 |
切點(pointCut) | 是指一系列連結點的組合 |
在Spring AOP中暫時不會用到切面的概念,但是在AspectJ AOP中則會用到。下面我們就實現一個Spring AOP吧。
Spring AOP例項
首先是TargetObject,TargetObjectSample.java
public class TargetObjectSample {
public void getMessage() {
System.out.println("getMsg!");
}
public void getPointCutMessage() {
System.out.println("get point cut Msg!");
}
}
再宣告一個連線點。PointCutSample.java
public class PointCutSample implements Pointcut{
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher(){
@Override
public boolean matches(Method method, Class<?> targetClass) {
if(method.getName().contains("PointCut")){
return true;
}
return false;
}
@Override
public boolean isRuntime() {
return true;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
if(method.getName().contains("PointCut")){
return true;
}
return false;
}};
}
}
還有AdvisorSample.java
public class AdvisorSample implements PointcutAdvisor{
@Override
public Advice getAdvice() {
return new BeforeAdviceSample();
}
@Override
public boolean isPerInstance() {
return false;
}
@Override
public Pointcut getPointcut() {
return new PointCutSample();
}
}
最後是AdviceBeforeAdviceSample.java
public class BeforeAdviceSample implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before method execution!");
}
}
配置檔案beans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="advisorSample" class="com.study.Spring.AdvisorSample"></bean>
<bean id="targetSample" class="com.study.Spring.TargetObjectSample"></bean>
<bean id="aopSample" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetName">
<value>targetSample</value>
</property>
<property name="interceptorNames">
<list>
<value>advisorSample</value>
</list>
</property>
</bean>
</beans>
Maven 依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
主函式App.java
public class App
{
public static void main( String[] args ) {
ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
TargetObjectSample obj = (TargetObjectSample)app.getBean("aopSample");
obj.getMessage();
//getMsg!
obj.getPointCutMessage();
//before method execution!
//get point cut Msg!
}
}
得到以上結果,就證明已經成功的完成了。我們在不修改業務邏輯程式碼的情況下,成功地在方法執行前加上了自己想要執行的程式碼。
Spring AOP 實現分析
在引言中提到過,AOP其實是Java代理的一種增強,所以我們也能夠想到,最後會生成一個代理的物件。
但是,具體的生成過程又是怎樣的呢? 我們下面先通過一個時序圖來進行分析。
因為最後生成的物件仍舊是一個Bean物件,所以前面仍然是呼叫getBean
方法獲取的。真正產生差異的地方在於,我們定義的bean是ProxyFactoryBean
,它實現了FactoryBean
這個介面。
public interface FactoryBean<T> {
//返回Bean例項
T getObject() throws Exception;
//返回Bean的具體型別
Class<?> getObjectType();
//這個bean是否為單例
boolean isSingleton();
}
因為必須要實現getBoject
方法,所以我們就能夠自定義生成代理物件的時候需要經歷哪些步驟了。
首先,會根據我們在property中宣告的interceptorNames
來初始化advisors
(如果代理物件不是單例,初始化的advisors也不是真正的bean物件,advisors
會在newPrototypeInstance
時再進行初始化)。
其次,就是根據advisors
生成cglib的代理物件。在Spring裡面會根據類的定義/bean的定義,決定要生成jdk原生的代理還是cglib的代理。因為我們示例中的類沒有實現任何介面,使用的是cglib的代理實現。
CglibAopProxy.getProxy
public Object getProxy(ClassLoader classLoader) {
//省略debug程式碼
try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
Class<?> proxySuperClass = rootClass;
if (ClassUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
validateClassIfNecessary(proxySuperClass, classLoader);
// 對cglib enhancer進行配置
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
//根據advisors來生成callback物件
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// 根據advisors生成callbackFilter,確定哪個方法將會被代理。
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// 建立代理物件
return createProxyClassAndInstance(enhancer, callbacks);
}
//異常處理
}
而對應的類圖,則如下圖所示。
這裡看大圖
小結
使用ProxyFactoryBean實現的AOP結構較為簡單,原理就是通過我們配置的屬性(interceptors),生成對應的callback以及callbackfilter,最後通過cglib生成動態代理類,實現代理的功能。
而對於使用JDK原生代理的,也不難想象代理的原理是通過配置的屬性,生成需要代理的介面以及設定對應的代理方法。
在下一篇文章,我們會對AspectJ中的代理進行分析。
參考文章
- 《Spring in Action 第三版》
- 《深入分析Java Web技術內幕》