国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

10分鐘看懂動(dòng)態(tài)代理設(shè)計(jì)模式

atinosun / 1393人閱讀

摘要:動(dòng)態(tài)代理是語(yǔ)言中非常經(jīng)典的一種設(shè)計(jì)模式,也是所有設(shè)計(jì)模式中最難理解的一種。本文將通過(guò)一個(gè)簡(jiǎn)單的例子模擬動(dòng)態(tài)代理實(shí)現(xiàn),讓你徹底明白動(dòng)態(tài)代理設(shè)計(jì)模式的本質(zhì),文章中可能會(huì)涉及到一些你沒(méi)有學(xué)習(xí)過(guò)的知識(shí)點(diǎn)或概念。

動(dòng)態(tài)代理是Java語(yǔ)言中非常經(jīng)典的一種設(shè)計(jì)模式,也是所有設(shè)計(jì)模式中最難理解的一種。本文將通過(guò)一個(gè)簡(jiǎn)單的例子模擬JDK動(dòng)態(tài)代理實(shí)現(xiàn),讓你徹底明白動(dòng)態(tài)代理設(shè)計(jì)模式的本質(zhì),文章中可能會(huì)涉及到一些你沒(méi)有學(xué)習(xí)過(guò)的知識(shí)點(diǎn)或概念。如果恰好遇到了這些知識(shí)盲點(diǎn),請(qǐng)先去學(xué)習(xí)這部分知識(shí),再來(lái)閱讀這篇文章。
什么是代理

從字面意思來(lái)看,代理比較好理解,無(wú)非就是代為處理的意思。舉個(gè)例子,你在上大學(xué)的時(shí)候,總是喜歡逃課。因此,你拜托你的同學(xué)幫你答到,而自己卻窩在宿舍玩游戲... 你的這個(gè)同學(xué)恰好就充當(dāng)了代理的作用,代替你去上課。

是的,你沒(méi)有看錯(cuò),代理就是這么簡(jiǎn)單!

理解了代理的意思,你腦海中恐怕還有兩個(gè)巨大的疑問(wèn):

怎么實(shí)現(xiàn)代理模式

代理模式有什么實(shí)際用途

要理解這兩個(gè)問(wèn)題,看一個(gè)簡(jiǎn)單的例子:

public interface Flyable {
    void fly();
}

public class Bird implements Flyable {

    @Override
    public void fly() {
        System.out.println("Bird is flying...");
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

很簡(jiǎn)單的一個(gè)例子,用一個(gè)隨機(jī)睡眠時(shí)間模擬小鳥在空中的飛行時(shí)間。接下來(lái)問(wèn)題來(lái)了,如果我要知道小鳥在天空中飛行了多久,怎么辦?

有人說(shuō),很簡(jiǎn)單,在Bird->fly()方法的開頭記錄起始時(shí)間,在方法結(jié)束記錄完成時(shí)間,兩個(gè)時(shí)間相減就得到了飛行時(shí)間。

   @Override
    public void fly() {
        long start = System.currentTimeMillis();
        System.out.println("Bird is flying...");
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
    }

的確,這個(gè)方法沒(méi)有任何問(wèn)題,接下來(lái)加大問(wèn)題的難度。如果Bird這個(gè)類來(lái)自于某個(gè)SDK(或者說(shuō)Jar包)提供,你無(wú)法改動(dòng)源碼,怎么辦?

一定會(huì)有人說(shuō),我可以在調(diào)用的地方這樣寫:

public static void main(String[] args) {
        Bird bird = new Bird();
        long start = System.currentTimeMillis();
        bird.fly();
        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
}

這個(gè)方案看起來(lái)似乎沒(méi)有問(wèn)題,但其實(shí)你忽略了準(zhǔn)備這些方法所需要的時(shí)間,執(zhí)行一個(gè)方法,需要開辟棧內(nèi)存、壓棧、出棧等操作,這部分時(shí)間也是不可以忽略的。因此,這個(gè)解決方案不可行。那么,還有什么方法可以做到呢?

a)使用繼承

繼承是最直觀的解決方案,相信你已經(jīng)想到了,至少我最開始想到的解決方案就是繼承。
為此,我們重新創(chuàng)建一個(gè)類Bird2,在Bird2中我們只做一件事情,就是調(diào)用父類的fly方法,在前后記錄時(shí)間,并打印時(shí)間差:

public class Bird2 extends Bird {

    @Override
    public void fly() {
        long start = System.currentTimeMillis();
        
        super.fly();
        
        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
    }
}

這是一種解決方案,還有一種解決方案叫做:聚合,其實(shí)也是比較容易想到的。
我們?cè)俅蝿?chuàng)建新類Bird3,在Bird3的構(gòu)造方法中傳入Bird實(shí)例。同時(shí),讓Bird3也實(shí)現(xiàn)Flyable接口,并在fly方法中調(diào)用傳入的Bird實(shí)例的fly方法:

public class Bird3 implements Flyable {
    private Bird bird;

    public Bird3(Bird bird) {
        this.bird = bird;
    }

    @Override
    public void fly() {
        long start = System.currentTimeMillis();

        bird.fly();

        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
    }
}

為了記錄Bird->fly()方法的執(zhí)行時(shí)間,我們?cè)谇昂筇砑恿擞涗洉r(shí)間的代碼。同樣地,通過(guò)這種方法我們也可以獲得小鳥的飛行時(shí)間。那么,這兩種方法孰優(yōu)孰劣呢?咋一看,不好評(píng)判!

繼續(xù)深入思考,用問(wèn)題推導(dǎo)來(lái)解答這個(gè)問(wèn)題:

問(wèn)題一:如果我還需要在fly方法前后打印日志,記錄飛行開始和飛行結(jié)束,怎么辦?
有人說(shuō),很簡(jiǎn)單!繼承Bird2并在在前后添加打印語(yǔ)句即可。那么,問(wèn)題來(lái)了,請(qǐng)看問(wèn)題二。

問(wèn)題二:如果我需要調(diào)換執(zhí)行順序,先打印日志,再獲取飛行時(shí)間,怎么辦?
有人說(shuō),再新建一個(gè)類Bird4繼承Bird,打印日志。再新建一個(gè)類Bird5繼承Bird4,獲取方法執(zhí)行時(shí)間。

問(wèn)題顯而易見(jiàn):使用繼承將導(dǎo)致類無(wú)限制擴(kuò)展,同時(shí)靈活性也無(wú)法獲得保障。那么,使用 聚合 是否可以避免這個(gè)問(wèn)題呢?
答案是:可以!但我們的類需要稍微改造一下。修改Bird3類,將聚合對(duì)象Bird類型修改為Flyable

public class Bird3 implements Flyable {
    private Flyable flyable;

    public Bird3(Flyable flyable) {
        this.flyable = flyable;
    }

    @Override
    public void fly() {
        long start = System.currentTimeMillis();

        flyable.fly();

        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
    }
}

為了讓你看的更清楚,我將Bird3更名為BirdTimeProxy,即用于獲取方法執(zhí)行時(shí)間的代理的意思。同時(shí)我們新建BirdLogProxy代理類用于打印日志:

public class BirdLogProxy implements Flyable {
    private Flyable flyable;

    public BirdLogProxy(Flyable flyable) {
        this.flyable = flyable;
    }

    @Override
    public void fly() {
        System.out.println("Bird fly start...");

        flyable.fly();

        System.out.println("Bird fly end...");
    }
}

接下來(lái)神奇的事情發(fā)生了,如果我們需要先記錄日志,再獲取飛行時(shí)間,可以在調(diào)用的地方這么做:

    public static void main(String[] args) {
        Bird bird = new Bird();
        BirdLogProxy p1 = new BirdLogProxy(bird);
        BirdTimeProxy p2 = new BirdTimeProxy(p1);

        p2.fly();
    }

反過(guò)來(lái),可以這么做:

 public static void main(String[] args) {
        Bird bird = new Bird();
        BirdTimeProxy p2 = new BirdTimeProxy(bird);
        BirdLogProxy p1 = new BirdLogProxy(p2);

        p1.fly();
 }

看到這里,有同學(xué)可能會(huì)有疑問(wèn)了。雖然現(xiàn)象看起來(lái),聚合可以靈活調(diào)換執(zhí)行順序。可是,為什么 聚合 可以做到,而繼承不行呢。我們用一張圖來(lái)解釋一下:

靜態(tài)代理

接下來(lái),觀察上面的類BirdTimeProxy,在它的fly方法中我們直接調(diào)用了flyable->fly()方法。換而言之,BirdTimeProxy其實(shí)代理了傳入的Flyable對(duì)象,這就是典型的靜態(tài)代理實(shí)現(xiàn)。

從表面上看,靜態(tài)代理已經(jīng)完美解決了我們的問(wèn)題。可是,試想一下,如果我們需要計(jì)算SDK中100個(gè)方法的運(yùn)行時(shí)間,同樣的代碼至少需要重復(fù)100次,并且創(chuàng)建至少100個(gè)代理類。往小了說(shuō),如果Bird類有多個(gè)方法,我們需要知道其他方法的運(yùn)行時(shí)間,同樣的代碼也至少需要重復(fù)多次。因此,靜態(tài)代理至少有以下兩個(gè)局限性問(wèn)題:

如果同時(shí)代理多個(gè)類,依然會(huì)導(dǎo)致類無(wú)限制擴(kuò)展

如果類中有多個(gè)方法,同樣的邏輯需要反復(fù)實(shí)現(xiàn)

那么,我們是否可以使用同一個(gè)代理類來(lái)代理任意對(duì)象呢?我們以獲取方法運(yùn)行時(shí)間為例,是否可以使用同一個(gè)類(例如:TimeProxy)來(lái)計(jì)算任意對(duì)象的任一方法的執(zhí)行時(shí)間呢?甚至再大膽一點(diǎn),代理的邏輯也可以自己指定。比如,獲取方法的執(zhí)行時(shí)間,打印日志,這類邏輯都可以自己指定。這就是本文重點(diǎn)探討的問(wèn)題,也是最難理解的部分:動(dòng)態(tài)代理

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

繼續(xù)回到上面這個(gè)問(wèn)題:是否可以使用同一個(gè)類(例如:TimeProxy)來(lái)計(jì)算任意對(duì)象的任一方法的執(zhí)行時(shí)間呢。

這個(gè)部分需要一定的抽象思維,我想,你腦海中的第一個(gè)解決方案應(yīng)該是使用反射。反射是用于獲取已創(chuàng)建實(shí)例的方法或者屬性,并對(duì)其進(jìn)行調(diào)用或者賦值。很明顯,在這里,反射解決不了問(wèn)題。但是,再大膽一點(diǎn),如果我們可以動(dòng)態(tài)生成TimeProxy這個(gè)類,并且動(dòng)態(tài)編譯。然后,再通過(guò)反射創(chuàng)建對(duì)象并加載到內(nèi)存中,不就實(shí)現(xiàn)了對(duì)任意對(duì)象進(jìn)行代理了嗎?為了防止你依然一頭霧水,我們用一張圖來(lái)描述接下來(lái)要做什么:

動(dòng)態(tài)生成Java源文件并且排版是一個(gè)非常繁瑣的工作,為了簡(jiǎn)化操作,我們使用 JavaPoet 這個(gè)第三方庫(kù)幫我們生成TimeProxy的源碼。希望 JavaPoet 不要成為你的負(fù)擔(dān),不理解 JavaPoet 沒(méi)有關(guān)系,你只要把它當(dāng)成一個(gè)Java源碼生成工具使用即可。

PS:你記住,任何工具庫(kù)的使用都不會(huì)太難,它是為了簡(jiǎn)化某些操作而出現(xiàn)的,目標(biāo)是簡(jiǎn)化而不是繁瑣。因此,只要你適應(yīng)它的規(guī)則就輕車熟路了。

第一步:生成TimeProxy源碼
public class Proxy {

    public static Object newProxyInstance() throws IOException {
        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TimeProxy")
                .addSuperinterface(Flyable.class);

        FieldSpec fieldSpec = FieldSpec.builder(Flyable.class, "flyable", Modifier.PRIVATE).build();
        typeSpecBuilder.addField(fieldSpec);

        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(Flyable.class, "flyable")
                .addStatement("this.flyable = flyable")
                .build();
        typeSpecBuilder.addMethod(constructorMethodSpec);

        Method[] methods = Flyable.class.getDeclaredMethods();
        for (Method method : methods) {
            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(method.getReturnType())
                    .addStatement("long start = $T.currentTimeMillis()", System.class)
                    .addCode("
")
                    .addStatement("this.flyable." + method.getName() + "()")
                    .addCode("
")
                    .addStatement("long end = $T.currentTimeMillis()", System.class)
                    .addStatement("$T.out.println("Fly Time =" + (end - start))", System.class)
                    .build();
            typeSpecBuilder.addMethod(methodSpec);
        }

        JavaFile javaFile = JavaFile.builder("com.youngfeng.proxy", typeSpecBuilder.build()).build();
        // 為了看的更清楚,我將源碼文件生成到桌面
        javaFile.writeTo(new File("/Users/ouyangfeng/Desktop/"));

        return null;
    }

}

在main方法中調(diào)用Proxy.newProxyInstance(),你將看到桌面已經(jīng)生成了TimeProxy.java文件,生成的內(nèi)容如下:

package com.youngfeng.proxy;

import java.lang.Override;
import java.lang.System;

class TimeProxy implements Flyable {
  private Flyable flyable;

  public TimeProxy(Flyable flyable) {
    this.flyable = flyable;
  }

  @Override
  public void fly() {
    long start = System.currentTimeMillis();

    this.flyable.fly();

    long end = System.currentTimeMillis();
    System.out.println("Fly Time =" + (end - start));
  }
}
第二步:編譯TimeProxy源碼

編譯TimeProxy源碼我們直接使用JDK提供的編譯工具即可,為了使你看起來(lái)更清晰,我使用一個(gè)新的輔助類來(lái)完成編譯操作:

public class JavaCompiler {

    public static void compile(File javaFile) throws IOException {
        javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable iterable = fileManager.getJavaFileObjects(javaFile);
        javax.tools.JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
        task.call();
        fileManager.close();
    }
}

在Proxy->newProxyInstance()方法中調(diào)用該方法,編譯順利完成:

// 為了看的更清楚,我將源碼文件生成到桌面
String sourcePath = "/Users/ouyangfeng/Desktop/";
javaFile.writeTo(new File(sourcePath));

// 編譯
JavaCompiler.compile(new File(sourcePath + "/com/youngfeng/proxy/TimeProxy.java"));

第三步:加載到內(nèi)存中并創(chuàng)建對(duì)象
  URL[] urls = new URL[] {new URL("file:/" + sourcePath)};
  URLClassLoader classLoader = new URLClassLoader(urls);
  Class clazz = classLoader.loadClass("com.youngfeng.proxy.TimeProxy");
  Constructor constructor = clazz.getConstructor(Flyable.class);
  Flyable flyable = (Flyable) constructor.newInstance(new Bird());
  flyable.fly();

通過(guò)以上三個(gè)步驟,我們至少解決了下面兩個(gè)問(wèn)題:

不再需要手動(dòng)創(chuàng)建TimeProxy

可以代理任意實(shí)現(xiàn)了Flyable接口的類對(duì)象,并獲取接口方法的執(zhí)行時(shí)間

可是,說(shuō)好的任意對(duì)象呢?

第四步:增加InvocationHandler接口

查看Proxy->newProxyInstance()的源碼,代理類繼承的接口我們是寫死的,為了增加靈活性,我們將接口類型作為參數(shù)傳入:

接口的靈活性問(wèn)題解決了,TimeProxy的局限性依然存在,它只能用于獲取方法的執(zhí)行時(shí)間,而如果要在方法執(zhí)行前后打印日志則需要重新創(chuàng)建一個(gè)代理類,顯然這是不妥的!

為了增加控制的靈活性,我們考慮針將代理的處理邏輯也抽離出來(lái)(這里的處理就是打印方法的執(zhí)行時(shí)間)。新增InvocationHandler接口,用于處理自定義邏輯:

public interface InvocationHandler {
    void invoke(Object proxy, Method method, Object[] args);
}

想象一下,如果客戶程序員需要對(duì)代理類進(jìn)行自定義的處理,只要實(shí)現(xiàn)該接口,并在invoke方法中進(jìn)行相應(yīng)的處理即可。這里我們?cè)诮涌谥性O(shè)置了三個(gè)參數(shù)(其實(shí)也是為了和JDK源碼保持一致):

proxy => 這個(gè)參數(shù)指定動(dòng)態(tài)生成的代理類,這里是TimeProxy

method => 這個(gè)參數(shù)表示傳入接口中的所有Method對(duì)象

args => 這個(gè)參數(shù)對(duì)應(yīng)當(dāng)前method方法中的參數(shù)

引入了InvocationHandler接口之后,我們的調(diào)用順序應(yīng)該變成了這樣:

MyInvocationHandler handler = new MyInvocationHandler();
Flyable proxy = Proxy.newProxyInstance(Flyable.class, handler);
proxy.fly();

方法執(zhí)行流:proxy.fly() => handler.invoke()

為此,我們需要在Proxy.newProxyInstance()方法中做如下改動(dòng):

在newProxyInstance方法中傳入InvocationHandler

在生成的代理類中增加成員變量handler

在生成的代理類方法中,調(diào)用invoke方法

  public static Object newProxyInstance(Class inf, InvocationHandler handler) throws Exception {
        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TimeProxy")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(inf);

        FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();
        typeSpecBuilder.addField(fieldSpec);

        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(InvocationHandler.class, "handler")
                .addStatement("this.handler = handler")
                .build();

        typeSpecBuilder.addMethod(constructorMethodSpec);

        Method[] methods = inf.getDeclaredMethods();
        for (Method method : methods) {
            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(method.getReturnType())
                    .addCode("try {
")
                    .addStatement("	$T method = " + inf.getName() + ".class.getMethod("" + method.getName() + "")", Method.class)
                    // 為了簡(jiǎn)單起見(jiàn),這里參數(shù)直接寫死為空
                    .addStatement("	this.handler.invoke(this, method, null)")
                    .addCode("} catch(Exception e) {
")
                    .addCode("	e.printStackTrace();
")
                    .addCode("}
")
                    .build();
            typeSpecBuilder.addMethod(methodSpec);
        }

        JavaFile javaFile = JavaFile.builder("com.youngfeng.proxy", typeSpecBuilder.build()).build();
        // 為了看的更清楚,我將源碼文件生成到桌面
        String sourcePath = "/Users/ouyangfeng/Desktop/";
        javaFile.writeTo(new File(sourcePath));

        // 編譯
        JavaCompiler.compile(new File(sourcePath + "/com/youngfeng/proxy/TimeProxy.java"));

        // 使用反射load到內(nèi)存
        URL[] urls = new URL[] {new URL("file:" + sourcePath)};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class clazz = classLoader.loadClass("com.youngfeng.proxy.TimeProxy");
        Constructor constructor = clazz.getConstructor(InvocationHandler.class);
        Object obj = constructor.newInstance(handler);

        return obj;
 }

上面的代碼你可能看起來(lái)比較吃力,我們直接調(diào)用該方法,查看最后生成的源碼。在main方法中測(cè)試newProxyInstance查看生成的TimeProxy源碼:

測(cè)試代碼

Proxy.newProxyInstance(Flyable.class, new MyInvocationHandler(new Bird()));

生成的TimeProxy.java源碼

package com.youngfeng.proxy;

import java.lang.Override;
import java.lang.reflect.Method;

public class TimeProxy implements Flyable {
  private InvocationHandler handler;

  public TimeProxy(InvocationHandler handler) {
    this.handler = handler;
  }

  @Override
  public void fly() {
    try {
        Method method = com.youngfeng.proxy.Flyable.class.getMethod("fly");
        this.handler.invoke(this, method, null);
    } catch(Exception e) {
        e.printStackTrace();
    }
  }
}

MyInvocationHandler.java

public class MyInvocationHandler implements InvocationHandler {
    private Bird bird;

    public MyInvocationHandler(Bird bird) {
        this.bird = bird;
    }

    @Override
    public void invoke(Object proxy, Method method, Object[] args) {
        long start = System.currentTimeMillis();

        try {
            method.invoke(bird, new Object[] {});
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
    }
}

至此,整個(gè)方法棧的調(diào)用棧變成了這樣:

看到這里,估計(jì)很多同學(xué)已經(jīng)暈了,在靜態(tài)代理部分,我們?cè)诖眍愔袀魅肓吮淮韺?duì)象。可是,使用newProxyInstance生成動(dòng)態(tài)代理對(duì)象的時(shí)候,我們居然不再需要傳入被代理對(duì)象了。我們傳入了的實(shí)際對(duì)象是InvocationHandler實(shí)現(xiàn)類的實(shí)例,這看起來(lái)有點(diǎn)像生成了InvocationHandler的代理對(duì)象,在動(dòng)態(tài)生成的代理類的任意方法中都會(huì)間接調(diào)用InvocationHandler->invoke(proxy, method, args)方法。

其實(shí)的確是這樣。TimeProxy真正代理的對(duì)象就是InvocationHandler,不過(guò)這里設(shè)計(jì)的巧妙之處在于,InvocationHandler是一個(gè)接口,真正的實(shí)現(xiàn)由用戶指定。另外,在每一個(gè)方法執(zhí)行的時(shí)候,invoke方法都會(huì)被調(diào)用 ,這個(gè)時(shí)候如果你需要對(duì)某個(gè)方法進(jìn)行自定義邏輯處理,可以根據(jù)method的特征信息進(jìn)行判斷分別處理。

如何使用

上面這段解釋是告訴你在執(zhí)行Proxy->newProxyInstance方法的時(shí)候真正發(fā)生的事情,而在實(shí)際使用過(guò)程中你完全可以忘掉上面的解釋。按照設(shè)計(jì)者的初衷,我們做如下簡(jiǎn)單歸納:

Proxy->newProxyInstance(infs, handler) 用于生成代理對(duì)象

InvocationHandler:這個(gè)接口主要用于自定義代理邏輯處理

為了完成對(duì)被代理對(duì)象的方法攔截,我們需要在InvocationHandler對(duì)象中傳入被代理對(duì)象實(shí)例。

查看上面的代碼,你可以看到我將Bird實(shí)例已經(jīng)傳入到了MyInvocationHandler中,原因就是第三點(diǎn)。

這樣設(shè)計(jì)有什么好處呢?有人說(shuō),我們大費(fèi)周章,饒了一大圈,最終變成了這個(gè)樣子,到底圖什么呢?

想象一下,到此為止,如果我們還需要對(duì)其它任意對(duì)象進(jìn)行代理,是否還需要改動(dòng)newProxyInstance方法的源碼,答案是:完全不需要!

只要你在newProxyInstance方法中指定代理需要實(shí)現(xiàn)的接口,指定用于自定義處理的InvocationHandler對(duì)象,整個(gè)代理的邏輯處理都在你自定義的InvocationHandler實(shí)現(xiàn)類中進(jìn)行處理。至此,而我們終于可以從不斷地寫代理類用于實(shí)現(xiàn)自定義邏輯的重復(fù)工作中解放出來(lái)了,從此需要做什么,交給InvocationHandler。

事實(shí)上,我們之前給自己定下的目標(biāo)“使用同一個(gè)類來(lái)計(jì)算任意對(duì)象的任一方法的執(zhí)行時(shí)間”已經(jīng)實(shí)現(xiàn)了。嚴(yán)格來(lái)說(shuō),是我們超額完成了任務(wù),TimeProxy不僅可以計(jì)算方法執(zhí)行的時(shí)間,也可以打印方法執(zhí)行日志,這完全取決于你的InvocationHandler接口實(shí)現(xiàn)。因此,這里取名為TimeProxy其實(shí)已經(jīng)不合適了。我們可以修改為和JDK命名一致,即$Proxy0,感興趣的同學(xué)請(qǐng)自行實(shí)踐,本篇文章的代碼將放到我的Github倉(cāng)庫(kù),文章結(jié)尾會(huì)給出代碼地址。

JDK實(shí)現(xiàn)揭秘

通過(guò)上面的這些步驟,我們完成了一個(gè)簡(jiǎn)易的仿JDK實(shí)現(xiàn)的動(dòng)態(tài)代理邏輯。接下來(lái),我們一起來(lái)看一看JDK實(shí)現(xiàn)的動(dòng)態(tài)代理和我們到底有什么不同。

Proxy.java

InvocationHandler

可以看到,官方版本Proxy類提供的方法多一些,而我們主要使用的接口newProxyInstance參數(shù)也和我們?cè)O(shè)計(jì)的不太一樣。這里給大家簡(jiǎn)單解釋一下,每個(gè)參數(shù)的意義:

Classloader:類加載器,你可以使用自定義的類加載器,我們的實(shí)現(xiàn)版本為了簡(jiǎn)化,直接在代碼中寫死了Classloader。

Class[]:第二個(gè)參數(shù)也和我們的實(shí)現(xiàn)版本不一致,這個(gè)其實(shí)很容易理解,我們應(yīng)該允許我們自己實(shí)現(xiàn)的代理類同時(shí)實(shí)現(xiàn)多個(gè)接口。前面設(shè)計(jì)只傳入一個(gè)接口,只是為了簡(jiǎn)化實(shí)現(xiàn),讓你專注核心邏輯實(shí)現(xiàn)而已。

最后一個(gè)參數(shù)就不用說(shuō)了,和我們實(shí)現(xiàn)的版本完全是一樣的。

仔細(xì)觀察官方版本的InvocationHandler,它和我們自己的實(shí)現(xiàn)的版本也有一個(gè)細(xì)微的差別:官方版本invoke方法有返回值,而我們的版本中是沒(méi)有返回值的。那么,返回值到底有什么作用呢?直接來(lái)看官方文檔:

核心思想:這里的返回值類型必須和傳入接口的返回值類型一致,或者與其封裝對(duì)象的類型一致。

遺憾的是,這里并沒(méi)有說(shuō)明返回值的用途,其實(shí)這里稍微發(fā)揮一下想象力就知道了。在我們的版本實(shí)現(xiàn)中,F(xiàn)lyable接口的所有方法都是沒(méi)有返回值的,問(wèn)題是,如果有返回值呢?是的,你沒(méi)有猜錯(cuò),這里的invoke方法對(duì)應(yīng)的就是傳入接口中方法的返回值。

答疑解惑
invoke方法的第一個(gè)參數(shù)proxy到底有什么作用?

這個(gè)問(wèn)題其實(shí)也好理解,如果你的接口中有方法需要返回自身,如果在invoke中沒(méi)有傳入這個(gè)參數(shù),將導(dǎo)致實(shí)例無(wú)法正常返回。在這種場(chǎng)景中,proxy的用途就表現(xiàn)出來(lái)了。簡(jiǎn)單來(lái)說(shuō),這其實(shí)就是最近非常火的鏈?zhǔn)骄幊痰囊环N應(yīng)用實(shí)現(xiàn)。

動(dòng)態(tài)代理到底有什么用?

學(xué)習(xí)任何一門技術(shù),一定要問(wèn)一問(wèn)自己,這到底有什么用。其實(shí),在這篇文章的講解過(guò)程中,我們已經(jīng)說(shuō)出了它的主要用途。你發(fā)現(xiàn)沒(méi),使用動(dòng)態(tài)代理我們居然可以在不改變?cè)创a的情況下,直接在方法中插入自定義邏輯。這有點(diǎn)不太符合我們的一條線走到底的編程邏輯,這種編程模型有一個(gè)專業(yè)名稱叫 AOP。所謂的AOP,就像刀一樣,抓住時(shí)機(jī),趁機(jī)插入。

基于這樣一種動(dòng)態(tài)特性,我們可以用它做很多事情,例如:

事務(wù)提交或回退(Web開發(fā)中很常見(jiàn))

權(quán)限管理

自定義緩存邏輯處理

SDK Bug修復(fù)

...

如果你閱讀過(guò) Android_Slide_To_Close 的源碼會(huì)發(fā)現(xiàn),它也在某個(gè)地方使用了動(dòng)態(tài)代理設(shè)計(jì)模式。

總結(jié)

到此為止,關(guān)于動(dòng)態(tài)代理的所有講解已經(jīng)結(jié)束了,原諒我使用了一個(gè)誘導(dǎo)性的標(biāo)題“騙”你進(jìn)來(lái)閱讀這篇文章。如果你不是一個(gè)久經(jīng)沙場(chǎng)的“老司機(jī)”,10分鐘完全看懂動(dòng)態(tài)代理設(shè)計(jì)模式還是有一定難度的。但即使沒(méi)有看懂也沒(méi)關(guān)系,如果你在第一次閱讀完這篇文章后依然一頭霧水,就不妨再仔細(xì)閱讀一次。在閱讀的過(guò)程中,一定要跟著文章思路去敲代碼。反反復(fù)復(fù),一定會(huì)看懂的。我在剛剛學(xué)習(xí)動(dòng)態(tài)代理設(shè)計(jì)模式的時(shí)候就反復(fù)看了不下5遍,并且親自敲代碼實(shí)踐了多次。

為了讓你少走彎路,我認(rèn)為看懂這篇文章,你至少需要學(xué)習(xí)以下知識(shí)點(diǎn):

至少已經(jīng)理解了面向?qū)ο笳Z(yǔ)言的多態(tài)特性

了解簡(jiǎn)單的反射用法

會(huì)簡(jiǎn)單使用 JavaPoet 生成Java源碼

如果你在閱讀文章的過(guò)程中,有任何不理解的問(wèn)題或者建議,歡迎在文章下方留言告訴我!

本篇文章例子代碼:https://github.com/yuanhoujun/java-dynamic-proxy

我是歐陽(yáng)鋒,設(shè)計(jì)模式是一種非常好的編程指導(dǎo)模型,它在所有編程語(yǔ)言中是通用的,并且是亙古不變的。我建議你在這個(gè)方面多下苦功,不要糾結(jié)在一些重復(fù)的勞動(dòng)中,活用設(shè)計(jì)模式會(huì)讓你的代碼更顯靈動(dòng)。想要了解我嗎?看這里:歐陽(yáng)鋒檔案館。

編程,我們是認(rèn)真的! 關(guān)注歐陽(yáng)鋒工作室公眾號(hào),你想要的都在這里:

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/68681.html

相關(guān)文章

  • 【 全干貨 】5 分鐘帶你看懂 Docker !

    摘要:本文從定義,作用,技術(shù)架構(gòu),安裝和使用等全方位帶你看懂。如圖中左邊紅框中和右邊的紅框中都唯一表示為同一個(gè)鏡像。最后,于開發(fā)者而言提供了一種開發(fā)環(huán)境的管理辦法,與測(cè)試人員而言保證了環(huán)境的同步,于運(yùn)維人員提供了可移植的標(biāo)準(zhǔn)化部署流程。 作者丨唐文廣:騰訊工程師,負(fù)責(zé)無(wú)線研發(fā)部地圖測(cè)試。 導(dǎo)語(yǔ):Docker,近兩年才流行起來(lái)的超輕量級(jí)虛擬機(jī),它可以讓你輕松完成持續(xù)集成、自動(dòng)交付、自動(dòng)部署...

    Edison 評(píng)論0 收藏0
  • 【 全干貨 】5 分鐘帶你看懂 Docker !

    摘要:本文從定義,作用,技術(shù)架構(gòu),安裝和使用等全方位帶你看懂。最后,于開發(fā)者而言提供了一種開發(fā)環(huán)境的管理辦法,與測(cè)試人員而言保證了環(huán)境的同步,于運(yùn)維人員提供了可移植的標(biāo)準(zhǔn)化部署流程。顯示上圖內(nèi)容就表明安裝完成。 作者丨唐文廣:騰訊工程師,負(fù)責(zé)無(wú)線研發(fā)部地圖測(cè)試。 導(dǎo)語(yǔ):Docker,近兩年才流行起來(lái)的超輕量級(jí)虛擬機(jī),它可以讓你輕松完成持續(xù)集成、自動(dòng)交付、自動(dòng)部署,并且實(shí)現(xiàn)開發(fā)環(huán)境、測(cè)試環(huán)...

    lavnFan 評(píng)論0 收藏0
  • 3分鐘看懂Activity啟動(dòng)流程

    摘要:在結(jié)合下面簡(jiǎn)要的分析,分鐘內(nèi)你就能搞明白的啟動(dòng)流程。關(guān)于的啟動(dòng),我在驚天秘密從開始,揭露線程通訊的詭計(jì)和主線程的陰謀一文中有提到過(guò)。從上圖可以看到,方法中主要做的事情有初始化主線程的主。并使主線程進(jìn)入等待接收消息的無(wú)限循環(huán)狀態(tài)。 showImg(https://segmentfault.com/img/remote/1460000009912818); 背景介紹 從事開發(fā)到了一定階段,...

    bang590 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<