Saul's blog Saul's blog
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)

Saul.J.Wu

立身之本,不在高低。
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)
  • Java入门基础

  • Java核心基础

    • 多线程

    • Java常用类

    • 枚举类与注解

    • Java集合

    • 数据结构与算法

    • 泛型

    • IO流

    • 网络编程

    • 反射

      • 反射概述
      • Class类
      • 类加载器
      • 访问字段
      • 调用方法
      • 调用构造方法
      • 获取继承关系
      • 动态代理
      • 函数式编程

    • 设计模式

    • Web开发

    • SpringBoot

    • 微服务

    • Elasticsearch

    • 运维

    • 后端
    • Java核心基础
    • 反射
    SaulJWu
    2021-01-12

    动态代理

    我们来比较Java的class和interface的区别:

    • 可以实例化class(非abstract);
    • 不能实例化interface。

    所有interface类型的变量总是通过向上转型并指向某个实例的:

    CharSequence cs = new StringBuilder();
    
    1

    有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?

    这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

    什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的。我们来看静态代码怎么写:

    定义接口:

    public interface Hello {
        void morning(String name);
    }
    
    1
    2
    3

    编写实现类:

    public class HelloWorld implements Hello {
        public void morning(String name) {
            System.out.println("Good morning, " + name);
        }
    }
    
    1
    2
    3
    4
    5

    创建实例,转型为接口并调用:

    Hello hello = new HelloWorld();
    hello.morning("Bob");
    
    1
    2

    这种方式就是我们通常编写代码的方式。

    还有一种方式是动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

    一个最简单的动态代理实现如下:

    public class Main {
        public static void main(String[] args) {
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println(method);
                    if (method.getName().equals("morning")) {
                        System.out.println("Good morning, " + args[0]);
                    }
                    return null;
                }
            };
            Hello hello = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(), // 传入ClassLoader
                new Class[] { Hello.class }, // 传入要实现的接口
                handler); // 传入处理调用方法的InvocationHandler
            hello.morning("Bob");
        }
    }
    
    interface Hello {
        void morning(String name);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    运行结果:

    public abstract void Hello.morning(java.lang.String)
    Good morning, Bob
    
    1
    2

    在运行期动态创建一个interface实例的方法如下:

    1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;

    2. 通过Proxy.newProxyInstance()创建interface

      实例,它需要3个参数:

      • 使用的ClassLoader,通常就是接口类的ClassLoader;
      • 需要实现的接口数组,至少需要传入一个接口进去;
      • 用来处理接口方法调用的InvocationHandler实例。
    3. 将返回的Object强制转型为接口。

    动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:

    public class HelloDynamicProxy implements Hello {
        InvocationHandler handler;
        public HelloDynamicProxy(InvocationHandler handler) {
            this.handler = handler;
        }
        public void morning(String name) {
            handler.invoke(
               this,
               Hello.class.getMethod("morning", String.class),
               new Object[] { name });
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    其实就是JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。

    # 小结

    Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;

    动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

    帮我改善此页面 (opens new window)
    上次更新: 2021/01/20, 05:46:57
    获取继承关系
    Lambda基础

    ← 获取继承关系 Lambda基础→

    最近更新
    01
    zabbix学习笔记二
    02-28
    02
    zabbix学习笔记一
    02-10
    03
    Linux访问不了github
    12-08
    更多文章>
    Theme by Vdoing | Copyright © 2020-2022 Saul.J.Wu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式