1、什么是動(dòng)態(tài)代理
可能很多小伙伴首次接觸動(dòng)態(tài)代理這個(gè)名詞的時(shí)候,或者是在面試過(guò)程中被問(wèn)到動(dòng)態(tài)代理的時(shí)候,不能很好的描述出來(lái),動(dòng)態(tài)代理到底是個(gè)什么高大上的技術(shù)。不方,其實(shí)動(dòng)態(tài)代理的使用非常廣泛,例如我們平常使用的 Spring
中的 @Transactional
注解,其依賴(lài)于 AOP
,而 AOP
的底層實(shí)現(xiàn)便是動(dòng)態(tài)代理,看到這里,是不是更有興趣去了解動(dòng)態(tài)代理了呢?
動(dòng)態(tài)代理:可以分解為"動(dòng)態(tài)"+“代理”。
- 代理:"代理"一詞,在我們的生活中也是隨處可見(jiàn)的,例如房屋中介,其是對(duì)房主的一種代理,房主需要出租其房屋,但是可能沒(méi)時(shí)間去接待租客,給租客介紹房屋信息,帶領(lǐng)租客看房,但是房屋中介可以為租客提供這些服務(wù),所以,代理其是對(duì)被代理對(duì)象的一個(gè)功能增強(qiáng)
- 動(dòng)態(tài):"動(dòng)態(tài)"通常與"靜態(tài)"相比較,"靜態(tài)"描述的是事物是固定存在的,"動(dòng)態(tài)"則描述的是事物是隨著需求而動(dòng)態(tài)生成的。
所以,靜態(tài)代理存在一定的局限性,不能很好的滿(mǎn)足需求的千變?nèi)f化,動(dòng)態(tài)代理的出現(xiàn),就是為了解決這些局限性。
我們先來(lái)看看靜態(tài)代理。
2.、靜態(tài)代理
在開(kāi)發(fā)中,通常需要為方法添加日志打印,能夠記錄程序的執(zhí)行過(guò)程,以便后續(xù)出現(xiàn)異常問(wèn)題的時(shí)候,能更好的排查定位。
假設(shè)我們現(xiàn)在已經(jīng)完成了系統(tǒng)用戶(hù)的增加、刪除、修改等功能,這些功能在類(lèi) UserServiceImpl
中已經(jīng)實(shí)現(xiàn)。
代碼示例:
public class UserServiceImpl {
public void add() {
System.out.println("添加用戶(hù)");
}
public void update() {
System.out.println("修改用戶(hù)");
}
public void delete() {
System.out.println("刪除用戶(hù)");
}
}
現(xiàn)在,我們需要在UserServiceImpl類(lèi)中的方法添加日志功能,那么怎么才能更好地去實(shí)現(xiàn)這個(gè)需求呢?
1)直接在目標(biāo)方法前后添加日志代碼
代碼示例:
public class UserServiceImpl {
public void add() {
System.out.println("====== add方法開(kāi)始 ======");
System.out.println("添加用戶(hù)");
System.out.println("====== add方法結(jié)束 ======");
}
?
public void update() {
System.out.println("====== update方法開(kāi)始 ======");
System.out.println("修改用戶(hù)");
System.out.println("====== update方法結(jié)束 ======");
}
?
public void delete() {
System.out.println("====== delete方法開(kāi)始 ======");
System.out.println("刪除用戶(hù)");
System.out.println("====== delete方法結(jié)束 ======");
}
}
觀察上述代碼,這種方式的缺點(diǎn)在于:
2)靜態(tài)代理方式實(shí)現(xiàn)
靜態(tài)代理需要我們將目標(biāo)類(lèi)的方法抽取到接口中,代理類(lèi)和目標(biāo)類(lèi)實(shí)現(xiàn)同一個(gè)接口,既然要實(shí)現(xiàn)代理,代理類(lèi)自然需要在其內(nèi)部維護(hù)目標(biāo)對(duì)象的引用,并通過(guò)構(gòu)造函數(shù)為其賦值,然后在代理類(lèi)的方法中調(diào)用目標(biāo)對(duì)象的同名方法,并在調(diào)用前后完成功能的增強(qiáng)。
實(shí)現(xiàn)步驟:
- 抽取UserService接口
- 創(chuàng)建目標(biāo)類(lèi)UserServiceImpl實(shí)現(xiàn)UserService接口
- 創(chuàng)建代理類(lèi)UserServiceProxy實(shí)現(xiàn)UserService接口
- 代理類(lèi)中完成功能的增強(qiáng)
代碼實(shí)現(xiàn):
// 目標(biāo)接口
public interface UserService {
void add();
void update();
void delete();
}
// 目標(biāo)類(lèi)
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加用戶(hù)");
}
@Override
public void update() {
System.out.println("修改用戶(hù)");
}
@Override
public void delete() {
System.out.println("刪除用戶(hù)");
}
}
// 代理類(lèi)
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void add() {
System.out.println("====== add方法開(kāi)始 ======");
userService.add();
System.out.println("====== add方法結(jié)束 ======");
}
@Override
public void update() {
System.out.println("====== update方法開(kāi)始 ======");
userService.update();
System.out.println("====== update方法結(jié)束 ======");
}
@Override
public void delete() {
System.out.println("====== delete方法開(kāi)始 ======");
userService.delete();
System.out.println("====== delete方法結(jié)束 ======");
}
}
觀察上述代碼,靜態(tài)代理遵循開(kāi)閉原則,在不修改目標(biāo)類(lèi)的前提下,完成了功能的增強(qiáng),但是依然存在大量重復(fù)的代碼,且一個(gè)代理類(lèi)只能代理一個(gè)目標(biāo)類(lèi),如果有n個(gè)目標(biāo)類(lèi)需要被代理,就需要同比增加n個(gè)代理類(lèi)。
那么,有沒(méi)有辦法可以使得我們不需要去定義這么多的代理類(lèi),就可以實(shí)現(xiàn)對(duì)目標(biāo)類(lèi)功能的增強(qiáng)?答案是有的:動(dòng)態(tài)代理。
3、JDK動(dòng)態(tài)代理
前面,我們提到靜態(tài)代理的實(shí)現(xiàn)方式:代理類(lèi)和目標(biāo)類(lèi)都實(shí)現(xiàn)同一個(gè)接口,在代理類(lèi)中維護(hù)目標(biāo)類(lèi)對(duì)象,并完成對(duì)目標(biāo)類(lèi)對(duì)象方法的增強(qiáng),這種方式雖然遵循開(kāi)閉原則,但是代理類(lèi)和目標(biāo)類(lèi)至少是"一對(duì)一"的綁定關(guān)系,如果需要被代理的目標(biāo)類(lèi)個(gè)數(shù)越多,代理類(lèi)就會(huì)越多,會(huì)產(chǎn)生大量重復(fù)的代碼,也不利于后期的維護(hù)。
從靜態(tài)代理中,我們知道代理類(lèi)也是接口的一個(gè)實(shí)現(xiàn)類(lèi),代理對(duì)象的類(lèi)型也是屬于接口類(lèi)型,我們來(lái)驗(yàn)證一下。
public class Test {
public static void main(String[] args) {
UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl());
System.out.println(userServiceProxy instanceof UserService);
}
}
// 打印結(jié)果:true
那么,能不能動(dòng)態(tài)生成這些代理對(duì)象呢?我們知道類(lèi)是構(gòu)造對(duì)象的模板,代理類(lèi)都還不存在,怎么去構(gòu)造代理對(duì)象呢?
除了不存在代理類(lèi),還剩下 UserService
接口和 UserServiceImpl
目標(biāo)類(lèi),JDK動(dòng)態(tài)代理的目的就是通過(guò)接口來(lái)生成代理類(lèi)以及代理類(lèi)的對(duì)象,我們知道接口是不能直接通過(guò)new關(guān)鍵字創(chuàng)建對(duì)象的。
那么JDK動(dòng)態(tài)代理是怎么創(chuàng)建出代理類(lèi)以及代理類(lèi)對(duì)象的呢?
我們先來(lái)看看通過(guò) new
關(guān)鍵字創(chuàng)建對(duì)象的過(guò)程。
UserServiceImpl userService = new UserServiceImpl();
/*
創(chuàng)建對(duì)象的過(guò)程:
1.執(zhí)行new指令,如果類(lèi)未加載,先執(zhí)行類(lèi)加載過(guò)程。
1.加載:JVM通過(guò)ClassLoader將UserServiceImpl.class文件加載到方法區(qū)(Method Area),在堆內(nèi)存中創(chuàng)建代表該類(lèi)的Class對(duì)象。
2.驗(yàn)證
3.準(zhǔn)備:為靜態(tài)變量分配內(nèi)存并設(shè)置類(lèi)型初始值。
4.解析
5.初始化:為靜態(tài)變量賦值、執(zhí)行靜態(tài)代碼塊
2.為對(duì)象分配內(nèi)存,將對(duì)象的實(shí)例字段初始化類(lèi)型零值。
3.執(zhí)行構(gòu)造方法,對(duì)對(duì)象進(jìn)行初始化
*/
追蹤上述過(guò)程,我們得知?jiǎng)?chuàng)建對(duì)象,需要先得到該類(lèi)的Class對(duì)象,通過(guò)Class對(duì)象去創(chuàng)建實(shí)例對(duì)象。為了驗(yàn)證這一點(diǎn),我們不妨來(lái)看看通過(guò)反射的方式創(chuàng)建對(duì)象的過(guò)程。
public class Test {
@SneakyThrows
public static void main(String[] args) {
// 獲取Class對(duì)象
Class<UserServiceImpl> userServiceClass = UserServiceImpl.class;
// 獲取構(gòu)造器
Constructor<?>[] constructors = userServiceClass.getConstructors();
for (Constructor<?> constructor : constructors) {
// 通過(guò)構(gòu)造器創(chuàng)建實(shí)例對(duì)象
System.out.println(constructor.newInstance());
}
}
}
現(xiàn)在,問(wèn)題回歸到接口不能直接new,也沒(méi)有構(gòu)造方法,并且不存在代理類(lèi)的class文件,怎么獲得Class對(duì)象了。
動(dòng)態(tài)代理關(guān)鍵類(lèi)
我們先來(lái)看看JDK動(dòng)態(tài)代理的實(shí)戰(zhàn)代碼:
- 需要自定義個(gè)
CustomInvocationHandler
實(shí)現(xiàn)InvocationHandler
接口。 - 利用
Proxy.newProxyInstance
構(gòu)建實(shí)例對(duì)象。
// UserService接口
public interface UserService {
void add();
void update();
void delete();
}
// 目標(biāo)類(lèi)
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加用戶(hù)");
}
@Override
public void update() {
System.out.println("修改用戶(hù)");
}
@Override
public void delete() {
System.out.println("刪除用戶(hù)");
}
}
// CustomInvocationHandler
public class CustomInvocationHandler implements InvocationHandler {
// 目標(biāo)對(duì)象
private Object target;
public CustomInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====== 方法開(kāi)始 ======");
Object result = method.invoke(target, args);
System.out.println("====== 方法結(jié)束 ======");
return result;
}
}
public class Test {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
// 關(guān)鍵代碼
UserService service = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new CustomInvocationHandler(userService));
service.add();
}
}
從測(cè)試代碼可以看出,Proxy類(lèi)是關(guān)鍵。我們來(lái)看看Proxy為我們提供的方法:
Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
雖然被標(biāo)注為過(guò)時(shí)方法,但是從名字上可以得知,其目的是為了獲得代理類(lèi)的Class對(duì)象。話不多說(shuō),我們來(lái)調(diào)用一下。
public class Test {
public static void main(String[] args) {
Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());
System.out.println(proxyClass.getName());
for (Method method : proxyClass.getDeclaredMethods()) {
System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");
}
System.out.println(Arrays.toString(proxyClass.getConstructors()));
}
}
可以看到:
- 獲得的Class對(duì)象的名稱(chēng)為
$Proxy0
。 - 定義了我們需要的
add();update();delete()
方法。 - 定義了一個(gè)有參構(gòu)造方法
$Proxy0(InvocationHandler handler)
。
雖然沒(méi)有無(wú)參構(gòu)造方法,我們還是得嘗試一下調(diào)用一下這個(gè)有參的構(gòu)造方法,需要我們傳入一個(gè) java.lang.reflect.InvocationHandler
對(duì)象
public class Test {
@SneakyThrows
public static void main(String[] args) {
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());
// 獲取$Proxy0(InvocationHandler handler)構(gòu)造方法
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
UserService userService = (UserService) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass());
System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");
return null;
}
});
userService.add();
}
}
看的出來(lái),**當(dāng)我們獲得代理對(duì)象之后,通過(guò)代理對(duì)象來(lái)調(diào)用接口方法,都會(huì)回調(diào)構(gòu)造時(shí)傳進(jìn)來(lái)的 InvocationHandler
對(duì)象的 invoke(Object proxy, Method method, Object[] args)
方法,**該方法有3個(gè)參數(shù):
- Object proxy:代表的是代理對(duì)象本身。
- Method method:代表的是被調(diào)用的方法的Method對(duì)象。
- Object[] args:代表的是被調(diào)用方法的參數(shù)。
可以猜測(cè),JDK動(dòng)態(tài)代理生成的代理類(lèi)中,維護(hù)了InvocationHandler類(lèi)的對(duì)象變量,并且在實(shí)現(xiàn)接口方法時(shí),通過(guò)InvocationHandler對(duì)象調(diào)用了 invoke(Object proxy, Method method, Object[] args)
方法。
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
不知道大家沒(méi)有看到這行代碼哈,當(dāng)添加了這行代碼之后,可以將在項(xiàng)目目錄下保存動(dòng)態(tài)創(chuàng)建的class文件, com/sun/proxy/$Proxy0.class
。
可以看到生成的代理類(lèi) $Proxy0
繼承自 Proxy
類(lèi),并實(shí)現(xiàn)了 UserService
接口,并且在 add()
方法中通過(guò)其父類(lèi) Proxy
中維護(hù)的 InvocationHandler
對(duì)象調(diào)用 invoke()
方法,這也就成功的解釋了前面調(diào)用 userService.add()
方法,會(huì)回調(diào)到invoke()方法。
這時(shí)候我們?cè)侔汛a改造一下,如下:
public class CustomInvocationHandler implements InvocationHandler {
// 目標(biāo)對(duì)象
private Object target;
public CustomInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====== 方法開(kāi)始 ======");
Object result = method.invoke(target, args);
System.out.println("====== 方法結(jié)束 ======");
return result;
}
}
public class Test {
@SneakyThrows
public static void main(String[] args) {
UserServiceImpl target = new UserServiceImpl();
Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
UserService userService = (UserService) constructor.newInstance(new CustomInvocationHandler(target));
userService.add();
}
}
這樣就完成了對(duì)目標(biāo)對(duì)象功能的增強(qiáng),前面我們提到過(guò) Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
已經(jīng)被標(biāo)注為過(guò)時(shí),推薦我們使用 Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法。
動(dòng)態(tài)代理設(shè)計(jì)思想
好的,到這里,我們來(lái)總結(jié)一下JDK動(dòng)態(tài)的設(shè)計(jì)思想:
使用 JDK動(dòng)態(tài)代理
,使得我們免去編寫(xiě)代理類(lèi),只需要將增強(qiáng)功能編寫(xiě)在 InvocationHandler
的 invoke
方法中。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-830977.html
4、CGLib動(dòng)態(tài)代理
CGLib代理的目標(biāo)對(duì)象不需要事先任何接口,它是通過(guò)動(dòng)態(tài)集成目標(biāo)對(duì)象實(shí)現(xiàn)動(dòng)態(tài)代理的。CGLib代理執(zhí)行代理方法的效率之所以比JDK高,是因?yàn)镃GLib采用了FastClass機(jī)制:為代理類(lèi)和被代理類(lèi)各生成一個(gè)類(lèi),這個(gè)類(lèi)會(huì)為代理類(lèi)或被代理類(lèi)的方法分配一個(gè)index(int類(lèi)型);這個(gè)index當(dāng)作一個(gè)入?yún)?,F(xiàn)astClass 就可以直接定位要調(diào)用的方法并直接進(jìn)行調(diào)用,省去了反射調(diào)用,所以調(diào)用效率比JDK代理通過(guò)反射調(diào)用高。FastClass并不是跟代理類(lèi)一起生成的,而是在第一次執(zhí)行MethodProxy的invoke()或invokeSuper()方法時(shí)產(chǎn)生并放在緩存中的。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-830977.html
5、CGLib和JDK動(dòng)態(tài)代理對(duì)比
- JDK動(dòng)態(tài)代理實(shí)現(xiàn)了被代理對(duì)象的接口,CGLib代理繼承了被代理對(duì)象。
- JDK動(dòng)態(tài)代理和CGLib代理在運(yùn)行期生成字節(jié)碼,JDK動(dòng)態(tài)代理直接寫(xiě)Class字節(jié)碼,CGLib代理使用ASM框架(字節(jié)碼操控框架)寫(xiě)Class字節(jié)碼,CGLib代理實(shí)現(xiàn)更復(fù)雜,生成代理類(lèi)比JDK動(dòng)態(tài)代理效率低。
- JDK動(dòng)態(tài)代理調(diào)用代理方法是通過(guò)反射機(jī)制調(diào)用的,CGLib代理是通過(guò)FastClass機(jī)制直接調(diào)用方法的,CGLib代理的執(zhí)行效率更高
6、Spring中的代理選擇原則
- 當(dāng)Bean有實(shí)現(xiàn)接口時(shí),Spring就會(huì)用JDK動(dòng)態(tài)代理
- 當(dāng)Bean沒(méi)有實(shí)現(xiàn)接口時(shí),Spring會(huì)選擇CGLib代理
- Spring可以通過(guò)配置強(qiáng)制使用CGLib代理,只需要在配置中加入<aop:aspectj-autoproxy roxy-target-clas=“true”>
到了這里,關(guān)于設(shè)計(jì)模式二:代理模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!