Java 代理

2017-06-03 Java

代理模式提供了目标对象另外的访问方式,即通过访问代理对象访问目标对象,这样可以做到在不修改目标对象的前提下,对目标功能进行扩展。根据使用方式可以分为静态代理和动态代理。

但是静态代理使用起来较为繁琐,不利于维护;而动态代理依托于 jvm 字节码技术动态代理大放异彩。

jdk proxy 原生

jdk 原生动态代理 API 接口如下:

  • Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • InvocationHandler#invoke(Object proxy, Method method, Object[] args) throws Throwable

示例如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class JDKProxyFactory implements InvocationHandler {
    private Object obj;

    public JDKProxyFactory() {}

    public JDKProxyFactory(Object obj) {
        this.obj = obj;
    }

    public Object createProxy() {
        ClassLoader classLoader = this.obj.getClass().getClassLoader();
        Class<?>[] interfaces = this.obj.getClass().getInterfaces();
        return Proxy.newProxyInstance(classLoader, interfaces, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method + " <> " + Arrays.asList(args));
        Object ret = method.invoke(this.obj, args);
        System.out.println(method + " <> " + ret);
        return ret;
    }

    public static void main(String[] args) {
        UserService service = new UserServiceImp();
        JDKProxyFactory factory = new JDKProxyFactory(service);
        // target object should be interface, not the implements.
     // UserServiceImp ret = (UserServiceImp) factory.createProxy();// error!
        UserService ret = (UserService) factory.createProxy();// this is right.
        ret.getAgeById(1);
        ret.getNameById(2);
    }
}

可以使用 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 保存 jdk 动态代理产生的 class 文件。

可以发现,目标对象必须实现接口。

CGLIB

不同于 jdk 动态代理,使用 CGLIB 可以在目标对象没有实现接口的情况下,使用目标对象子类的方式实现动态代理。

  • jdk 动态代理要求目标对象必须实现接口
  • CGLIB 可以在运行期扩展 java 类和接口
  • CGLIB 底层基于 ASM 框架
    • ASM 字节码操控框架很复杂
    • 不推荐直接使用 ASN
  • final 对象不可以被代理
  • final 方法无法被代理

可以使用 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class"); 保存 CGLIB 动态代理产生的 class 文件。

此处不再举例。

Javassist

Java Programming Assistant 主要的优点在于简单而且快速,直接使用 java 编码的形式而不需要了解虚拟机指令就能动态改变类的结构或者动态生成类。

性能低于 CGLIB。

AspectJ

AspectJ 是一个面向切面的框架,一个代码生成工具,扩展了 java 语言,是一个代码编译器(也就是 AJC),在 Java 编译器的基础上增加了一些它自己的关键字识别和编译方法。因此,AJC 也可以编译 Java 代码。它在编译期将开发者编写的 Aspect 程序编织到目标程序中,对目标程序作了重构,目的就是建立目标程序与 Aspect 程序的连接从而达到 AOP 的目的(这里在编译期还是修改了原来程序的代码,但是是 AJC 替我们做的)。

使用 AspectJ 有两种方法,一种是完全使用 AspectJ 的语言。这语言一点也不难,和 Java 几乎一样,也能在 AspectJ 中调用Java的任何类库。另一种是或者使用纯 Java 语言开发,然后使用 AspectJ 注解,也就是 @AspectJ 等。不论哪种方法,最后都需要 AspectJ 的编译工具 AJC 来编译。


动态代理技术总结如下:

image

Search

    Post Directory