Java 入门系列

Java 代理 入门

2020-03-13  本文已影响0人  ZeralZhang

什么是代理

代理是一种设计模式。当我们想要添加或修改现有类的某些功能时,我们创建并使用代理对象。通常,代理对象具有与原始代理对象相同的方法,并且在 Java 代理类中通常扩展原始类。代理的主要目的是控制对目标对象的访问,而不是增强目标对象的功能。

这样,代理类可以通过方便的方式实现许多功能:

在实际应用中,代理类不直接实现功能。遵循单一责任原则,代理类仅执行代理,并且实际行为在处理程序中实现。

与静态代理相比,动态代理需要在运行时进行 Java 反射的字节码生成。使用动态方法,无需创建代理类,这可以带来更多便利。

动态代理类

动态代理类(下面简称为代理类)是一个实现在运行时实现指定接口列表的类,这样通过其中一个接口实例类上的方法调用将可以被编码并通过统一接口分配给另一个对象。因此,动态代理类可用于为接口列表创建类型安全的代理对象,而无需预生成代理类,例如使用编译时工具。动态代理类的实例上的方法调用被分派到实例的调用处理程序中的单个方法,它们使用 java.lang.reflect.Method 对象进行编码,该对象标识被调用的方法以及包含参数的 Object 类型数组。

动态代理类对于需要在呈现接口 API 的对象上提供类型安全反射调度调用的应用程序或库非常有用。应用程序可以使用动态代理类来创建实现多个任意事件侦听器接口的对象,扩展 java.util.EventListener 的接口,以统一的方式处理不同类型的各种事件,例如将所有此类事件记录到文件中。

动态代理类 API

动态代理类(下面简称为代理类)是一个实现在运行时创建指定的接口列表的类的类。

代理接口就是由代理类实现的接口。

代理实例是代理类的实例。

创建代理类

使用类 java.lang.reflect.Proxy 的静态方法创建代理类及其实例。

在给定类加载器和接口数组的情况下,Proxy.getProxyClass 方法返回代理类的 java.lang.Class 对象。代理类将在指定的类加载器中定义,并将实现所有提供的接口。如果已经在类加载器中定义了相同的接口排列的代理类,将返回现有的代理类;否则,将动态生成这些接口的代理类,并在类加载器中定义。

可以传递给 Proxy.getProxyClass 的参数有几个限制:

Class.forName(i.getName(), false, cl) == i

请注意,指定代理接口的顺序很重要:对具有相同接口组合但顺序不同的代理类的两个请求将导致两个不同的代理类。代理类通过其代理接口的顺序来区分,以便在两个或更多代理接口共享具有相同名称和参数签名的方法的情况下提供确定性方法调用编码;

因此,每次使用相同的类加载器和接口列表调用 Proxy.getProxyClass 时,不需要生成新的代理类,动态代理类API 的实现应保留生成的代理类的缓存,由其相应的加载器和接口列表键入。实现时应注意不要引用类加载器,接口和代理类,以防止类加载器及其所有类在适当时被垃圾收集。

代理类属性

代理类具有以下属性:

创建代理实例

每个代理类都有一个公共构造函数,它接受一个参数,即 InvocationHandler 接口的实现。

每个代理实例都有一个关联的 InvocationHandler 对象,该对象是传递给其构造函数的对象。不必非要使用反射 API 来访问公共构造函数,也可以通过调用 Proxy.newProxyInstance 方法创建代理实例。 Proxy.newProxyInstance 因与 Proxy.getProxyClass 相同的原因抛出 IllegalArgumentException(比如接口数量不能超过 65535)。

代理实例属性

代理实例有以下属性:

proxy instanceof Foo

并且以下转换操作将成功(而不是抛出 ClassCastException):

 (Foo) proxy

序列化

由于 java.lang.reflect.Proxy 实现了 java.io.Serializable,因此可以序列化代理实例。

Examples

下面是一个简单的示例,它在实现任意接口列表的对象上的方法调用之前和之后打印出一条消息:

public interface Foo {
    Object bar(Object obj) throws BazException;
}

public class FooImpl implements Foo {
    Object bar(Object obj) throws BazException {
        // ...
    }
}

public class DebugProxy implements java.lang.reflect.InvocationHandler {

    private Object obj;

    public static Object newInstance(Object obj) {
        return java.lang.reflect.Proxy.newProxyInstance(
            obj.getClass().getClassLoader(),
            obj.getClass().getInterfaces(),
            new DebugProxy(obj));
    }

    private DebugProxy(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method m, Object[] args)
        throws Throwable
    {
        Object result;
        try {
            System.out.println("before method " + m.getName());
            result = m.invoke(obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (Exception e) {
            throw new RuntimeException("unexpected invocation exception: " +
                                       e.getMessage());
        } finally {
            System.out.println("after method " + m.getName());
        }
        return result;
    }
}

要为 Foo 接口的实现构造 DebugProxy 并调用其方法之一:

    Foo foo = (Foo) DebugProxy.newInstance(new FooImpl());
    foo.bar(null);

下面是一个实用程序调用处理程序类的示例,它为从 java.lang.Object 继承的方法提供默认代理行为,并根据被调用方法的接口实现对不同对象的某些代理方法调用的委派:

import java.lang.reflect.*;

public class Delegator implements InvocationHandler {

    // preloaded Method objects for the methods in java.lang.Object
    private static Method hashCodeMethod;
    private static Method equalsMethod;
    private static Method toStringMethod;
    static {
        try {
            hashCodeMethod = Object.class.getMethod("hashCode", null);
            equalsMethod =
                Object.class.getMethod("equals", new Class[] { Object.class });
            toStringMethod = Object.class.getMethod("toString", null);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    private Class[] interfaces;
    private Object[] delegates;

    public Delegator(Class[] interfaces, Object[] delegates) {
        this.interfaces = (Class[]) interfaces.clone();
        this.delegates = (Object[]) delegates.clone();
    }

    public Object invoke(Object proxy, Method m, Object[] args)
        throws Throwable
    {
        Class declaringClass = m.getDeclaringClass();

        if (declaringClass == Object.class) {
            if (m.equals(hashCodeMethod)) {
                return proxyHashCode(proxy);
            } else if (m.equals(equalsMethod)) {
                return proxyEquals(proxy, args[0]);
            } else if (m.equals(toStringMethod)) {
                return proxyToString(proxy);
            } else {
                throw new InternalError(
                    "unexpected Object method dispatched: " + m);
            }
        } else {
            for (int i = 0; i < interfaces.length; i++) {
                if (declaringClass.isAssignableFrom(interfaces[i])) {
                    try {
                        return m.invoke(delegates[i], args);
                    } catch (InvocationTargetException e) {
                        throw e.getTargetException();
                    }
                }
            }

            return invokeNotDelegated(proxy, m, args);
        }
    }

    protected Object invokeNotDelegated(Object proxy, Method m,
                                        Object[] args) throws Throwable {
        throw new InternalError("unexpected method dispatched: " + m);
    }

    protected Integer proxyHashCode(Object proxy) {
        return new Integer(System.identityHashCode(proxy));
    }

    protected Boolean proxyEquals(Object proxy, Object other) {
        return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
    }

    protected String proxyToString(Object proxy) {
        return proxy.getClass().getName() + '@' +
            Integer.toHexString(proxy.hashCode());
    }
}

Delegator 的子类可以覆盖 invokeNotDelegated 以实现代理方法调用的行为,而不是直接委托给其他对象,它们可以覆盖 proxyHashCode,proxyEquals 和 proxyToString,以覆盖代理从 java.lang.Object 继承的方法的默认行为。

要为 Foo 接口的实现构造 Delegator:

Class[] proxyInterfaces = new Class[] { Foo.class };
    Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
        proxyInterfaces,
        new Delegator(proxyInterfaces, new Object[] { new FooImpl() }));

请注意,上面给出的 Delegator 类的实现旨在说明而不是优化;例如,不是缓存和比较 hashCode,equals 和 toString 方法的 Method 对象,而是可以通过它们的字符串名称匹配它们,因为这些方法名称都没有在 java.lang.Object 中重载。

由于 InvocationHandler 是一个函数接口,因此可以使用 lambda 表达式内联定义处理程序。

CGLIB 代理

JDK 动态代理通常用于动态创建代理。JDK 动态代理很容易使用,但 JDK 动态代理方法要求目标对象实现一个或多个接口。对象不一定实现接口,对象集合不一定共享相同的接口。面对这样的需求,JDK 动态代理无法提供答案。

CGLIB 是一个功能强大的高性能代码生成库。它在基于代理的面向方面编程(AOP)框架(如 Spring AOP 和 dynaop)中广泛使用,以提供方法拦截。Hibernate 使用 CGLIB 为持久化类生成代理。EasyMock 和 jMock 是使用模拟对象测试 Java 代码的库。它们都使用 CGLIB 库为没有接口的类创建模拟对象。

在幕后,CGLIB 库使用 ASM,一个小但快速的字节码操作框架,来转换现有的字节码并生成新的类。除了 CGLIB 库之外,脚本语言(如 Groovy 和 BeanShell)也使用 ASM 生成 Java 字节码。 ASM 使用类似 SAX 解析器的机制来实现高性能。不鼓励直接使用 ASM,因为它需要熟悉 JVM,包括类文件格式和指令集。

CGLIB and ASM

请注意,某些框架(如 Spring AOP 和 Hibernate)通常同时使用 CGLIB 库和 JDK 动态代理来满足其需求。Hibernate 使用 JDK 动态代理为 WebShere 应用程序服务器实现事务管理器适配器;默认情况下,Spring AOP 使用 JDK 动态代理来代理接口,除非您强制使用 CGLIB 代理。

CGLIB 代理 API

CGLIB 库代码库很小,但由于缺少文档,很难学习。CGLIB 库的版本组织如下:

要动态创建代理,大多数情况下,您只需要使用代理包中的一些 API。

如前一节所述,CGLIB 库是 ASM 之上的高级层。它对代理不实现接口的类非常有用。本质上,它动态生成一个子类来覆盖代理类的非 final 方法,并连接回调用户定义的拦截器的钩子。它比 JDK 动态代理方法更快。

CGLIB APIs commonly used for proxying classes

通常用于代理具体类的 CGLIB 库 API 如上图所示。net.sf.cglib.proxy.Callback 接口是一个标记接口。 net.sf.cglib.proxy.Enhancer 类使用的所有回调接口都扩展了此接口。

net.sf.cglib.proxy.MethodInterceptor 是最常用的回调类型。它在 AOP 术语中启用 ”around advice“(围绕建议) - 也就是说,您可以在调用 “super” 方法之前和之后调用自定义代码。此外,您可以在调用 super 方法之前修改参数,或者根本不调用它:

public Object intercept(Object object, java.lang.reflect.Method method,
        Object[] args, MethodProxy proxy) throws Throwable;

net.sf.cglib.proxy.MethodInterceptor 是代理的所有方法的回调时,代理上的方法调用将在调用原始对象上的方法之前路由到此方法。如下图所示。第一个参数是代理对象。第二个和第三个参数分别是截获的方法和方法参数。可以使用 java.lang.reflect.Method 对象或使用 net.sf.cglib.proxy.MethodProxy 对象通过常规反射调用原始方法。net.sf.cglib.proxy.MethodProxy 通常是首选,因为它更快。在此方法中,可以在调用原始方法之前或之后注入自定义代码。

CGLIB MethodInterceptor

net.sf.cglib.proxy.MethodInterceptor 满足任何拦截需求,但在某些情况下可能有点过分。为了简化和性能,提供了开箱即用的其他专用回调类型。举些例子,

回调通常用于代理类中的所有方法,如上图所示,但您可以使用 net.sf.cglib.proxy.CallbackFilter 有选择地对方法应用回调。JDK 动态代理中没有这种细粒度控制功能,其中 java.lang.reflect.InvocationHandlerinvoke 方法适用于代理对象的所有方法。

除了代理类之外,CGLIB 还可以通过提供 java.lang.reflect.Proxy 的替代代码 net.sf.cglib.proxy.Proxy 来代理接口,以支持 JDK 1.3 之前的 Java 代理。由于很少使用此替换代理功能,因此此处不涉及相关代理 API。

代理包还提供对 net.sf.cglib.proxy.Mixin 的支持。基本上,它允许将多个对象组合成一个更大的对象。代理上的方法调用委托给底层对象。

让我们看看如何使用 CGLIB 代理 API 创建代理。

创建代理类

CGLIB 代理的核心是 net.sf.cglib.proxy.Enhancer 类。要创建 CGLIB 代理,至少需要一个类。让我们先使用内置的 NoOp 回调:

/**
 * 使用NoOp回调创建代理。目标类必须有一个默认的零参数构造函数。
 *
 * @param targetClass 代理的超类
 * @return 目标类实例的新代理
 */
public Object createProxy(Class targetClass) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(targetClass);
    enhancer.setCallback(NoOp.INSTANCE);
    return enhancer.create();
}

返回值是目标类实例的代理。在此示例中,为 net.sf.cglib.proxy.Enhancer 类配置了单个 net.sf.cglib.proxy.Callback。可以看出,创建一个简单的代理是相当简单的。而不是创建 net.sf.cglib.proxy.Enhancer 的新实例,您可以简单地使用 net.sf.cglib.proxy.Enhancer 类中的静态帮助器方法来创建代理。但是最好使用上例中显示的方法,因为它允许您配置 net.sf.cglib.proxy.Enhancer 实例以精确控制生成的代理。

请注意,目标类作为生成的代理的超类传入。与 JDK 动态代理不同,您无法在代理创建期间传递目标对象。目标对象必须由 CGLIB 库创建。在此示例中,默认的零参数构造函数用于创建目标实例。如果您希望 CGLIB 使用一些参数创建实例,net.sf.cglib.proxy.Enhancer.create(Class[],Object[]) 应当使用该方法替代 net.sf.cglib.proxy.Enhancer.create()。第一个参数指定参数类型和第二个指定参数值。原始类型在参数中应该使用包装类型。

使用 MethodInterceptor

要使代理更有用,可以使用自定义 net.sf.cglib.proxy.MethodInterceptor 替换 net.sf.cglib.proxy.NoOp 回调。代理上的所有方法调用都被调度到 net.sf.cglib.proxy.MethodInterceptor 的单个 intercept 方法。然后,intercept 方法将调用委托给底层对象。

假设您要对目标对象的所有方法调用应用授权检查。如果授权失败,将抛出运行时异常 AuthorizationException。 Authorization.java 接口如下所示:

import java.lang.reflect.Method;

/**
 *  用于说明目的的简单授权服务。
 */
public interface AuthorizationService {
    /**
     * 方法调用的授权检查。如果检查失败将抛出 AuthorizationException。
     */
    void authorize(Method method);
}

net.sf.cglib.proxy.MethodInterceptor 的实现如下:

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.lizjason.cglibproxy.AuthorizationService;

/**
 * 一个简单的 MethodInterceptor 实现,对代理方法调用应用授权检查。
 */
public class AuthorizationInterceptor implements MethodInterceptor {
    private AuthorizationService authorizationService;

    /**
     * 使用给定的 AuthorizationServic 创建 AuthorizationInterceptor 
     */
    public AuthorizationInterceptor (AuthorizationService authorizationService) {
        this.authorizationService = authorizationService;
    }

    /**
     * 拦截代理方法调用以注入授权检查。通过 MethodProxy 调用原始方法。
     *
     * @param object 代理对象
     * @param method 拦截方法
     * @param args 方法传参
     * @param proxy 用于调用原始方法的代理
     * @throws 可抛出任何异常;如果抛出异常,将不会调用 super 方法
     * @return 与代理方法的签名兼容的任何值。
     */
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy ) throws Throwable {
        if (authorizationService != null) {
            //may throw an AuthorizationException if authorization failed
            authorizationService.authorize(method);
        }
        return methodProxy.invokeSuper(object, args);
    }
}

在 intercept 方法中,首先检查授权。如果授权通过,则 intercept 方法将调用目标对象上的原始方法。出于性能原因,使用 CGLIB net.sf.cglib.proxy.MethodProxy 对象而不是使用 java.lang.reflect.Method 对象进行常规反射来调用原始方法。

使用 CallbackFilter

net.sf.cglib.proxy.CallbackFilter 允许您在方法级别设置回调。假设您有一个 PersistenceServiceImpl 类,它有两个方法:save 和 load。 save 方法需要授权检查,但 load 方法不需要。

import com.lizjason.cglibproxy.PersistenceService;

/**
 * PersistenceService 接口的简单实现
 */
public class PersistenceServiceImpl implements PersistenceService {

    public void save(long id, String data) {
        System.out.println(data + " has been saved successfully.");
    }

    public String load(long id) {
        return "Jason Zhicheng Li";
    }
}

请注意,PersistenceServiceImpl 类实现了 PersistenceService 接口,但这不是使用 CGLIB 生成代理所必需的。 PersistenceServiceImpl 的 net.sf.cglib.proxy.CallbackFilter 实现如下:

import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;

/**
 * PersistenceServiceImpl 的 CallbackFilter 实现
 */
public class PersistenceServiceCallbackFilter implements CallbackFilter {

    // save 方法的回调索引
    private static final int SAVE = 0;

    // load 方法的回调索引
    private static final int LOAD = 1;

    /**
     * 指定要用于所调用方法的回调。
     *
     * @method 被调用的方法。
     * @return 此方法的回调数组中的回调索引
     */
    public int accept(Method method) {
        String name = method.getName();
        if ("save".equals(name)) {
            return SAVE;
        }
        // 对于其他方法,包括 load方法,使用第二个回调
        return LOAD;
    }
}

accept 方法将代理方法映射到回调。返回值是特定方法的回调数组中的索引。以下是 PersistenceServiceImpl 类的代理创建实现:

...
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersistenceServiceImpl.class);

CallbackFilter callbackFilter = new PersistenceServiceCallbackFilter();
enhancer.setCallbackFilter(callbackFilter);

AuthorizationService authorizationService = ...
Callback saveCallback = new AuthorizationInterceptor(authorizationService);
Callback loadCallback = NoOp.INSTANCE;
Callback[] callbacks = new Callback[]{saveCallback, loadCallback };
enhancer.setCallbacks(callbacks);
...
return (PersistenceServiceImpl)enhancer.create();

在此示例中,AuthorizationInterceptor 实例应用于 save 方法,NoOp.INSTANCE 应用于 load 方法。可选的,您可以通过 net.sf.cglib.proxy.Enhancer.setInterfaces(Class []) 方法指定代理对象要实现的接口。

除了设置 net.sf.cglib.proxy.Enhancer 的回调数组外,还可以通过 net.sf.cglib.proxy.Enhancer.setCallbackTypes(Class []) 方法指定回调类型数组。如果在代理创建期间没有实际回调实例数组,则回调类型很有用。与回调类似,您需要使用 net.sf.cglib.proxy.CallbackFilter 为每个方法指定回调类型索引。

总结

JDK 动态代理允许我们在运行时动态的创建代理类。但必须要求代理对象实现一个或多个接口。它大量使用反射进行操作,使用原生 Java 代码。

CGLIB 是一个功能强大的高性能代码生成库。它是 JDK 动态代理的补充,除了实现接口之外,还允许代理扩展具体的基类。底层,它使用 ASM 字节码操作框架。实质上,CGLIB 动态生成一个子类来覆盖代理类的非 final 方法。它比使用 Java 反射的 JDK 动态代理方法更快。

CGLIB 不能代理 final 类或任何 final 方法。

对于一般情况,您使用 JDK 动态代理方法来创建代理。 当接口不可用或性能问题时,CGLIB 是一个很好的选择。

原文链接:使用 CGLIB 库动态创建代理

上一篇 下一篇

猜你喜欢

热点阅读