CGlib使用筆記

       CGlib,即Code Generation Library,是Java中普遍使用的動態代理類庫,尤爲是AOP框架。相比於JDK動態代理,它不要求被代理的類實現一個或多個接口,它的底層經過一個小而快的字節碼處理框架ASM來轉換字節碼生成新的類,並且正是由於它直接生成字節碼,因此效率比JDK動態代理要高。 java

1. CGlib生成動態代理類的方式

       CGlib提供了好幾種產生動態代理類的方法,基本都是是利用net.sf.cglib.proxy.Enhancer這個類。首先建立要代理的類: web

public class Student {
	String name;
	public Student() {}
	
	public Student(String n) {
		name = n;
	}
	
	public void setName(String name) {
		this.name = name;
	}

	public void printName() {
		System.out.println("Student name is: " + name);
	}
	
	public final void finalTest() {
		System.out.println("This is a final method");
	}
}

方法一直接用Enhancer的靜態方法生成 數據庫

// 定義一個實現了MethodInterceptor接口的回調類,相似JDK動態代理的InvocationHandler
class CGLibProxy implements MethodInterceptor {

	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<T> cls) {
                // 傳入兩個參數分別表明被代理類和代理運做時的回調類,就相似給一個按鈕綁定listener同樣
		return (T) Enhancer.create(cls, this); 
	}
	
        // 實現回調處理方法
	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		System.out.println("~~~ Before Log ~~~");
		System.out.println("calling method: " + proxy.getSignature().getName());
                /*
                 * proxy是代理方法,因此這裏必需要經過proxy.invokeSuper(obj, args)來調用原來Student類
                 * 中的方法,若是這裏是proxy.invoke(obj, args),則調用的仍是proxy方法自己,從而致使無限
                 * 遞歸,注意必定不要調用錯誤了
                 */
                Object result = proxy.invokeSuper(obj, args);
		System.out.println("~~~ After Log ~~~");
		
		return result;
	}
	
}
        當須要建立一個動態代理對象的時候,首先初始化一個CGLibProxy的實例,而後調用它的getProxy方法,傳入的參數是被代理類的Class,在咱們的例子中就是Student.class,測試代碼以下:
public static void main(String[] args) {
	CGLibProxy cgLibProxy = new CGLibProxy();

       // 這裏返回的student就是一個動態代理類了,其實它指向的是Student的一個子類
	Student student = cgLibProxy.getProxy(Student.class);
	student.setName("Joey");
	student.printName();
	student.finalTest();
}
      輸出結果以下:
~~~ Before Log ~~~
calling method: setName
~~~ After Log ~~~
~~~ Before Log ~~~
calling method: printName
Student name is: Joey
~~~ After Log ~~~
This is a final method
      能夠看到在調用setName()和printName()方法的先後都打印了日誌,說明動態代理是成功的,這裏有兩點要注意:
  1. Enhancer的靜態create方法默認是使用被代理類的一個無參構造函數來初始化的,因此被代理類Student必需要有一個public的無參構造函數,若是這個無參構造函數是被設置爲了private則會報錯;
  2. CGlib是基於繼承實現的,final方法沒法被子類override,因此在調用Student類的final方法時並無被代理的效果

方法二經過Enhancer的對象來建立動態代理類 數組

     這個方法跟方法一大體相同,只是須要稍微修改一下getProxy()方法的代碼: 框架

public <T> T getProxy(Class<T> cls) {
	Enhancer enhancer = new Enhancer();
	enhancer.setSuperclass(cls);
	enhancer.setCallback(this);
	return (T) enhancer.create(); 
}
      在方法一中咱們直接傳入了要被代理類的Class和回調處理類,而在方法二中,須要經過Enhancer的對象來set這兩個參數值,最後經過無參的create方法來產生動態代理類,這個測試的輸出結果是同樣的。

      須要注意的是,無參的create()方法默認調用被代理類Student的無參構造函數來初始化,若是但願經過有參的構造函數初始化Student,也能夠調用create()的一個重載版本: ide

enhancer.create(new Class<?>[]{String.class}, new Object[]{"Jack"});

      須要傳入兩個參數,第一個是Student構造函數的參數列表的Class數組,第二個是對應的值。

2. CGlib中的攔截器

      CGlib除了效率比JDK動態代理更高之外,它還增長了一些實用的功能,攔截器就是其中之一。以前的例子中咱們對於被代理類Student中的每個非final方法都是採用同一套代理邏輯,即在方法執行先後都打印一下輸出,可是在實際的應用中可能須要對不一樣的方法產生不一樣的代理效果。舉個例子,在web開發中,咱們但願每個service方法能夠被事務管理,即每個方法執行前要開始事務,執行後要提交事務,可是對於另外的一些輔助方法,如isEmpty()這種,咱們並不須要作這些數據庫事務操做,也許只要打印日誌就能夠了,若是咱們對於這些類全都採用一樣的代理邏輯去生成代理類明顯是沒有意義的,也很容易產生問題,因此咱們須要一個攔截器,對不一樣的方法產生不一樣代理邏輯。 函數

      首先給Student類增長兩個方法用於測試: 測試

public void myservice() {
         //咱們但願這種以service結尾的方法在調用前打開事務,調用後提交事務
         System.out.println("\t***Student service method***");
}

public boolean hasName() {
        //咱們但願這種輔助方法不參與事務的管理,只要打印日誌便可
	System.out.println("\t***Student hasName() method***");
	return null == name;
}

      從新寫一個產生動態代理對象的類: this

public class MultipleProxy {
	// 這兩個常數分別對應於callbackClasses數組的下標
	private static final int TRANSACTION_MANAGE = 0;
	private static final int LOG_RECORD = 1;
	// 把全部回調類都放到一個數組中,攔截器根據獲得的返回結果來從數組中獲取對應的回調邏輯類
	private static Callback[] callbackClasses = new Callback[]{new TransactionManagerProxy(), new LogRecordProxy()};
	
	// 靜態內部類,處理跟事務相關邏輯的動態代理
	private static class TransactionManagerProxy implements MethodInterceptor {
		@Override
		public Object intercept(Object obj, Method method, Object[] args,
				MethodProxy proxy) throws Throwable {
			System.out.println("~~~ Begin Transaction ~~~");
			Object result = proxy.invokeSuper(obj, args);
			System.out.println("~~~ Commit Transaction ~~~");
			
			return result;
		}
	}
	
	// 靜態內部類,處理跟日誌相關、跟事務無關的動態代理
	private static class LogRecordProxy implements MethodInterceptor {
		@Override
		public Object intercept(Object obj, Method method, Object[] args,
				MethodProxy proxy) throws Throwable {
			System.out.println("~~~ Before invoking method ~~~");
			Object result = proxy.invokeSuper(obj, args);
			System.out.println("~~~ After invoking method ~~~");
			
			return result;
		}
	}
	
	// 獲取代理類的方法
	public static <T> T getProxy(Class<?> cls) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(cls);
		// 把全部回調類的數組給它保存
		enhancer.setCallbacks(callbackClasses);
		// 經過匿名內部類設置過濾器,它將accept方法的返回值做爲數組下標去獲取對應的callback類
		enhancer.setCallbackFilter(new CallbackFilter() {
			
			@Override
			public int accept(Method method) {
				String methodName = method.getName();
				// 方法名以service結尾的話則使用事務相關的回調類
				if (methodName.endsWith("service")) {
					return TRANSACTION_MANAGE;
				} else {
					// 不然使用日誌記錄的回調類
					return LOG_RECORD;
				}
			}
		});
		
		// 構造動態代理類
		return (T) enhancer.create();
	}
}



        這個類提供一個靜態的方法getProxy()去獲取動態代理對象,在獲取動態代理對象以前,它給enhancer設置了一個攔截器,攔截器中的accept方法會根據被調用方法的名字來返回不一樣的下標,最後enhancer會利用這個返回的下標去執行不一樣的回調類中方法。測試代碼以下:
Student student = MultipleProxy.getProxy(Student.class);
student.myservice();
student.hasName();



        輸出結果以下:

~~~ Begin Transaction ~~~
	***Student service method***
~~~ Commit Transaction ~~~
~~~ Before invoking method ~~~
	***Student hasName() method***
~~~ After invoking method ~~~



       從輸出結果咱們能夠看到,在調用myservice方法先後的輸出和調用hasName方法先後的輸出是不同的,從而實現了更靈活的動態綁定。

3. 總結

    使用CGlib產生動態代理對象主要有兩種方法: spa

  1. 使用Enhancer的靜態create()方法,同時傳入要被代理類的Class和實現了MethodInterceptor接口的回調類;
  2. 構造一個Enhancer類的實例,用這個實例的create方法產生動態代理對象。create方法還有多個重載版本,分別對應被代理類的無參構造函數和有參構造函數的狀況

     CGlib使用中的一些注意點:

  1. CGlib是採用繼承的方式實現動態代理的,所以final類不能代理,非final類中的final方法也不能被代理
  2. 使用無參的create方法產生動態代理對象時,被代理類須要有一個非private的無參構造函數
  3. 能夠給enhancer設置攔截器,從而在調用代理類方法時根據不一樣的狀況運行不一樣的代理邏輯
相關文章
相關標籤/搜索