Spring_12_AOP的引出_裝飾設計模式與代理機制

問題的引出

一些僞代碼:

public class AccountServiceImpl implements IAccountService{	
	/* 轉賬操作 */
	public void transfer() {
		try{	
		// 開啓事務	
		// 具體的轉賬業務操作
		}catch(Exception e){
			// 事務回滾	
		}finally{
			// 提交事務	
		}		
	}
}

若上述類中還存在轉入、轉出的方法。那麼,每個方法總都必須寫事務處理的代碼。

在設計上存在的問題:

 1、責任不分離。

業務方法只需要關心如何完成該業務功能,不需要去關係事務管理/日誌管理/權限管理等等。        

2、代碼結構重複。

在開發中不要重複代碼,重複就意味着維護成本增大。

 

 

裝飾設計模式

在不改變源代碼基礎上,動態地擴展一個對象的功能。通過包裹真實的對象,對已有對象進行功能增強。

特點:

1、裝飾對象和真實對象有相同的接口。這樣客戶端對象就能以和真實對象相同的方式和裝飾對象交互。

2、裝飾對象包含一個真實對象的引用(reference)

3、裝飾對象接受所有來自客戶端的請求。它把這些請求轉發給真實的對象。

4、裝飾對象可以在轉發這些請求以前或以後增加一些附加功能。

這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。

在面向對象的設計中,通常是通過繼承來實現對給定類的功能擴展。

一些僞代碼

public class AccountSerivceImplWapper implements IAccountService{
	// 事務對象的引用	
	public AccountSerivceImplWapper(AccountServiceImpl target) {
		// 傳入真實對象的引用
	}
	public void transfer() {
		try{
			// 開啓事務		
			// 真實對象調用轉賬方法	
		}catch(Exception e){
			// 事務回滾		
		}finally{
			// 提交事務			
		}		
	}
	public void withdraw() {
		try{
			// 開啓事務		
			// 真實對象調用取錢方法		
		}catch(Exception e){
			// 事務回滾		
		}finally{
			// 提交事務			
		}	
	}
}

 

小結:

可以看出,使用裝飾設計模式讓責任分離了。真實對象可以專注於完成業務邏輯。

但是,還是存在着代碼結構重複的問題。而且,若存在多個需要增強的類,每個類都要定義一個增強類。

此外,還是暴露了真實對象,客戶端可直接使用真實對象,該真實對象無事務相關的代碼,僅有業務操作,很不安全。

 

靜態代理模式

客戶端直接使用的是代理對象,不知道真實對象是誰。代理對象在客戶端和真實對象之間其中介作用。

類比現實中的房屋中介模式:租客,中介,房東。

租客不知道房東是誰,籤合同、交租金都是直接與中介公司打交道。

 

特點:

1、代理對象完全包含真實對象,客戶端使用的都是代理對象的方法,和真實對象沒有直接關係

2、代理模式的職責:把不是真實對象該做的事情從真實對象上撇開——職責清晰

public class AccountSerivceImplWapper implements IAccountService{
	// 事務對象的引用	
        // 真實對象的引用target
	public void transfer() {
		try{
			// 開啓事務		
			// 真實對象調用轉賬方法	
		}catch(Exception e){
			// 事務回滾		
		}finally{
			// 提交事務			
		}		
	}
	public void withdraw() {
		try{
			// 開啓事務		
			// 真實對象調用取錢方法		
		}catch(Exception e){
			// 事務回滾		
		}finally{
			// 提交事務			
		}	
	}
}

 

小結: 

靜態代理模式讓真實對象安全了,不會暴露在客戶端。但是除此之外,仍舊存在裝飾設計模式一樣的問題。

 

JDK動態代理

· 靜態代理與動態代理對比

靜態代理:在程序運行前就已經存在代理類的字節碼文件,代理對象和真實對象的關係在程序運行前就確定了。

動態代理:動態代理類是在程序運行期間由JVM通過反射等機制動態的生成的,所以不存在代理類的字節碼文件。

代理對象和真實對象的關係是在程序運行時期才確定的。

 

· JDK動態代理API

1、java.lang.reflect.Proxy類

Java動態代理機制生成的所有動態代理類的父類。提供了一組靜態方法,來爲一組接口動態地生成代理類及其對象。

真實對象必須實現至少一個接口。

主要方法:

newProxyInstance

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
                               throws IllegalArgumentException

 

參數:

loader - 定義代理類的類加載器

interfaces - 代理類要實現的接口列表

h - 指派方法調用的調用處理程序

返回:

一個帶有代理類的指定調用處理程序的代理實例,它由指定的類加載器定義,並實現指定的接口

 

2、java.lang.reflect.InvocationHandler接口

定義一個實現類實現該接口,並在實現類中編寫增強的代碼。

invoke

Object invoke(Object proxy, Method method,Object[] args)throws Throwable

參數:

proxy-代理對象

method-真實對象中要增強的方法

arg-真實對象中要增強的方法的參數

 

· 使用思路

1、創建增強程序類,該類實現InvocationHandler接口。

實現invoke方法,在該方法中編寫對真實方法的增強代碼。這個方法我們不會直接調用。

2、在增強程序類中,提供獲取代理對象的方法。

通過Proxy類的newProxyInstance方法創建代理對象。

增強對象中存有對真實對象的引用,通過Spring注入,即可不暴露真實對象。

其中InvocationHandler類型的參數就是當前增強程序類的實例。

3、通過增強程序實例獲取代理對象,通過代理對象增強的操作真實對象的方法。

 

一些代碼 

public class TranscationManagerHandler // 增強程序類必須實現InvocationHandler接口
                implements java.lang.reflect.InvocationHandler {
	
	/* 真實對象的引用 */
	@Setter
	private Object target; 
	
	private TransactionManager tx = new TransactionManager();
	
        /* 增強程序中的具體操作  */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object ret = null;
		try{
			tx.begin();
			ret = method.invoke(target, args);
		}catch(Exception e){
			tx.rollback();
			e.printStackTrace();
		}finally{
			tx.commit();
		}
		return ret;
	}

	/* 創建代理對象 */
	@SuppressWarnings("unchecked")
	public <T>T getProxyInstance(){
		Object ret=  Proxy.newProxyInstance(target.getClass().getClassLoader(),  // 真實對象的類加載器
				target.getClass().getInterfaces(), 	// 真實對象的類實現的接口
				this);	// 增強程序對象
		return (T)ret;
	}
}

Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
      <bean id="transationManager" class="com.hanaii.common.TransactionManager"/>
       <!-- 隱藏真實對象 -->
      <bean id="accountServiceImpl" class="com.hanaii.jdk_proxy.TranscationManagerHandler">
      	<property name="target" >
      		<bean class="com.hanaii.common.AccountServiceImpl" />
      	</property>
      </bean>
</beans>

測試代碼

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JdkProxyTest {
	@Autowired
	@Qualifier("accountServiceImpl") 
	private TranscationManagerHandler handler;
	
	@Test
	public void test() throws Exception {
	 IAccountService service= handler.getProxyInstance();
	 service.transfer();
	}
}

測試結果

Transcation begin ...
service: 轉賬操作 ...
Transcation commit ...

 

 

· JDK動態代理存在的問題

1、代理的對象必須要實現一個接口。

2、需要爲每個對象創建代理對象

3、動態代理的最小單位是類,類中的所有實現於接口的方法都會被增強。(有時候我們想要有些方法不要被增強)

 

Spring提供的動態代理:CGLIB

CGLIB提供了和JDK動態代理類似的API接口。

其原理是對指定的目標類生產一個子類,並覆寫其中方法進行增強。

(類必須可繼承,不能是final修飾。)

 

· API

1、org.springframework.cglib.proxy.InvocationHandler接口

該接口也存在invoke方法,使用基本同JDK動態代理相同。

另外,該接口繼承了 org.springframework.cglib.proxy.Callback 接口。

 

2、org.springframework.cglib.proxy.Enhancer類

其主要方法用於創建代理對象。其使用案例如下:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 設置代理對象父類
enhancer.setCallback(this);     // 設置增強程序
return (T)enhancer.create();    // 創建代理對象

 

· 使用思路

基本同JDK動態代理

一些代碼:

public class TranscationManagerHandler // 增強程序類必須實現InvocationHandler接口
						implements org.springframework.cglib.proxy.InvocationHandler {
	
	/* 真實對象的引用 */
	@Setter
	private Object target; 
	
	private TransactionManager tx = new TransactionManager();
	
	/* 創建代理對象 */
	@SuppressWarnings("unchecked")
	public <T>T getProxyInstance(){
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());
		enhancer.setCallback(this);
		return (T)enhancer.create();
	}
	
	/* 增強程序中的具體操作  */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("-d-");
		Object ret = null;
		try{
			tx.begin();
			ret = method.invoke(target, args);
		}catch(Exception e){
			tx.rollback();
			e.printStackTrace();
		}finally{
			tx.commit();
		}
		return ret;
	}
}

 

· 小結

1、CGLIB原理生成目標類的子類,所以目標類可以不實現接口。

2、要求類不能是final的(可繼承),要攔截的方法要是非final、非static、非private的(可覆寫)。

3、動態代理的最小單位是類(所有類中的方法都會被處理)。

 

Spring中的動態代理機制

若目標對象實現了若干接口,Spring就會使用JDK動態代理。

若目標對象沒有實現任何接口,Spring就使用CGLIB庫生成目標對象的子類。

對接口創建代理優於對類創建代理,因爲會產生更加鬆耦合的系統,也更符合面向接口編程規則。

cglib和javassist代理的機制都是一樣的,都是通過繼承實現的。

 

 

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