1.CGLIB包的介紹
代理爲控制要訪問的目標對象提供了一種途徑。當訪問對象時,它引入了一個間接的層。JDK自從1.3版本開始,就引入了動態代理,並且經常被用來動態地創建代理。JDK的動態代理用起來非常簡單,當它有一個限制,就是使用動態代理的對象必須實現一個或多個接口。如果想代理沒有實現接口的繼承的類,該怎麼辦?現在我們可以使用CGLIB包
CGLIB是一個強大的高性能的代碼生成包。它廣泛的被許多AOP的框架使用,例如SpringAOP和dynaop,爲他們提供方法的interception(攔截)。最流行的ORMapping工具hibernate也使用CGLIB來代理單端single-ended(多對一和一對一)關聯(對集合的延遲抓取,是採用其他機制實現的)。EasyMock和jMock是通過使用模仿(moke)對象來測試java代碼的包。它們都通過使用CGLIB來爲那些沒有接口的類創建模仿(moke)對象。
CGLIB包的底層是通過使用一個小而快的字節碼處理框架ASM,來轉換字節碼並生成新的類。除了CGLIB包,腳本語言例如Groovy和BeanShell,也是使用ASM來生成java的字節碼。當不鼓勵直接使用ASM,因爲它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。
2.CGLIB 代理的APIS
CGLIB包的基本代碼很少,當學起來有一定的困難,主要是缺少文檔,這也是開源軟件的一個不足之處。目前CGLIB的版本是(2.1.2),主要由一下部分組成:
底層字節碼處理類,他們大部分與ASM有關係。
• net.sf.cglib.transform
編譯期或運行期類和類文件的轉換
• net.sf.cglib.proxy
實現創建代理和方法攔截器的類
• net.sf.cglib.reflect
實現快速反射和C#風格代理的類
• net.sf.cglib.util
集合排序工具類
• net.sf.cglib.beans
JavaBean相關的工具類
大多時候,僅僅爲了動態地創建代理,你僅需要使用到代理包中很少的一些API。
正如我們先前所討論的,CGLIB包是在ASM之上的一個高級別的層。對代理那些沒有實現接口的類非常有用。本質上,它是通過動態的生成一個子類去覆蓋所要代理類的不是final的方法,並設置好callback,則原有類的每個方法調用就會轉變成調用用戶定義的攔截方法(interceptors),這比JDK動態代理方法快多了。
圖2爲我們演示了創建一個具體類的代理時,通常要用到的CGLIB包的APIs。net.sf.cglib.proxy.Callback接口在CGLIB包中是一個很關鍵的接口,所有被net.sf.cglib.proxy.Enhancer類調用的回調(callback)接口都要繼承這個接口。net.sf.cglib.pr用oxy.MethodInterceptor接口是最通用的回調(callback)類型,它經常被基於代理的AOP用來實現攔截(intercept)方法的調用。這個接口只定義了一個方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;
Figure 3: CGLIB MethodInterceptor
net.sf.cglib.proxy.MethodInterceptor能夠滿足任何的攔截(interception )需要,當對有些情況下可能過度。爲了簡化和提高性能,CGLIB包提供了一些專門的回調(callback)類型。例如:
net.sf.cglib.proxy.FixedValue
爲提高性能,FixedValue回調對強制某一特別方法返回固定值是有用的。
net.sf.cglib.proxy.NoOp
NoOp回調把對方法調用直接委派到這個方法在父類中的實現。
net.sf.cglib.proxy.LazyLoader
當實際的對象需要延遲裝載時,可以使用LazyLoader回調。一旦實際對象被裝載,它將被每一個調用代理對象的方法使用。
net.sf.cglib.proxy.Dispatcher
Dispathcer回調和LazyLoader回調有相同的特點,不同的是,當代理方法被調用時,裝載對象的方法也總要被調用。
net.sf.cglib.proxy.ProxyRefDispatcher
ProxyRefDispatcher回調和Dispatcher一樣,不同的是,它可以把代理對象作爲裝載對象方法的一個參數傳遞。
如圖3所示,代理類的所以方法經常會用到回調(callback),當是你也可以使用net.sf.cglib.proxy.CallbackFilter有選擇的對一些方法使用回調(callback),這種考慮周詳的控制特性在JDK的動態代理中是沒有的。在JDK代理中,對java.lang.reflect.InvocationHandler方法的調用對代理類的所以方法都有效。
除了代理類外,CGLIB通過提供一個對java.lang.reflect.Proxy的drop-in替代來實現對對接口的代理。因爲這種代理能力的替代很少被用到,因此相應的APIs也很少提到。
CGLIB的代理包也對net.sf.cglib.proxy.Mixin提供支持。基本上,它允許多個對象被綁定到一個單個的大對象。在代理中對方法的調用委託到下面相應的對象中。
接下來我們看看如何使用CGLIB代理APIs創建代理。
創建一個簡單的代理CGLIB代理最核心的是net.sf.cglib.proxy.Enhancer類,爲了創建一個代理,最起碼你要用到這個類。首先,讓我們使用NoOp回調創建一個代理:
/**
* Create a proxy using NoOp
callback. The target class
* must have a default zero-argument constructor.
*
* @param targetClass the super class of the proxy
* @return a new proxy for a target class instance
*/
public Object createProxy(Class targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(NoOp.INSTANCE);
return enhancer.create();
}
返回值是target類一個實例的代理。在這個例子中,我們爲net.sf.cglib.proxy.Enhancer配置了一個單一的回調(callback)。我們可以看到很少直接創建一個簡單的代理,而是創建一個net.sf.cglib.proxy.Enhancer的實例,在net.sf.cglib.proxy.Enhancer類中你可使用靜態幫助方法創建一個簡單的代理。一般推薦使用上面例子的方法創建代理,因爲它允許你通過配置net.sf.cglib.proxy.Enhancer實例很好的控制代理的創建。
要注意的是,target類是作爲產生的代理的父類傳進來的。不同於JDK的動態代理,它不能在創建代理時傳target對象,target對象必須被CGLIB包來創建。在這個例子中,默認的無參數構造器時用來創建target實例的。如果你想用CGLIB來創建有參數的實例,用net.sf.cglib.proxy.Enhancer.create(Class[],Object[])方法替代net.sf.cglib.proxy.Enhancer.create()就可以了。方法中第一個參數定義了參數的類型,第二個是參數的值。在參數中,基本類型應被轉化成類的類型。
Use a MethodInterceptor爲了更好的使用代理,我們可以使用自己定義的MethodInterceptor類型回調(callback)來代替net.sf.cglib.proxy.NoOp回調。當對代理中所有方法的調用時,都會轉向MethodInterceptor類型的攔截(intercept)方法,在攔截方法中再調用底層對象相應的方法。下面我們舉個例子,假設你想對目標對象的所有方法調用進行權限的檢查,如果沒有經過授權,就拋出一個運行時的異常AuthorizationException。其中AuthorizationService.java接口的代碼如下:
package com.lizjason.cglibproxy;
import java.lang.reflect.Method;
/**
* A simple authorization service for illustration purpose.
*
* @author Jason Zhicheng Li ([email protected])
*/
public interface AuthorizationService {
/**
* Authorization check for a method call. An AuthorizationException
* will be thrown if the check fails.
*/
void authorize(Method method);
}
對net.sf.cglib.proxy.MethodInterceptor接口的實現的類AuthorizationInterceptor.java代碼如下:
package com.lizjason.cglibproxy.impl;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.lizjason.cglibproxy.AuthorizationService;
/**
* A simple MethodInterceptor implementation to
* apply authorization checks for proxy method calls.
*
* @author Jason Zhicheng Li ([email protected])
*
*/
public class AuthorizationInterceptor implements MethodInterceptor {
private AuthorizationService authorizationService;
/**
* Create a AuthorizationInterceptor with the given
* AuthorizationService
*/
public AuthorizationInterceptor (AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
/**
* Intercept the proxy method invocations to inject authorization check.
* The original method is invoked through MethodProxy.
* @param object the proxy object
* @param method intercepted Method
* @param args arguments of the method
* @param proxy the proxy used to invoke the original method
* @throws Throwable any exception may be thrown; if so, super method will not be invoked
* @return any value compatible with the signature of the proxied method.
*/
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy ) throws Throwable {
if (authorizationService != null) {
//may throw an AuthorizationException if authorization failed
authorizationService.authorize(method);
}
return methodProxy.invokeSuper(object, args);
}
}
我們可以看到在攔截方法中,首先進行權限的檢查,如果通過權限的檢查,攔截方法再調用目標對象的原始方法。由於性能的原因,對原始方法的調用我們使用CGLIB的net.sf.cglib.proxy.MethodProxy對象,而不是反射中一般使用
java.lang.reflect.Method對象。
Use a CallbackFilter
net.sf.cglib.proxy.CallbackFilter允許我們在方法層設置回調(callback)。假如你有一個PersistenceServiceImpl類,它有兩個方法:save和load,其中方法save需要權限檢查,而方法load不需要權限檢查。
package com.lizjason.cglibproxy.impl;
import com.lizjason.cglibproxy.PersistenceService;
/**
* A simple implementation of PersistenceService interface
*
* @author Jason Zhicheng Li ([email protected])
*/
public class PersistenceServiceImpl implements PersistenceService {
public void save(long id, String data) {
System.out.println(data + " has been saved successfully.");
}
public String load(long id) {
return "Jason Zhicheng Li";
}
}
注意到PersistenceServiceImpl類實現了PersistenceService
接口,因此沒有要求要使用CGLIB創建代理。
net.sf.cglib.proxy.CallbackFilter 接口的實現如下:
package com.lizjason.cglibproxy.impl;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;
/**
* An implementation of CallbackFilter for PersistenceServiceImpl
*
* @author Jason Zhicheng Li ([email protected])
*/
public class PersistenceServiceCallbackFilter implements CallbackFilter {
//callback index for save method
private static final int SAVE = 0;
//callback index for load method
private static final int LOAD = 1;
/**
* Specify which callback to use for the method being invoked.
* @method the method being invoked.
* @return the callback index in the callback array for this method
*/
public int accept(Method method) {
String name = method.getName();
if ("save".equals(name)) {
return SAVE;
}
// for other methods, including the load method, use the
// second callback
return LOAD;
}
}
accept方法中對代理方法和回調進行了匹配,返回的值是某方法在回調數組中的索引。下面是PersistenceServiceImpl類代理的實現。
...
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersistenceServiceImpl.class);
CallbackFilter callbackFilter = new PersistenceServiceCallbackFilter();
enhancer.setCallbackFilter(callbackFilter);
AuthorizationService authorizationService = ...
Callback saveCallback = new AuthorizationInterceptor(authorizationService);
Callback loadCallback = NoOp.INSTANCE;
Callback[] callbacks = new Callback[]{saveCallback, loadCallback };
enhancer.setCallbacks(callbacks);
...
return (PersistenceServiceImpl)enhancer.create();
在這個例子中save方法使用了AuthorizationInterceptor實例,load方法使用了NoOp實例。此外,你也可以通過
net.sf.cglib.proxy.Enhancer.setInterfaces(Class[])方法指定代理對象所實現的接口。
除了爲net.sf.cglib.proxy.Enhancer指定回調數組,你還可以通過net.sf.cglib.proxy.Enhancer.setCallbackTypes(Class[])
方法指定回調類型數組。當創建代理時,如果你沒有回調實例的數組,就可以使用回調類型。象使用回調一樣,你必須使用,net.sf.cglib.proxy.CallbackFilter爲每一個方法指定一個回調類型索引。你可以從http://www.lizjason.com/downloads/下載設置回調類型和接口的完整代碼。
增強一個已有類
public class MyClass {
public void method() {
System.out.println("MyClass.method()");
}
}
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.MethodInterceptor;
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyClass.class);
enhancer.setCallback( new MethodInterceptorImpl() );
MyClass my = (MyClass)enhancer.create();
my.method();
}
private static class MethodInterceptorImpl implements MethodInterceptor {
public Object intercept(Object obj,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println(method);
proxy.invokeSuper(obj, args);
return null;
}
}
}
執行結果:
public void cglib_test.MyClass.method()
MyClass.method()
使用CallbackFilter
public class MyClass {
public void method() {
System.out.println("MyClass.method()");
}
public void method2() {
System.out.println("MyClass.method2()");
}
}
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.NoOp;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
public class Main {
public static void main(String[] args) {
Callback[] callbacks =
new Callback[] { new MethodInterceptorImpl(), NoOp.INSTANCE };
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyClass.class);
enhancer.setCallbacks( callbacks );
enhancer.setCallbackFilter( new CallbackFilterImpl() );
MyClass my = (MyClass)enhancer.create();
my.method();
my.method2();
}
private static class CallbackFilterImpl implements CallbackFilter {
public int accept(Method method) {
if ( method.getName().equals("method2") ) {
return 1;
} else {
return 0;
}
}
}
private static class MethodInterceptorImpl implements MethodInterceptor {
public Object intercept(Object obj,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println(method);
return proxy.invokeSuper(obj, args);
}
}
}
執行結果:
public void cglib_test.MyClass.method()
MyClass.method()
MyClass.method2()
使用Mixin
public interface MyInterfaceA { public void methodA(); } public interface MyInterfaceB { public void methodB(); } public class MyInterfaceAImpl implements MyInterfaceA { public void methodA() { System.out.println("MyInterfaceAImpl.methodA()"); } } public class MyInterfaceBImpl implements MyInterfaceB { public void methodB() { System.out.println("MyInterfaceBImpl.methodB()"); } }
import net.sf.cglib.proxy.Mixin; public class Main { public static void main(String[] args) { Class[] interfaces = new Class[] { MyInterfaceA.class, MyInterfaceB.class }; Object[] delegates = new Object[] { new MyInterfaceAImpl(), new MyInterfaceBImpl() }; Object obj = Mixin.create(interfaces, delegates); MyInterfaceA myA = (MyInterfaceA)obj; myA.methodA(); MyInterfaceB myB = (MyInterfaceB)obj; myB.methodB(); } }
執行結果:
MyInterfaceAImpl.methodA() MyInterfaceBImpl.methodB()