設(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)飛哥