Dynamic Proxies in Java – Java中的动态代理

最后修改: 2017年 5月 1日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

This article is about Java’s dynamic proxies – which is one of the primary proxy mechanisms available to us in the language.

这篇文章是关于Java的动态代理–这是该语言中可供我们使用的主要代理机制之一。

Simply put, proxies are fronts or wrappers that pass function invocation through their own facilities (usually onto real methods) – potentially adding some functionality.

简单地说,代理是通过自己的设施(通常是真正的方法)传递函数调用的前台或包装器–可能会增加一些功能。

Dynamic proxies allow one single class with one single method to service multiple method calls to arbitrary classes with an arbitrary number of methods. A dynamic proxy can be thought of as a kind of Facade, but one that can pretend to be an implementation of any interface. Under the cover, it routes all method invocations to a single handler – the invoke() method.

动态代理允许拥有一个方法的单一类为拥有任意数量的方法的任意类的多个方法调用提供服务。动态代理可以被认为是一种Facade,但它可以假装是任何接口的实现。在伪装之下,它将所有的方法调用路由到一个处理程序invoke()方法。

While it’s not a tool meant for everyday programming tasks, dynamic proxies can be quite useful for framework writers. It may also be used in those cases where concrete class implementations won’t be known until run-time.

虽然它不是一个用于日常编程任务的工具,但动态代理对框架编写者来说是相当有用的。它也可以用于那些在运行时才知道具体类的实现的情况。

This feature is built into the standard JDK, hence no additional dependencies are required.

该功能内置于标准的JDK中,因此不需要额外的依赖性。

2. Invocation Handler

2.调用处理程序

Let us build a simple proxy that doesn’t actually do anything except printing what method was requested to be invoked and return a hard-coded number.

让我们建立一个简单的代理,除了打印请求调用的方法和返回一个硬编码的数字外,实际上什么都不做。

First, we need to create a subtype of java.lang.reflect.InvocationHandler:

首先,我们需要创建一个java.lang.reflect.InvocationHandler的子类型。

public class DynamicInvocationHandler implements InvocationHandler {

    private static Logger LOGGER = LoggerFactory.getLogger(
      DynamicInvocationHandler.class);

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
      throws Throwable {
        LOGGER.info("Invoked method: {}", method.getName());

        return 42;
    }
}

Here we’ve defined a simple proxy that logs which method was invoked and returns 42.

在这里,我们定义了一个简单的代理,记录哪个方法被调用并返回42。

3. Creating Proxy Instance

3.创建代理实例

A proxy instance serviced by the invocation handler we have just defined is created via a factory method call on the java.lang.reflect.Proxy class:

我们刚刚定义的调用处理程序所服务的代理实例是通过对java.lang.reflect.Proxy类的工厂方法调用而创建的。

Map proxyInstance = (Map) Proxy.newProxyInstance(
  DynamicProxyTest.class.getClassLoader(), 
  new Class[] { Map.class }, 
  new DynamicInvocationHandler());

Once we have a proxy instance we can invoke its interface methods as normal:

一旦我们有了一个代理实例,我们就可以像平常一样调用它的接口方法。

proxyInstance.put("hello", "world");

As expected a message about put() method being invoked is printed out in the log file.

正如预期的那样,关于put()方法被调用的消息被打印在日志文件中。

4. Invocation Handler via Lambda Expressions

4.通过Lambda表达式的调用处理程序

Since InvocationHandler is a functional interface, it is possible to define the handler inline using lambda expression:

由于InvocationHandler是一个功能接口,所以可以使用lambda表达式来内联定义处理程序。

Map proxyInstance = (Map) Proxy.newProxyInstance(
  DynamicProxyTest.class.getClassLoader(), 
  new Class[] { Map.class }, 
  (proxy, method, methodArgs) -> {
    if (method.getName().equals("get")) {
        return 42;
    } else {
        throw new UnsupportedOperationException(
          "Unsupported method: " + method.getName());
    }
});

Here, we defined a handler that returns 42 for all get operations and throws UnsupportedOperationException for everything else.

在这里,我们定义了一个处理程序,对所有的获取操作返回42,对其他的操作抛出UnsupportedOperationException

It’s invoked in the exactly the same way:

它是以完全相同的方式调用的。

(int) proxyInstance.get("hello"); // 42
proxyInstance.put("hello", "world"); // exception

5. Timing Dynamic Proxy Example

5.定时动态代理实例

Let’s examine one potential real-world scenario for dynamic proxies.

让我们来研究一下动态代理的一个潜在的现实世界场景。

Suppose we want to record how long our functions take to execute. To this extent, we first define a handler capable of wrapping the “real” object, tracking timing information and reflective invocation:

假设我们想记录我们的函数需要多长时间来执行。在这种情况下,我们首先定义一个能够包装 “真实 “对象的处理程序,跟踪时间信息和反射性调用。

public class TimingDynamicInvocationHandler implements InvocationHandler {

    private static Logger LOGGER = LoggerFactory.getLogger(
      TimingDynamicInvocationHandler.class);
    
    private final Map<String, Method> methods = new HashMap<>();

    private Object target;

    public TimingDynamicInvocationHandler(Object target) {
        this.target = target;

        for(Method method: target.getClass().getDeclaredMethods()) {
            this.methods.put(method.getName(), method);
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
      throws Throwable {
        long start = System.nanoTime();
        Object result = methods.get(method.getName()).invoke(target, args);
        long elapsed = System.nanoTime() - start;

        LOGGER.info("Executing {} finished in {} ns", method.getName(), 
          elapsed);

        return result;
    }
}

Subsequently, this proxy can be used on various object types:

随后,这个代理可以在各种对象类型上使用。

Map mapProxyInstance = (Map) Proxy.newProxyInstance(
  DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, 
  new TimingDynamicInvocationHandler(new HashMap<>()));

mapProxyInstance.put("hello", "world");

CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance(
  DynamicProxyTest.class.getClassLoader(), 
  new Class[] { CharSequence.class }, 
  new TimingDynamicInvocationHandler("Hello World"));

csProxyInstance.length()

Here, we have proxied a map and a char sequence (String).

这里,我们代理了一个地图和一个char序列(String)。

Invocations of the proxy methods will delegate to the wrapped object as well as produce logging statements:

对代理方法的调用将委托给被包装的对象,并产生日志声明。

Executing put finished in 19153 ns 
Executing get finished in 8891 ns 
Executing charAt finished in 11152 ns 
Executing length finished in 10087 ns

6. Conclusion

6.结论

In this quick tutorial, we have examined Java’s dynamic proxies as well as some of its possible usages.

在这个快速教程中,我们研究了Java的动态代理,以及它的一些可能的用途。

As always, the code in the examples can be found over on GitHub.

一如既往,例子中的代码可以在GitHub上找到over