從靜態(tài)代理開始
假設(shè)我們有個業(yè)務(wù)類,類的接口和實現(xiàn)是這么定義的:
public interface Player {
void play();
}
public PlayerImpl implements Player {
@Override
public void play(){
System.out.println("playing...");
}
}
如果我想在玩游戲之前和游戲結(jié)束之后,記錄日志,可是不允許改動PlayerImpl的代碼,該怎么辦呢?
這時候可以構(gòu)造一個代理類來做這件事情:
public PlayerProxy implements Player {
private Player player;
public PlayerProxy(){
this.player = new PlayerImpl();
}
@Override
public void play(){
beforeLog();
player.play();
afterLog();
}
private void beforeLog(){
System.out.println("init...");
}
private void afterLog(){
System.out.println("destroy...");
}
}
這就是靜態(tài)代理,我們可以在不改動源碼的情況下,通過代理對類功能進(jìn)行擴(kuò)展,很符合開閉原則吧:)
動態(tài)代理
靜態(tài)代理的方式很直觀,但是缺點也很明顯——它只能針對特定類進(jìn)行代理,擴(kuò)展性很差,如果有一百個類要代理,難道要寫一百個代理類嗎?顯然我們應(yīng)該擁有更加通用和高效的解決方案,就是動態(tài)代理啦。
JDK動態(tài)代理
JDK動態(tài)代理,需要實現(xiàn)InvocationHandler接口;下面我們用JDK代理的方式重寫上面的代理類:
public PlayerProxyFactory implements InvocationHandler {
private Object target;
public PlayerProxyFactory(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
public Object buildProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
// 省略beforeLog和afterLog方法
}
實際使用:
public static void main(String[] args) {
Player player = new PlayerImpl();
// 構(gòu)造實現(xiàn)
PlayerProxyFactory proxy = new PlayerProxyFactory(player);
// 生成代理
Player playerProxy = (Player) proxy.buildProxy();
playerProxy.play();
}
可以看出來,動態(tài)代理可以通過接口自動構(gòu)造代理類(其實就是一個工廠)。
cglib動態(tài)代理
JDK InvocationHandler雖好,卻仍有一個缺陷,無法代理未實現(xiàn)接口的類。如果剛好有這樣的需求,該怎么解決呢?這就得靠cglib了。
cglib是一個在運行期動態(tài)生成字節(jié)碼的工具。我承認(rèn)我第一次看到這個介紹的時候被嚇到了,不過撇開原理不說,使用起來與InvocationHandler非常類似:
public class CGLibProxy implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(target, args);
after();
return result;
}
public <T> T getProxy(Class<T> clazz) {
return (T) Enhancer.create(clazz, this);
}
// 省略beforeLog和afterLog方法
}
實際使用:
public static void main(String[] args) {
Player playerProxy = new CGLibProxy().getProxy(PlayerImpl.class);
playerProxy.play();
}
JAVASSIST
Javassist是一個開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計算機(jī)科學(xué)系的 Shigeru Chiba (千葉 滋)所創(chuàng)建的。它已加入了開放源代碼JBoss 應(yīng)用服務(wù)器項目,通過使用Javassist對字節(jié)碼操作為JBoss實現(xiàn)動態(tài)"AOP"框架。
Javassist是一個十分簡單粗暴的工具,它的用法簡單來看是這樣的:
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//創(chuàng)建Programmer類
CtClass cc= pool.makeClass("com.samples.Programmer");
//定義code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代碼
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字節(jié)碼
cc.writeFile("f://myProxy");
}
這么靈活,寫個代理還不是分分鐘......
JAVA動態(tài)代理比較
| JDK動態(tài)代理 | CGLIB | JAVASSIST | |
|---|---|---|---|
| 實現(xiàn)原理 | 利用反射機(jī)制生成一個實現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來處理 | 利用asm開源包,對代理對象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理 | 通過修改字節(jié)碼生成類來處理 |
| 目標(biāo)類是否需要實現(xiàn)接口 | 是 | 否 | 否 |