java的动态代理机制

动态代理是指什么?它有哪几种实现方法? 答:- 动态代理是指在程序运行时生成代理类。

  • 有两种实现方式:

    • JDK 动态代理,被代理对象必须实现接口,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理;
    • 字节码实现(比如说 cglib/asm 等),得用 ASM 开源包,将代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。

动态代理是和反射机制一脉相承的,其核心也就是两个:动态 + 代理。动态是指在在运行时生成的代理类,与此对应的就是静态代理,代理类在程序编译时就实现。现在很多框架都是利用类似机制来提供灵活性的扩展性,比如用来包装 RPC 调用,面向切面编程(AOP)等。

​ 从 JDK 1.3 开始,Java 提供了原生动态代理技术,允许开发者在运行时创建接口的代理实例,主要包括 Proxy 类和 InvocationHandler 接口。

代码示例:JDK 动态代理

1.假设有一个写文件的需求,首先需要定义接口:

public interface Writer {

public void write(String fileName, String str);

public void write(String fileName, byte[] bs);

}
复制代码

2.再定义写文件的类并实现Writer接口

public class FileWriter implements Writer {

@Override public void write(String fileName, String str) {

System.out.println("call write str in FileWriter");

}

@Override

public void write(String fileName, byte[] bs) {

System.out.println("call write bytes in FileWriter");

}

}
复制代码

如果我们要写文件的话,通过 Writer writer = new FileWriter() 就可以实现了。假设某天突然来了个需求,说我们写磁盘的时候要判断

服务器存储空间是否达到某个临界值,如果达到了就不能再写了。对于这个需求来说我们可以直接修改 FileWriter 类来实现,但这样做有

两个问题:

  1. 改现有代码风险高,可能改动过程中影响原有逻辑,不符合开闭原则——对扩展开放,对修改关闭

  2. 这个需求跟写文件的业务无关,直接放在业务代码里面会导致耦合度比较大,不利于维护

通过代理模式就可以避免上述两个问题,接下来我们看看如何利用 Java 的动态代理来实现这个需求。

首先,我们需要创建一个实现 java.lang.reflect.InvocationHandler 接口的类:

public class FileWriterInvocationHandler implements InvocationHandler {

    Writer writer = null;
    public FileWriterInvocationHandler(Writer writer) {

        this.writer = writer;

    }

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {

            boolean localNoSpace = false;

            System.out.println("check local filesystem space."); //检测磁盘空间代码,返回值             //可以更新 localNoSpace 变量

            if (localNoSpace) {

                 throw new Exception("no space."); //如果空间不足,抛出空间不足的异常

             }

             return method.invoke(writer, args); //调用真实对象(FileWriter)的方法

     }

}
复制代码

FileWriterInvocationHandler 的构造方法中会保存实际用于写文件的对象,即 FileWriter 对象。 invoke 方法中先检查磁盘,如果没问题再调用文件的写方法 method.invoke(writer, args) ,这个写法是Java反射机制提供的。看起来 invoke 方法就是我们想要的功能。 但我们要怎么调用 invoke 呢?这里就用到 Java 的动态代理技术了,在运行时将 Writer 接口动态地跟代理对象(FileWriterInvocationHandler对象)绑定在一起。

下面,我们看看如何创建代理对象并进行绑定:

public class DynamicProxyDriver { 
      public static void main(String[] args) {

        /**

        * Proxy.newProxyInstance 包括三个参数

        * 第一个参数:定义代理类的 classloader,一般用被代理接口的 classloader

        * 第二个参数:需要被代理的接口列表

        * 第三个参数:实现了 InvocationHandler 接口的对象

        * 返回值:代理对象

        */

        Writer writer = (Writer) Proxy.newProxyInstance(

                Writer.class.getClassLoader(),

                new Class[]{Writer.class},

                new FileWriterInvocationHandler(new FileWriter())); //这就是动态的原因,运行时才创建代理类

                try {

                          writer.write("file1.txt", "text"); //调用代理对象的write方法

                } catch (Exception e) {

                          e.printStackTrace();

                }

                writer.write("file2.txt", new byte[]{}); //调用代理对象的write方法

        }
  }
复制代码

通过Java语言提供的 Proxy.newProxyInstance() 即可创建 Writer 接口的动态代理对象,代码注释中有该方法的参数说明。对照本例,简

单梳理一下Java动态代理机制

  1. 当通过 Proxy.newProxyInstance() 创建代理对象后,在 Writer 接口中调用 write 方法便会跳转到 FileWriterInvocationHandler 对象的 invoke 方法中执行

  2. 比如,执行 writer.write("file1.txt", "text"); 时,程序跳转到 invoke 方法,它的第二个参数 method 对象是 write 方法,第三个

参数 args 是调用 write 方法的实参 file1.txt 和 text 。3. invoke 方法中的最后一行 method.invoke(writer, args); 代表 method 方法(即 write 方法)由 writer 对象调用,参数是 args ,

跟 writer.write("file1.txt", "text") 是一样的。

这样我们就通过代理模式既实现新需求,有没有修改现有的代码。经过上述的讲解,希望你对代理模式的概念和优势有一定的了解。

代理模式除了上述提到的用处外,还有一个用处是转发调用请求,以 Manis 为例,假如我们为 ClientProtocol 创建一个代理对象 manisD

b ,在 manisDb 上调用 getTableCount 方法时,便会跳转到代理对象的 invoke 方法中执行,在 invoke 方法中我们就可以将调用的方法和

参数反序列化,并通过网络发送服务端,这就实现了调用请求的转发。

Guess you like

Origin juejin.im/post/7049905153092091918