設(shè)計(jì)模式--代理模式

前言

日常生活中,我們想買(mǎi)房或者租房又沒(méi)有房源的時(shí)候,我們通常會(huì)找中介,由中介負(fù)責(zé)幫我們聯(lián)系房主,我們只需要最后簽合同就行了,而不用去關(guān)心中介怎么聯(lián)系房東的,怎么談價(jià)格的。

什么是代理模式

代理模式(Proxy Pattern): 給某一個(gè)對(duì)象提供一個(gè)代理,并由代理對(duì)象來(lái)控制對(duì)真實(shí)對(duì)象的訪問(wèn)。代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式。

所謂的代理模式,是指客戶(hù)端不想或者不能直接訪問(wèn)或引用一個(gè)對(duì)象,這時(shí)候可以通過(guò)代理對(duì)象在客戶(hù)端和目標(biāo)對(duì)象之間起到一個(gè)中介作用。

代理(Proxy)模式提供了對(duì)目標(biāo)對(duì)象另外的訪問(wèn)方式,即通過(guò)代理對(duì)象訪問(wèn)目標(biāo)對(duì)象。這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能。


代理模式的三個(gè)角色

Subject(抽象角色):

聲明代理角色和真實(shí)角色的公共接口,也是代理角色代理真實(shí)角色的方法。

RealSubject(真實(shí)角色):

定義代理對(duì)象需要代理的真實(shí)對(duì)象,真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類(lèi);

Proxy(代理角色):

用來(lái)代理和封裝真實(shí)主題,來(lái)控制對(duì)真實(shí)對(duì)象的訪問(wèn),代理對(duì)象持有真實(shí)對(duì)象的應(yīng)用,從而可以隨時(shí)控制客戶(hù)端對(duì)真實(shí)對(duì)象的訪問(wèn)。


代理模式UML圖

代理模式UML圖

靜態(tài)代理

抽象主題角色

package com.java.design.proxy;

/**
 * 抽象主題角色:明星
 * @author liyongfu
 */
public interface Star {

    /**
     * 唱歌
     */
    void sing();
}

真實(shí)主題角色

package com.java.design.proxy;

/**
 * 真實(shí)主題角色:具體的明星
 * @author liyongfu
 */
public class RealStar implements Star{

    /**
     * 唱歌
     */
    @Override
    public void sing() {
        System.out.println("do----真正明星唱歌");
    }
}

代理主題角色

package com.java.design.proxy.statics;

/**
 *
 * 代理主題角色
 * 靜態(tài)代理:
 *     被代理類(lèi)含有接口和具體實(shí)現(xiàn)類(lèi);
 *     代理類(lèi)實(shí)現(xiàn)被代理接口,并含有被代理接口屬性、相應(yīng)屬性構(gòu)造器
 * @author liyongfu
 */
public  class ProxyStar implements Star{

    /**被代理的對(duì)象*/
    private Star star;

    public ProxyStar(Star star) {
        this.star = star;
    }

    @Override
    public void sing() {
        doBeforeSing();
        star.sing();
        doAfterSing();
    }

    /**
     * 明星唱歌前:經(jīng)紀(jì)人賣(mài)票
     */
    private void doBeforeSing() {
        System.out.println("before---經(jīng)紀(jì)人賣(mài)票");
    }

    /**
     * 明星唱歌后:經(jīng)紀(jì)人收錢(qián)
     */
    private void doAfterSing() {
        System.out.println("after---經(jīng)紀(jì)人收錢(qián)");
    }

    public static void main(String[] args){
        ProxyStar proxy= new ProxyStar(new RealStar());
        proxy.sing();
    }
}

控制臺(tái)打印

before---經(jīng)紀(jì)人賣(mài)票
do----真正明星唱歌
after---經(jīng)紀(jì)人收錢(qián)

觀察靜態(tài)代理運(yùn)行的整個(gè)流程,可以看出來(lái):

  1. 代理對(duì)象和目標(biāo)對(duì)象都實(shí)現(xiàn)了同樣的接口。
  2. 可以在不修改目標(biāo)對(duì)象的前提下增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能。
  3. 在明星唱歌之前,真實(shí)主題角色和代理主題角色就被確定下來(lái)了,也就是說(shuō)在編譯時(shí)就已經(jīng)將接口,被代理類(lèi),代理類(lèi)等確定下來(lái)。在程序運(yùn)行之前,代理類(lèi)的.class文件就已經(jīng)生成,是由程序員編寫(xiě)的代理類(lèi),并在程序運(yùn)行前就編譯好的,而不是由程序動(dòng)態(tài)產(chǎn)生代理類(lèi)。

JDK動(dòng)態(tài)代理

在上面的示例中,一個(gè)代理只能代理一種類(lèi)型,而且是在編譯期間就已經(jīng)確定被代理的對(duì)象。而動(dòng)態(tài)代理是在運(yùn)行時(shí),通過(guò)反射機(jī)制實(shí)現(xiàn)動(dòng)態(tài)代理,并且能夠代理各種類(lèi)型的對(duì)象。
由于靜態(tài)代理的局限性,為了解決上面的問(wèn)題,動(dòng)態(tài)代理模式就橫空出世了,我們最常見(jiàn)的就是Spring AOP了,看過(guò)源碼的都知道spring aop有兩種動(dòng)態(tài)代理模式,JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理,那么這兩者之間又有什么區(qū)別呢?

在Java中要想實(shí)現(xiàn)JDK動(dòng)態(tài)代理機(jī)制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 類(lèi)的支持。我們先來(lái)看一下他們的源代碼。

package java.lang.reflect;

/**
 * {@code InvocationHandler} is the interface implemented by
 * the <i>invocation handler</i> of a proxy instance.
 *
 * <p>Each proxy instance has an associated invocation handler.
 * When a method is invoked on a proxy instance, the method
 * invocation is encoded and dispatched to the {@code invoke}
 * method of its invocation handler.
 *
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */
public interface InvocationHandler {

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param   proxy the proxy instance that the method was invoked on
     *
     * @param   method the {@code Method} instance corresponding to
     * the interface method invoked on the proxy instance.  The declaring
     * class of the {@code Method} object will be the interface that
     * the method was declared in, which may be a superinterface of the
     * proxy interface that the proxy class inherits the method through.
     *
     * @param   args an array of objects containing the values of the
     * arguments passed in the method invocation on the proxy instance,
     * or {@code null} if interface method takes no arguments.
     * Arguments of primitive types are wrapped in instances of the
     * appropriate primitive wrapper class, such as
     * {@code java.lang.Integer} or {@code java.lang.Boolean}.
     *
     * @return  the value to return from the method invocation on the
     * proxy instance.  If the declared return type of the interface
     * method is a primitive type, then the value returned by
     * this method must be an instance of the corresponding primitive
     * wrapper class; otherwise, it must be a type assignable to the
     * declared return type.  If the value returned by this method is
     * {@code null} and the interface method's return type is
     * primitive, then a {@code NullPointerException} will be
     * thrown by the method invocation on the proxy instance.  If the
     * value returned by this method is otherwise not compatible with
     * the interface method's declared return type as described above,
     * a {@code ClassCastException} will be thrown by the method
     * invocation on the proxy instance.
     *
     * @throws  Throwable the exception to throw from the method
     * invocation on the proxy instance.  The exception's type must be
     * assignable either to any of the exception types declared in the
     * {@code throws} clause of the interface method or to the
     * unchecked exception types {@code java.lang.RuntimeException}
     * or {@code java.lang.Error}.  If a checked exception is
     * thrown by this method that is not assignable to any of the
     * exception types declared in the {@code throws} clause of
     * the interface method, then an
     * {@link UndeclaredThrowableException} containing the
     * exception that was thrown by this method will be thrown by the
     * method invocation on the proxy instance.
     *
     * @see     UndeclaredThrowableException
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

/**
 * Returns an instance of a proxy class for the specified interfaces
 * that dispatches method invocations to the specified invocation
 * handler.
 *
 * <p>{@code Proxy.newProxyInstance} throws
 * {@code IllegalArgumentException} for the same reasons that
 * {@code Proxy.getProxyClass} does.
 *
 * @param   loader the class loader to define the proxy class
 * @param   interfaces the list of interfaces for the proxy class
 *          to implement
 * @param   h the invocation handler to dispatch method invocations to
 * @return  a proxy instance with the specified invocation handler of a
 *          proxy class that is defined by the specified class loader
 *          and that implements the specified interfaces
 * @throws  IllegalArgumentException if any of the restrictions on the
 *          parameters that may be passed to {@code getProxyClass}
 *          are violated
 * @throws  SecurityException if a security manager, <em>s</em>, is present
 *          and any of the following conditions is met:
 *          <ul>
 *          <li> the given {@code loader} is {@code null} and
 *               the caller's class loader is not {@code null} and the
 *               invocation of {@link SecurityManager#checkPermission
 *               s.checkPermission} with
 *               {@code RuntimePermission("getClassLoader")} permission
 *               denies access;</li>
 *          <li> for each proxy interface, {@code intf},
 *               the caller's class loader is not the same as or an
 *               ancestor of the class loader for {@code intf} and
 *               invocation of {@link SecurityManager#checkPackageAccess
 *               s.checkPackageAccess()} denies access to {@code intf};</li>
 *          <li> any of the given proxy interfaces is non-public and the
 *               caller class is not in the same {@linkplain Package runtime package}
 *               as the non-public interface and the invocation of
 *               {@link SecurityManager#checkPermission s.checkPermission} with
 *               {@code ReflectPermission("newProxyInPackage.{package name}")}
 *               permission denies access.</li>
 *          </ul>
 * @throws  NullPointerException if the {@code interfaces} array
 *          argument or any of its elements are {@code null}, or
 *          if the invocation handler, {@code h}, is
 *          {@code null}
 */
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);
    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

抽象主題角色(同上,略)

真實(shí)主題角色(同上,略)

代理主題角色

package com.java.design.proxy.dynamic;

import com.java.design.proxy.RealStar;
import com.java.design.proxy.Star;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK動(dòng)態(tài)代理
 * @author liyongfu
 */
public class DynamicProxy implements InvocationHandler {

    /**被代理的對(duì)象**/
    private Star star;

    public DynamicProxy(Star star) {
        super();
        this.star = star;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("jdk動(dòng)態(tài)代理");
        doBeforeSing();
        method.invoke(star, args);
        doAfterSing();
        return null;
    }


    /**
     * 明星唱歌前:經(jīng)紀(jì)人賣(mài)票
     */
    private void doBeforeSing() {
        System.out.println("before---經(jīng)紀(jì)人賣(mài)票");
    }

    /**
     * 明星唱歌后:經(jīng)紀(jì)人收錢(qián)
     */
    private void doAfterSing() {
        System.out.println("after---經(jīng)紀(jì)人收錢(qián)");
    }

    public static void main(String[] args) {
        Star star = new RealStar();
        Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, new DynamicProxy(star));
        proxy.sing();
    }
}

比較: CGLIB創(chuàng)建的動(dòng)態(tài)代理對(duì)象比JDK創(chuàng)建的動(dòng)態(tài)代理對(duì)象的性能更高,但是CGLIB創(chuàng)建代理對(duì)象時(shí)所花費(fèi)的時(shí)間卻比JDK多得多。所以對(duì)于單例的對(duì)象,因?yàn)闊o(wú)需頻繁創(chuàng)建對(duì)象,用CGLIB合適,反之使用JDK方式要更為合適一些。JDK動(dòng)態(tài)代理與CGLib動(dòng)態(tài)代理均是實(shí)現(xiàn)Spring AOP的基礎(chǔ)。

源碼實(shí)例

// TODO 待處理

項(xiàng)目實(shí)戰(zhàn)

// TODO 待處理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容