設(shè)計(jì)模式學(xué)習(xí)08----之代理模式
概述
今天接著學(xué)習(xí)設(shè)計(jì)模式,今天要學(xué)習(xí)的模式是代理模式。代理模式的應(yīng)用場(chǎng)景有很多,例如:生活中的代購(gòu),明星的經(jīng)紀(jì)人。
定義與結(jié)構(gòu)
代理模式(Proxy)是一種設(shè)計(jì)模式,為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。
在軟件開(kāi)發(fā)中有個(gè)原則:就是開(kāi)-閉原則,對(duì)新增開(kāi)放,對(duì)修改關(guān)閉。盡量不要去修改已經(jīng)寫好的代碼。如果需要可以增加一個(gè)代理類,來(lái)擴(kuò)展目標(biāo)代碼。
實(shí)例
假設(shè)某人需要購(gòu)買一塊不在國(guó)內(nèi)銷售的化妝品,這時(shí)他找到了一個(gè)代購(gòu)來(lái)幫他買。
ISubject 買化妝品的接口類,
RealSubject 某人
Proxy 代理類
靜態(tài)代理模式
靜態(tài)代理模式,是目標(biāo)類和代理類都要實(shí)現(xiàn)相同的接口。代理角色依賴于真實(shí)角色,因?yàn)榇斫巧珒?nèi)部有對(duì)真實(shí)角色的引用,代理在操作真實(shí)角色去執(zhí)行動(dòng)作時(shí),必須要知道是哪個(gè)真實(shí)角色。
接口:
public interface ISubject {
/**
*
* @return
*/
void buyCosmetics();
}
目標(biāo)類
public class RealSubject implements ISubject {
@Override
public void buyCosmetics() {
System.out.println("買某種進(jìn)口化妝品");
}
}
代理類:
public class Proxy implements ISubject {
//接收目標(biāo)對(duì)象
private ISubject target;
public Proxy(ISubject iSubject) {
this.target = iSubject;
}
@Override
public void buyCosmetics() {
target.buyCosmetics();
}
}
測(cè)試類
public class Client {
public static void main(String[] args) {
ISubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.buyCosmetics();
}
}
靜態(tài)代理類的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):可以在不修改目標(biāo)類的前提下擴(kuò)展目標(biāo)類
缺點(diǎn):每個(gè)目標(biāo)類都對(duì)應(yīng)一個(gè)代理類,當(dāng)目標(biāo)類過(guò)多時(shí),會(huì)導(dǎo)致類急劇增加,而一旦接口增加方法,目標(biāo)類都代理類都要維護(hù)。
針對(duì)這個(gè)缺點(diǎn),動(dòng)態(tài)代理可以解決
JDK動(dòng)態(tài)代理
動(dòng)態(tài)代理的特點(diǎn)
代理對(duì)象不需要實(shí)現(xiàn)接口
代理對(duì)象的生成是利用JDK的API,動(dòng)態(tài)的在內(nèi)存中創(chuàng)建代理對(duì)象(需要我們制定代理對(duì)象/目標(biāo)對(duì)象實(shí)現(xiàn)的接口類型)
動(dòng)態(tài)代理也叫做:JDK代理,接口代理。
JDK中生成代理對(duì)象的API
代理類所在的包是 :java.lang.reflect.Proxy
JDK 實(shí)現(xiàn)代理只需要調(diào)用newProxyInstance方法,該方法需要接受三個(gè)參數(shù)。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h){}
該方法是Proxy類中的靜態(tài)方法, 且接收的三個(gè)參數(shù)依次為:
ClassLoader loader:指定當(dāng)前目標(biāo)對(duì)象使用的類加載器,獲取類加載器的方法是RealSubject.class.getClassLoader()
Class<?>[] interfaces:目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型。
InvocationHandler h:事件處理,執(zhí)行目標(biāo)對(duì)象的方法是,會(huì)觸發(fā)時(shí)間處理器的方法。會(huì)把當(dāng)前執(zhí)行目標(biāo)的方法作為參數(shù)傳入。
代碼示例:
接口類ISubject.java 以及接口實(shí)現(xiàn)類,目標(biāo)對(duì)象RealSubject也是一樣的,不做修改,在此基礎(chǔ)上增加一個(gè)代理工廠類(ProxyFactory),將代理類寫在這個(gè)地方,然后在測(cè)試類中先建立目標(biāo)對(duì)象和代理對(duì)象的聯(lián)系,然后代用代理對(duì)象中的同名方法。
代理工廠類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author xiang.wei
*/
public class ProxyFactory {
/**
* 目標(biāo)對(duì)象
*/
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 給目標(biāo)對(duì)象生成代理對(duì)象
* @return
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置處理");
//執(zhí)行目標(biāo)對(duì)象方法
Object returnValue = method.invoke(target, args);
System.out.println("后置處理");
return returnValue;
}
}
);
}
}
如上,該代理工程實(shí)現(xiàn)了InvocationHandler接口,并且實(shí)現(xiàn)了接口中的invoke方法,就是在這個(gè)方法中加入切面的。
測(cè)試類:
public class Client {
public static void main(String[] args) {
ISubject target = new RealSubject();
ProxyFactory proxyFactory = new ProxyFactory(target);
ISubject proxy = (ISubject) proxyFactory.getProxyInstance();
proxy.buyCosmetics();
}
}
總結(jié):
代理對(duì)象不需要實(shí)現(xiàn)接口,但是目標(biāo)對(duì)象一定要實(shí)現(xiàn)接口,否則不能使用動(dòng)態(tài)代理。
Cglib動(dòng)態(tài)代理
以上的靜態(tài)代理和動(dòng)態(tài)代理模式都要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口的目標(biāo)對(duì)象,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象。并沒(méi)有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候就可以使用以目標(biāo)對(duì)象子類的方式來(lái)實(shí)現(xiàn)代理,這種方法就叫做:Cglib代理。
Cglib 代理,也叫做子類代理,它是在內(nèi)存中構(gòu)建一個(gè)子類對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能的擴(kuò)展,
JDK 的動(dòng)態(tài)代理有一個(gè)限制,就是使用動(dòng)態(tài)代理的對(duì)象必須實(shí)現(xiàn)一個(gè)或者多個(gè)接口,如果代理沒(méi)有實(shí)現(xiàn)接口的類,可以使用Cglib實(shí)現(xiàn)。
Cglib 是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口,他廣泛的被許多AOP的框架使用,例如,Spring AOP和synaop,為他們提供方法的interception(攔截)
Cglib 包的底層是通過(guò)使用一個(gè)小而快的字節(jié)碼處理框架ASM來(lái)轉(zhuǎn)換字節(jié)碼并生成新的類。不鼓勵(lì)直接使用ASM,因?yàn)樗竽惚仨殞?duì)JVM內(nèi)部接口包括class文件的格式和指令集都很熟悉。
Cglib 子類代理實(shí)現(xiàn)方法:
需要引入cglib的jar文件,但是Spring的核心包中已經(jīng)包括了Cglib功能,所以直接添加spring-core的依賴即可。
添加完依賴之后,就可以在內(nèi)存中動(dòng)態(tài)的構(gòu)建子類。
代理類不能為final,否則報(bào)錯(cuò)。
目標(biāo)對(duì)象的方法如果是final/static, 那么就不會(huì)被攔截,即不會(huì)執(zhí)行目標(biāo)對(duì)象額外的業(yè)務(wù)方法。
代碼示例:
目標(biāo)對(duì)象類:
package com.proxy.cglib;
/**
* @author xiang.wei
* @create 2018/5/28 15:02
*/
public class RealSubject {
public void buyCosmetics() {
System.out.println("買某種進(jìn)口化妝品");
}
}
Cglib 代理類
package com.proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author xiang.wei
* @create 2018/5/28 15:03
*/
public class ProxyFactory implements MethodInterceptor{
//維護(hù)目標(biāo)對(duì)象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 給目標(biāo)對(duì)象創(chuàng)建一個(gè)代理對(duì)象
* @return
*/
public Object getProxyInstance() {
//1.工具類
Enhancer enhancer = new Enhancer();
//2.設(shè)置代理目標(biāo)
enhancer.setSuperclass(target.getClass());
//3.設(shè)置回調(diào)函數(shù)
enhancer.setCallback(this);
//4.創(chuàng)建子類(代理對(duì)象)
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置處理");
Object returnValue = methodProxy.invoke(target, objects);
System.out.println("后置處理");
return returnValue;
}
}
測(cè)試類:
public class Client {
public static void main(String[] args) {
//目標(biāo)對(duì)象
RealSubject realSubject = new RealSubject();
//代理對(duì)象
RealSubject proxy = (RealSubject) new ProxyFactory(realSubject).getProxyInstance();
proxy.buyCosmetics();
}
}
總結(jié):
如果代理對(duì)象有實(shí)現(xiàn)接口,那么使用JDK代理,如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)接口,那么使用Cglib代理。
作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥