重學設計模式——代理模式之手寫JDK動態實現

代理模式

爲其餘對象提供一種代理以控制對這個對象的訪問。java

最重要的三要素:app

  • 有執行者、被代理人
  • 對被代理人來講,這件事是必定要去作,可是本身由於某些緣由暫時不能去作,只能經過代理來作
  • 代理能獲取到被代理人的資料(拿到被代理人的引用)

動態代理

動態代理經過反射機制動態地生成代理者的對象,咱們在code的時候沒必要要關心代理誰,代理誰咱們將在執行階段來決定。JDK爲咱們已提供了很方便的動態代理接口InvocationHandlerjvm

先來定義一個場景,我要去申請專利,這個時候就能夠經過專利代理人來進行專利申請。那麼我就是被代理者,專利代理人就是代理。ide

業務接口測試

public interface Person {
    void apply();
}
複製代碼

業務實現類ui

public class Apkcore implements Person {
    @Override
    public void apply() {
        System.out.println("apkcore開始申請專利");
    }
}
複製代碼

業務處理類(專利代理人)this

public class PatentAgent implements InvocationHandler {

    private Person mPersion;

    public Object getInstance(Person persion) {
        this.mPersion = persion;
        Class clazz = persion.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(mPersion, args);
        after();
        return result;
    }

    private void after() {
        System.out.println("申請提交成功了");
    }

    private void before() {
        System.out.println("準備申請專利的材料");
    }
}
複製代碼

測試類spa

public static void main(String[] args) {
        PatentAgent patentAgent = new PatentAgent();
        Person person = (Person) patentAgent.getInstance(new Apkcore());
        person.apply();
    }
複製代碼

能夠看到生成的結果爲:.net

準備申請專利的材料
apkcore開始申請專利
申請提交成功了
複製代碼

到此,使用JDK給咱們提供的InvocationHandler實現動態代理就已經完成了。代理

動態代理的實現原理

咱們能夠打印一下測試中獲得的person對象

PatentAgent patentAgent = new PatentAgent();
        Person person = (Person) patentAgent.getInstance(new Apkcore());
        System.out.println(person.getClass());
        person.apply();
複製代碼

能夠看到輸出結果爲class com.sun.proxy.$Proxy0

咱們在生成的class中,是不能找到這個$Proxy0的,它只是JVM在內存中生成的動態代理類。

那麼咱們能夠把這個類用一個文件寫出來。

//獲取字節碼內容
        byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Persion.class});
        FileOutputStream os = null;
        try {
            String path = "$Proxy0.class";
            File file = new File(path);
            if (file.exists()) {
                file.delete();
            }
            os = new FileOutputStream("$Proxy0.class");
            os.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != os) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
複製代碼

能夠在根目錄下生成$Proxy0.class文件,打開這個文件

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //這是person.apply方法調用的地方
    public final void apply() throws {
        try {
            //是調用了父類的h的invoke方法
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            //m3就是apply方法,另外三個都是Object的方法,能夠忽略不看
            m3 = Class.forName("com.apkcore.proxy.blog.Person").getMethod("apply");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製代碼

能夠看到,主要就看apply方法,這是咱們調用的方法super.h在父類Proxy中

protected InvocationHandler h;
複製代碼

而咱們在PatentAgent中,使用Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)這個this就是PatentAgent,它是個InvocationHandler的實現類,因此能夠知道,super.h.invoke其實就是調用了PatentAgent中的invoke方法,而invoke的第二個參數m3就是m3 = Class.forName("com.apkcore.proxy.blog.Person").getMethod("apply");,因此咱們在PatentAgent中調用method.invoke(mPersion, args)就至關於mPerson.apply方法。

動態代理的過程:

  • Proxy經過傳遞給它的參數(interface/invocationHandler)生成代理類$Proxy0
  • Proxy經過傳遞給它的參數(ClassLoade)來加載生成的代理類$Proxy0的字節碼文件

手寫JDK動態代理

咱們先看源碼中InvocationHandler

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
複製代碼

因此咱們能夠模仿着實現本身的InvocationHandler

public interface MyInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
複製代碼

那麼對應的,咱們的動態代理PatentAgent要改成

public class MyPatentAgent implements MyInvocationHandler {

    private Person mPersion;

    public Object getInstance(Person persion) {
        this.mPersion = persion;
        Class clazz = persion.getClass();
        return MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(mPersion, args);
        after();
        return result;
    }

    private void after() {
        System.out.println("申請提交成功了");
    }

    private void before() {
        System.out.println("準備申請專利的材料");
    }
}
複製代碼

其中的ClassLoader與Proxy都使用本身的而不是JDK提供的。

public class MyProxy {
    public static Object newProxyInstance(MyClassLoader classLoader, Class[] interfaces, MyPatentAgent h) {
        return null;
    }
}

public class MyClassLoader extends ClassLoader{
}
複製代碼
生成java文件並保存

由上面的知識咱們能夠知道,咱們要先在運行時生成一個$Proxy0這樣的java文件

public class MyProxy {

    private static final String ln = "\r\n";

    public static Object newProxyInstance(MyClassLoader classLoader, Class[] interfaces, MyPatentAgent h) {
        if (h == null) {
            throw new NullPointerException();
        }
        //1.生成源代碼$MyProxy0這個源文件
        //爲了實現方便,默認只代理一個接口
        String proxySrc = generateSrc(interfaces[0]);
        //2.保存到文件
        String filePath = MyProxy.class.getResource("").getPath();
        File f = new File(filePath + "$MyProxy0.java");
        FileWriter fw = null;
        try {
            fw = new FileWriter(f);
            fw.write(proxySrc);
            fw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fw) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return null;
    }

    private static String generateSrc(Class interfaces) {
        Method[] methods = interfaces.getMethods();
        StringBuilder sb = new StringBuilder();
        sb.append("package ")
                .append(MyProxy.class.getPackage().getName()).append(";").append(ln)
                .append("import java.lang.reflect.Method;").append(ln)
                .append("public class $MyProxy0 implements ").append(interfaces.getName()).append("{").append(ln)
                .append("MyInvocationHandler h;").append(ln)
                .append("public $MyProxy0(MyInvocationHandler h) {").append(ln)
                .append("this.h = h;").append(ln)
                .append("}").append(ln);

        for (Method method : methods) {
            sb.append("public ")
                    //使用最簡單的不傳參的方法
                    .append(method.getReturnType().getName()).append(" ").append(method.getName())
                    .append(" () {").append(ln)
                    .append("try {").append(ln)
                    .append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\",new Class[]{});").append(ln)
                    .append("this.h.invoke(this,m,null);").append(ln)
                    .append("}catch(Throwable throwable){ }").append(ln)
                    .append("}").append(ln);
        }
        sb.append("}");

        return sb.toString();
    }
}
複製代碼

append手寫代碼的時候,能夠邊寫邊生成,查看哪裏寫得有問題

調用

public static void main(String[] args) {
        MyPatentAgent patentAgent = new MyPatentAgent();
// Person person = (Person)
                patentAgent.getInstance(new Apkcore());
// System.out.println(person.getClass());
// person.apply();

    }
}
複製代碼

來進行文件的生成,生成的文件在

而後把這個java文件複製到咱們的工程中,能夠查看咱們是否寫漏或者寫錯。

生成的$MyProxy0文件內容以下

package com.apkcore.proxy.blog.custom;

import java.lang.reflect.Method;

public class $MyProxy0 implements com.apkcore.proxy.blog.Person {
    MyInvocationHandler h;

    public $MyProxy0(MyInvocationHandler h) {
        this.h = h;
    }

    public void apply() {
        try {
            Method m = com.apkcore.proxy.blog.Person.class.getMethod("apply", new Class[]{});
            this.h.invoke(this, m, null);
        } catch (Throwable throwable) {
        }
    }
}
複製代碼
把java文件編譯成class文件

JavaCompiler:jdk用來編譯java的源程式,提供在運行期動態編譯java代碼爲字節碼的功能

//3.編譯源代碼,並生成class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manager.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();
複製代碼

運行測試代碼,就會在編譯目錄下生成一個class文件。如圖

將class文件中的內容,加載到JVM中

如今咱們已經獲得了咱們所須要的class文件,只須要把它加載到jvm中便可

//4.將class文件中的內容動態加載到JVM中
            //5.返回被代理的代理對象
            Class proxyClass = classLoader.findClass("$MyProxy0");
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
            return constructor.newInstance(h);
複製代碼

整個MyProxy的代碼以下

public class MyProxy {

    private static final String ln = "\r\n";

    public static Object newProxyInstance(MyClassLoader classLoader, Class[] interfaces, MyPatentAgent h) {
        if (h == null) {
            throw new NullPointerException();
        }
        //1.生成源代碼$MyProxy0這個源文件
        //爲了實現方便,默認只代理一個接口
        String proxySrc = generateSrc(interfaces[0]);
        //2.保存到文件
        String filePath = MyProxy.class.getResource("").getPath();
        File f = new File(filePath + "$MyProxy0.java");
        FileWriter fw = null;
        try {
            fw = new FileWriter(f);
            fw.write(proxySrc);
            fw.flush();

            //3.編譯源代碼,並生成class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manager.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();

            //4.將class文件中的內容動態加載到JVM中
            //5.返回被代理的代理對象
            Class proxyClass = classLoader.findClass("$MyProxy0");
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != fw) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    private static String generateSrc(Class interfaces) {
        Method[] methods = interfaces.getMethods();
        StringBuilder sb = new StringBuilder();
        sb.append("package ")
                .append(MyProxy.class.getPackage().getName()).append(";").append(ln)
                .append("import java.lang.reflect.Method;").append(ln)
                .append("public class $MyProxy0 implements ").append(interfaces.getName()).append("{").append(ln)
                .append("MyInvocationHandler h;").append(ln)
                .append("public $MyProxy0(MyInvocationHandler h) {").append(ln)
                .append("this.h = h;").append(ln)
                .append("}").append(ln);

        for (Method method : methods) {
            sb.append("public ")
                    //使用最簡單的不傳參的方法
                    .append(method.getReturnType().getName()).append(" ").append(method.getName())
                    .append(" () {").append(ln)
                    .append("try {").append(ln)
                    .append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\",new Class[]{});").append(ln)
                    .append("this.h.invoke(this,m,null);").append(ln)
                    .append("}catch(Throwable throwable){ }").append(ln)
                    .append("}").append(ln);
        }
        sb.append("}");

        return sb.toString();
    }
}
複製代碼

能夠知道,咱們如今只須要在MyClassLoader中findClasss進行加載

public class MyClassLoader extends ClassLoader{

    private File baseDir;

    public MyClassLoader() {
        this.baseDir = new File(MyClassLoader.class.getResource("").getPath());
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName()+"."+name;
        if (null!=baseDir) {
            File classFile = new File(baseDir,name.replaceAll("\\.","/")+".class");
            if (classFile.exists()){
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try {
                    //先把class文件轉化爲字節流
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1) {
                        out.write(buff, 0, len);
                    }
                    //使用defineClass把字節流傳jvm中
                    return defineClass(className, out.toByteArray(), 0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (null != in) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (null!=out){
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return super.findClass(name);
    }
}
複製代碼

運行測試代碼

public static void main(String[] args) {
        MyPatentAgent patentAgent = new MyPatentAgent();
        Person person = (Person)
                patentAgent.getInstance(new Apkcore());
        System.out.println(person.getClass());
        person.apply();

    }

class com.apkcore.proxy.blog.custom.$MyProxy0 準備申請專利的材料 apkcore開始申請專利 申請提交成功了 複製代碼

這時咱們發現,在咱們的編譯目錄下是有$MyProxy0.,java和.class兩個文件存在的,咱們也能夠在使用完以後增長一個刪除語句,分別在MyProxy和MyClassLoader的finally語句中加入文件delete()方法就好了

總結

經過手寫了JDK的動態代理,對代理模式的動態代理認識又加深了一步,原理就是拿到被代理對象的引用,而後獲取它的接口,jdk代理從新生成一個類,同時實現咱們給的代理對象所實現的接口,把被代理的對象引用也拿到了,從新動態生成一個class字節碼,而後編譯。


參考

手寫jdk動態代理


個人CSDN

下面是個人公衆號,歡迎你們關注我

相關文章
相關標籤/搜索