Java 靜態(tài)代理和動態(tài)代理

什么是代理模式香追?

為其他對象提供一種代理以控制對這個對象的訪問悔政√阑眨——《Java設(shè)計模式》

image.png

■ 抽象主題(Subject)角色:該角色是真實主題和代理主題的共同接口涵叮,以便在任何可以使用真實主題的地方都可以使用代理主題。
■ 代理主題(Proxy Subject)角色:也叫做委托類熙含、代理類罚缕,該角色
1)負責(zé)控制對真實主題的引用,
2)負責(zé)在需要的時候創(chuàng)建或刪除真實主題對象
3)在真實主題角色處理完畢前后做預(yù)處理和善后處理工作怎静。
■ 真實主題(Real Subject)角色:該角色也叫做被委托角色邮弹、被代理角色,是業(yè)務(wù)邏輯的具體執(zhí)行者蚓聘。

1.靜態(tài)代理

Java中的靜態(tài)代理要求代理類(ProxySubject)和委托類(RealSubject)都實現(xiàn)同一個接口(Subject)腌乡。

1)靜態(tài)代理中代理類在編譯期就已經(jīng)確定
2)靜態(tài)代理的效率相對動態(tài)代理來說相對高一些
3)靜態(tài)代理代碼冗余大夜牡,一單需要修改接口导饲,代理類和委托類都需要修改。

舉例:
接口 Developer:

public interface Developer {
    void coding();
    void debug();
}

委托類 AndroidDeveloper:

public class AndroidDeveloper implements Developer{
    private String name;

    public AndroidDeveloper(String name){
        this.name = name;
    }

    public String getDeveloperName(){
        return name;
    }

    @Override
    public void coding(){
        System.out.println("Coding: "+this.name+" is developing Android!");
    }

    @Override
    public void debug(){
        System.out.println("Coding: "+this.name+" is debugging his APP!");
    }
}

靜態(tài)代理類 JavaStaticProxy

public class JavaStaticProxy implements Developer{
    private Developer target;
    public JavaStaticProxy(Developer developer){
        this.target = developer;
    }

    @Override
    public void coding() {
        System.out.println("before!");
        this.target.coding();
        System.out.println("after!");
    }

    @Override
    public void debug() {
        System.out.println("before!");
        this.target.debug();
        System.out.println("after!");
    }
}

測試:

public class Main {
    public static void main(String[] args){
        StaticProxyTest();
    }

    public static void StaticProxyTest(){
        System.out.println("StaticProxy:");
        Developer Breeze = new AndroidDeveloper("Breeze");
        JavaStaticProxy staticProxy = new JavaStaticProxy(Breeze);
        staticProxy.coding();
        staticProxy.debug();
    }
}

輸出:

StaticProxy:
before!
Coding: Breeze is developing Android!
after!
before!
Coding: Breeze is debugging his APP!
after!

2.動態(tài)代理

Java中的動態(tài)代理依靠反射來實現(xiàn)氯材,代理類和委托類不需要實現(xiàn)同一個接口。委托類需要實現(xiàn)接口硝岗,否則無法創(chuàng)建動態(tài)代理氢哮。代理類在JVM運行時動態(tài)生成,而不是編譯期就能確定型檀。

Java動態(tài)代理主要涉及到兩個類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler冗尤。代理類需要實現(xiàn)InvocationHandler接口或者創(chuàng)建匿名內(nèi)部類,而Proxy用于創(chuàng)建動態(tài)動態(tài)胀溺。

http://www.reibang.com/p/f56e123817b5

特點:
1)不要求代理類與委托類實現(xiàn)統(tǒng)一接口裂七,避免了靜態(tài)代理生成大量代理類的問題,但比較消耗性能仓坞。
2)可以非常靈活地在某個類背零,某個方法,某個代碼點上切入我們想要的內(nèi)容无埃,就是動態(tài)代理其中的內(nèi)容徙瓶。

2.1 例子:

動態(tài)代理類 JavaDynamicProxy

package java_proxy;

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

public class JavaDynamicProxy {
    private Developer target;
    public  JavaDynamicProxy(Developer target){
        this.target = target;
    }

    public Object getDeveloperProxy(){
        Developer developerProxy = (Developer) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().
                getInterfaces(), (proxy, method, args1) -> {
            Field field = target.getClass().getDeclaredField("name");
            field.setAccessible(true);
            String name = (String) field.get(target);
            if(method.getName().equals("coding")){


                beforeCoding(name);
                method.invoke(target, args1);
                afterCoding(name);
            }
            if(method.getName().equals("debug")){
                beforeDebug(name);
                method.invoke(target, args1);
                afterDebug(name);
            }
            return null;
        });
        return developerProxy;
    }

    public void beforeCoding(String name){
        System.out.println("Before coding: "+name+" is very happy!");
    }

    public void afterCoding(String name){
        System.out.println("After coding: "+name+" is very tired!");
    }

    public void beforeDebug(String name){
        System.out.println("Before debugging: "+name+"'s App has no bug! Nobody knows Android better than me!");
    }
    public void afterDebug(String name){
        System.out.println("After debugging:  What trash is "+name+"?");
    }
}




測試:

package java_proxy;

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

public class Main {
    public static void main(String[] args){
        DynamicProxyTest();
    }

    public static void DynamicProxyTest(){
        System.out.println("DynamicProxy:");
        AndroidDeveloper target = new AndroidDeveloper("Breeze");
        Developer proxy = (Developer)new JavaDynamicProxy(target).getDeveloperProxy();
        proxy.coding();
        proxy.debug();
    }
}

輸出:

DynamicProxy:
Before coding: Breeze is very happy!
Coding: Breeze is developing Android!
After coding: Breeze is very tired!
Before debugging: Breeze's App has no bug! Nobody knows Android better than me!
Coding: Breeze is debugging his APP!
After debugging:  What trash is Breeze?

2.2 動態(tài)代理的原理

Proxy.newProxyInstance()

把接口復(fù)制出來毛雇,通過這些接口和類加載器,拿到這個代理類cl侦镇。然后通過反射的技術(shù)復(fù)制拿到代理類的構(gòu)造函數(shù)(這部分代碼在Class類中的getConstructor0方法)灵疮,最后通過這個構(gòu)造函數(shù)new個一對象出來,同時用InvocationHandler綁定這個對象壳繁。

     /* @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 <a href="#restrictions">
     *          restrictions</a> on the parameters 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}
     *
     * @see <a href="#membership">Package and Module Membership of Proxy Class</a>
     * @revised 9
     * @spec JPMS
     */
    @CallerSensitive

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }

InvocationHandler

InvocationHandler作用就是震捣,當(dāng)代理對象的原本方法被調(diào)用的時候,會綁定執(zhí)行一個方法闹炉,這個方法就是InvocationHandler里面定義的內(nèi)容蒿赢,同時會替代原本方法的結(jié)果返回。
InvocationHandler接收三個參數(shù)

  • proxy剩胁,代理后的實例對象诉植。
  • method,對象被調(diào)用方法昵观。
  • args晾腔,調(diào)用時的參數(shù)。
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.
    ……
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啊犬,一起剝皮案震驚了整個濱河市灼擂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌觉至,老刑警劉巖撵幽,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件办成,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機擦秽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來期丰,“玉大人欢瞪,你說我怎么就攤上這事〉锓模” “怎么了船万?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長骨田。 經(jīng)常有香客問我耿导,道長,這世上最難降的妖魔是什么态贤? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任舱呻,我火速辦了婚禮,結(jié)果婚禮上悠汽,老公的妹妹穿的比我還像新娘狮荔。我一直安慰自己胎撇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布殖氏。 她就那樣靜靜地躺著晚树,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雅采。 梳的紋絲不亂的頭發(fā)上爵憎,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音婚瓜,去河邊找鬼宝鼓。 笑死,一個胖子當(dāng)著我的面吹牛巴刻,可吹牛的內(nèi)容都是我干的愚铡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼胡陪,長吁一口氣:“原來是場噩夢啊……” “哼沥寥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柠座,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤邑雅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妈经,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淮野,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年吹泡,在試婚紗的時候發(fā)現(xiàn)自己被綠了骤星。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡爆哑,死狀恐怖妈踊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泪漂,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布歪泳,位于F島的核電站萝勤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呐伞。R本人自食惡果不足惜敌卓,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伶氢。 院中可真熱鬧趟径,春花似錦瘪吏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幕屹,卻和暖如春蓝丙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背望拖。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工渺尘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人说敏。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓鸥跟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盔沫。 傳聞我的和親對象是個殘疾皇子医咨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348