Java中的動態(tài)代理以及在框架中的應(yīng)用
一、靜態(tài)代理&動態(tài)代理
1. 靜態(tài)代理
我們先假設(shè)現(xiàn)在有怎么一個需求,要求你在不改動原有代碼的情況下在所有類的方法前后打印日志。我們很容易想到靜態(tài)代理,具體做法如下:
為現(xiàn)有的所有類都編寫一個對應(yīng)的代理類,并且還需要讓代理類與原有類實現(xiàn)相同的接口;

在創(chuàng)建代理對象時,通過構(gòu)造器傳入一個目標對象,然后在代理對象的方法內(nèi)部調(diào)用目標對象同名方法,并且在調(diào)用方法的前后打印日志。換而言之,代理對象=增強代碼+原對象。有了代理對象后,我們在客戶端就不再使用源對象,而是使用代理對象了。

靜態(tài)代理的缺陷:從上面的靜態(tài)代理實現(xiàn)方式上,我們很容易發(fā)現(xiàn)靜態(tài)代理的缺陷。假設(shè)我們現(xiàn)在有很多類,那么就需要手動去實現(xiàn)很多個代理類,這樣并不現(xiàn)實,那么我們應(yīng)該考慮將這個任務(wù)交由計算機完成,接下來我們就來討論動態(tài)代理的實現(xiàn)。
2. 動態(tài)代理
在講解動態(tài)代理實現(xiàn)之前,我們先來回顧一下對象的創(chuàng)建過程。

從上面我們可以看出,創(chuàng)建一個對象并不僅僅是寫一行 new 這么簡單,底層還是隱含了許多信息的。不過我們至少可以了解到,一個對象的生成至少經(jīng)歷了以下這幾個階段:

那么到這里我們應(yīng)該有大概的思路了。我們或許可以不寫代理類,然后通過攔截器得到我們要代理的Class對象,之后再根據(jù)它加上反射機制創(chuàng)建代理實例(JDK動態(tài)代理的實現(xiàn));或者讓代理對象的class文件加載進來,然后通過修改其字節(jié)碼來生成一個子類從而完成我們要做到的效果(CGLIB動態(tài)代理的實現(xiàn))。
二、動態(tài)代理的實現(xiàn)
1. JDK動態(tài)代理
JDK動態(tài)代理的實現(xiàn)是利用攔截器(這個攔截器需要實現(xiàn)InvocationHandler接口),以及反射機制最終實現(xiàn)一個代理接口的匿名類。
所以在JDK中,提供了java.lang.reflect.InvocationHandler接口,此外還有一個比較重要的類java.lang.reflect.Proxy類。利用這兩個類之間的相互配合完成動態(tài)代理的配置。那接下來我們來看代碼實現(xiàn):
首先定義一個接口和一個實現(xiàn)類:
public interface UserService {
void addUser(String username, String password);
}
---------------------------------------------------
public class UserServiceImpl implements UserService{
@Override
public void addUser(String username, String password) {
System.out.println("UserService.addUser(String, String)方法被調(diào)用");
}
}
接下來我們就需要去定義一個攔截器去實現(xiàn)InvocationHandler以實現(xiàn)我們的業(yè)務(wù)邏輯,代碼如下:
public class JDKProxyHandler implements InvocationHandler {
// 被代理的原對象
private Object targetObject;
/**
* 傳入一個目標對象,生成一個代理對象并返回
*
* @param targetObject 原對象(目標對象)
* @return 代理對象
*/
public Object newProxy(Object targetObject) {
this.targetObject = targetObject;
// 返回一個代理對象
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 調(diào)用日志打印
log("權(quán)限校驗中...");
// 聲明方法的返回值
Object ret = null;
// 調(diào)用invoke方法,所返回的值賦值給ret
ret = method.invoke(targetObject, args);
return ret;
}
/**
* 模擬日志打印
*
* @param message 信息
*/
private void log(String message) {
System.out.println("【" + new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(new Date()) + "】"
+ message);
}
}
接下來我們就可以在客戶端進行測試了:
public class Client {
public static void main(String[] args) {
JDKProxyHandler jdkProxyHandler = new JDKProxyHandler();
UserService userService = (UserService) jdkProxyHandler.newProxy(new UserServiceImpl());
userService.addUser("jo", "8820");
}
}
最終結(jié)果如下:

2. CGLIB動態(tài)代理
CGLIB采用了非常底層的字節(jié)碼技術(shù),其原理是通過目標類(原來的類)的字節(jié)碼創(chuàng)建一個新的子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢植入增強代碼,所以代理類會將目標類作為自己的父類并為其中每個方法創(chuàng)建兩個方法:
一個是于目標方法簽名相同的類,它在方法中通過調(diào)用super來調(diào)用目標類中的方法;
以及另外一個Callback回調(diào)方法,它會判斷這個方法是否綁定了攔截器(即實現(xiàn)了MethodInterceptor接口的對象),若存在則將調(diào)用intercept方法對目標方法進行代理,也就是在前后加上一些增強邏輯。intercept中就會調(diào)用上面介紹的簽名相同的方法。
簡而言之,就是CGLIB底層使用了ASM字節(jié)碼處理框架,來修改字節(jié)碼并生成新的類。那么接下來我們就用CGLIB來實現(xiàn)動態(tài)代理。
首先接口和實現(xiàn)接口的業(yè)務(wù)類還是復(fù)用上面的代碼,不過我們還需要引入cglib的依賴,如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
實現(xiàn)一個代理類處理類:
public class CGLIBProxyHandler implements MethodInterceptor {
// CGLIB 需要代理的目標對象
private Object targetObject;
/**
* 創(chuàng)建一個代理對象
*
* @param targetObject 目標類
* @return 代理對象
*/
public Object crateProxyObject(Object targetObject) {
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetObject.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object ret = null;
// 過濾方法
if ("addUser".equals(method.getName())) {
// 打印日志
log("權(quán)限檢驗中...");
}
ret = method.invoke(targetObject, objects);
return ret;
}
/**
* 模擬日志打印
*
* @param message 信息
*/
private void log(String message) {
System.out.println("【" + new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(new Date()) + "】" + message);
}
}
客戶端調(diào)用:
public class Client {
public static void main(String[] args) {
CGLIBProxyHandler cglibProxyHandler = new CGLIBProxyHandler();
UserService userService = (UserService) cglibProxyHandler.crateProxyObject(new UserServiceImpl());
userService.addUser("jo", "8820");
}
}
最終執(zhí)行結(jié)果如下:

以上就是JDK以及CGLIB兩種實現(xiàn)動態(tài)代理方式的演示了。
三、CGLIB和JDK兩種動態(tài)代理的應(yīng)用與區(qū)別
1. 兩者間區(qū)別
其中最主要的區(qū)別莫過于JDK是針對接口類生成代理,而不是針對類。而CGLIB則是針對類實現(xiàn)的動態(tài)代理。除此之外,上面我們提到CGLIB實現(xiàn)是通過目標類的字節(jié)碼生成一個子類,所以我們可以很明顯知道,這種方式不能適用與被final修飾的類。
2. Spring中的動態(tài)代理
2.1 Spring何時使用JDK/CGLIB實現(xiàn)AOP
如果目標對象實現(xiàn)了接口,默認情況下Spring會采用JDK的動態(tài)代理實現(xiàn)AOP(不過可以通過配置強制使用CGLIB實現(xiàn));
如果目標對象沒有實現(xiàn)接口,那么Spring就只會采用CGLIB庫來完成動態(tài)代理。
2.2 如何強制使用CGLIB
添加CGLIB庫的引用(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar);
在Spring配置文件中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>。

騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)
面試:史上最全多線程面試題 !
最新阿里內(nèi)推Java后端面試題
JVM難學(xué)?那是因為你沒認真看完這篇文章

關(guān)注作者微信公眾號 —《JAVA爛豬皮》
了解更多java后端架構(gòu)知識以及最新面試寶典


看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力
作者:周二鴨
出處:https://www.cnblogs.com/jojop/p/14117982.html
