Adynamic proxy class is a class that implements a listof interfaces specified at runtime such that a method invocationthrough one of the interfaces on an instance of the class will beencoded and dispatched to another object through a uniforminterface. Thus, a dynamic proxy class can be used to create atype-safe proxy object for a list of interfaces without requiringpre-generation of the proxy class, such as with compile-time tools.Method invocations on an instance of a dynamic proxy class aredispatched to a single method in the instance'sinvocationhandler, and they are encoded with ajava.lang.reflect.Method
object identifying the methodthat was invoked and an array of typeObject
containing the arguments.
Dynamic proxy classes are useful to an application or librarythat needs to provide type-safe reflective dispatch of invocationson objects that present interface APIs. For example, an applicationcan use a dynamic proxy class to create an object that implementsmultiple arbitrary event listener interfaces-- interfaces thatextendjava.util.EventListener
-- to process a varietyof events of different types in a uniform fashion, such as bylogging all such events to a file.
Adynamic proxy class (simply referred to as aproxyclass below) is a class that implements a list of interfacesspecified at runtime when the class is created.
Aproxy interface is such an interface that isimplemented by a proxy class.
Aproxy instance is an instance of aproxyclass.
Proxy classes, as well as instances of them, are created usingthe static methods of the classjava.lang.reflect.Proxy.
TheProxy.getProxyClass
method returns thejava.lang.Class
object for a proxy class given a classloader and an array of interfaces. The proxy class will be definedin the specified class loader and will implement all of thesupplied interfaces. If a proxy class for the same permutation ofinterfaces has already been defined in the class loader, then theexisting proxy class will be returned; otherwise, a proxy class forthose interfaces will be generated dynamically and defined in theclass loader.
There are several restrictions on the parameters that may bepassed toProxy.getProxyClass
:
Class
objects in theinterfaces
array must represent interfaces, notclasses or primitive types.interfaces
array may referto identicalClass
objects.cl
and every interfacei
, the followingexpression must be true:Class.forName(i.getName(), false, cl) == i
interfaces
array must not exceed65535.If any of these restrictions are violated,Proxy.getProxyClass
will throw anIllegalArgumentException
. If theinterfaces
array argument or any of its elements arenull
, aNullPointerException
will bethrown.
Note that the order of the specified proxy interfaces issignificant: two requests for a proxy class with the samecombination of interfaces but in a different order will result intwo distinct proxy classes. Proxy classes are distinguished by theorder of their proxy interfaces in order to provide deterministicmethod invocation encoding in cases where two or more of the proxyinterfaces share a method with the same name and parametersignature; this reasoning is described in more detail in thesection below titledMethods Duplicated inMultiple Proxy Interfaces.
So that a new proxy class does not need to be generated eachtimeProxy.getProxyClass
is invoked with the sameclass loader and list of interfaces, the implementation of thedynamic proxy class API should keep a cache of generated proxyclasses, keyed by their corresponding loaders and interface list.The implementation should be careful not to refer to the classloaders, interfaces, and proxy classes in such a way as to preventclass loaders, and all of their classes, from being garbagecollected when appropriate.
A proxy class has the following properties:
"$Proxy"
is,however, to be reserved for proxy classes.java.lang.reflect.Proxy
.getInterfaces
on itsClass
object will return an array containing the samelist of interfaces (in the order specified at its creation),invokinggetMethods
on itsClass
objectwill return an array ofMethod
objects that includeall of the methods in those interfaces, and invokinggetMethod
will find methods in the proxy interfaces aswould be expected.Proxy.isProxyClass
method will return true ifit is passed a proxy class-- a class returned byProxy.getProxyClass
or the class of an object returnedbyProxy.newProxyInstance
-- and false otherwise. Thereliability of this method is important for the ability to use itto make security decisions, so its implementation should not justtest if the class in question extendsjava.lang.reflect.Proxy
.java.security.ProtectionDomain
of a proxyclass is the same as that of system classes loaded by the bootstrapclass loader, such asjava.lang.Object
, because thecode for a proxy class is generated by trusted system code. Thisprotection domain will typically be grantedjava.security.AllPermission
.Each proxy class has one public constructor that takes oneargument, 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 touse the reflection API to access the public constructor, a proxyinstance can be also be created by calling theProxy.newProxyInstance
method, which combines theactions of callingProxy.getProxyClass
with invokingthe constructor with an invocation handler.Proxy.newProxyInstance
throwsIllegalArgumentException
for the same reasons thatProxy.getProxyClass
does.
A proxy instance has the following properties:
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 throwingaClassCastException
):(Foo) proxy
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,then anIllegalArgumentException
will be thrown.invoke
method as described below.The proxy instance itself will be passed as the first argumentofinvoke
, which is of typeObject
.
The second argument passed toinvoke
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 toinvoke
will be anarray of objects containing the values of the arguments passed inthe method invocation on the proxy instance. Arguments of primitivetypes are wrapped in an instance of the appropriate primitivewrapper class, such asjava.lang.Integer
orjava.lang.Boolean
. The implementation of theinvoke
method is free to modify the contents of thisarray.
The value returned by theinvoke
method will becomethe return value of the method invocation on the proxy instance. Ifthe declared return value of the interface method is a primitivetype, then the value returned byinvoke
must be aninstance of the corresponding primitive wrapper class; otherwise,it must be a type assignable to the declared return type. If thevalue returned byinvoke
isnull
and theinterface method's return type is primitive, then aNullPointerException
will be thrown by the methodinvocation on the proxy instance. If the value returned byinvoke
is otherwise not compatible with the method'sdeclared return type as described above, aClassCastException
will be thrown by the proxyinstance.
If an exception is thrown by theinvoke
method, itwill be also thrown by the method invocation on the proxy instance.The exception's type must be assignable to either any of theexception types declared in the signature of the interface methodor 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 exceptiontypes declared in thethrows
clause of the interfacemethod, then anUndeclaredThrowableException
will be thrown by the method invocation on the proxy instance. TheUndeclaredThrowableException
will be constructed withthe exception that was thrown by theinvoke
method.
hashCode
,equals
, ortoString
methods declared injava.lang.Object
on a proxy instance will be encodedand dispatched to the invocation handler'sinvoke
method in the same manner as interface method invocations areencoded and dispatched, as described above. The declaring class oftheMethod
object passed toinvoke
willbejava.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
.When two or more interfaces of a proxy class contain a methodwith the same name and parameter signature, the order of the proxyclass's interfaces becomes significant. When such aduplicatemethod is invoked on a proxy instance, theMethod
object passed to the invocation handler will not necessarily be theone whose declaring class is assignable from the reference type ofthe interface that the proxy's method was invoked through. Thislimitation exists because the corresponding method implementationin the generated proxy class cannot determine which interface itwas invoked through. Therefore, when a duplicate method is invokedon a proxy instance, theMethod
object for the methodin the foremost interface that contains the method (either directlyor inherited through a superinterface) in the proxy class's list ofinterfaces is passed to the invocation handler'sinvoke
method, regardless of the reference typethrough which the method invocation occurred.
If a proxy interface contains a method with the same name andparameter signature as thehashCode
,equals
, ortoString
methods ofjava.lang.Object
, when such a method is invoked on aproxy instance, theMethod
object passed to theinvocation handler will havejava.lang.Object
as itsdeclaring class. In other words, the public, non-final methods ofjava.lang.Object
logically precede all of the proxyinterfaces for the determination of whichMethod
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 inallof the 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 inone of the proxy interfaces that it can be invoked through, then anuncheckedUndeclaredThrowableException
will be thrownby the invocation on the proxy instance. This restriction meansthat not all of the exception types returned by invokinggetExceptionTypes
on theMethod
objectpassed to theinvoke
method can necessarily be thrownsuccessfully by theinvoke
method.
Sincejava.lang.reflect.Proxy
implementsjava.io.Serializable
, proxy instances can beserialized, as described in this section. If a proxy instancecontains an invocation handler that is not assignable tojava.io.Serializable
, however, then ajava.io.NotSerializableException
will be thrown ifsuch an instance is written to ajava.io.ObjectOutputStream
. Note that for proxyclasses, implementingjava.io.Externalizable
has thesame effect with respect to serialization as implementingjava.io.Serializable
: thewriteExternal
andreadExternal
methods of theExternalizable
interface will never be invoked on aproxy instance (or an invocation handler) as part of itsserialization process. As with allClass
objects, theClass
object for a proxy class is alwaysserializable.
A proxy class has no serializable fields and aserialVersionUID
of0L
. In other words,when theClass
object for a proxy class is passed tothe staticlookup
method ofjava.io.ObjectStreamClass
, the returnedObjectStreamClass
instance will have the followingproperties:
getSerialVersionUID
method willreturn0L
.getFields
method will return an arrayof length zero.getField
method with anyString
argument will returnnull
.The stream protocol for Object Serialization supports a typecode namedTC_PROXYCLASSDESC
, which is a terminalsymbol in the grammar for the stream format; its type and value aredefined 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 firstbeing an alternate expansion of the originalnewClassDescrule:
newClassDesc:
TC_PROXYCLASSDESC
newHandleproxyClassDescInfo
proxyClassDescInfo:
(int)<count>
proxyInterfaceName[count]classAnnotationsuperClassDesc
proxyInterfaceName:
(utf)
When anObjectOutputStream
serializes the classdescriptor for a class that is a proxy class, as determined bypassing itsClass
object to theProxy.isProxyClass
method, it uses theTC_PROXYCLASSDESC
type code instead ofTC_CLASSDESC
, following the rules above. In theexpansion ofproxyClassDescInfo, the sequence ofproxyInterfaceName items are the names of all of theinterfaces implemented by the proxy class, in the order that theyare returned by invoking thegetInterfaces
method ontheClass
object. TheclassAnnotation 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 descriptorallows for the evolution of the serialized representation of theclassProxy
for proxy instances.
For non-proxy classes,ObjectOutputStream
calls itsprotectedannotateClass
method to allow subclasses towrite custom data to the stream for a particular class. For proxyclasses, instead ofannotateClass
, the followingmethod injava.io.ObjectOutputStream
is called withtheClass
object for the proxy class:
protected void annotateProxyClass(Class cl) throws IOException;
The default implementation ofannotateProxyClass
inObjectOutputStream
does nothing.
When anObjectInputStream
encounters the type codeTC_PROXYCLASSDESC
, it deserializes the classdescriptor for a proxy class from the stream, formatted asdescribed above. Instead of calling itsresolveClass
method to resolve theClass
object for the classdescriptor, 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 proxyclass descriptor are passed as theinterfaces
argumenttoresolveProxyClass
.
The default implementation ofresolveProxyClass
inObjectInputStream
returns the results of callingProxy.getProxyClass
with the list ofClass
objects for the interfaces named in theinterfaces
parameter. TheClass
objectused for each interface namei
is the value retuned bycalling
Class.forName(i, false, loader)where
loader
is the first non-null class loader up theexecution stack, ornull
if no non-null class loadersare on the stack. This is the same class loader choice made by thedefault behavior of theresolveClass
method. This samevalue 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, 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.
Here is a simple example that prints out a message before andafter a method invocation on an object that implements an arbitrarylist 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 aDebugProxy
for an implementation oftheFoo
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 ofDelegator
can overrideinvokeNotDelegated
to implement the behavior of proxymethod invocations not to be directly delegated to other objects,and they can overrideproxyHashCode
,proxyEquals
, andproxyToString
tooverride the default behavior of the methods the proxy inheritsfromjava.lang.Object
.
To construct aDelegator
for an implementation oftheFoo
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 theDelegator
classgiven above is intended to be more illustrative than optimized; forexample, instead of caching and comparing theMethod
objects for thehashCode
,equals
, andtoString
methods, it could just match them by theirstring names, because none of those method names are overloaded injava.lang.Object
.