1. 程式人生 > >Java的代理和CGLib的比較及使用

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提供的執行時呼叫物件方法的一種機制,動態代理中使用了反射這種技術。