開(kāi)篇
Java 的代理就是客戶(hù)類(lèi)不再直接和委托類(lèi)打交道, 而是通過(guò)一個(gè)中間層來(lái)訪問(wèn), 這個(gè)中間層就是代理。為啥要這樣呢, 是因?yàn)槭褂么碛?2 個(gè)優(yōu)勢(shì):
可以隱藏委托類(lèi)的實(shí)現(xiàn)
可以實(shí)現(xiàn)客戶(hù)與委托類(lèi)之間的解耦, 在不修改委托類(lèi)代碼的情況下能夠做一些額外的處理
我們舉個(gè)很常見(jiàn)的例子: 工廠會(huì)生產(chǎn)很多的玩具, 但是我們買(mǎi)玩具都是到商店買(mǎi)的, 而不是到工廠去買(mǎi)的, 工廠怎么生產(chǎn)我們并不關(guān)心, 我們只知道到商店可以買(mǎi)到自己想要的玩具,并且,如果我們需要送人的話商店可以把這些玩具使用禮品盒包裝。這個(gè)工廠就是委托類(lèi), 商店就是代理類(lèi), 我們就是客戶(hù)類(lèi)。
在 Java 中我們有很多場(chǎng)景需要使用代理類(lèi), 比如遠(yuǎn)程 RPC 調(diào)用的時(shí)候我們就是通過(guò)代理類(lèi)去實(shí)現(xiàn)的, 還有 Spring 的 AOP 切面中我們也是為切面生成了一個(gè)代理類(lèi)等等。
代理類(lèi)主要分為靜態(tài)代理、JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理,它們各有優(yōu)缺點(diǎn),沒(méi)有最好的, 存在就是有意義的,在不同的場(chǎng)景下它們會(huì)有不同的用武之地。
1. Java 靜態(tài)代理
首先, 定義接口和接口的實(shí)現(xiàn)類(lèi), 然后定義接口的代理對(duì)象, 將接口的實(shí)例注入到代理對(duì)象中, 然后通過(guò)代理對(duì)象去調(diào)用真正的實(shí)現(xiàn)類(lèi),實(shí)現(xiàn)過(guò)程非常簡(jiǎn)單也比較容易理解, 靜態(tài)代理的代理關(guān)系在編譯期間就已經(jīng)確定了的。它適合于代理類(lèi)較少且確定的情況。它可實(shí)現(xiàn)在怒修改委托類(lèi)代碼的情況下做一些額外的處理,比如包裝禮盒,實(shí)現(xiàn)客戶(hù)類(lèi)與委托類(lèi)的解耦。缺點(diǎn)是只適用委托方法少的情況下, 試想一下如果委托類(lèi)有幾百上千個(gè)方法, 豈不是很難受, 要在代理類(lèi)中寫(xiě)一堆的代理方法。這個(gè)需求動(dòng)態(tài)代理可以搞定
// 委托接口
public interface IHelloService {
/**
* 定義接口方法
* @param userName
* @return
*/
String sayHello(String userName);
}
// 委托類(lèi)實(shí)現(xiàn)
public class HelloService implements IHelloService {
@Override
public String sayHello(String userName) {
System.out.println("helloService" + userName);
return "HelloService" + userName;
}
}
// 代理類(lèi)
public class StaticProxyHello implements IHelloService {
private IHelloService helloService = new HelloService();
@Override
public String sayHello(String userName) {
/** 代理對(duì)象可以在此處包裝一下*/
System.out.println("代理對(duì)象包裝禮盒...");
return helloService.sayHello(userName);
}
}
// 測(cè)試靜態(tài)代理類(lèi)
public class MainStatic {
public static void main(String[] args) {
StaticProxyHello staticProxyHello = new StaticProxyHello();
staticProxyHello.sayHello("isole");
}
}
2. 動(dòng)態(tài)代理技術(shù)
代理類(lèi)在程序運(yùn)行時(shí)創(chuàng)建的代理方式被成為 動(dòng)態(tài)代理。在了解動(dòng)態(tài)代理之前, 我們先簡(jiǎn)回顧一下 JVM 的類(lèi)加載機(jī)制中的加載階段要做的三件事情
( 附 Java 中的類(lèi)加載器 )
通過(guò)一個(gè)類(lèi)的全名或其它途徑來(lái)獲取這個(gè)類(lèi)的二進(jìn)制字節(jié)流
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個(gè)代表這個(gè)類(lèi)的 Class 對(duì)象, 作為方法區(qū)中對(duì)這個(gè)類(lèi)訪問(wèn)的入口
而我們要說(shuō)的動(dòng)態(tài)代理,主要就發(fā)生在第一個(gè)階段, 這個(gè)階段類(lèi)的二進(jìn)制字節(jié)流的來(lái)源可以有很多, 比如 zip 包、網(wǎng)絡(luò)、運(yùn)行時(shí)計(jì)算生成、其它文件生成 (JSP)、數(shù)據(jù)庫(kù)獲取。其中運(yùn)行時(shí)計(jì)算生成就是我們所說(shuō)的動(dòng)態(tài)代理技術(shù),在 Proxy 類(lèi)中, 就是運(yùn)用了 ProxyGenerator.generateProxyClass 來(lái)為特定接口生成形式為 *$Proxy 的代理類(lèi)的二進(jìn)制字節(jié)流。所謂的動(dòng)態(tài)代理就是想辦法根據(jù)接口或者目標(biāo)對(duì)象計(jì)算出代理類(lèi)的字節(jié)碼然后加載進(jìn) JVM 中。實(shí)際計(jì)算的情況會(huì)很復(fù)雜,我們借助一些諸如 JDK 動(dòng)態(tài)代理實(shí)現(xiàn)、CGLIB 第三方庫(kù)來(lái)完成的
另一方面為了讓生成的代理類(lèi)與目標(biāo)對(duì)象 (就是委托類(lèi)) 保持一致, 我們有 2 種做法:通過(guò)接口的 JDK 動(dòng)態(tài)代理 和通過(guò)繼承類(lèi)的 CGLIB 動(dòng)態(tài)代理。(還有一個(gè)使用了 ASM 框架的 javassist 太復(fù)雜了,我還沒(méi)研究過(guò), 這里TODO下)
3. JDK 動(dòng)態(tài)代理
在 Java 的動(dòng)態(tài)代理中, 主要涉及 2 個(gè)類(lèi),java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler
我們需要一個(gè)實(shí)現(xiàn) InvocationHandler 接口的中間類(lèi), 這個(gè)接口只有一個(gè)方法 invoke 方法, 方法的每個(gè)參數(shù)的注釋如下代碼。
我們對(duì)處理類(lèi)中的所有方法的調(diào)用都會(huì)變成對(duì) invoke 方法的調(diào)用,這樣我們可以在 invoke 方法中添加統(tǒng)一的處理邏輯(也可以根據(jù) method 參數(shù)判斷是哪個(gè)方法)。中間類(lèi) (實(shí)現(xiàn)了 InvocationHandler 的類(lèi)) 有一個(gè)委托類(lèi)對(duì)象引用, 在 Invoke 方法中調(diào)用了委托類(lèi)對(duì)象的相應(yīng)方法,通過(guò)這種聚合的方式持有委托類(lèi)對(duì)象引用,把外部對(duì) invoke 的調(diào)用最終都轉(zhuǎn)為對(duì)委托類(lèi)對(duì)象的調(diào)用。
實(shí)際上,中間類(lèi)與委托類(lèi)構(gòu)成了靜態(tài)代理關(guān)系,在這個(gè)關(guān)系中,中間類(lèi)是代理類(lèi),委托類(lèi)是委托類(lèi)。然后代理類(lèi)與中間類(lèi)也構(gòu)成一個(gè)靜態(tài)代理關(guān)系,在這個(gè)關(guān)系中,中間類(lèi)是委托類(lèi),代理類(lèi)是代理類(lèi)。也就是說(shuō),動(dòng)態(tài)代理關(guān)系由兩組靜態(tài)代理關(guān)系組成,這就是動(dòng)態(tài)代理的原理。
public interface InvocationHandler {
/**
* 調(diào)用處理
* @param proxy 代理類(lèi)對(duì)象
* @param methon 標(biāo)識(shí)具體調(diào)用的是代理類(lèi)的哪個(gè)方法
* @param args 代理類(lèi)方法的參數(shù)
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
Demo 如下:
// 委托類(lèi)接口
public interface IHelloService {
/**
* 方法1
* @param userName
* @return
*/
String sayHello(String userName);
/**
* 方法2
* @param userName
* @return
*/
String sayByeBye(String userName);
}
// 委托類(lèi)
public class HelloService implements IHelloService {
@Override
public String sayHello(String userName) {
System.out.println(userName + " hello");
return userName + " hello";
}
@Override
public String sayByeBye(String userName) {
System.out.println(userName + " ByeBye");
return userName + " ByeBye";
}
}
// 中間類(lèi)
public class JavaProxyInvocationHandler implements InvocationHandler {
/**
* 中間類(lèi)持有委托類(lèi)對(duì)象的引用,這里會(huì)構(gòu)成一種靜態(tài)代理關(guān)系
*/
private Object obj ;
/**
* 有參構(gòu)造器,傳入委托類(lèi)的對(duì)象
* @param obj 委托類(lèi)的對(duì)象
*/
public JavaProxyInvocationHandler(Object obj){
this.obj = obj;
}
/**
* 動(dòng)態(tài)生成代理類(lèi)對(duì)象,Proxy.newProxyInstance
* @return 返回代理類(lèi)的實(shí)例
*/
public Object newProxyInstance() {
return Proxy.newProxyInstance(
//指定代理對(duì)象的類(lèi)加載器
obj.getClass().getClassLoader(),
//代理對(duì)象需要實(shí)現(xiàn)的接口,可以同時(shí)指定多個(gè)接口
obj.getClass().getInterfaces(),
//方法調(diào)用的實(shí)際處理者,代理對(duì)象的方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到這里
this);
}
/**
*
* @param proxy 代理對(duì)象
* @param method 代理方法
* @param args 方法的參數(shù)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke before");
Object result = method.invoke(obj, args);
System.out.println("invoke after");
return result;
}
}
// 測(cè)試動(dòng)態(tài)代理類(lèi)
public class MainJavaProxy {
public static void main(String[] args) {
JavaProxyInvocationHandler proxyInvocationHandler = new JavaProxyInvocationHandler(new HelloService());
IHelloService helloService = (IHelloService) proxyInvocationHandler.newProxyInstance();
helloService.sayByeBye("paopao");
helloService.sayHello("yupao");
}
}
在上面的測(cè)試動(dòng)態(tài)代理類(lèi)中, 我們調(diào)用 Proxy 類(lèi)的 newProxyInstance 方法來(lái)獲取一個(gè)代理類(lèi)實(shí)例。這個(gè)代理類(lèi)實(shí)現(xiàn)了我們指定的接口并且會(huì)把方法調(diào)用分發(fā)到指定的調(diào)用處理器。
首先通過(guò) newProxyInstance 方法獲取代理類(lèi)的實(shí)例, 之后就可以通過(guò)這個(gè)代理類(lèi)的實(shí)例調(diào)用代理類(lèi)的方法,對(duì)代理類(lèi)的方法調(diào)用都會(huì)調(diào)用中間類(lèi) (實(shí)現(xiàn)了 invocationHandle 的類(lèi)) 的 invoke 方法,在 invoke 方法中我們調(diào)用委托類(lèi)的對(duì)應(yīng)方法,然后加上自己的處理邏輯。
java 動(dòng)態(tài)代理最大的特點(diǎn)就是動(dòng)態(tài)生成的代理類(lèi)和委托類(lèi)實(shí)現(xiàn)同一個(gè)接口。java 動(dòng)態(tài)代理其實(shí)內(nèi)部是通過(guò)反射機(jī)制實(shí)現(xiàn)的,也就是已知的一個(gè)對(duì)象,在運(yùn)行的時(shí)候動(dòng)態(tài)調(diào)用它的方法,并且調(diào)用的時(shí)候還可以加一些自己的邏輯在里面。(附: Java 反射)
3.2 Proxy.newProxyInstance 源碼閱讀
上面說(shuō)過(guò), Proxy.newProxyInstance 通過(guò)反射機(jī)制用來(lái)動(dòng)態(tài)生成代理類(lèi)對(duì)象, 為接口創(chuàng)建一個(gè)代理類(lèi),這個(gè)代理類(lèi)實(shí)現(xiàn)這個(gè)接口。具體源碼如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 檢查空指針
Objects.requireNonNull(h);
// 用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象
final Class<?>[] intfs = interfaces.clone();
// 獲取系統(tǒng)的安全接口,不為空的話需要驗(yàn)證是否允許訪問(wèn)這種關(guān)系的代理訪問(wèn)
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 生成代理類(lèi) Class,通過(guò)類(lèi)加載器和接口
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 通過(guò)構(gòu)造器來(lái)創(chuàng)建實(shí)例
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//獲取所有的構(gòu)造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 構(gòu)造器不是public的話需要設(shè)置可以訪問(wèn)
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 返回創(chuàng)建的代理類(lèi)Class的實(shí)例對(duì)象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
4. CGLIB 動(dòng)態(tài)代理
JDK 動(dòng)態(tài)代理依賴(lài)接口實(shí)現(xiàn),而當(dāng)我們只有類(lèi)沒(méi)有接口的時(shí)候就需要使用另一種動(dòng)態(tài)代理技術(shù) CGLIB 動(dòng)態(tài)代理。首先 CGLIB 動(dòng)態(tài)代理是第三方框架實(shí)現(xiàn)的,在 maven 工程中我們需要引入 cglib 的包, 如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
CGLIB 代理是針對(duì)類(lèi)來(lái)實(shí)現(xiàn)代理的,原理是對(duì)指定的委托類(lèi)生成一個(gè)子類(lèi)并重寫(xiě)其中業(yè)務(wù)方法來(lái)實(shí)現(xiàn)代理。代理類(lèi)對(duì)象是由 Enhancer 類(lèi)創(chuàng)建的。CGLIB 創(chuàng)建動(dòng)態(tài)代理類(lèi)的模式是:
查找目標(biāo)類(lèi)上的所有非 final 的 public 類(lèi)型的方法 (final 的不能被重寫(xiě))
將這些方法的定義轉(zhuǎn)成字節(jié)碼
將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的 Class 對(duì)象然后通過(guò)反射獲得代理類(lèi)的實(shí)例對(duì)象
實(shí)現(xiàn) MethodInterceptor 接口, 用來(lái)處理對(duì)代理類(lèi)上所有方法的請(qǐng)求
// 委托類(lèi),是一個(gè)簡(jiǎn)單類(lèi)
public class CglibHelloClass {
/**
* 方法1
* @param userName
* @return
*/
public String sayHello(String userName){
System.out.println("目標(biāo)對(duì)象的方法執(zhí)行了");
return userName + " sayHello";
}
public String sayByeBye(String userName){
System.out.println("目標(biāo)對(duì)象的方法執(zhí)行了");
return userName + " sayByeBye";
}
}
/**
* CglibInterceptor 用于對(duì)方法調(diào)用攔截以及回調(diào)
*
*/
public class CglibInterceptor implements MethodInterceptor {
/**
* CGLIB 增強(qiáng)類(lèi)對(duì)象,代理類(lèi)對(duì)象是由 Enhancer 類(lèi)創(chuàng)建的,
* Enhancer 是 CGLIB 的字節(jié)碼增強(qiáng)器,可以很方便的對(duì)類(lèi)進(jìn)行拓展
*/
private Enhancer enhancer = new Enhancer();
/**
*
* @param obj 被代理的對(duì)象
* @param method 代理的方法
* @param args 方法的參數(shù)
* @param proxy CGLIB方法代理對(duì)象
* @return cglib生成用來(lái)代替Method對(duì)象的一個(gè)對(duì)象,使用MethodProxy比調(diào)用JDK自身的Method直接執(zhí)行方法效率會(huì)有提升
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("方法調(diào)用之前");
Object o = proxy.invokeSuper(obj, args);
System.out.println("方法調(diào)用之后");
return o;
}
/**
* 使用動(dòng)態(tài)代理創(chuàng)建一個(gè)代理對(duì)象
* @param c
* @return
*/
public Object newProxyInstance(Class<?> c) {
/**
* 設(shè)置產(chǎn)生的代理對(duì)象的父類(lèi),增強(qiáng)類(lèi)型
*/
enhancer.setSuperclass(c);
/**
* 定義代理邏輯對(duì)象為當(dāng)前對(duì)象,要求當(dāng)前對(duì)象實(shí)現(xiàn) MethodInterceptor 接口
*/
enhancer.setCallback(this);
/**
* 使用默認(rèn)無(wú)參數(shù)的構(gòu)造函數(shù)創(chuàng)建目標(biāo)對(duì)象,這是一個(gè)前提,被代理的類(lèi)要提供無(wú)參構(gòu)造方法
*/
return enhancer.create();
}
}
//測(cè)試類(lèi)
public class MainCglibProxy {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
CglibHelloClass cglibHelloClass = (CglibHelloClass) cglibProxy.newProxyInstance(CglibHelloClass.class);
cglibHelloClass.sayHello("isole");
cglibHelloClass.sayByeBye("sss");
}
}
對(duì)于需要被代理的類(lèi),它只是動(dòng)態(tài)生成一個(gè)子類(lèi)以覆蓋非 final 的方法,同時(shí)綁定鉤子回調(diào)自定義的攔截器。值得說(shuō)的是,它比 JDK 動(dòng)態(tài)代理還要快。值得注意的是,我們傳入目標(biāo)類(lèi)作為代理的父類(lèi)。不同于 JDK 動(dòng)態(tài)代理,我們不能使用目標(biāo)對(duì)象來(lái)創(chuàng)建代理。目標(biāo)對(duì)象只能被 CGLIB 創(chuàng)建。在例子中,默認(rèn)的無(wú)參構(gòu)造方法被使用來(lái)創(chuàng)建目標(biāo)對(duì)象。
總結(jié)
靜態(tài)代理比較容易理解, 需要被代理的類(lèi)和代理類(lèi)實(shí)現(xiàn)自同一個(gè)接口, 然后在代理類(lèi)中調(diào)用真正實(shí)現(xiàn)類(lèi), 并且靜態(tài)代理的關(guān)系在編譯期間就已經(jīng)確定了。而動(dòng)態(tài)代理的關(guān)系是在運(yùn)行期間確定的。靜態(tài)代理實(shí)現(xiàn)簡(jiǎn)單,適合于代理類(lèi)較少且確定的情況,而動(dòng)態(tài)代理則給我們提供了更大的靈活性。
JDK 動(dòng)態(tài)代理所用到的代理類(lèi)在程序調(diào)用到代理類(lèi)對(duì)象時(shí)才由 JVM 真正創(chuàng)建,JVM 根據(jù)傳進(jìn)來(lái)的 業(yè)務(wù)實(shí)現(xiàn)類(lèi)對(duì)象 以及 方法名 ,動(dòng)態(tài)地創(chuàng)建了一個(gè)代理類(lèi)的 class 文件并被字節(jié)碼引擎執(zhí)行,然后通過(guò)該代理類(lèi)對(duì)象進(jìn)行方法調(diào)用。我們需要做的,只需指定代理類(lèi)的預(yù)處理、調(diào)用后操作即可。
靜態(tài)代理和動(dòng)態(tài)代理都是基于接口實(shí)現(xiàn)的, 而對(duì)于那些沒(méi)有提供接口只是提供了實(shí)現(xiàn)類(lèi)的而言, 就只能選擇 CGLIB 動(dòng)態(tài)代理了
JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理的區(qū)別
JDK 動(dòng)態(tài)代理基于 Java 反射機(jī)制實(shí)現(xiàn), 必須要實(shí)現(xiàn)了接口的業(yè)務(wù)類(lèi)才能用這種方法生成代理對(duì)象。
CGLIB 動(dòng)態(tài)代理基于 ASM 框架通過(guò)生成業(yè)務(wù)類(lèi)的子類(lèi)來(lái)實(shí)現(xiàn)。
JDK 動(dòng)態(tài)代理的優(yōu)勢(shì)是最小化依賴(lài)關(guān)系,減少依賴(lài)意味著簡(jiǎn)化開(kāi)發(fā)和維護(hù)并且有 JDK 自身支持。還可以平滑進(jìn)行 JDK 版本升級(jí),代碼實(shí)現(xiàn)簡(jiǎn)單。基于 CGLIB 框架的優(yōu)勢(shì)是無(wú)須實(shí)現(xiàn)接口,達(dá)到代理類(lèi)無(wú)侵入,我們只需操作我們關(guān)系的類(lèi),不必為其它相關(guān)類(lèi)增加工作量,性能比較高。
描述代理的幾種實(shí)現(xiàn)方式? 分別說(shuō)出優(yōu)缺點(diǎn)?
代理可以分為 "靜態(tài)代理" 和 "動(dòng)態(tài)代理",動(dòng)態(tài)代理又分為 "JDK 動(dòng)態(tài)代理" 和 "CGLIB 動(dòng)態(tài)代理" 實(shí)現(xiàn)。
靜態(tài)代理:代理對(duì)象和實(shí)際對(duì)象都繼承了同一個(gè)接口,在代理對(duì)象中指向的是實(shí)際對(duì)象的實(shí)例,這樣對(duì)外暴露的是代理對(duì)象而真正調(diào)用的是 Real Object.
優(yōu)點(diǎn):可以很好的保護(hù)實(shí)際對(duì)象的業(yè)務(wù)邏輯對(duì)外暴露,從而提高安全性。*
缺點(diǎn):不同的接口要有不同的代理類(lèi)實(shí)現(xiàn),會(huì)很冗余
JDK 動(dòng)態(tài)代理:
為了解決靜態(tài)代理中,生成大量的代理類(lèi)造成的冗余;
JDK 動(dòng)態(tài)代理只需要實(shí)現(xiàn) InvocationHandler 接口,重寫(xiě) invoke 方法便可以完成代理的實(shí)現(xiàn),
jdk 的代理是利用反射生成代理類(lèi) Proxyxx.class 代理類(lèi)字節(jié)碼,并生成對(duì)象
jdk 動(dòng)態(tài)代理之所以只能代理接口是因?yàn)榇眍?lèi)本身已經(jīng) extends 了 Proxy,而 java 是不允許多重繼承的,但是允許實(shí)現(xiàn)多個(gè)接口
優(yōu)點(diǎn):解決了靜態(tài)代理中冗余的代理實(shí)現(xiàn)類(lèi)問(wèn)題。
缺點(diǎn):JDK 動(dòng)態(tài)代理是基于接口設(shè)計(jì)實(shí)現(xiàn)的,如果沒(méi)有接口,會(huì)拋異常。
CGLIB 代理:
由于 JDK 動(dòng)態(tài)代理限制了只能基于接口設(shè)計(jì),而對(duì)于沒(méi)有接口的情況,JDK 方式解決不了;
CGLib 采用了非常底層的字節(jié)碼技術(shù),其原理是通過(guò)字節(jié)碼技術(shù)為一個(gè)類(lèi)創(chuàng)建子類(lèi),并在子類(lèi)中采用方法攔截的技術(shù)攔截所有父類(lèi)方法的調(diào)用,順勢(shì)織入橫切邏輯,來(lái)完成動(dòng)態(tài)代理的實(shí)現(xiàn)。
實(shí)現(xiàn)方式實(shí)現(xiàn) MethodInterceptor 接口,重寫(xiě) intercept 方法,通過(guò) Enhancer 類(lèi)的回調(diào)方法來(lái)實(shí)現(xiàn)。
但是 CGLib 在創(chuàng)建代理對(duì)象時(shí)所花費(fèi)的時(shí)間卻比 JDK 多得多,所以對(duì)于單例的對(duì)象,因?yàn)闊o(wú)需頻繁創(chuàng)建對(duì)象,用 CGLib 合適,反之,使用 JDK 方式要更為合適一些。
同時(shí),由于 CGLib 由于是采用動(dòng)態(tài)創(chuàng)建子類(lèi)的方法,對(duì)于 final 方法,無(wú)法進(jìn)行代理。
優(yōu)點(diǎn):沒(méi)有接口也能實(shí)現(xiàn)動(dòng)態(tài)代理,而且采用字節(jié)碼增強(qiáng)技術(shù),性能也不錯(cuò)。
缺點(diǎn):技術(shù)實(shí)現(xiàn)相對(duì)難理解些。