Java代理模式

0x00 一个需求

比如在Action中要添加日志,记录输入和输出

原来

public class Action {
    public void execute(HttpServletRequest request, HttpServletResponse response) {
        // 业务代码
    }
}

直接修改的方式

public class Action {
    public void execute(HttpServletRequest request, HttpServletResponse response) {
        System.out.println(request);
        // 业务代码
        System.out.println(response);
    }
}

我们可以预见,如果一个项目有100个action,就要加两百行代码,而且每次新增接口都要加,如果有一天要把System.out.println改成log,就要把这几百行重写一变

0x01 静态代理

先看一下实现

public class ActionProxy {
    private Action target;
    public ActionProxy(Action action) {
        this.target = target;
    }
    public void execute(HttpServletRequest request, HttpServletResponse response) {
        System.out.println(request);
        target.execute(request, response);
        System.out.println(response);
    }
}

这样是不是很简洁明了,将原来需要执行的类做为参数传入,执行我们需要添加的操作,还是和Python的装饰器类似

存在的不足

  • 如果Action的默认方法就几百个,那我们的Proxy就要重写这几百个方法,同时,重写方法中打印也存在代码重复
  • 如果不是Action类,而是其他类型,我们又要重新完成一个Proxy

0x02 动态代理

先来看一下实现

interface Interface { public void foo(); }

class A implements Interface {
    public void foo { System.out.println("Method a of class A!"); }
}

class Consumer {
    public static void consume(Interface i) {
        i.foo();
    }
}

class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // do before
            return method.invoke(proxied, args);
            // do after
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
    }
}

public class Test {
    public static void main(String[] args) {
        A a = new A();
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class<?>[]{Interface.class}, new DynamicProxyHandler(a));
        Consumer.consume(proxy);
    }
}

我们看看这种情况下比上面静态代理都改进了哪里

不用对同一个接口的所有方式都进行重写了,接口方法作为参数传入,然后接口类型也作为参数传入,实现相同都功能就不用为一个接口实现一个代理

一些原理

实际上使用上对动态代理的使用也就是这样了,但是我在学习的时候看了很多资料,试图解释动态代理实现上究竟做了什么事情,这些内容其实比较难懂,需要多看几遍,也不一定全弄懂

浅谈JDK动态代理(中)

Java JDK动态代理Proxy类的原理是什么?

我们看JDK实现动态使用的主要方法newProxyInstance

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)

它有三个参数

  • ClassLoader loader:指定当前对象使用类加载器,获取加载器的方法是固定的
  • Class<?> interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

根据上面文章的意思,前两个参数用来动态生成类名、方法名、继承关系的一个空壳

class $ProxyN implements Interface {
    public void foo() {
    }
}

也就上面的A类变成了上面$ProxyN这类,有一样的方法

然后当我们调用上面的$ProxyN类的时候,会调用实现了InvocationHandler接口的DynamicProxyHandler里的invoke方法

大致是这样一个实现

看一下newProxyInstance方法源码

/*
 * Choose a name for the proxy class to generate.
 */
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);
try {
    return defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
    /*
     * A ClassFormatError here means that (barring bugs in the
     * proxy class generation code) there was some other
     * invalid aspect of the arguments supplied to the proxy
     * class creation (such as virtual machine limitations
     * exceeded).
     */
    throw new IllegalArgumentException(e.toString());
}

上面是一段文章中说的newProxyInstance中重要的实现代码

$ProxyN是动态生成的代理类的名称,N代表N次生成动态代理

ProxyGenerator.generateProxyClass方法生成了类加载器需要的字节码,根据类名和事件的接口Class对象,相当与凭空编译好一个.class文件

通过上面这个方法生成的字节码加上加载器和类型被交到defineClass0里,由它生成代理类的Class对象

坚持原创技术分享,您的支持将鼓励我继续创作!