代理模式是软件开发中常见的设计模式,它的目的是让调用者不用持有具体操作者的引用,而是通过代理者去对具体操作者执行具体的操作。
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(); //代理者代替真实者做事情 } } 复制代码
可以看到,静态代理让调用者不用再直接持有操作者的引用,而是将一切操作交由代理者去完成。但是静态代理也有它的局限性:
可能有人想到可以用策略模式和工厂模式分别解决上面两个问题,但是,有没有更加巧妙的方法呢?首先,我们了解一下 Java 代码的执行过程。
要从根本上理解动态代理的实现原理,得先从 Java 代码的执行流程说起:
JVM 在运行 .class 文件之前,首先通过 ClassLoader 将 .class 文件以二进制的形式解析并生成实例以供调用,我们的代码执行逻辑是在 JVM 的运行期系统中进行工作的,那么,我们可不可以在自己的代码里面按照 .class 的格式生成自己的 .class 文件,进而调用自定义的 ClassLoader 将其加载出来呢?答案是肯定的,这样我们就可以动态地创建一个类了。
当然我们不用手动去一点一点拼装 .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 文件如下:
反编译后查看内容:
// // 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); } } 复制代码
后台输出如下:
成功执行了 show 方法!
使用动态代理的初衷是简化代码,不管是 ASM 还是 Javassist,在进行动态代理的时候操作还是不够简便,这也违背了我们的初衷。我们来看一下怎么 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(); } } 复制代码
跟静态代理不同的是,动态代理的过程主要分为三个步骤
用 Proxy 类生成代理类的方法为 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ,第二个参数是操作者的接口数组,意味着只能代理它实现的接口里的方法,对于本来在操作者类中定义的方法表示无能为力,CGLIB(Code Generation Library) 解决了这个问题。
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 进行动态代理的过程分为四个步骤:
无论是静态代理还是动态代理,都能一定程度地解决我们的问题,在开发过程中可以根据实际情况选择合适的方案。总之,没有好不好的方案,只有适不适合自己项目的方案,我们应该深入研究和理解方案背后的原理,以便能够应对开发过程中产生的变数。
文章中的代码已经上传至我的 Github ,如果你对文章内容有不同意见,欢迎留言,我们一同探讨。