眾所周知,Spring AOP中涉及到了動態(tài)代理模式,那么有動態(tài)代理相應(yīng)的就會有靜態(tài)代理。那么動態(tài)代理分為哪幾種,相對應(yīng)的區(qū)別又是什么呢?
首先什么是代理?
找一個東西或者一個人去幫你做事,比如常說的中介就是一個代理,各大經(jīng)銷商的代理商等等。JAVA中的代理即是指將自己的事情委派給別人幫忙去完成。
靜態(tài)代理:代理的是程序員已經(jīng)創(chuàng)建好的類,也就是說當(dāng)前僅有一個對象能被成功代理。上代碼看下
首先是一個需要代理的接口類
該類描述了兩個方法,一個是eat(),一個是run();
public interface UserAction {
void eat();
void run();
}

接下來是該類的實(shí)現(xiàn)類,較為簡單的實(shí)現(xiàn)方式,僅僅打印內(nèi)容而已。
public class UserActionImpl implements UserAction {
@Override
public void eat() {
System.out.println("吃飯");
}
@Override
public void run() {
System.out.println("跑步");
}
}

接口已經(jīng)實(shí)現(xiàn)完成,剩下的即是代理對象了。上述的過程中靜態(tài)代理和JDK的動態(tài)代理還沒有區(qū)別。區(qū)別在于下面
public class UserActionStaticProxy implements UserAction{
private UserAction userAction;
public UserActionStaticProxy(UserAction userAction){
this.userAction = userAction;
}
@Override
public void eat() {
System.out.println("靜態(tài)代理eat方法開始");
userAction.eat();
System.out.println("靜態(tài)代理eat方法結(jié)束");
}
@Override
public void run() {
System.out.println("靜態(tài)代理run方法開始");
userAction.run();
System.out.println("靜態(tài)代理run方法結(jié)束");
}
}

從上述代碼可以看到,我們實(shí)現(xiàn)了接口類并定義了一個新的類 UserActionStaticProxy 。然后定義了他的有參構(gòu)造方法,將接口類傳入即可。方法重寫的同時加入了方法的監(jiān)控。
public static void main(String[] args) {
UserAction userAction = new UserActionStaticProxy(new UserActionImpl());
userAction.eat();
}

在調(diào)用的時候,我們可以看到傳入了UserActionImpl類去轉(zhuǎn)換為UserAction類(向下轉(zhuǎn)型),而后可以直接調(diào)用他的方法即可。運(yùn)行上述方法以后即執(zhí)行了 Proxy 類下的eat方法。甚至于我們可以在 Proxy 類下的eat方法調(diào)用一次 this.run() 方法,可以自己拼接為自己所想要的方式,有點(diǎn)和策略模式靠近了。(下圖是在eat方法中加入了this.run()方法)


?
從靜態(tài)代理模式中可以看出來,如果我們需要代理多個類的話,那么就需要新建對應(yīng)的接口實(shí)現(xiàn)類(Imp.class)和對應(yīng)的代理類(Proxy,class)。所以實(shí)現(xiàn)起來會比較繁瑣,因此就應(yīng)運(yùn)而生出了動態(tài)代理。
動態(tài)代理和靜態(tài)代理的本質(zhì)區(qū)別其實(shí)不是很大,都需要接口以及接口的實(shí)現(xiàn)類,動態(tài)代理解決了需要重復(fù)新增大量的單體代理文件,而是把所有的對象都在一個地方進(jìn)行了代理,也就是涉及到了JAVA中的反射機(jī)制。
可以看下動態(tài)代理的代碼:
public class LogHandler implements InvocationHandler {
private Object object;
public Object newProxyInstance(Object object){
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("動態(tài)代理" + method.getName() + "開始");
Object ret = method.invoke(object,args);
System.out.println("動態(tài)代理" + method.getName() + "結(jié)束");
return ret;
}
}

可以看到,動態(tài)代理的時候,我們將對象替換成了所有對象的父類------Object類,在代理類的同時,我們通過反射Proxy.newProxyInstance 獲取了該類的對象。而后使用了java對應(yīng)的invoke方法去執(zhí)行被代理類的方法。
對應(yīng)的執(zhí)行的主方法:
public static void main(String[] args) {
UserAction userAction1 = (UserAction)new LogHandler().newProxyInstance(new UserActionImpl());
userAction1.eat();
}

執(zhí)行的結(jié)果:


?
因此可以看到,靜態(tài)代理和動態(tài)代理的區(qū)在于代理類的區(qū)別,靜態(tài)代理在代碼擴(kuò)容時,每增加一個接口類需要代理,那么就需要新增一個對應(yīng)的代理類。而動態(tài)代理的好處在于需要新增代理接口時,不需要新增代理類,可以直接通過反射的方式調(diào)用被代理類。從上述就可以看出,代理的好處就是對方法的增強(qiáng),可以在方法的前后進(jìn)行一系列的操作,比如打印日志,驗(yàn)證權(quán)限,方法之后可以統(tǒng)一返回格式,統(tǒng)一異常捕獲等等......(其實(shí)也就是AOP能做的事情)。所以動態(tài)代理相比于靜態(tài)代理最本質(zhì)的區(qū)別就在于我們需要對一個新的接口類代理時,不需要再去增加繁瑣的代理類了。
前文提到,動態(tài)代理又分為JDK的動態(tài)代理以及CGLIB的動態(tài)代理。JDK的動態(tài)代理是依據(jù)的JAVA強(qiáng)大的反射機(jī)制。而CGLIB動態(tài)代理是利用asm開源包,對代理對象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理,也就是說生成被代理類的一個子類將其方法覆蓋,以達(dá)到代理的目的。
AOP是會自動切換這兩種動態(tài)代理的類型的,具體的區(qū)別如下:
1、如果目標(biāo)對象實(shí)現(xiàn)了接口,默認(rèn)情況下會采用JDK的動態(tài)代理實(shí)現(xiàn)AOP
2、如果目標(biāo)對象實(shí)現(xiàn)了接口,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP
3、如果目標(biāo)對象沒有實(shí)現(xiàn)了接口,必須采用CGLIB庫,spring會自動在JDK動態(tài)代理和CGLIB之間轉(zhuǎn)換
這里借用別人的CGLIB代碼來看下具體的區(qū)別
public class UserAction {
public void eat(){
System.out.println("CGLIB動態(tài)代理吃飯");
}
}
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目標(biāo)對象
//重寫攔截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib動態(tài)代理,監(jiān)聽開始!");
Object invoke = method.invoke(target, arr);//方法執(zhí)行,參數(shù):target 目標(biāo)對象 arr參數(shù)數(shù)組
System.out.println("Cglib動態(tài)代理,監(jiān)聽結(jié)束!");
return invoke;
}
//定義獲取代理對象方法
public Object getCglibProxy(Object objectTarget){
//為目標(biāo)對象target賦值
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//設(shè)置父類,因?yàn)镃glib是針對指定的類生成一個子類,所以需要指定父類
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 設(shè)置回調(diào)
Object result = enhancer.create();//創(chuàng)建并返回代理對象
return result;
}
public static void main(String[] args) {
CglibProxy cglib = new CglibProxy();//實(shí)例化CglibProxy對象
UserAction user = (UserAction) cglib.getCglibProxy(new UserAction());//獲取代理對象
user.eat();//執(zhí)行方法
}
}

代碼本質(zhì)上其實(shí)和JDK的動態(tài)代理的區(qū)別并不是很大,而JDK的動態(tài)代理是基于接口的,必須要對應(yīng)接口的實(shí)現(xiàn)類才可以實(shí)現(xiàn)JDK的動態(tài)代理,而CGLIB彌補(bǔ)了這方面的缺點(diǎn),CGLIB是基于類的繼承關(guān)系,因此在沒有接口的實(shí)現(xiàn)下我們可以使用CGLIB去實(shí)現(xiàn)動態(tài)代理。