在開發(fā)過程中掌腰,我們常需要在不更改原代碼的前提下實(shí)現(xiàn)業(yè)務(wù)的擴(kuò)展(也就是我們常說的OCP原則)播瞳,為此交汤,就需要用到代理模式來進(jìn)行開發(fā)少梁。
常用的代理模式有兩種,靜態(tài)代理和動(dòng)態(tài)代理炎码,而常用的動(dòng)態(tài)代理技術(shù)又包括JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理郑叠。
一、靜態(tài)代理
我們先來編寫一個(gè)業(yè)務(wù)接口:
package proxy.part01;
/**
- 核心業(yè)務(wù)接口
/
public interface HelloService {
void sayHello(String msg);
}
編寫實(shí)現(xiàn)類:
package proxy.part01;
/* - 核心業(yè)務(wù)的實(shí)現(xiàn)類
/
public class HelloServiceImpl implements HelloService{
//核心業(yè)務(wù)
public void sayHello(String msg) {
System.out.println("hello "+msg);
};
}
現(xiàn)在,我們要在不修改HelloServiceImpl的前提下添加新的業(yè)務(wù)排作,例如牵啦,要在核心業(yè)務(wù)的前后輸出當(dāng)前時(shí)間。很容易想到妄痪,我們可以再寫一個(gè)新的實(shí)現(xiàn)類哈雏,這個(gè)新實(shí)現(xiàn)類也實(shí)現(xiàn)HelloService接口,并且實(shí)現(xiàn)sayHello方法衫生,在新的實(shí)現(xiàn)方法中添加新的業(yè)務(wù)裳瘪,并且調(diào)用HelloServiceImpl類的sayHello方法,這個(gè)新的實(shí)現(xiàn)類就是我們說的“代理類(代理對(duì)象)”障簿。
package proxy.part01;
/* - 代理對(duì)象
/
public class HelloServiceProxy implements HelloService{
//創(chuàng)建依賴關(guān)系
private HelloService helloService;
public HelloServiceProxy(HelloService helloService) {
this.helloService=helloService;
}
//重新實(shí)現(xiàn)業(yè)務(wù)方法
public void sayHello(String msg) {
//添加擴(kuò)展業(yè)務(wù)
System.out.println("method.start:"+System.nanoTime());
//核心業(yè)務(wù)
helloService.sayHello(msg);
//添加拓展業(yè)務(wù)
System.out.println("method.down:"+System.nanoTime());
}
}
測(cè)試代理類:
package proxy.part01;
/* - 測(cè)試代理
/
public class TestProxy01 {
public static void main(String[] args) {
//目標(biāo)對(duì)象
HelloService helloService;
//目標(biāo)對(duì)象(被代理類)
helloService=new HelloServiceImpl();
//代理對(duì)象(擴(kuò)展的類)
helloService=new HelloServiceProxy(helloService);
//調(diào)用業(yè)務(wù)方法
helloService.sayHello("我是核心業(yè)務(wù)");
}
}
輸出結(jié)果:
method.start:1838981357912485
hello 我是核心業(yè)務(wù)
method.down:1838981358133359
輸出結(jié)果顯示我們?cè)诓恍薷脑瓕?shí)現(xiàn)類的情況下盹愚,成功為核心業(yè)務(wù)添加了擴(kuò)展業(yè)務(wù)。但是站故,如果我們要為很多個(gè)類或者方法添加擴(kuò)展業(yè)務(wù)皆怕,例如輸出日志、權(quán)限檢查等西篓,我們就要為所有類都創(chuàng)建一個(gè)代理對(duì)象愈腾,這無疑會(huì)造成大量重復(fù)的代碼片段,并且維護(hù)難度也很大岂津。所以我們希望能夠?qū)崿F(xiàn)動(dòng)態(tài)地為不同的類創(chuàng)建代理對(duì)象虱黄,這是便要用到動(dòng)態(tài)代理技術(shù)。
二吮成、動(dòng)態(tài)代理
動(dòng)態(tài)代理技術(shù)有兩種常用的實(shí)現(xiàn)方式:基于反射機(jī)制實(shí)現(xiàn)的JDK動(dòng)態(tài)代理和直接生成字節(jié)碼文件的CGLIB動(dòng)態(tài)代理橱乱。
1.JDK動(dòng)態(tài)代理
先寫一個(gè)核心業(yè)務(wù)接口:
package proxy.part02;
/* - 核心業(yè)務(wù)接口
/
public interface OrgSerivce {
//保存一個(gè)組織
void saveOrg(String org);
//根據(jù)ID更新一個(gè)組織
int updateOrg(Integer id);
}
實(shí)現(xiàn)核心業(yè)務(wù)接口:
package proxy.part02;
/* - 實(shí)現(xiàn)核心業(yè)務(wù)接口
*/
public class OrgServiceImpl implements OrgSerivce {
@Override
public void saveOrg(String org){
System.out.println("save org "+org);
}
@Override
public int updateOrg(Integer id) {
System.out.println("update org which`s id is "+id);
return 1;
}
}
我們還需要再編寫一個(gè)用于處理擴(kuò)展業(yè)務(wù)的類:
package proxy.part02;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
- 擴(kuò)展業(yè)務(wù)的處理對(duì)象,要實(shí)現(xiàn)Java反射包中的InvocationHandler接口
/
class ServiceHandler implements InvocationHandler{
//定義目標(biāo)對(duì)象的引用粱甫,并建立聯(lián)系
private Object target;
public ServiceHandler(Object target) {
this.target=target;
}
/*執(zhí)行代理對(duì)象的業(yè)務(wù)方法時(shí),會(huì)自動(dòng)執(zhí)行這個(gè)handler對(duì)象的invoke方法- @param proxy 指向代理對(duì)象
- @param method 指向接口中的方法對(duì)象
- @param args 指向method對(duì)象執(zhí)行時(shí)需要的實(shí)際參數(shù)
- */
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
System.out.println("begin transaction");
//執(zhí)行目標(biāo)對(duì)象的method(核心業(yè)務(wù))方法,參數(shù)為args
Object result=method.invoke(target,args);
System.out.println("end transaction");
return result;
}
}
測(cè)試JDK動(dòng)態(tài)代理:
package proxy.part02;
import java.lang.reflect.Proxy;
/**
-
測(cè)試JDK動(dòng)態(tài)代理
/
public class TestDynamicProxy {
public static void main(String[] args) {
//創(chuàng)建一個(gè)目標(biāo)對(duì)象
OrgSerivce orgService=new OrgServiceImpl();
//獲得這個(gè)目標(biāo)對(duì)象的類加載器對(duì)象
ClassLoader loader=orgService.getClass().getClassLoader();
//獲得目標(biāo)對(duì)象所實(shí)現(xiàn)的接口的Class對(duì)象
Class<?>[] interfaces=orgService.getClass().getInterfaces();
//創(chuàng)建一個(gè)Handler對(duì)象泳叠,處理擴(kuò)展業(yè)務(wù)
ServiceHandler handler=new ServiceHandler(orgService);
/*
* 通過Proxy.newProxyInstance方法獲得代理對(duì)象
* @param loader 目標(biāo)對(duì)象的類加載器對(duì)象
* @param interfaces 目標(biāo)對(duì)象實(shí)現(xiàn)的所有接口的Class對(duì)象數(shù)組
* @param handler 處理擴(kuò)展業(yè)務(wù)的對(duì)象
*/
orgService=(OrgSerivce)Proxy.newProxyInstance(loader,interfaces,handler);
//執(zhí)行代理對(duì)象業(yè)務(wù)方法
orgService.saveOrg("軟件開發(fā)部");
orgService.updateOrg(1);
}
}運(yùn)行結(jié)果:
begin transaction
save org 軟件開發(fā)部
end transaction
begin transaction
update org which`s id is 1
end transaction
這樣我們便可以通過Proxy.newProxyInstance方法獲取不同類的代理對(duì)象,通過調(diào)用代理對(duì)象的相應(yīng)方法茶宵,底層會(huì)自動(dòng)幫我們完成擴(kuò)展業(yè)務(wù)類的invoke方法的調(diào)用危纫,并且返回結(jié)果。
2.CGLIB動(dòng)態(tài)代理
同樣的套路我們來測(cè)試CGLIB動(dòng)態(tài)代理乌庶。
package proxy.part03;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
/**
- 定義核心業(yè)務(wù)接口(或父類)
/
abstract class MessageService{
abstract void sendMsg(String msg);
}
/* - 實(shí)現(xiàn)核心業(yè)務(wù)(或繼承父類)
/
class MessageServiceImpl extends MessageService{
public void sendMsg(String msg){
System.out.println(msg);
}
}
/* - 定義管理事務(wù)的非核心業(yè)務(wù)類
/
class TxManager{
//核心業(yè)務(wù)前執(zhí)行
public void begin(){
System.out.println("begin transaction");
}
//核心業(yè)務(wù)后執(zhí)行
public void end(){
System.out.println("end transaction");
}
}
/* - 測(cè)試CGLIB動(dòng)態(tài)代理
*/
public class TestCGLIBProxy01 {
public static void main(String[] args) {
//1.創(chuàng)建目標(biāo)對(duì)象
MessageService msgService=new MessageServiceImpl();
//2.創(chuàng)建代理對(duì)象
//2.1創(chuàng)建一個(gè)代理增強(qiáng)器負(fù)責(zé)創(chuàng)建代理
Enhancer enhancer=new Enhancer();
//2.2設(shè)置增強(qiáng)器對(duì)應(yīng)的類加載器
enhancer.setClassLoader(msgService.getClass().getClassLoader());
//2.3設(shè)置代理對(duì)象要實(shí)現(xiàn)的接口或繼承的父類
//當(dāng)目標(biāo)對(duì)象實(shí)現(xiàn)了接口就用這個(gè)方法
enhancer.setInterfaces(msgService.getClass().getInterfaces());
//當(dāng)目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口就用這個(gè)方法种蝶,這兩個(gè)方法可根據(jù)情況決定用哪一個(gè),也可以都是用
enhancer.setSuperclass(msgService.getClass());
//2.4指定回調(diào)函數(shù)
enhancer.setCallback(new InvocationHandler() {
//關(guān)聯(lián)非核心業(yè)務(wù)對(duì)象
TxManager txManager=new TxManager();
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
txManager.begin();
Object result=method.invoke(msgService, args);
txManager.end();
return result;
}
});
//2.5創(chuàng)建代理對(duì)象
MessageService proxy=(MessageService)enhancer.create();
//3.執(zhí)行代理對(duì)象業(yè)務(wù)
//在執(zhí)行業(yè)務(wù)方法時(shí)會(huì)回調(diào)Enhancer指定的callback接口中的函數(shù)
proxy.sendMsg("hello");
}
}
輸出結(jié)果:
begin transaction
hello
end transaction
這樣就實(shí)現(xiàn)了CGLIB動(dòng)態(tài)代理瞒大。
總結(jié):
代理模式是一種解決方案螃征,也是一項(xiàng)技術(shù),使用代理模式透敌,我們可以在不修改原代碼的情況下会傲,完成業(yè)務(wù)的拓展锅棕,并且保證了代碼的復(fù)用率和可維護(hù)性。
博主編寫的這兩種動(dòng)態(tài)代理的實(shí)現(xiàn)方式略有不同淌山,這也是博主故意為大家挖的坑裸燎,如果你仔細(xì)理解上面的兩個(gè)案例,會(huì)發(fā)現(xiàn)其實(shí)JDK動(dòng)態(tài)代理與CGLIB動(dòng)態(tài)代理的實(shí)現(xiàn)方式只有增強(qiáng)代理器對(duì)象(Enhancer)的setSuperclass方法不同泼疑,其他步驟邏輯上完全相同德绿,都需要被代理對(duì)象的類加載器、被代理對(duì)象實(shí)現(xiàn)的接口的Classd對(duì)象(或者所繼承的父類的Class對(duì)象退渗,也可以是本類的Class對(duì)象)移稳、一個(gè)處理擴(kuò)展業(yè)務(wù)的對(duì)象(擴(kuò)展業(yè)務(wù)代碼可以單獨(dú)寫到其他類中,也可以直接寫在InvocationHandler接口的實(shí)現(xiàn)類中)会油,交由API后个粱,我們就可以獲得代理對(duì)象。
這兩種動(dòng)態(tài)代理各有優(yōu)缺點(diǎn):
JDK動(dòng)態(tài)代理:優(yōu)點(diǎn)是翻翩,生成動(dòng)態(tài)代理對(duì)象的速度快都许,適合頻繁創(chuàng)建代理對(duì)象的程序;
缺點(diǎn)是嫂冻,執(zhí)行速度慢胶征,且不能為未實(shí)現(xiàn)接口的類創(chuàng)建代理。
CGLIB動(dòng)態(tài)代理:優(yōu)點(diǎn)是桨仿,執(zhí)行速度快睛低,且能夠?yàn)槲磳?shí)現(xiàn)接口的類創(chuàng)建代理;
缺點(diǎn)是服傍,不能代理final修飾的方法钱雷,且生成代理對(duì)象的速度慢,不適合頻繁創(chuàng)建代理對(duì)象的程序吹零。
在Spring中罩抗,默認(rèn)使用JDK動(dòng)態(tài)代理,如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口瘪校,會(huì)轉(zhuǎn)而使用CGLIB動(dòng)態(tài)代理。