面試官讓手寫動態代理實現?——JDK代理和CGLIB代理解析
技術標籤:springbootjavaspringproxy反射aop
什麼是動態代理
動態代理是相對於靜態代理產生的概念。
可以簡單理解為,靜態代理是在程式碼中編寫好代理類,動態代理的代理類是在執行時動態生成的。
使用代理一般是為了在原類中增加一些增強功能,如Spring的AOP就是通過動態代理實現的。
Spring中使用動態代理
有JDK代理和CGLIB代理兩種實現。
JDK代理和CGLIB代理區別
JDK
利用攔截器(攔截器必須實現InvocationHanlder)加上反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。
CJLIB
利用ASM開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。
何時選用
- 當Bean實現介面時,Spring就會用JDK的動態代理。
- 當Bean沒有實現介面時,Spring使用CGlib是實現。
- 可以強制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。
具體實現
JDK動態代理實現
定義一個介面:
interface Demo{
int add(int, int);
}
實現類:
class Real implements Demo { @Override public int add(int x, int y) { return x + y; }
這裡Real就是我們需要代理的類。
動態代理程式碼:
class Handler implements InvocationHandler { private final Real real; public Handler(Real real) { this.real = real; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { System.out.println("=== BEFORE ==="); Object re = method.invoke(real, args); System.out.println("=== AFTER ==="); return re; } }
構造方法把要代理的物件傳入Handler中。
這裡invoke方法很重要,可以看到方法呼叫是通過我們傳遞一個Method型別引數,然後呼叫method.invoke來實現,即通過反射來實現。
生成代理類的樣子:
public ProxyClass implements Demo {
private static Method mAdd;
private InvocationHandler handler;
static {
Class clazz = Class.forName("Demo");
mAdd = clazz.getMethod("add", int.class, int.class);
}
@Override
public int add(int x, int y) {
return (Integer)handler.invoke(this, mAdd, new Object[] {x, y});
}
}
這裡add方法是呼叫了handler的invoke方法,傳遞三個引數,第一個是代理類本身,第二個是add方法的反射類,最後一個是引數列表。我們是通過InvocationHandler來完成攔截與代理。
JDK Proxy具體使用:
Handler handler = new Handler(new Real());
ifc p = (ifc)Proxy.newProxyInstance(ifc.class.getClassLoader(),
new Class[] {Demo},
handler);
p.add(1, 2);
CGLIB動態代理實現
沒有介面,直接是實現類:
class Real {
public int add(int x, int y) {
return x + y;
}
}
使用了一個與JDK Proxy中Handler類似的類:
public class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("=== BEFORE ===");
Object re = proxy.invokeSuper(obj, args);
System.out.println("=== AFTER ===");
return re;
}
}
代理類的大致樣子:
public ProxyClass extends Real {
private static Method mAdd;
private static MethodProxy mAddProxy;
private MethodInterceptor interceptor;
static {
Class clazz = Class.forName("ifc");
mAdd = clazz.getMethod("add", int.class, int.class);
// Some logic to generate mAddProxy.
// ...
}
@Override
public int add(int x, int y) {
return (Integer)interceptor.invoke(
this, mAdd, new Object[] {x, y}, mAddProxy);
}
}
與jdk代理大致相同,只是多了一個MethodProxy。
去上面Interceptor可以看到,呼叫invoke方法是
Object re = proxy.invokeSuper(obj, args);
而不是
Object re = method.invoke(obj, args);
原因是代理類繼承了原始類,obj指向的就是代理類物件的例項,如果第二種寫法就會遞迴呼叫代理類的add方法。
因此cglib封裝了一個MethodProxy類,其中invokeSuper方法可以呼叫原始基類的真正方法。
使用:
public static void main(String[] args) {
Enhancer eh = new Enhancer();
eh.setSuperclass(Real.class);
eh.setCallback(new Interceptor());
Real r = (Real)eh.create();
int result = r.add(1, 2);
}
總結
JDK代理和CGLIB代理實現本質上是很相似的。
都包含一下兩點內容:
- 有一個介面或者基類,定義了一個代理類。
- 一個方法攔截器,完成方法的攔截和代理,是所有呼叫鏈的橋樑。