设计模式之代理模式(一)

136 阅读4分钟

一、什么是代理

现在老板安排了一个活下来让我来做,我正好有时间,且我也会做,那我直接做了。这也是我们平时较多的场景。那么,现在情况发生了变化,老板把活安排下来后,我这边没时间做,所以我现在去找个人帮我做了,此时对于老板来说,同样的活我还是正常做完了。这个过程,我们就叫做代理。

在上面描述过程中,我们可以发现,代理出现了两个角色:我和帮我干活的人。

在这里,我们将具体干活的人称为执行人,将我称为被代理人。

二、JDK代理

2.1 场景

作为开发者,现在来了一个活:打印今天是几月几号?

2.2 准备工作

2.2.1 准备一个接口

为了后续代码重用,我们准备一个开发者接口,这个接口中有一个打印日期的方法。具体代码如下:

public interface Developer {
    void printTodayDate();
}
2.2.2 接口实现

首先,我是一名开发者,我是公子奇,所以我的具体实现如下:

public class DevGongZiQi implements Developer {
    @Override
    public void printTodayDate() {
        System.out.println("公子奇告诉我们:今天的日期是【" + LocalDate.now() + "】");
    }
}
2.2.3 不使用代理

我们先来看看,不使用代理的情况下,这个具体的活是由谁来做的。

public class NoProxyMain {
    public static void main(String[] args) {
        // 实例化一个开发人员
        Developer developer = new DevGongZiQi();
        System.out.println("看看不使用代理的情况下,具体干活的对象类型: " + developer.getClass());

        // 开始干活
        developer.printTodayDate();
    }
}

我们可以观察上面的输出:

看看不使用代理的情况下,具体干活的对象类型: class com.hz.design.mode.proxy.DevGongZiQi 公子奇告诉我们:今天的日期是【2022-06-14】

根据上面的信息,我们知道具体干活的就是 DevGongZiQi。

2.3 使用代理

现在情况发生了变化,我想让开发人员张三来帮我做这件事,所以我们需要新建一个张三的代理。具体代码如下:

public class DevZhangSan implements InvocationHandler {

    Developer developer;

    // 将具体执行对象【代理实例】返回
    public Object getInstance(Developer developer) {
        this.developer = developer;
        System.out.println("代理之前,代理的对象类型为:" + developer.getClass().getName());
// 从这里我们也可知道,为什么我们要定义接口,第二个参数需要的必须为接口类型
        return Proxy.newProxyInstance(DevZhangSan.class.getClassLoader(), developer.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.developer, args);
    }
}

在上面的代码中,此时开发人员张三,不仅仅是一名普通的开发人员,他同时也是一个代理人员。所以,我们将开发者接口作为一个引用放入代理类中,即:开发者接口引用哪个对象,则代理类就执行谁的具体方法。

2.3.1 执行

当使用代理后,我们执行情况如下:

public class DevProxyMain {
    public static void main(String[] args) {
        Developer developer = (Developer) new DevZhangSan().getInstance(new DevGongZiQi());
        System.out.println("使用代理后,具体干活的对象类型为: " + developer.getClass().getName());

        developer.printTodayDate();
    }
}

结果:代理之前,代理的对象类型为:com.hz.design.mode.proxy.DevGongZiQi 使用代理后,具体干活的对象类型为: com.sun.proxy.$Proxy0 公子奇告诉我们:今天的日期是【2022-06-14】

根据上面结果,我们发现具体干活的对象类型已经发生了变化为 $Proxy0,不过由于他代理的是 DevGongZiQi 的活,所以结果不变,外部看来,还是公子奇做的。

2.4 代理分析

为了更好的观察结果,我们将代理 developer 对应的 class 文件给输出,并反编译查看,JDK的代理后背后帮我们做了哪些工作:

public class DevProxyMain {
    public static void main(String[] args) throws IOException {
        Developer developer = (Developer) new DevZhangSan().getInstance(new DevGongZiQi());
        System.out.println("使用代理后,具体干活的对象类型为: " + developer.getClass().getName());

        developer.printTodayDate();

        String path = new DevProxyMain().getClass().getResource("").getPath();
// 获取developer的字节码信息        
byte[] classBytes = ProxyGenerator.generateProxyClass("$Proxy0", developer.getClass().getInterfaces());
// 将字节码输出到一个 .class 文件        
FileOutputStream out = new FileOutputStream(new File(path, "$Proxy0.class"));
        out.write(classBytes);
        out.close();

    }
}

此时,我们可以在 DevProxyMain 类的同级编译目录下找到一个 $Proxy0.class 文件。IDEA自带了反编译,打开后即为代理背后帮我们生成的一个java文件。这里我们列出主要的信息:

// 这里导入相关反射类
....

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

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

    public final boolean equals(Object var1) throws  {
        ...
    }

// 重点看看这段代码
    public final void printTodayDate() throws  {
        try {
// h:在Proxy类中,为InvocationHandler的一个引用,
// 这也是为什么我们在定义DevZhangSan时,实现InvocationHandler,
// 且在返回代理方法中,传入this的原因。
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        ...
    }

    public final int hashCode() throws  {
        ...
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
// 重点看看这一行            
m3 = Class.forName("com.hz.design.mode.proxy.Developer").getMethod("printTodayDate");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

三、实现自己的代理

在JDK代理中,我们在实现代理对象时,需要实现接口InvocationHandler,那么我们可不可以不使用JDK代理,完全实现自己的代理框架呢?

3.1 参考JDK代理

在前面实现代理的过程及 2.4 节中,我们可以发现,JDK代理主要有以下几个关键类和接口:

Proxy.newProxyInstance(DevZhangSan.class.getClassLoader(), developer.getClass().getInterfaces(), this);

  • 一个类加载器 ClassLoader,即 DevZhangSan.class.getClassLoader()
  • 一个 InvocationHandler 实现,即 this
  • 一个Proxy

3.2 创建自己代理的相关接口和类

既然JDK需要上面三个类和接口,那么我们就参照他,也来健三个:

// 类加载器
public class HZClassLoader extends ClassLoader {

    private File baseDir;

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

// 在编译目录下,找到编译成功的 name 类
    public Class<?> findClass(String name) {
        String className = HZClassLoader.class.getPackage().getName() + "." + name;
        if (baseDir != null) {
            File classFile = new File(baseDir, name.replaceAll("\.", "/") + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                ByteArrayOutputStream out = null;

                try {
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = in.read(buffer)) != -1) {
                        out.write(buffer, 0, len);
                    }

                    return defineClass(className, out.toByteArray(), 0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
        return null;
    }
}


// 自己的调用处理逻辑接口
public interface HZInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}


// 自己的代理逻辑
public class HZProxy {
    HZInvocationHandler h;

    public static Object newProxyInstance(HZClassLoader loader, Class<?>[] interfaces, HZInvocationHandler h) throws IllegalArgumentException {

        try {
            // 1. 生成代理类源码 .java
            String src = generateSrc(interfaces[0]);

            String baseDir = HZProxy.class.getResource("").getPath();
            FileWriter srcFile = new FileWriter(new File(baseDir, "$HZProxy0.java"));
            srcFile.write(src);
            srcFile.close();

// 平时我们开发中,都是由IDEA帮我们编译的,这里需要我们自己使用代码来完成编译过程
            // 2. 编译代理类 .class
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> fileObjects = manager.getJavaFileObjects(new File(baseDir, "$HZProxy0.java"));

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

            manager.close();

            // 3. 将 class载入JVM

            // 4. 返回代理对象
            Class<?> proxyClass = loader.findClass("$HZProxy0");
            Constructor<?> constructor = proxyClass.getConstructor(HZInvocationHandler.class);
            return constructor.newInstance(h);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

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

    // 生成源码,相当于我们上面反编译 $Proxy.class 的过程
    private static String generateSrc(Class interfaceType) {
        StringBuffer src = new StringBuffer();

        src.append("package com.hz.design.mode.proxy;" + ln);
        src.append("import java.lang.reflect.Method;" + ln);

        src.append("public class $HZProxy0 implements " + interfaceType.getName() + " {" + ln);

        src.append("HZInvocationHandler h;" + ln);

        src.append("public $HZProxy0(HZInvocationHandler h) {" + ln);
        src.append("this.h = h;" + ln);
        src.append("}" + ln);

        Method[] methods = interfaceType.getDeclaredMethods();
        for (Method method : methods) {
            src.append("public " + method.getReturnType() + " " + method.getName() + "() {" + ln);
            src.append("try {" + ln);
            src.append("Method m = " + interfaceType.getName() + ".class.getMethod(""+method.getName()+"", new Class[]{});" + ln);
            src.append("this.h.invoke(this, m ,null);" + ln);
            src.append("} catch (Throwable e) {e.printStackTrace();}" +ln);
            src.append("}" + ln);
        }

        src.append("}" + ln);

        return src.toString();
    }
}

3.3 验证自己的代理

这里我们让李四来代理我们自己的:

public class DevLiSi implements HZInvocationHandler {
    private Developer developer;

    public Object getInstance(Developer developer) {
        this.developer = developer;
        System.out.println("实现代理前的类型: " + this.developer.getClass().getName());
        return HZProxy.newProxyInstance(new HZClassLoader(), developer.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.writeCode, args);
    }
}


public class Main {
    public static void main(String[] args) {
        Developer proxyLiSi = (Developer) new DevLiSi().getInstance(new DevGongZiQi());
        System.out.println("生成的代理类: " + proxyLiSi.getClass().getName());
        proxyLiSi.printTodayDate();
    }
}