Dynamic Proxy Classes

来源:互联网 发布:java二进制流转字符串 编辑:程序博客网 时间:2024/06/05 15:48

http://docs.oracle.com/javase/1.4.2/docs/guide/reflection/proxy.html

Contents

Introduction
Dynamic Proxy API
Serialization
Examples

Introduction

A dynamic proxy class is a class that implements a list ofinterfaces specified at runtime such that a method invocation throughone of the interfaces on an instance of the class will be encoded anddispatched to another object through a uniform interface. Thus, adynamic proxy class can be used to create a type-safe proxy object fora list of interfaces without requiring pre-generation of the proxyclass, such as with compile-time tools. Method invocations on aninstance of a dynamic proxy class are dispatched to a single method inthe instance's invocation handler, and they are encoded with ajava.lang.reflect.Method object identifying the methodthat was invoked and an array of typeObject containingthe arguments.

Dynamic proxy classes are useful to an application or library thatneeds to provide type-safe reflective dispatch of invocations onobjects that present interface APIs. For example, an application canuse a dynamic proxy class to create an object that implements multiplearbitrary event listener interfaces-- interfaces that extendjava.util.EventListener-- to process a variety of eventsof different types in a uniform fashion, such as by logging all suchevents to a file.

Dynamic Proxy Class API

A dynamic proxy class (simply referred to as a proxyclass below) is a class that implements a list of interfacesspecified at runtime when the class is created.

A proxy interface is such an interface that is implementedby a proxy class.

A proxy instance is an instance of a proxy class.

Creating a Proxy Class

Proxy classes, as well as instances of them, are created using thestatic methods of the classjava.lang.reflect.Proxy.

The Proxy.getProxyClass method returns thejava.lang.Class object for a proxy class given a classloader and an array of interfaces. The proxy class will be defined inthe specified class loader and will implement all of the suppliedinterfaces. If a proxy class for the same permutation of interfaceshas already been defined in the class loader, then the existing proxyclass will be returned; otherwise, a proxy class for those interfaceswill be generated dynamically and defined in the class loader.

There are several restrictions on the parameters that may be passedto Proxy.getProxyClass:

  • All of the Class objects in the interfacesarray must represent interfaces, not classes or primitive types.
  • No two elements in the interfaces array may refer toidentical Class objects.
  • All of the interface types must be visible by name through thespecified class loader. In other words, for class loadercl and every interfacei, the followingexpression must be true:
    Class.forName(i.getName(), false, cl) == i
  • All non-public interfaces must be in the same package; otherwise,it would not be possible for the proxy class to implement all of theinterfaces, regardless of what package it is defined in.
  • No two interfaces may each have a method with the same name andparameter signature but different return type.
  • The resulting proxy class must not exceed any limits imposed onclasses by the virtual machine. For example, the VM may limit thenumber of interfaces that a class may implement to 65535; in thatcase, the size of theinterfaces array must not exceed65535.

If any of these restrictions are violated,Proxy.getProxyClass will throw anIllegalArgumentException. If theinterfacesarray argument or any of its elements are null, aNullPointerException will be thrown.

Note that the order of the specified proxy interfaces issignificant: two requests for a proxy class with the same combinationof interfaces but in a different order will result in two distinctproxy classes. Proxy classes are distinguished by the order of theirproxy interfaces in order to provide deterministic method invocationencoding in cases where two or more of the proxy interfaces share amethod with the same name and parameter signature; this reasoning isdescribed in more detail in the section below titledMethods Duplicated in Multiple Proxy Interfaces.

So that a new proxy class does not need to be generated each timeProxy.getProxyClass is invoked with the same class loaderand list of interfaces, the implementation of the dynamic proxy classAPI should keep a cache of generated proxy classes, keyed by theircorresponding loaders and interface list. The implementation shouldbe careful not to refer to the class loaders, interfaces, and proxyclasses in such a way as to prevent class loaders, and all of theirclasses, from being garbage collected when appropriate.

Proxy Class Properties

A proxy class has the following properties:

  • Proxy classes are public, final, and not abstract.
  • The unqualified name of a proxy class is unspecified. The spaceof class names that begin with the string"$Proxy" is,however, to be reserved for proxy classes.
  • A proxy class extends java.lang.reflect.Proxy.
  • A proxy class implements exactly the interfaces specified at itscreation, in the same order.
  • If a proxy class implements a non-public interface, then it willbe defined in the same package as that interface. Otherwise, thepackage of a proxy class is also unspecified. Note that packagesealing will not prevent a proxy class from being successfully definedin a particular package at runtime, and neither will classes alreadydefined in the same class loader and the same package with particularsigners.
  • Since a proxy class implements all of the interfaces specified atits creation, invokinggetInterfaces on itsClass object will return an array containing the samelist of interfaces (in the order specified at its creation), invokinggetMethods on itsClass object will returnan array of Method objects that include all of themethods in those interfaces, and invokinggetMethod willfind methods in the proxy interfaces as would be expected.
  • The Proxy.isProxyClass method will return true if itis passed a proxy class-- a class returned byProxy.getProxyClass or the class of an object returned byProxy.newProxyInstance-- and false otherwise. Thereliability of this method is important for the ability to use it tomake security decisions, so its implementation should not just test ifthe class in question extendsjava.lang.reflect.Proxy.
  • The java.security.ProtectionDomain of a proxy classis the same as that of system classes loaded by the bootstrap classloader, such asjava.lang.Object, because the code for aproxy class is generated by trusted system code. This protectiondomain will typically be grantedjava.security.AllPermission.

Creating a Proxy Instance

Each proxy class has one public constructor that takes one argument,an implementation of the interfaceInvocationHandler.

Each proxy instance has an associated invocation handler object,the one that was passed to its constructor. Rather than having to usethe reflection API to access the public constructor, a proxy instancecan be also be created by calling theProxy.newProxyInstance method, which combines the actionsof calling Proxy.getProxyClass with invoking theconstructor with an invocation handler.Proxy.newProxyInstance throwsIllegalArgumentException for the same reasons thatProxy.getProxyClass does.

Proxy Instance Properties

A proxy instance has the following properties:

  • Given a proxy instance proxy and one of theinterfaces implemented by its proxy classFoo, thefollowing expression will return true:
    proxy instanceof Foo
    and the following cast operation will succeed (rather than throwinga ClassCastException):
    (Foo) proxy
  • The static Proxy.getInvocationHandler method willreturn the invocation handler associated with the proxy instancepassed as its argument. If the object passed toProxy.getInvocationHandler is not a proxy instance, thenanIllegalArgumentException will be thrown.
  • An interface method invocation on a proxy instance will beencoded and dispatched to the invocation handler'sinvokemethod as described below.

    The proxy instance itself will be passed as the first argument ofinvoke, which is of typeObject.

    The second argument passed to invoke will be thejava.lang.reflect.Method instance corresponding to theinterface method invoked on the proxy instance. The declaring classof theMethod object will be the interface that themethod was declared in, which may be a superinterface of the proxyinterface that the proxy class inherits the method through.

    The third argument passed to invoke will be an arrayof objects containing the values of the arguments passed in the methodinvocation on the proxy instance. Arguments of primitive types arewrapped in an instance of the appropriate primitive wrapper class,such as java.lang.Integer orjava.lang.Boolean. The implementation of theinvoke method is free to modify the contents of thisarray.

    The value returned by the invoke method will becomethe return value of the method invocation on the proxy instance. Ifthe declared return value of the interface method is a primitive type,then the value returned byinvoke must be an instance ofthe corresponding primitive wrapper class; otherwise, it must be atype assignable to the declared return type. If the value returned byinvoke isnull and the interface method'sreturn type is primitive, then a NullPointerExceptionwill be thrown by the method invocation on the proxy instance. If thevalue returned byinvoke is otherwise not compatible withthe method's declared return type as described above, aClassCastException will be thrown by the proxy instance.

    If an exception is thrown by the invoke method, itwill be also thrown by the method invocation on the proxy instance.The exception's type must be assignable to either any of the exceptiontypes declared in the signature of the interface method or to theunchecked exception types java.lang.RuntimeException orjava.lang.Error. If a checked exception is thrown byinvoke that is not assignable to any of the exceptiontypes declared in thethrows clause of the interfacemethod, then an UndeclaredThrowableException will bethrown by the method invocation on the proxy instance. The UndeclaredThrowableException will be constructed withthe exception that was thrown by theinvoke method.

  • An invocation of the hashCode,equals, or toString methods declared injava.lang.Object on a proxy instance will be encoded anddispatched to the invocation handler'sinvoke method inthe same manner as interface method invocations are encoded anddispatched, as described above. The declaring class of theMethod object passed toinvoke will bejava.lang.Object. Other public methods of a proxyinstance inherited fromjava.lang.Object are notoverridden by a proxy class, so invocations of those methods behavelike they do for instances ofjava.lang.Object.

Methods Duplicated in Multiple Proxy Interfaces

When two or more interfaces of a proxy class contain a method withthe same name and parameter signature, the order of the proxy class'sinterfaces becomes significant. When such aduplicate methodis invoked on a proxy instance, the Method object passedto the invocation handler will not necessarily be the one whosedeclaring class is assignable from the reference type of the interfacethat the proxy's method was invoked through. This limitation existsbecause the corresponding method implementation in the generated proxyclass cannot determine which interface it was invoked through.Therefore, when a duplicate method is invoked on a proxy instance, theMethod object for the method in the foremost interfacethat contains the method (either directly or inherited through asuperinterface) in the proxy class's list of interfaces is passed tothe invocation handler'sinvoke method, regardless of thereference type through which the method invocation occurred.

If a proxy interface contains a method with the same name andparameter signature as thehashCode, equals,or toString methods of java.lang.Object,when such a method is invoked on a proxy instance, theMethod object passed to the invocation handler will havejava.lang.Object as its declaring class. In other words,the public, non-final methods ofjava.lang.Objectlogically precede all of the proxy interfaces for the determination ofwhichMethod object to pass to the invocation handler.

Note also that when a duplicate method is dispatched to aninvocation handler, theinvoke method may only throwchecked exception types that are assignable to one of the exceptiontypes in thethrows clause of the method in all ofthe proxy interfaces that it can be invoked through. If theinvoke method throws a checked exception that is notassignable to any of the exception types declared by the method in oneof the the proxy interfaces that it can be invoked through, then anunchecked UndeclaredThrowableException will be thrown bythe invocation on the proxy instance. This restriction means that notall of the exception types returned by invokinggetExceptionTypes on the Method objectpassed to the invoke method can necessarily be thrownsuccessfully by theinvoke method.

Serialization

Since java.lang.reflect.Proxy implementsjava.io.Serializable, proxy instances can be serialized,as described in this section. If a proxy instance contains aninvocation handler that is not assignable tojava.io.Serializable, however, then ajava.io.NotSerializableException will be thrown if suchan instance is written to ajava.io.ObjectOutputStream.Note that for proxy classes, implementingjava.io.Externalizable has the same effect with respectto serialization as implementingjava.io.Serializable:the writeExternal and readExternal methodsof theExternalizable interface will never be invoked ona proxy instance (or an invocation handler) as part of itsserialization process. As with allClass objects, theClass object for a proxy class is always serializable.

A proxy class has no serializable fields and aserialVersionUID of0L. In other words,when the Class object for a proxy class is passed to thestaticlookup method ofjava.io.ObjectStreamClass, the returnedObjectStreamClass instance will have the followingproperties:

  • Invoking its getSerialVersionUID method will return0L.
  • Invoking its getFields method will return an arrayof length zero.
  • Invoking its getField method with anyString argument will returnnull.

The stream protocol for Object Serialization supports a type codenamed TC_PROXYCLASSDESC, which is a terminal symbol inthe grammar for the stream format; its type and value are defined bythe following constant field in thejava.io.ObjectStreamConstants interface:

    final static byte TC_PROXYCLASSDESC = (byte)0x7D;

The grammar also includes the following two rules, the first beingan alternate expansion of the originalnewClassDesc rule:

newClassDesc:
        TC_PROXYCLASSDESC newHandle proxyClassDescInfo

proxyClassDescInfo:
        (int)<count> proxyInterfaceName[count] classAnnotation superClassDesc

proxyInterfaceName:
        (utf)

When an ObjectOutputStream serializes the classdescriptor for a class that is a proxy class, as determined by passingitsClass object to the Proxy.isProxyClassmethod, it uses theTC_PROXYCLASSDESC type code insteadof TC_CLASSDESC, following the rules above. In theexpansion ofproxyClassDescInfo, the sequence ofproxyInterfaceName items are the names of all of the interfacesimplemented by the proxy class, in the order that they are returned byinvoking thegetInterfaces method on theClass object. The classAnnotation andsuperClassDesc items have the same meaning as they do in theclassDescInfo rule. For a proxy class,superClassDescis the class descriptor for its superclass,java.lang.reflect.Proxy; including this descriptor allowsfor the evolution of the serialized representation of the classProxy for proxy instances.

For non-proxy classes, ObjectOutputStream calls itsprotected annotateClass method to allow subclasses towrite custom data to the stream for a particular class. For proxyclasses, instead ofannotateClass, the following methodin java.io.ObjectOutputStream is called with theClass object for the proxy class:

    protected void annotateProxyClass(Class cl) throws IOException;

The default implementation of annotateProxyClass inObjectOutputStream does nothing.

When an ObjectInputStream encounters the type codeTC_PROXYCLASSDESC, it deserializes the class descriptorfor a proxy class from the stream, formatted as described above.Instead of calling itsresolveClass method to resolve theClass object for the class descriptor, the followingmethod injava.io.ObjectInputStream is called:

    protected Class resolveProxyClass(String[] interfaces)throws IOException, ClassNotFoundException;

The list of interface names that were deserialized in the proxyclass descriptor are passed as theinterfaces argument toresolveProxyClass.

The default implementation of resolveProxyClass inObjectInputStream returns the results of callingProxy.getProxyClass with the list ofClassobjects for the interfaces named in the interfacesparameter. TheClass object used for each interface namei is the value retuned by calling

Class.forName(i, false, loader)
where loader is the first non-null class loader up theexecution stack, ornull if no non-null class loaders areon the stack. This is the same class loader choice made by thedefault behavior of theresolveClass method. This samevalue of loader is also the class loader passed toProxy.getProxyClass. IfProxy.getProxyClassthrows an IllegalArgumentException,resolveClass will throw aClassNotFoundException containing theIllegalArgumentException.

Since a proxy class never has its own serializable fields, theclassdata[] in the stream representation of a proxy instanceconsists wholly of the instance data for its superclass,java.lang.reflect.Proxy.Proxy has oneserializable field, h, which contains the invocationhandler for the proxy instance.

Examples

Here is a simple example that prints out a message before and aftera method invocation on an object that implements an arbitrary list ofinterfaces:

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;    }}

To construct a DebugProxy for an implementation of theFoo interface and call one of its methods:

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

Here is an example of a utility invocation handler class thatprovides default proxy behavior for methods inherited fromjava.lang.Object and implements delegation of certainproxy method invocations to distinct objects depending on theinterface of the invoked method:

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());    }}

Subclasses of Delegator can overrideinvokeNotDelegated to implement the behavior of proxymethod invocations not to be directly delegated to other objects, andthey can overrideproxyHashCode,proxyEquals, and proxyToString to overridethe default behavior of the methods the proxy inherits fromjava.lang.Object.

To construct a Delegator for an implementation of theFoo interface:

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

Note that the implementation of the Delegator classgiven above is intended to be more illustrative than optimized; forexample, instead of caching and comparing theMethodobjects for the hashCode, equals, andtoString methods, it could just match them by theirstring names, because none of those method names are overloaded injava.lang.Object.


原创粉丝点击