影子的知识库

影子的知识库

  • 知识库
  • GitHub

›Java系列

JVM系列

  • JVM内存区域
  • 对象创建-布局-访问
  • 内存溢出实战
  • 内存区域回收
  • 四大引用
  • 垃圾回收算法
  • HotSpot回收算法细节

Java系列

  • java注解
  • springboot请求参数绑定
  • springboot请求参数校验框架
  • YAML语法
  • 动态代理
  • classpath和java命令
  • springboot-aop编程
  • springboot统一异常处理
  • springboot数据库和事务
  • springboot拦截器
  • springboot中的web配置
  • docker的简单开发
  • springboot自动配置
  • 数据库的隔离级别
  • springboot监控
  • java类加载
  • java-agent的相关内容
  • 类加载器详解
  • java的SecurityManager
  • maven学习

Node

    JS 基础

    • 语法基础和数据类型
    • 数据类型转换
    • 语句 表达式 运算符
    • 变量与对象
    • 函数
    • 数据处理
    • 常用 API
    • 重点知识

    ES6

    • 块级作用域
    • 字符串和正则表达式
    • 函数
    • 对象
    • Symbol
    • Set和Map
    • 迭代器和生成器
    • 类
    • 数组
    • Promise

    Node 基础

    • 模块系统
    • package.json
    • 内置对象
    • npm脚本的使用
    • Buffer
    • Stream
    • 事件循环机制
    • 示例代码

    stream系列

    • 流的缓冲
    • 可读流
    • 可写流
    • 双工流和转换流
    • 自定义流

后期计划

  • 学习计划
  • 专题研究计划
Edit

本文内容

参考

《Java 编程的逻辑》

这书写的确实挺好的,虽然我一般只推荐外国人写的书,但是这本国人写的书确实不造作,写的内容十分有条理性和细致,推荐可以阅读一下

代理模式

代理模式说白了就是:

  • 已经有一个被代理的对象存在了
  • 代理对象持有这个被代理的对象
  • 我们只使用代理对象的方法
  • 代理对象对方法前后和方法执行时做一些动作,然后调用被代理的对象的对应方法

代理对象和被代理的对象实现相同的接口,代理对象仅仅是在代理的过程中做一些别的操作,最后使用被代理的对象实现真正的功能。

代理模式有很多的价值,例如:

  • 节省成本比较高的对象的创建开销,只在需要时再加载或者创建
  • 执行权限检查,检查完成权限后再调用实际对象
  • 屏蔽网络差异和复杂性,本地使用代理对象,代理对象搞定网络传输这些比较麻烦的操作

静态代理

public class JavaDemoApplication {
    interface Serve {
        public void serve();
    }

    static class RealServe implements Serve {
        @Override
        public void serve() {
            System.out.println("真实对象运行");
        }
    }

    static class RealServeProxy implements Serve {
        private Serve serve;

        public RealServeProxy(Serve serve) {
            this.serve = serve;
        }

        @Override
        public void serve() {
            System.out.println("真实对象运行前做了一些操作");
            serve.serve();
            System.out.println("真实对象运行后做了一些操作");
        }
    }

    public static void main(String[] args) {
        Serve serve = new RealServe();
        Serve serveProxy = new RealServeProxy(serve);
        serveProxy.serve();
    }
}

这个看起来并不难,也有一些用处,主要的问题在于不够灵活。

比如我们想对所有的方法都记录日志,这种方式就要在编译期对每个类都生成代理类,这个工作量太大而且不好维护。所以这种方式用的并不多,一般像是适配器模式可能会用一下。

日常工作中更有价值的是动态代理,在运行时动态生成代理类,有两种方式实现动态代理,Java SDK 和第三方库cglib。

Java 内置的动态代理

public class JavaDemoApplication {
    interface Serve {
        Object serve();
    }

    static class RealServe implements Serve {
        @Override
        public Object serve() {
            System.out.println("真实对象运行");
            return null;
        }
    }

    static class SimpleInvocationHandler implements InvocationHandler {
        private Object realObject;

        public SimpleInvocationHandler(Object o) {
            this.realObject = o;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("开始代理方法:" + method.getName());
            Object result = method.invoke(realObject, args);
            System.out.println("代理方法结束:" + method.getName());
            return proxy;
        }
    }

    public static void main(String[] args) {
        Serve realServe = new RealServe();
        Serve proxyServe = (Serve) Proxy.newProxyInstance(
                Serve.class.getClassLoader(),
                new Class<?>[]{Serve.class},
                new SimpleInvocationHandler(realServe)
        );
        Object o = proxyServe.serve();
        System.out.println(o == proxyServe);
    }
}

以上的代码应该这样来理解:

  • class SimpleInvocationHandler implements InvocationHandler

    • 实现了这个接口的类表示实现了一种代理的行为
    • 所谓代理的行为就是说:我代理的过程中想要做什么,这个具体的逻辑是在代理方法 invoke 中体现出来的
  • public Object invoke(Object proxy, Method method, Object[] args) 这里就是具体实现代理逻辑的地方

    • 第一个参数 proxy 表示:代理对象,注意不是被代理的对象,而是代理对象,也就是 JVM 动态生成的一个对象,这个对象的所有方法调用都会被拦截到正在说明的这个 invoke 方法,表示说:这个代理对象正在调用 xxx 方法,参数是 xxx,我们的 invoke 方法所要做的就是控制这个代理对象的行为
    • 第二个参数 method 表示:代理对象正在执行的方法,我们可以从这个 method 参数获取到方法名等反射信息
    • 第三个参数 args 表示:代理对象正在执行的方法的入参
    • 所以这个 invoke 方法的含义就是:proxy 对象正在执行名为 method 的方法,入参是 args,此时我们应该做什么?
  • Object result = method.invoke(realObject, args);

    • 这里表示:我们直接调用 被代理对象 的这个 method 方法,把 args 原封不动的传给它
    • 实际上我们可以做的更多,把参数改一改也是可以的
    • 这里得到的 result 实际上就是 realObject 使用 args 参数调用 method 的方法时的返回值
    • 在我们下面的方法中,realServe 调用 serve 方法的返回值是 null,所以这里的 result 其实也是 null
  • return proxy;

    • Invoke 方法的返回值其实代表着:代理对象使用入参 args 调用 method 方法的返回值
    • 如果我们把上一步的 result 作为返回值,那么代理对象的 method 方法的返回值就是 null
    • 一般来说都是用上一步 result 作为返回值的,我这里为了更明确 proxy 到底是什么,所以将 proxy 返回了
  • 生成代理对象的逻辑

    Serve proxyServe = (Serve) Proxy.newProxyInstance(
      Serve.class.getClassLoader(),
      new Class<?>[]{Serve.class},
      new SimpleInvocationHandler(realServe)
    );
    
    • Proxy.newProxyInstance 表示:我要生成一个代理对象
    • 第一个参数表示:这个代理对象被什么类加载器进行加载
    • 第二个参数表示:这个代理对象实现了什么接口,代理对象可以实现很多接口,所以这个参数是一个数组
    • 第三个参数表示:这个代理对象有哪些行为
      • 这里的行为其实就是我们实现的核心逻辑 invoke 方法
      • 我们实现的行为就是:这个代理对象在内部持有的被代理对象的方法前后打印一段话,并且返回代理对象自身作为方法的返回值
      • 最后所有的方法都返回这个代理对象本身
      • 其实这个 invoke 方法就是让我们用一个方法来定义代理对象所有的方法的逻辑,我们其实可以根本不持有内部被代理对象,只单独定义 invoke 方法,相当于在运行时动态生成了一个实现了某些接口的类的对象,invoke 里定义了这个对象的所有方法的行为
  • 最后的输出是

    开始代理方法:serve
    真实对象运行
    代理方法结束:serve
    true
    
  • 前面三行很好理解,注意最后一行的 true

  • 它表示两个含义

    • Proxy.newProxyInstance 生成的代理对象确实等于 invoke 里的 proxy 这个参数
    • invoke 的返回值确实变成了代理对象的 serve 方法的返回值

代理类具体长什么样

我们可以使用如下命令,来让代理对象生成时动态创建的类被保存下来,然后就可以反编译看它的源码

注意:以下操作需要在 Oracle JDK 下进行,openJDK 不支持这个参数

  • java -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true -cp . com.example.javademo.JavaDemoApplication

    • -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 使得动态代理类被保存下来了
    • -cp . 表示把当前目录加进了 classpath
    • java 规定类文件需要按照包名的层次在文件夹中存放,所以执行这个 java 命令的时候必须是在根目录,也就是 com 文件夹的上一层
    • 主类是 com.example.javademo.JavaDemoApplication ,记得提前使用 javac 编译这个类,这个应该都会吧
  • 运行之后我们会在 JavaDemoApplication 同目录下看到 $Proxy0.class 文件,这就是动态代理类的文件

  • 直接把这个文件挪到 idea 等具有反编译功能的工具里查看,反编译后的代码如下

    package com.example.javademo;
    
    import com.example.javademo.JavaDemoApplication.Serve;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    final class $Proxy0 extends Proxy implements Serve {
        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  {
            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 Object serve() throws  {
            try {
                return (Object)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  {
            try {
                return (String)super.h.invoke(this, m2, (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"));
                m3 = Class.forName("com.example.javademo.JavaDemoApplication$Serve").getMethod("serve");
                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());
            }
        }
    }
    

代理类的逻辑分析

  • 代理类继承了 Proxy 类,实现了我们要它代理的接口

    • 从这里可以看出来,代理类只能代理接口不能直接代理 class,因为 java 只能单继承
  • 代理类具有 4 个静态属性,就是 4 个 Method,初始化的过程如下

    static {
      try {
        m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
        m3 = Class.forName("com.example.javademo.JavaDemoApplication$Serve").getMethod("serve");
        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());
      }
    }
    
    • 这4个 Method 其中的三个是所有对象都有的方法:equals、toString、hashCode
    • 剩下的最后一个 Method 就是代理的接口的方法:serve
  • 初始化方法,其实就是持有一个 InvocationHandler 对象,用来定义代理对象的行为

    public $Proxy0(InvocationHandler var1) throws  {
      super(var1);
    }
    
  • 剩下的四个方法,也就是代理对象具有的四个方法,它们的逻辑全部都是直接调用 InvocationHandler 的 invoke 方法,也就是说将代理对象的行为全部交给了这个 handler 的 invoke 方法来定义

        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 Object serve() throws  {
            try {
                return (Object)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  {
            try {
                return (String)super.h.invoke(this, m2, (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);
            }
        }
    
    • 以 serve 为例,可以看到最终是调用了 return (Object)super.h.invoke(this, m3, (Object[])null);
    • 这个逻辑就是:直接调用持有的 InvocationHandler 的 invoke 方法
    • invoke 方法的第一个参数 this 就指代这个代理对象本身
    • 第二个参数就是当前调用的这个方法:m3 = Class.forName("com.example.javademo.JavaDemoApplication$Serve").getMethod("serve");
    • 第三个参数就是当前调用的这个方法的参数,这里是 null,因为本身就没有参数

从以上分析可以看出,代理类实际上并不持有被代理的对象,跟被代理的对象其实一点关系都没有,它只是全权将所有操作都交给了 InvocationHandler 来处理,所以真正代理的动作需要定义在 InvocationHandler 里

Java 内置动态代理的优缺点、特点和限制

  • 只能代理接口,不能代理 class,从上面代理类的实现方式也看的出来为什么会有这个限制
  • 代理类最终继承了 Proxy 类,实现了我们想要它实现的接口,而实现这些接口的方法的逻辑,则全部都是转发给 InvocationHandler 的 invoke 方法
  • 因此我们生成的代理对象可以强制转换成我们传入的接口列表中的任意一个或者 Proxy 类
  • InvocationHandler 的 invoke 方法 3 个参数的定义
    • proxy 代表这个代理对象本身
    • method 表示代理对象正在执行的方法
    • args 表示代理对象正在执行的方法的入参
  • 使用 java -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 参数可以保存这个动态生成的代理类的class文件
  • 使用动态代理,我们可以编写通用的代理逻辑来处理被代理的对象,而不需要单独为每个被代理的类编写Java代码了。
    • 比如说我们想要在某个接口的实现类每个方法前后都打印日志,就可以代理这个接口,然后把这个实现类的对象传给 InvocationHandler 来处理
    • 这样只需要编写一个通用的 InvocationHandler 就可以了

可以看到,这种代理方式最主要的缺点就是只能代理接口

cglib 动态代理

public class JavaDemoApplication {
    static class RealServe {
        public String serve() {
            System.out.println("原始对象运行");
            return "原始对象";
        }
    }

    static class SimpleInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("进入方法:" + method.getName());
            Object result = methodProxy.invokeSuper(o, objects);
            System.out.println("离开方法:" + method.getName());
            return result;
        }
    }

    private static <T> T getProxy(Class<T> cls) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);
        enhancer.setCallback(new SimpleInterceptor());
        return (T) enhancer.create();
    }

    public static void main(String[] args) {
        RealServe proxyService = getProxy(RealServe.class);
        System.out.println(proxyService.serve());
    }
}

以上的代码应该这样来理解:

  • RealServe

    • 原始对象是一个类,没有继承任何接口
    • 它输出一句话:原始对象运行
    • 然后返回字符串:原始对象
  • SimpleInterceptor

    static class SimpleInterceptor implements MethodInterceptor {
      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("进入方法:" + method.getName());
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("离开方法:" + method.getName());
        return result;
      }
    }
    
    • 它实现了 MethodInterceptor 接口,表示了一种方法拦截的行为,表示要如何拦截方法
    • Object o
      • 表示代理对象,与 InvocationHandler 里的第一个参数的含义是一致的
    • Method method
      • 表示当前代理对象被拦截的方法
    • Object[] objects
      • 表示当前代理对象被拦截的方法的入参
    • MethodProxy methodProxy
      • 用来调用父类方法的一个代理,一般都是使用 methodProxy.invokeSuper(o, objects); 来调用父类的原始方法
      • 之所以要有这个方法代理,是因为光靠代理对象 o 和反射方法 method,我们是没办法调用父类的原始方法的。因为这里没有父类对象,想要调用父类的 method 方法,只能是 method.invoke(superObj,args),但是这里是没有 superObj 存在的,所以要靠 methodProxy 来调用父类的原始方法
      • 我们使用 methodProxy.getSignature().getName() 获取方法名,会发现它也是 serve 方法,我们一般只用它来调用父类的方法,因为子类的方法都被拦截了,在这里调用子类的方法就会陷入无限递归,从而导致栈溢出
    • Object result = methodProxy.invokeSuper(o, objects);
      • 这里表示调用了父类的方法,然后得到了它的返回值
    • return result;
      • 我们没有对父类的返回值做任何处理,直接将其原样返回,因此我们动态生成的子类也具有一样的返回值
  • 构造代理类和生成代理对象的逻辑

    private static <T> T getProxy(Class<T> cls) {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(cls);
      enhancer.setCallback(new SimpleInterceptor());
      return (T) enhancer.create();
    }
    
    • 方法的功能是:接收一个我们想要代理的类,构造出它的一个子类对象
    • Enhancer enhancer = new Enhancer();
      • 这个 enhancer 就代表了我们构造的代理类
    • enhancer.setSuperclass(cls);
      • 表示我们让这个动态生成的代理类继承于 cls 这个父类
    • enhancer.setCallback(new SimpleInterceptor());
      • 表示这个动态生成的代理类的非 final 的public 方法,全部都被这个 MethodInterceptor 拦截处理,拦截的逻辑在上面已经讲明白了
      • 所以我们要实现的最重要的代理逻辑就是自定义一个 MethodInterceptor
    • return (T) enhancer.create();
      • 表示使用我们这个代理类生成代理对象,然后强转成父类的类型
  • main 方法的逻辑

    public static void main(String[] args) {
      RealServe proxyService = getProxy(RealServe.class);
      System.out.println(proxyService.serve());
    }
    
    • 生成代理对象
    • 调用代理对象的方法

cglib 动态代理的优缺点、特点和限制

  • cglib 的实现机制与 jdk 内置的动态代理不同,cglib 是通过继承来实现的
  • 代理类重写了父类所有的非 final 的public 方法,改为调用 MethodInterceptor 的 intercept 方法,类似于 JDK 的 InvocationHandler
  • cglib 是通过继承父类来构造代理类的,不需要一个现存的父类对象。
  • 总体来说 cglib 比 jdk 动态代理的限制少一点
  • 主要的限制是不能重写 private 方法、final 方法,只能重写非 final 的public 方法

两种动态代理的选择

  • JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
  • 如果是单例的代理,推荐使用CGLib
  • 如果是多例的我们最好使用JDK代理
Last updated on 11/8/2020
← YAML语法classpath和java命令 →
  • 参考
  • 代理模式
  • 静态代理
  • Java 内置的动态代理
    • 代理类具体长什么样
    • 代理类的逻辑分析
    • Java 内置动态代理的优缺点、特点和限制
  • cglib 动态代理
    • cglib 动态代理的优缺点、特点和限制
  • 两种动态代理的选择
影子的知识库
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHub
Copyright © 2020 Cen ZhiPeng