Dynamic Proxy Classes |
Documentation Contents |
Introduction
Dynamic Proxy API
Serialization
Examples
A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface. Thus, a dynamic proxy class can be used to create a type-safe proxy object for a list of interfaces without requiring pre-generation of the proxy class, such as with compile-time tools. Method invocations on an instance of a dynamic proxy class are dispatched to a single method in the instance's invocation handler, and they are encoded with a
java.lang.reflect.Method
object identifying the method that was invoked and an array of typeObject
containing the arguments.Dynamic proxy classes are useful to an application or library that needs to provide type-safe reflective dispatch of invocations on objects that present interface APIs. For example, an application can use a dynamic proxy class to create an object that implements multiple arbitrary event listener interfaces-- interfaces that extend
java.util.EventListener
-- to process a variety of events of different types in a uniform fashion, such as by logging all such events to a file.
A dynamic proxy class (simply referred to as a proxy class below) is a class that implements a list of interfaces specified at runtime when the class is created.
A proxy interface is such an interface that is implemented by 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 the static methods of the class java.lang.reflect.Proxy.
The
Proxy.getProxyClass
method returns thejava.lang.Class
object for a proxy class given a class loader and an array of interfaces. The proxy class will be defined in the specified class loader and will implement all of the supplied interfaces. If a proxy class for the same permutation of interfaces has already been defined in the class loader, then the existing proxy class will be returned; otherwise, a proxy class for those interfaces will be generated dynamically and defined in the class loader.There are several restrictions on the parameters that may be passed to
Proxy.getProxyClass
:
- All of the
Class
objects in theinterfaces
array must represent interfaces, not classes or primitive types.- No two elements in the
interfaces
array may refer to identicalClass
objects.- All of the interface types must be visible by name through the specified class loader. In other words, for class loader
cl
and every interfacei
, the following expression 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 the interfaces, regardless of what package it is defined in.
- For any set of member methods of the specified interfaces that have the same signature:
- If the return type of any of the methods is a primitive type or void, then all of the methods must have that same return type.
- Otherwise, one of the methods must have a return type that is assignable to all of the return types of the rest of the methods.
- The resulting proxy class must not exceed any limits imposed on classes by the virtual machine. For example, the VM may limit the number of interfaces that a class may implement to 65535; in that case, the size of the
interfaces
array must not exceed 65535.If any of these restrictions are violated,
Proxy.getProxyClass
will throw anIllegalArgumentException
. If theinterfaces
array argument or any of its elements arenull
, aNullPointerException
will be thrown.Note that the order of the specified proxy interfaces is significant: two requests for a proxy class with the same combination of interfaces but in a different order will result in two distinct proxy classes. Proxy classes are distinguished by the order of their proxy interfaces in order to provide deterministic method invocation encoding in cases where two or more of the proxy interfaces share a method with the same name and parameter signature; this reasoning is described in more detail in the section below titled Methods Duplicated in Multiple Proxy Interfaces.
So that a new proxy class does not need to be generated each time
Proxy.getProxyClass
is invoked with the same class loader and list of interfaces, the implementation of the dynamic proxy class API should keep a cache of generated proxy classes, keyed by their corresponding loaders and interface list. The implementation should be careful not to refer to the class loaders, interfaces, and proxy classes in such a way as to prevent class loaders, and all of their classes, 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 space of 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 its creation, in the same order.
- If a proxy class implements a non-public interface, then it will be defined in the same package as that interface. Otherwise, the package of a proxy class is also unspecified. Note that package sealing will not prevent a proxy class from being successfully defined in a particular package at runtime, and neither will classes already defined in the same class loader and the same package with particular signers.
- Since a proxy class implements all of the interfaces specified at its creation, invoking
getInterfaces
on itsClass
object will return an array containing the same list of interfaces (in the order specified at its creation), invokinggetMethods
on itsClass
object will return an array ofMethod
objects that include all of the methods in those interfaces, and invokinggetMethod
will find methods in the proxy interfaces as would be expected.- The
Proxy.isProxyClass
method will return true if it is passed a proxy class-- a class returned byProxy.getProxyClass
or the class of an object returned byProxy.newProxyInstance
-- and false otherwise. The reliability of this method is important for the ability to use it to make security decisions, so its implementation should not just test if the class in question extendsjava.lang.reflect.Proxy
.- The
java.security.ProtectionDomain
of a proxy class is the same as that of system classes loaded by the bootstrap class loader, such asjava.lang.Object
, because the code for a proxy class is generated by trusted system code. This protection domain 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 interface
InvocationHandler
.Each proxy instance has an associated invocation handler object, the one that was passed to its constructor. Rather than having to use the reflection API to access the public constructor, a proxy instance can be also be created by calling the
Proxy.newProxyInstance
method, which combines the actions of callingProxy.getProxyClass
with invoking the constructor 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 the interfaces implemented by its proxy classFoo
, the following expression will return true:and the following cast operation will succeed (rather than throwing aproxy instanceof Foo
ClassCastException
):(Foo) proxy
- The static
Proxy.getInvocationHandler
method will return the invocation handler associated with the proxy instance passed as its argument. If the object passed toProxy.getInvocationHandler
is not a proxy instance, then anIllegalArgumentException
will be thrown.- An interface method invocation on a proxy instance will be encoded and dispatched to the invocation handler's
invoke
method as described below.The proxy instance itself will be passed as the first argument of
invoke
, which is of typeObject
.The second argument passed to
invoke
will be thejava.lang.reflect.Method
instance corresponding to the interface method invoked on the proxy instance. The declaring class of theMethod
object will be the interface that the method was declared in, which may be a superinterface of the proxy interface that the proxy class inherits the method through.The third argument passed to
invoke
will be an array of objects containing the values of the arguments passed in the method invocation on the proxy instance. Arguments of primitive types are wrapped in an instance of the appropriate primitive wrapper class, such asjava.lang.Integer
orjava.lang.Boolean
. The implementation of theinvoke
method is free to modify the contents of this array.The value returned by the
invoke
method will become the return value of the method invocation on the proxy instance. If the declared return value of the interface method is a primitive type, then the value returned byinvoke
must be an instance of the corresponding primitive wrapper class; otherwise, it must be a type assignable to the declared return type. If the value returned byinvoke
isnull
and the interface method's return type is primitive, then aNullPointerException
will be thrown by the method invocation on the proxy instance. If the value returned byinvoke
is otherwise not compatible with the 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, it will be also thrown by the method invocation on the proxy instance. The exception's type must be assignable to either any of the exception types declared in the signature of the interface method or to the unchecked exception typesjava.lang.RuntimeException
orjava.lang.Error
. If a checked exception is thrown byinvoke
that is not assignable to any of the exception types declared in thethrows
clause of the interface method, then anUndeclaredThrowableException
will be thrown by the method invocation on the proxy instance. TheUndeclaredThrowableException
will be constructed with the exception that was thrown by theinvoke
method.- An invocation of the
hashCode
,equals
, ortoString
methods declared injava.lang.Object
on a proxy instance will be encoded and dispatched to the invocation handler'sinvoke
method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of theMethod
object passed toinvoke
will bejava.lang.Object
. Other public methods of a proxy instance inherited fromjava.lang.Object
are not overridden by a proxy class, so invocations of those methods behave like 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 with the same name and parameter signature, the order of the proxy class's interfaces becomes significant. When such a duplicate method is invoked on a proxy instance, the
Method
object passed to the invocation handler will not necessarily be the one whose declaring class is assignable from the reference type of the interface that the proxy's method was invoked through. This limitation exists because the corresponding method implementation in the generated proxy class 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 interface that contains the method (either directly or inherited through a superinterface) in the proxy class's list of interfaces is passed to the invocation handler'sinvoke
method, regardless of the reference type through which the method invocation occurred.If a proxy interface contains a method with the same name and parameter signature as the
hashCode
,equals
, ortoString
methods ofjava.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.Object
logically precede all of the proxy interfaces for the determination of whichMethod
object to pass to the invocation handler.Note also that when a duplicate method is dispatched to an invocation handler, the
invoke
method may only throw checked exception types that are assignable to one of the exception types in thethrows
clause of the method in all of the proxy interfaces that it can be invoked through. If theinvoke
method throws a checked exception that is not assignable to any of the exception types declared by the method in one of the the proxy interfaces that it can be invoked through, then an uncheckedUndeclaredThrowableException
will be thrown by the invocation on the proxy instance. This restriction means that not all of the exception types returned by invokinggetExceptionTypes
on theMethod
object passed to theinvoke
method can necessarily be thrown successfully by theinvoke
method.
Since
java.lang.reflect.Proxy
implementsjava.io.Serializable
, proxy instances can be serialized, as described in this section. If a proxy instance contains an invocation handler that is not assignable tojava.io.Serializable
, however, then ajava.io.NotSerializableException
will be thrown if such an instance is written to ajava.io.ObjectOutputStream
. Note that for proxy classes, implementingjava.io.Externalizable
has the same effect with respect to serialization as implementingjava.io.Serializable
: thewriteExternal
andreadExternal
methods of theExternalizable
interface will never be invoked on a proxy instance (or an invocation handler) as part of its serialization process. As with allClass
objects, theClass
object for a proxy class is always serializable.A proxy class has no serializable fields and a
serialVersionUID
of0L
. In other words, when theClass
object for a proxy class is passed to the staticlookup
method ofjava.io.ObjectStreamClass
, the returnedObjectStreamClass
instance will have the following properties:
- Invoking its
getSerialVersionUID
method will return0L
.- Invoking its
getFields
method will return an array of length zero.- Invoking its
getField
method with anyString
argument will returnnull
.The stream protocol for Object Serialization supports a type code named
TC_PROXYCLASSDESC
, which is a terminal symbol in the grammar for the stream format; its type and value are defined by the 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 being an alternate expansion of the original newClassDesc rule:
newClassDesc:
TC_PROXYCLASSDESC
newHandle proxyClassDescInfoproxyClassDescInfo:
(int)<count>
proxyInterfaceName[count] classAnnotation superClassDescproxyInterfaceName:
(utf)
When an
ObjectOutputStream
serializes the class descriptor for a class that is a proxy class, as determined by passing itsClass
object to theProxy.isProxyClass
method, it uses theTC_PROXYCLASSDESC
type code instead ofTC_CLASSDESC
, following the rules above. In the expansion of proxyClassDescInfo, the sequence of proxyInterfaceName items are the names of all of the interfaces implemented by the proxy class, in the order that they are returned by invoking thegetInterfaces
method on theClass
object. The classAnnotation and superClassDesc items have the same meaning as they do in the classDescInfo rule. For a proxy class, superClassDesc is the class descriptor for its superclass,java.lang.reflect.Proxy
; including this descriptor allows for the evolution of the serialized representation of the classProxy
for proxy instances.For non-proxy classes,
ObjectOutputStream
calls its protectedannotateClass
method to allow subclasses to write custom data to the stream for a particular class. For proxy classes, instead ofannotateClass
, the following method injava.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 descriptor for a proxy class from the stream, formatted as described above. Instead of calling itsresolveClass
method to resolve theClass
object for the class descriptor, the following method injava.io.ObjectInputStream
is called:protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException;The list of interface names that were deserialized in the proxy class descriptor are passed as the
interfaces
argument toresolveProxyClass
.The default implementation of
resolveProxyClass
inObjectInputStream
returns the results of callingProxy.getProxyClass
with the list ofClass
objects for the interfaces named in theinterfaces
parameter. TheClass
object used for each interface namei
is the value retuned by callingClass.forName(i, false, loader)whereloader
is the first non-null class loader up the execution stack, ornull
if no non-null class loaders are on the stack. This is the same class loader choice made by the default behavior of theresolveClass
method. This same value ofloader
is also the class loader passed toProxy.getProxyClass
. IfProxy.getProxyClass
throws anIllegalArgumentException
,resolveClass
will throw aClassNotFoundException
containing theIllegalArgumentException
.Since a proxy class never has its own serializable fields, the classdata[] in the stream representation of a proxy instance consists wholly of the instance data for its superclass,
java.lang.reflect.Proxy
.Proxy
has one serializable field,h
, which contains the invocation handler for the proxy instance.
Here is a simple example that prints out a message before and after a method invocation on an object that implements an arbitrary list of interfaces:
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 that provides default proxy behavior for methods inherited from
java.lang.Object
and implements delegation of certain proxy method invocations to distinct objects depending on the interface 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 proxy method invocations not to be directly delegated to other objects, and they can overrideproxyHashCode
,proxyEquals
, andproxyToString
to override the 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
class given above is intended to be more illustrative than optimized; for example, instead of caching and comparing theMethod
objects for thehashCode
,equals
, andtoString
methods, it could just match them by their string names, because none of those method names are overloaded injava.lang.Object
.
Copyright © 1999-2004 Sun Microsystems, Inc. All Rights Reserved. |
Java Software |