设计模式之代理,手动实现动态代理,揭秘原理实现

  • 时间:
  • 浏览:0

前言

  开心一刻

    周末,带着一个女人儿子一起逛公园。儿子另四个 人跑在前面,吧唧一下不小心摔了一跤,脑袋瓜子摔了个包,稀里哗啦的哭道:“爸爸,我会不不摔成傻子!”

    我指了指我背后的伤痕安慰道:“不不的,你看,这是爸爸小刚刚摔的。”

    话还没有说话,小家伙哭的更厉害了:“那后来我说我长大后就会和你一样傻了,我太少,我太少!”

    一个女人忍不住发飙:“别哭了,你缘何会变傻呢?你看你爸,你爸傻吗?”

    我赶紧发表声明 道:“是啊,你看我多聪明!”

    儿子:“真的,不骗我?”

    一个女人:“当然!”

    儿子:“另另四个 可能老爸都在傻子,当年缘何会娶你刚刚 母老虎呢?”

    我、一个女人:……

哪此是代理模式

  所谓代理,后来我另四个 人可能另四个 机构代表另另四个 人可能另另四个 机构采取行动。在刚刚 状态下,另四个 客户我应该 可可不可否 直接引用另四个 对象,而代理对象可不可否 在客户端和目标对象之间起到中介的左右。

  代理模式:给某另四个 对象提供另四个 代理或占位符,并由代理对象来控制对原对象的访问,通过代理对象访问目标对象,另另四个 可不可否 在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。说简单点,代理模式后来我设置另四个 底下代理来控制访问原目标对象,以达到增强原对象的功能和复杂化访问最好的土办法。一般而言会分并有无:静态代理、动态代理和CGLIB代理

  代理模式型态如下:

静态代理

  静态代理需用代理对象和被代理对象实现一样的接口,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 来看个例子就清楚了

  示例代理:static-proxy

  代理类:UserDaoProxy.java

  UserDaoProxy代理IUserDao类型,此时后来我到代理IUserDao类型的被代理对象。测试结果就不展示了,相信其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 想看 代码也知道了

  优点:可不可否 在不修改目标对象的前提下扩展目标对象的功能

  缺点:可能需用代理多个类,每个类都在有另四个 代理类,会原因分析 代理类无限制扩展;可能类暗含多个最好的土办法,同样的代理逻辑需用反复实现、应用到每个最好的土办法上,一旦接口增加最好的土办法,目标对象与代理对象都在进行修改

  另四个 静态代理不到代理另四个 类,没有有没有哪此最好的土办法可不可否 实现同另四个 代理类来代理任意对象呢?肯定有的,也后来我下面讲到的:动态代理

动态代理

  代理类在线程运行运行时创建的代理最好的土办法被成为动态代理。 也后来我说,刚刚 状态下,代理类并都在在Java代码中定义的,后来我在运行时根据其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 在Java代码中的“指示”动态生成的。下面其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 一步一步手动来实现动态代理。下面的示例都在直接针对接口的,就都在针对接口的具体实现类了,静态代理示例中,UserDaoProxy代理的是IUserDao的实现类:UserDaoImpl,没有动态代理示例就直接针对接口了,下面示例针对的都在UserMapper接口,模拟的mybatis,但不局限于UserMapper接口

  代理类源代码持久化

    1、先利用反射动态生成代理类,并持久化代理类到磁盘(也后来我生成代理类的java源文件),generateJavaFile最好的土办法如下

      生成的代理类:$Proxy0.java 如下

      刚刚 代理类的生成过程是其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 所有人实现的,实现好难,但排版太繁琐,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 可不可否 用javapoet来生成代理类源代码,generateJavaFileByJavaPoet最好的土办法如下

      生成的代理类:JavaPoet$Proxy0.java 如下

    利用javapoet生成的代理类更接近其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 平时手动实现的类,排版更符合其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 的编码习惯,看上去更自然刚刚 ;两者的实现过程是一样的,后来我javapoet排版更好

    2、既然代理类的源代码可能有了,没有需用对其编译了,compileJavaFile最好的土办法如下

      会在指定目录下想看 :$Proxy0.class

    3、加载$Proxy0.class,并创建嘴笨 例对象(代理实例对象)

public static <T> T newInstance(Class<T> interface_) throws Exception{
    String proxyJavaFileDir = SRC_JAVA_PATH + interface_.getPackage().getName().replace(".", File.separator) + File.separator;

    // 1、生成interface_接口的实现类,并持久化到磁盘:$Proxy0.java
    generateJavaFile(interface_, proxyJavaFileDir);

    // 2、编译$Proxy0.java,生成$Proxy0.class到磁盘
    compileJavaFile(proxyJavaFileDir);

    // 3、加载$Proxy0.class,并创建嘴笨

例对象(代理实例对象)
    MyClassLoader loader = new MyClassLoader(proxyJavaFileDir, interface_);
    Class<?> $Proxy0 = loader.findClass(PROXY_CLASS_NAME);
    return (T)$Proxy0.newInstance();
}

private static class MyClassLoader<T> extends ClassLoader {

    private String proxyJavaFileDir;
    private Class<T> interface_;

    public MyClassLoader(String proxyJavaFileDir, Class<T> interface_) {
        this.proxyJavaFileDir = proxyJavaFileDir;
        this.interface_ = interface_;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        File clazzFile = new File(proxyJavaFileDir, name + ".class");
        //可能字节码文件所处
        if (clazzFile.exists()) {
            //把字节码文件加载到VM
            try {
                //文件流对接class文件
                FileInputStream inputStream = new FileInputStream(clazzFile);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);                     // 将buffer中的内容读取到baos中的buffer
                }
                //将buffer中的字节读到内存加载为class
                return defineClass(interface_.getPackage().getName() + "." + name, baos.toByteArray(), 0, baos.size());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return super.findClass(name);
    }
}
View Code

      有了代理实例对象,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 就可不可否 利用它进行操作了,演示结果如下

    详细工程地址:proxy-java-file,详细流程图如下

    此时的Proxy类能创建任何接口的实例,正确处理了静态代理所处的代理类泛滥、多个最好的土办法中代理逻辑反复实现的问題;但有个问題谁能谁能告诉我其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 注意到:$Proxy0.java有必要持久化到磁盘吗,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 可不可否 直接编译内存中的代理类的字符串源代码,得到$Proxy0.class呢?

  代理类源代码不持久化

    $Proxy0.java和$Proxy0.class是没必要生成到磁盘的,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 直接编译内存中的代理类的字符串源代码,一起直接在内存中加载$Proxy0.class,不不写、读磁盘,可不可否 提升不少性能

    详细工程地址:proxy-none-java-file,此时的流程图如下

    Proxy.java源代码如下

    相比有代理类源代码持久化,核心的动态代理生成过程不变,后来我减少了.java和.class文件的持久化;其中用到了第三方工具:com.itranswarp.compile(其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 也可不可否 拓展jdk,实现内存中操作),完成了字符串在内存中的编译、class在内存中的加载,直接用jdk的编译工具,会在磁盘生成$Proxy0.class

    测试结果如下

      可不可否 想看 ,没有.java和.class的持久化

    此时就完美了吗?可能现在有另外另四个 接口ISendMessage,代理逻辑都在

System.out.println("数据库操作, 并获取执行结果...")

    其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 该缘何办? 针对ISendMessage又重新写另四个 Proxy?显然还不足灵活,说的简单点:此种代理可不可否 代理任何接口,后来我代理逻辑确是固定死的,不到自定义,另另四个 会造成并有无代理逻辑会有另四个 代理工厂(Proxy),会造成代理工厂的泛滥

  代理逻辑接口化,供用户自定义

    既然无代理类源代码持久化中的代理逻辑不到自定义,没有其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 就将它抽出来,提供代理逻辑接口

    详细工程地址:proxy-none-java-file-plus,流程图与无代理类源代码持久化中一样,此时代理类的生成过程复杂化了不少,涉及到代理逻辑接口:InvacationHandler的正确处理

    generateJavaFile(...)最好的土办法

/**
 * 生成接口实现类的源代码
 * @param interface_
 * @throws Exception
 */
private static String generateJavaFile(Class<?> interface_, InvocationHandler handler) throws Exception {
    StringBuilder proxyJava = new StringBuilder();
    proxyJava.append("package ").append(PROXY_PACKAGE_NAME).append(";").append(ENTER).append(ENTER)
            .append("import java.lang.reflect.Method;").append(ENTER).append(ENTER)
            .append("public class ").append(PROXY_FILE_NAME).append(" implements ").append(interface_.getName()).append(" {").append(ENTER)
            .append(ENTER).append(TAB_STR).append("private InvocationHandler  handler;").append(ENTER).append(ENTER);

    // 代理对象构造最好的土办法
    proxyJava.append(TAB_STR).append("public ").append(PROXY_FILE_NAME).append("(InvocationHandler handler) {").append(ENTER)
            .append(TAB_STR).append(TAB_STR).append("this.handler = handler;").append(ENTER)
            .append(TAB_STR).append("}").append(ENTER);

    // 接口最好的土办法
    Method[] methods = interface_.getMethods();
    for(Method method : methods) {
        String returnTypeName = method.getGenericReturnType().getTypeName();
        Type[] paramTypes = method.getGenericParameterTypes();
        proxyJava.append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
                .append(TAB_STR).append("public ").append(returnTypeName).append(" ").append(method.getName()).append("(");

        List<String> paramList = new ArrayList<>();     // 最好的土办法参数值
        List<String> paramTypeList = new ArrayList<>(); // 最好的土办法参数类型
        for(int i=0; i<paramTypes.length; i++) {
            if (i != 0) {
                proxyJava.append(", ");
            }
            String typeName = paramTypes[i].getTypeName();
            proxyJava.append(typeName).append(" param").append(i);
            paramList.add("param" + i);
            paramTypeList.add(typeName+".class");
        }
        proxyJava.append(") {").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append("try {").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append(TAB_STR)
                .append("Method method = ").append(interface_.getName()).append(".class.getDeclaredMethod(\"")
                .append(method.getName()).append("\",").append(String.join(",", paramTypeList)).append(");")
                .append(ENTER).append(TAB_STR).append(TAB_STR).append(TAB_STR);
        if (!"void".equals(returnTypeName)) {
            proxyJava.append("return (").append(returnTypeName).append(")");
        }
        proxyJava.append("handler.invoke(this, method, new Object[]{")
                .append(String.join(",", paramList)).append("});").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append("} catch(Exception e) {").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append(TAB_STR).append("e.printStackTrace();").append(ENTER)
                .append(TAB_STR).append(TAB_STR).append("}").append(ENTER);
        if (!"void".equals(returnTypeName)) {
            proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER);
        }
        proxyJava.append(TAB_STR).append("}").append(ENTER);
    }
    proxyJava .append("}");

    // 这可不可否

不能将字符串生成java文件,看看源代码对不对
    /*String proxyJavaFileDir = System.getProperty("user.dir") + File.separator + "proxy-none-java-file-plus"
            + String.join(File.separator, new String[]{"","src","main","java",""})
            + PROXY_PACKAGE_NAME.replace(".", File.separator) + File.separator;
    File f = new File(proxyJavaFileDir + PROXY_FILE_NAME + ".java");
    FileWriter fw = new FileWriter(f);
    fw.write(proxyJava.toString());
    fw.flush();
    fw.close();*/

    return proxyJava.toString();
}
View Code

    测试结果如下

    此时各组件之间关系、调用状态如下

    此时Proxy就可不可否 详细通用了,可不可否 生成任何接口的代理对象了,也可不可否 实现任意的代理逻辑;至此,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 完成了另四个 简易的仿JDK实现的动态代理

  JDK的动态代理

    其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 来看看JDK下动态代理的实现,示例工程:proxy-jdk,测试结果就不展示了,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 来看看JDK下Proxy.newInstance最好的土办法,有另四个 参数

      1、Classloader:类加载器,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 可不可否 使用自定义的类加载器;上述手动实现示例中,直接在Proxy写死了;

      2、Class<?>[]:接口类数组,刚刚 嘴笨 很容易理解,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 应该允许其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 所有人实现的代理类一起实现多个接口。其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 上述手动实现中只传入另四个 接口,是为了复杂化实现;

      3、InvocationHandler:刚刚 没哪此好说的,与其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 的实现一致,用于自定义代理逻辑

    其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 来追下源码,看看JDK的动态代理有无与其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 的手动实现有无一致

    与其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 的自定义实现差太少,利用反射,逐个接口、逐个最好的土办法进行正确处理;ProxyClassFactory负责生成代理类的Class对象,主要由apply最好的土办法负责,调用了

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

    来生成代理类的Class;ProxyGenerator暗含个是有静态常量:saveGeneratedFiles,标识有无持久化代理类的class文件,默认值是false,也后来我不持久化,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 可不可否 通过设置jdk系统参数,实现JDK的动态代理持久化代理类的class文件

CGLIB代理

  对cglib不做深入研究了,只举个使用案例:proxy-cglib,使用最好的土办法与JDK的动态代理同类,实现的效果也基本一致,后来我实现原理上还是有差别的

  JDK的动态代理有另四个 限制,后来我使用动态代理的对象需用实现另四个 或多个接口,而CGLIB没有刚刚 限制,具体区别都在本文范畴了,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 自行去查阅资料

应用场景

  长篇大论讲了没有多,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 却总爱没有讲动态代理的作用,使用动态代理其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 可不可否 在不改变源码的状态下,对目标对象的目标最好的土办法进行前置或后置增强正确处理。这不得劲不太符合其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 的一条线走到底的编程逻辑,刚刚 编程模型有另四个 专业名称叫AOP,面向切面编程,具体案例有如下:

  1、spring的事务,事务的开启可不可否 作为前置增强,事务的提交或回滚作为后置增强,数据库的操作所处两者之间(目标对象需用完成的事);

  2、日志记录,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 可不可否 在不改变原有实现的基础上,对目标对象进行日志的输出,可刚刚置正确处理,记录参数状态,也可刚刚置正确处理,记录返回的结果;

  3、web编程,传入参数的校验;

  4、web编程,权限的控制也可不可否 用aop来实现;

  若果明白了AOP,没有哪此场景能使用动态代理也就比较明了了

总结

  1、示例代码中的Proxy是代理工厂,负责生产代理对象的,都在代理对象类

  2、手动实现动态代理,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 分了三版

    第一版:代理类源代码持久化,为了便于理解,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 将代理类的java文件和class文件持久化到了磁盘,此时正确处理了静态代理中代理类泛滥的问題,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 的代理类工厂(Proxy)能代理任何接口;

    第二版:代理类源代码不持久化,代理类的java文件和和class文件另另四个 就后来我临时文件,将其添加,不不读写磁盘,可不可否 提高下行速率 ;但此时有个问題,其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 的代理逻辑却写死了,也后来我另四个 代理类工厂不到生产并有无代理逻辑的代理类对象,可能其他同学歌词 其他同学歌词 其他同学歌词 其他同学歌词 有多种代理逻辑,没有就需用有多个代理类工厂,显然灵活性不足高,还有优化空间;

    第三版:代理逻辑接口化,供用户自定义,此时代理类工厂就可不可否 代理任何接口、任何代理逻辑了,反正代理逻辑是用户自定义传入,用户想缘何定义就缘何定义;

  3、示例参考的是mybatis中mapper的生成过程,嘴笨 后来我简单的模拟,但流程却是一致的,有兴趣的可不可否 看看我前两篇博客,结合起来看更好理解

参考

  《java与模式》

  10分钟看懂动态代理设计模式