Java的代理和CGLib的比較及使用
代理模式是我們寫程式碼過程中經常使用的模式,其通常由兩部分構成,即代理類和委託類,他們對外暴露同樣的業務方法(即實現相同的介面)。委託類是實際進行業務操作的類,代理類中會封裝一個委託類例項,對業務方法的處理就是轉調委託類例項相應方法。實際使用中,使用者得到的是代理類例項,那為什麼要在委託類上封裝一層呢?我們先看一個實際生活的例子吧。
我們以教育局為例吧,目前假設教育局就局長一人,局長有義務向社會公眾提供教育政策的解答,這就是局長對外提供的“業務方法”。每天局長接到好多電話(即方法呼叫)。但統計發現,這些電話很大部分打錯了,或這個人根本言語不清,局長的精力都耗費在這些沒有意義的電話上了。最後,局長沒法了,就找個祕書吧,祕書的責任也是提供教育政策的解答,公眾熱線現在全部就轉給祕書了。祕書接到電話後,先判斷這個人言語表達還可以並且沒有打錯才轉接給局長處理,否則就直接結束通話。這樣局長的精力全部能去處理正事,無意義的電話全被祕書過濾掉了。
上述例子就是代理模式在實際的典型應用,例子中,祕書是代理類,局長就是委託類。代理類會進行一些與實際業務相關的小的業務點的操作,如資格審查,資訊記錄等等,然後根據結果確定是否轉調委託類。
從實際編寫程式碼的角度,代理模式分為靜態代理和動態代理。
【靜態代理】
靜態代理是指,代理類是由程式設計師寫好的,並手動編譯為class檔案進行使用。我們直接看個例子吧:
package cn.test;
/**
* 服務介面
*/
public interface OrderService {
public boolean placeOrder(String...goods);
}
package cn.test; /** * 服務的實現類!實際業務處理的類,代理中的委託類。 */ public class OrderServiceImpl implements OrderService { @Override public boolean placeOrder(String... goods) { System.out.println("開始下訂單, 請等待...."); System.out.println("訂單已生效...."); return true; } }
package cn.test; /** * 訂單服務的靜態代理類! */ public class OrderServiceStaticProxy implements OrderService{ private OrderServiceImpl orderService; public OrderServiceStaticProxy(OrderServiceImpl orderService) { this.orderService = orderService; } @Override public boolean placeOrder(String... goods) { System.out.println("使用者許可權校驗...."); System.out.println("訂單有效性校驗...."); return orderService.placeOrder(goods); } }
package cn.test;
/**
* 靜態代理測試類
*/
public class OrderServiceTest {
public static void main(String[] args) {
// 使用者最終使用的訂單服務類為代理類
OrderService orderService = new OrderServiceStaticProxy(new OrderServiceImpl());
orderService.placeOrder("apple");
}
}
測試輸出為:
使用者許可權校驗....
訂單有效性校驗....
開始下訂單, 請等待....
訂單已生效....
上面就是一個訂單服務靜態代理的應用,靜態代理使用十分簡單方便,其代理類也是程式設計師手動書寫出來並編譯執行的。但從上例我們也可以看出靜態代理的缺點:靜態代理只能面型某一個確定的服務介面或服務實現,如果我們的系統有幾百個服務類,現在要統一新增代理,則我們的系統就要額外新增幾百個靜態的代理類,這會導致系統類特別多不易管理。以後我們每當向系統中新增一個新的服務類,還得記得為他編寫一個代理類,這點也很難實現。因此,Java同時還提供了動態代理,意如其名,就是代理類物件是在執行期動態生成的,這個和靜態代理在本質上是有分別的。
【動態代理】
關於動態代理,我們先直接看一個例子吧:
package cn.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 帶許可權校驗的動態代理生成器類
*/
public class ChkUserProxy implements InvocationHandler {
/**
* 委託類物件
*/
private Object target;
/**
* 繫結委託類的方法,同時會返回一個代理類物件。這個代理類會兼具許可權檢查的功能。
* @param target
* @return
*/
public Object bind(Object target){
this.target = target;
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
// 將動態生成的代理類物件返回給使用者即可
return proxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("使用者許可權校驗....");
// 許可權校驗完成後,呼叫委託類中的方法即可
return method.invoke(target, args);
}
}
我們接著用訂單服務類來測試我們的動態代理:
package cn.test;
/**
* 動態代理測試類
*/
public class OrderServiceTest {
public static void main(String[] args) {
OrderService orderService = (OrderService)new ChkUserProxy().bind(new OrderServiceImpl());
orderService.placeOrder("apple");
}
}
使用者許可權校驗....
開始下訂單, 請等待....
訂單已生效....
由輸出可以看出,代理生效。動態代理使用了Java在java.lang.reflect包中提供的介面InvocationHandler 和 類Proxy來實現的。上例中,我們通過實現InvocationHandler介面來實現使用者校驗的動態代理生成器,然後通過Proxy類來生成最終的動態代理物件。這種方式比靜態代理的優勢就在於:通過實現InvocationHandler的動態代理生成器可以為不同的委託類物件生成動態代理物件。這些動態代理物件公共的地方在於,都在委託類業務方法呼叫前增加了使用者許可權校驗的功能。這就有點“面向切面”程式設計的概念了。我們這裡的切面是業務方法呼叫前,我們在這個切面引入的邏輯是使用者許可權校驗。
動態代理有個缺陷是:委託類必須實現某個介面,否則不能為這個委託類生成動態代理類。利用CGLib(Code Generation Lib)去生成代理類物件可以解決這個缺陷。
【CGLib】
從上例可以看出,Java本身動態代理是基於介面去生成的。而CGLib生成代理是基於類去生成的,即直接採用繼承的方式生成委託類的子類,然後覆寫相應的業務方法。我們繼續上例子:
package cn.test;
/**
* 訂單服務類,沒有實現任何介面。
*/
public class OrderService2 {
public boolean placeOrder(String... goods) {
System.out.println("開始下訂單, 請等待....");
System.out.println("訂單已生效....");
return true;
}
}
package cn.test;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 利用CGLib實現的動態代理物件生成器
*/
public class ChkUserCGLib implements MethodInterceptor {
// 委託類物件
private Object target;
/**
* 建立並返回委託類物件的代理類物件
*/
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
// 設定最後生成的代理類的父類
enhancer.setSuperclass(target.getClass());
// 設定切面回撥
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("使用者許可權校驗....");
// 許可權校驗完成後,呼叫委託類中的方法即可
method.invoke(target, args);
return null;
}
}
package cn.test;
/**
* CGLib生成代理的測試類
*/
public class OrderServiceTest {
public static void main(String[] args) {
OrderService2 orderService = (OrderService2)new ChkUserCGLib().getInstance(new OrderService2());
orderService.placeOrder("apple");
}
}
輸出為:
使用者許可權校驗....
開始下訂單, 請等待....
訂單已生效....
由輸出看,我們通過CGLib可以順利地生成沒有實現任何介面的類物件的動態代理物件。因為CGLib是基於繼承的方式去生成委託類的代理類物件,所以對於final來修改的類無法利用CGLib生成動態代理物件。注意,利用CGLib去開發時,需要CGLib和ASM相關的jar包,CGLib是基於ASM的,對ASM的介面進行了更好的封裝,易用性更強,我的資源中有下載。
綜上,就是使用代理相關的3種方式。代理模式在各種框架中被廣泛使用,如Hibernate,Spring等。AOP(面向切面程式設計)也是動態代理的經典應用。
最後說一下代理和反射的一些聯絡吧,這兩者是完全不同的概念,代理是一種設計模式,反射是Java提供的執行時呼叫物件方法的一種機制,動態代理中使用了反射這種技術。