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