原文出自【佔小狼的部落格】,轉載請註明出處
前言
之前講過《JDK的動態代理實現》,內部透過反射類 Proxy
和 InvocationHandler
回呼介面實現,要求委託類必須實現一個介面,只能對該類介面中定義的方法進行代理,這在實際的專案中有一定的侷限性,遇到沒有介面的委託類,就無法應對。
幸好,有CGLIB這個神器的存在。
CGLIB實現
使用CGLIB(Code Generation Library)實現動態代理,並不要求委託類必須實現介面,底層採用ASM位元組碼生成框架生成代理類的位元組碼,下麵透過一個例子看看如何實現動態代理。
1、定義業務邏輯
public class UserServiceImpl {
public void add() {
System.out.println("This is add service");
}
public void delete(int id) {
System.out.println("This is delete service:delete " + id );
}
}
2、實現 MethodInterceptor
介面,定義方法的攔截器
public class MyMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
System.out.println("Before:" + method);
Object object = proxy.invokeSuper(obj, arg);
System.out.println("After:" + method);
return object;
}
}
3、利用 Enhancer
類生成代理類;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new MyMethodInterceptor());
UserServiceImpl userService = (UserServiceImpl)enhancer.create();
4、 userService.add()
的執行結果:
Before: add
This is add service
After: add
代理物件的生成過程由Enhancer類實現,大概步驟如下:
1、生成代理類Class的二進位制位元組碼;
2、透過 Class.forName
載入二進位制位元組碼,生成Class物件;
3、透過反射機制獲取實體構造,並初始化代理類物件。
位元組碼生成
Enhancer是CGLib的位元組碼增強器,可以方便的對類進行擴充套件,內部呼叫 GeneratorStrategy.generate
方法生成代理類的位元組碼,透過以下方式可以生成class檔案。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\\\Code\\\\whywhy\\\\target\\\\classes\\\\zzzzzz")
使用 反編譯工具 procyon 檢視代理類實現
java -jar procyon-decompiler-0.5.30.jar UserService$$EnhancerByCGLIB$$394dddeb;
反編譯之後的代理類add方法實現如下:
import net.sf.cglib.core.Signature;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
//
// Decompiled by Procyon v0.5.30
//
public class UserService$$EnhancerByCGLIB$$394dddeb extends UserService implements Factory
{
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$add$0$Method;
private static final MethodProxy CGLIB$add$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
static void CGLIB$STATICHOOK2() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
final Class> forName = Class.forName("UserService$$EnhancerByCGLIB$$394dddeb");
final Class> forName3;
CGLIB$add$0$Method = ReflectUtils.findMethods(new String[] { "add", "()V" }, (forName3 = Class.forName("UserService")).getDeclaredMethods())[0];
CGLIB$add$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "add", "CGLIB$add$0");
}
final void CGLIB$add$0() {
super.add();
}
public final void add() {
MethodInterceptor cglib$CALLBACK_2;
MethodInterceptor cglib$CALLBACK_0;
if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
}
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object)this, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Method, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$emptyArgs, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Proxy);
return;
}
super.add();
}
static {
CGLIB$STATICHOOK2();
}
}
透過CGLIB生成的位元組碼相比JDK實現來說顯得更加複雜。
1、代理類 UserService$$EnhancerByCGLIB$$394dddeb
繼承了委託類 UserSevice
,且委託類的final方法不能被代理;
2、代理類為每個委託方法都生成兩個方法,以add方法為例,一個是重寫的add方法,一個是CGLIB$add$0方法,該方法直接呼叫委託類的add方法;
3、當執行代理物件的add方法時,會先判斷是否存在實現了MethodInterceptor介面的物件 cglib$CALLBACK_0
,如果存在,則呼叫MethodInterceptor物件的 intercept
方法:
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) {
System.out.println("Before:" + method);
Object object = proxy.invokeSuper(obj, arg);
System.out.println("After:" + method);
return object;
}
引數分別為:1、代理物件;2、委託類方法;3、方法引數;4、代理方法的MethodProxy物件。
4、每個被代理的方法都對應一個MethodProxy物件, methodProxy.invokeSuper
方法最終呼叫委託類的add方法,實現如下:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
單看 invokeSuper
方法的實現,似乎看不出委託類add方法呼叫,在MethodProxy實現中,透過FastClassInfo維護了委託類和代理類的FastClass。
private static class FastClassInfo {
FastClass f1;
FastClass f2;
int i1;
int i2;
}
以add方法的methodProxy為例,f1指向委託類物件,f2指向代理類物件,i1和i2分別是方法add和CGLIB$add$0在物件中索引位置。
FastClass實現機制
FastClass其實就是對Class物件進行特殊處理,提出下標概念index,透過索引儲存方法的取用資訊,將原先的反射呼叫,轉化為方法的直接呼叫,從而體現所謂的fast,下麵透過一個例子瞭解一下FastClass的實現機制。 1、定義原類
class Test {
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
2、定義Fast類
class FastTest {
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
}
在FastTest中有兩個方法, getIndex
中對Test類的每個方法根據hash建立索引, invoke
根據指定的索引,直接呼叫標的方法,避免了反射呼叫。所以當呼叫 methodProxy.invokeSuper
方法時,實際上是呼叫代理類的 CGLIB$add$0
方法, CGLIB$add$0
直接呼叫了委託類的add方法。
JDK和CGLIB動態代理的區別
1、JDK動態代理生成的代理類和委託類實現了相同的介面;
2、CGLIB動態代理中生成的位元組碼更加複雜,生成的代理類是委託類的子類,且不能處理被final關鍵字修飾的方法;
3、JDK採用反射機制呼叫委託類的方法,CGLIB採用類似索引的方式直接呼叫委託類方法;
END