問題的引出
一些僞代碼:
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代理的機制都是一樣的,都是通過繼承實現的。