Java的动态代理

Published on 2017 - 02 - 19

前言

Suppose you want to construct an object of a class that implements one or more interfaces whose exact nature you may not know at compile time. This is a difficult problem. To construct an actual class, you can simply use the newInstance method or use reflection to find a constructor. But you can’t instantiate an interface. You need to define a new class in a running program.

To overcome this problem, some programs generate code, place it into a file, invoke the compiler, and then load the resulting class file. Naturally, this is slow, and it also requires deployment of the compiler together with the program. The proxy mechanism is a better solution. The proxy class can create brand-new classes at runtime. Such a proxy class implements the interfaces that you specify. In particular, the proxy class has the following methods:

  • All methods required by the specified interfaces; and
  • All methods defined in the Object class (toString, equals, and so on).

However, you cannot define new code for these methods at runtime. Instead, you must supply an invocation handler. An invocation handler is an object of any class that implements the InvocationHandler interface. That interface has a single method:

Object invoke(Object proxy, Method method, Object[] args)

Whenever a method is called on the proxy object, the invoke method of the invocation handler gets called, with the Method object and parameters of the original call. The invocation handler must then figure out how to handle the call.

To create a proxy object, use the newProxyInstance method of the Proxy class. The method has three parameters:

  • A class loader. As part of the Java security model, different class loaders can be used for system classes, classes that are downloaded from the Internet, and so on. For now, we specify null to use the default class loader.
  • An array of Class objects, one for each interface to be implemented.
  • An invocation handler.

There are two remaining questions. How do we define the handler? And what can we do with the resulting proxy object? The answers depend, of course, on the problem that we want to solve with the proxy mechanism. Proxies can be used for many purposes, such as

  • Routing method calls to remote servers
  • Associating user interface events with actions in a running program
  • Tracing method calls for debugging purposes

这本书对Proxy讲解的不是很清晰,所以这边仅仅记录一下这本书对Proxy的解释,后面的代码以及示例都不会采用这本书的介绍。

Dynamic proxies let you wrap a proxy object around another object. You can arrange for the outer object—the proxy—to intercept all the calls intended for the wrapped object. The proxy will usually pass these calls on to the wrapped object, but you can add code that executes before or after the intercepted calls. Limitations to dynamic proxies prevent you from wrapping any arbitrary object. Under the right conditions, though, dynamic proxies give you complete control over the operation of an object that you want to wrap with a proxy.

Dynamic proxies work with the interfaces that an object’s class implements. The calls that the proxy can intercept are calls that one of these interfaces defines. If you have a class that implements an interface with methods you want to intercept, you can use dynamic proxies to wrap an instance of that class.

批注:这个解释比上面两个解释要好太多了,解释真心太好了。文中解释了newProxyInstance的三个参数,第二个参数为什么是Interface,因为需要通过Interface来intercept(干预)方法调用的过程。

如何使用Proxy

To create a dynamic proxy, you must have a list of the interfaces that you want to intercept. Fortunately, you can usually obtain this list by interrogating the object that you want to wrap, using a line such as:

Class[] classes = obj.getClass().getInterfaces();

This code establishes that the methods you want to intercept are those that belong to interfaces that an object’s class implements. To build a dynamic proxy, you need two other ingredients: a class loader and a class that contains the behavior that you want to execute when your proxy intercepts a call. As with the list of interfaces, you can obtain an appropriate class loader by using the one associated with the object that you want to wrap:

ClassLoader loader = obj.getClass().getClassLoader();

The last ingredient that you need is the proxy object itself. This object must be an instance of a class that implements the InvocationHandler interface in the java.lang.reflect package. That interface declares the following operation:

public Object invoke(Object proxy, Method m, Object[] args)
    throws Throwable;

When you wrap an object in a dynamic proxy, calls intended for the wrapped object are diverted to this invoke() operation, in a class that you supply. Your code for the invoke() method will probably need to pass each method call on to the wrapped object. You can pass on the invocation with a line such as:

result = m.invoke(obj, args);

This line uses reflection to pass along the desired call to the wrapped object. The beauty of dynamic proxies is that you can add any behavior you like before or after executing this line.

Proxy示例

Suppose that you want to log a warning if a method takes a long time to execute. You might create an ImpatientProxy class with the following code:

package app.proxy.dynamic;
import java.lang.reflect.*;
public class ImpatientProxy implements InvocationHandler {
    private Object obj;
    private ImpatientProxy(Object obj) {
        this.obj = obj;
    }

    public Object invoke(
            Object proxy, Method m, Object[] args)
            throws Throwable {
        Object result;
        long t1 = System.currentTimeMillis();
        result = m.invoke(obj, args);
        long t2 = System.currentTimeMillis();
        if (t2 - t1 > 10) {
            System.out.println(
                "> It takes " + (t2 - t1)
                + " millis to invoke " + m.getName()
                + "() with");
            for (int i = 0; i < args.length; i++)
                System.out.println(
                    ">     arg[" + i + "]: " + args[i]);
        }
        return result;
    }
}

This class implements the invoke() method so that it checks the time it takes for the wrapped object to complete an invoked operation. If that execution time is too long, the ImpatientProxy class prints a warning.

To put an ImpatientProxy object to use, you need to use the Proxy class in the java.lang.reflect package. The Proxy class will need a list of interfaces and a class loader, as well as an instance of ImpatientProxy. To simplify the creation of a dynamic proxy, we might add the following method to the ImpatientProxy class:

public static Object newInstance(Object obj) {
    ClassLoader loader = obj.getClass().getClassLoader();
    Class[] classes = obj.getClass().getInterfaces();
    return Proxy.newProxyInstance(
        loader, classes, new ImpatientProxy(obj));
}

This static method creates the dynamic proxy for us. Given an object to wrap, the newInstance() method extracts the object’s list of interfaces and class loader. The method instantiates the ImpatientProxy class, passing it the object to wrap. All these ingredients are then passed to the Proxy class’s newProxyInstance() method.

The returned object will implement all the interfaces that the wrapped object’s class implements. We can cast the returned object to any of these interfaces.

Suppose that you are working with a set of objects, and some operations seem to run slowly for some objects. To find which objects are behaving sluggishly, you can wrap the set in an ImpatientProxy object. The following code shows this example:

package app.proxy.dynamic;
import java.util.HashSet;
import java.util.Set;
import com.oozinoz.firework.Firecracker;
import com.oozinoz.firework.Sparkler;
import com.oozinoz.utility.Dollars;

public class ShowDynamicProxy {
    public static void main(String[] args) {
        Set s = new HashSet();
        s = (Set)ImpatientProxy.newInstance(s);
        s.add(new Sparkler(
            "Mr. Twinkle", new Dollars(0.05)));
        s.add(new BadApple("Lemon"));
        s.add(new Firecracker(
            "Mr. Boomy", new Dollars(0.25)));
        System.out.println(
            "The set contains " + s.size() + " things.");
    }
}

This code creates a Set object to hold a few items. The code then wraps this set, using an ImpatientProxy object, casting the result of the newInstance() method back to a Set. The result is that the s object behaves just like a set, except that the code in ImpatientProxy will issue a complaint if any method takes too long to execute. For example, when the program calls the set’s add() method, our ImpatientProxy object intercepts the call. The ImpatientProxy object passes the call along to the real set but times the result of each call.

批注:InvocationHandler中的invoke方法的第一个参数Proxy指的就是Proxy这个对象。

Reference