Java 静态代理和动态代理的使用及原理解析

栏目: Java · 发布时间: 7年前

内容简介:代理模式是软件开发中常见的设计模式,它的目的是让调用者不用持有具体操作者的引用,而是通过代理者去对具体操作者执行具体的操作。可以看到,静态代理让调用者不用再直接持有操作者的引用,而是将一切操作交由代理者去完成。但是静态代理也有它的局限性:可能有人想到可以用策略模式和工厂模式分别解决上面两个问题,但是,有没有更加巧妙的方法呢?首先,我们了解一下 Java 代码的执行过程。

代理模式是软件开发中常见的设计模式,它的目的是让调用者不用持有具体操作者的引用,而是通过代理者去对具体操作者执行具体的操作。

静态代理的实现

操作接口:

public interface Operate {
    void doSomething();
}
复制代码

操作者:

public class Operator implements Operate {
    @Override
    public void doSomething() {
        System.out.println("I'm doing something");
    }
}
复制代码

代理者:

public class OperationProxy implements Operate {
    private Operator operator = null;

    @Override
    public void doSomething() {
        beforeDoSomething();
        if(operator == null){
            operator =  new Operator();
        }
        operator.doSomething();
        afterDoSomething();
    }

    private void beforeDoSomething() {
        System.out.println("before doing something");
    }

    private void afterDoSomething() {
        System.out.println("after doing something");
    }
}
复制代码

调用者:

public class StaticProxyTest {
    public static void main(String[] args) {
        Operate operate = new OperationProxy();//使用OperationProxy代替Operator
        operate.doSomething();  //代理者代替真实者做事情
    }
}
复制代码

静态代理的局限性

可以看到,静态代理让调用者不用再直接持有操作者的引用,而是将一切操作交由代理者去完成。但是静态代理也有它的局限性:

  1. 如果需要增加一个需要代理的方法,代理者的代码也必须改动进而适配新的操作;
  2. 如果需要代理者代理另外一个操作者,同样需要对代理者进行扩展并且更加麻烦。

可能有人想到可以用策略模式和工厂模式分别解决上面两个问题,但是,有没有更加巧妙的方法呢?首先,我们了解一下 Java 代码的执行过程。

理解 Java 代码执行流程

要从根本上理解动态代理的实现原理,得先从 Java 代码的执行流程说起:

Java 静态代理和动态代理的使用及原理解析

JVM 在运行 .class 文件之前,首先通过 ClassLoader 将 .class 文件以二进制的形式解析并生成实例以供调用,我们的代码执行逻辑是在 JVM 的运行期系统中进行工作的,那么,我们可不可以在自己的代码里面按照 .class 的格式生成自己的 .class 文件,进而调用自定义的 ClassLoader 将其加载出来呢?答案是肯定的,这样我们就可以动态地创建一个类了。

Java 静态代理和动态代理的使用及原理解析

生成自己的 .class 文件

当然我们不用手动去一点一点拼装 .class 文件,目前比较常用的字节码生成 工具 有ASM 和Javassist,根据这个思路,生成 .class 文件的过程如下:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
 
public class Test {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        //创建 AutoGenerateClass 类
        CtClass cc= pool.makeClass("com.guanpj.AutoGenerateClass");
        //定义 show 方法
        CtMethod method = CtNewMethod.make("public void show(){}", cc);
        //插入方法代码
        method.insertBefore("System.out.println(\"I'm just test generate .class file by javassit.....\");");
        cc.addMethod(method);
        //保存生成的字节码
        cc.writeFile("D://temp");
    }
}
复制代码

生成的 .class 文件如下:

Java 静态代理和动态代理的使用及原理解析

反编译后查看内容:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.guanpj;

public class AutoGenerateClass {
    public void show() {
        System.out.println("I'm just test generate .class file by javassit.....");
    }

    public AutoGenerateClass() {
    }
}
复制代码

可以看到,javassit 生成的类中,除了 show() 方法之外还默认生成了一个无参的构造方法。

自定义类加载器加载

为了能够让自定的类被加载出来,我们自定义了一个类加载器来加载指定的 .class 文件:

public class CustomClassLoader extends ClassLoader {

    public CustomClassLoader() {
    }

    protected Class<?> findClass(String className) {
        String path = "D://temp//" + className.replace(".","//") + ".class";
        byte[] classData = getClassData(path);
        return defineClass(className, classData, 0, classData.length);
    }

    private byte[] getClassData(String path) {
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
复制代码

接着,用 ClassLoader 加载刚才生成的 .class 文件:

public class TestLoadClass {
    public static void main(String[] args) throws Exception {
        CustomClassLoader classLoader = new CustomClassLoader();
        Class clazz = classLoader.findClass("com.guanpj.AutoGenerateClass");

        Object object = clazz.newInstance();
        Method showMethod = clazz.getMethod("show", null);
        showMethod.invoke(object, null);
    }
}
复制代码

后台输出如下:

Java 静态代理和动态代理的使用及原理解析

成功执行了 show 方法!

利用 JDK 中的 Proxy 类进行动态代理

使用动态代理的初衷是简化代码,不管是 ASM 还是 Javassist,在进行动态代理的时候操作还是不够简便,这也违背了我们的初衷。我们来看一下怎么 InvocationHandler 怎么做:

InvocationHandler:

public class InvocationHandlerImpl implements InvocationHandler {
    Operate operate;

    //注入操作者对象
    public InvocationHandlerImpl(Operate operate) {
        this.operate = operate;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before calling method: " + method.getName());
        //调用操纵者的具体操作方法
        method.invoke(operate, args);
        System.out.println("after calling method: " + method.getName());
        return null;
    }
}
复制代码

调用者:

public class DynamicProxyTest {
    public static void main(String[] args) {
        //实例化操作者
        Operate operate = new Operator();
        //将操作者对象进行注入
        InvocationHandlerImpl handler = new InvocationHandlerImpl(operate);
        //生成代理对象
        Operate operationProxy = (Operate) Proxy.newProxyInstance(operate.getClass().getClassLoader(),
                operate.getClass().getInterfaces(), handler);
        //调用操作方法
        operationProxy.doSomething();
    }
}
复制代码

跟静态代理不同的是,动态代理的过程主要分为三个步骤

  • 将操作者对象注入 InvocationHandlerImpl 类中。
  • 将 InvocationHandlerImpl 对象注入 Proxy 类中并返回代理者对象,并在 invoke 方法中进行额外的操作
  • 调用代理对象的操作方法

利用 CGLIB 进行动态代理

用 Proxy 类生成代理类的方法为 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ,第二个参数是操作者的接口数组,意味着只能代理它实现的接口里的方法,对于本来在操作者类中定义的方法表示无能为力,CGLIB(Code Generation Library) 解决了这个问题。

MethodInterceptorImpl:

public class MethodInterceptorImpl implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before calling method:" + method.getName());
        proxy.invokeSuper(obj, args);
        System.out.println("after calling method:" + method.getName());
        return null;
    }
}
复制代码

调用者:

public class ProxyTest {
    public static void main(String[] args) {
        Operator operator = new Operator();
        MethodInterceptorImpl methodInterceptorImpl = new MethodInterceptorImpl();

        //初始化加强器对象
        Enhancer enhancer = new Enhancer();
        //设置代理类
        enhancer.setSuperclass(operator.getClass());
        //设置代理回调
        enhancer.setCallback(methodInterceptorImpl);

        //创建代理对象
        Operator operationProxy = (Operator) enhancer.create();
        //调用操作方法
        operationProxy.doSomething();
    }
}
复制代码

使用 CGLIB 进行动态代理的过程分为四个步骤:

  • 使用 MethodInterceptorImpl 实现 MethodInterceptor 接口,并在 intercept 方法中进行额外的操作
  • 创建增强器 Enhance 并设置被代理的操作类
  • 生成代理类
  • 调用代理对象的操作方法

总结

无论是静态代理还是动态代理,都能一定程度地解决我们的问题,在开发过程中可以根据实际情况选择合适的方案。总之,没有好不好的方案,只有适不适合自己项目的方案,我们应该深入研究和理解方案背后的原理,以便能够应对开发过程中产生的变数。

文章中的代码已经上传至我的 Github ,如果你对文章内容有不同意见,欢迎留言,我们一同探讨。


以上所述就是小编给大家介绍的《Java 静态代理和动态代理的使用及原理解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Developer's Guide to Social Programming

Developer's Guide to Social Programming

Mark D. Hawker / Addison-Wesley Professional / 2010-8-25 / USD 39.99

In The Developer's Guide to Social Programming, Mark Hawker shows developers how to build applications that integrate with the major social networking sites. Unlike competitive books that focus on a s......一起来看看 《Developer's Guide to Social Programming》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试