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 type Object containing the arguments.
动态代理类是一种在运行时实现指定接口列表的类,这样通过该类实例上的某个接口进行的方法调用将被编码并通过统一接口分派给另一个对象。因此,动态代理类可用于为接口列表创建类型安全的代理对象,而无需预先生成代理类(例如使用编译时工具)。动态代理类实例的方法调用会被分派给该实例调用处理程序中的单个方法,并使用一个 java.lang.reflect.Method 对象(用于标识被调用的方法)和一个包含参数的 Object 类型数组进行编码。
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.
对于需要对提供接口 API 的对象进行类型安全的反射式调用分派的应用程序或库来说,动态代理类非常有用。例如,应用程序可以使用动态代理类来创建一个实现多个任意事件监听器接口(扩展 java.util.EventListener 的接口)的对象,以便以统一的方式处理各种不同类型的事件,例如将所有这些事件记录到文件中。
以上是 Oracle 在发布动态代理机制 —— Java 8 中对动态代理的描述定义。
动态代理的类 —— java.lang.reflect.Proxy 是 java.lang.reflect (反射包)的类。
动态代理的实现机制是反射。
From : Proxy (Java SE 21 & JDK 21)
在 Oracle GraalVM 新的Java高性能虚拟机也有说明 GraalVM Dynamic Proxy in Native Image 。
Dynamic Proxy Class API
- 动态代理类 API
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.
代理实例是代理类的实例。
动态代理类实例 InvocationHandler (Java Platform SE 8 )
Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable
Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.
Parameters:
proxy - the proxy instance that the method was invoked on method - the Method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the Method 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.
args - an array of objects containing the values of the arguments passed in the method invocation on the proxy instance, or null if interface method takes no arguments. Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper class, such as java.lang.Integer or java.lang.Boolean.
代理 - 方法被调用时所基于的代理实例
方法 - 对应于在代理实例上调用的接口方法的Method实例。该Method对象的声明类将是该方法声明所在的接口,该接口可能是代理类继承该方法的代理接口的父接口。
参数 - 包含代理实例上方法调用时传入参数值的对象数组,如果接口方法不接受参数则为null。基本类型的参数会被包装在对应的基本类型包装类实例中,例如java.lang.Integer或java.lang.Boolean。
Returns:
the value to return from the method invocation on the proxy instance. If the declared return type of the interface method is a primitive type, then the value returned by this method 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 by this method is null and the interface method's return type is primitive, then a NullPointerException will be thrown by the method invocation on the proxy instance. If the value returned by this method is otherwise not compatible with the interface method's declared return type as described above, a ClassCastException will be thrown by the method invocation on the proxy instance.
从代理实例方法调用返回的值。如果接口方法声明的返回类型是基本类型,则此方法返回的值必须是相应基本包装类的实例;否则,它必须是可以分配给声明返回类型的类型。如果此方法返回的值为 null 且接口方法的返回类型是基本类型,则在代理实例上调用该方法时将抛出 NullPointerException。如果此方法返回的值与上述接口方法声明的返回类型不兼容,则在代理实例上调用该方法时将抛出 ClassCastException。
Throws:
Throwable - the exception to throw from the method invocation on the proxy instance. The exception's type must be assignable either to any of the exception types declared in the throws clause of the interface method or to the unchecked exception types java.lang.RuntimeException or java.lang.Error. If a checked exception is thrown by this method that is not assignable to any of the exception types declared in the throws clause of the interface method, then an UndeclaredThrowableException containing the exception that was thrown by this method will be thrown by the method invocation on the proxy instance.
Throwable - 从代理实例方法调用中抛出的异常。该异常的类型必须可分配给接口方法throws子句中声明的任何异常类型,或未检查的异常类型java.lang.RuntimeException或java.lang.Error。如果此方法抛出一个已检查异常,且该异常不可分配给接口方法throws子句中声明的任何异常类型,则代理实例上的方法调用将抛出一个包含该异常的UndeclaredThrowableException。
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 the java.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 the interfaces array must represent interfaces, not classes or primitive types.
No two elements in the interfaces array may refer to identical Class 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 interface i, 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 an IllegalArgumentException. If the interfaces array argument or any of its elements are null, a NullPointerException 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 its Class object will return an array containing the same list of interfaces (in the order specified at its creation), invoking getMethods on its Class object will return an array of Method objects that include all of the methods in those interfaces, and invoking getMethod 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 by Proxy.getProxyClass or the class of an object returned by Proxy.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 extends java.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 as java.lang.Object, because the code for a proxy class is generated by trusted system code. This protection domain will typically be granted java.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 calling Proxy.getProxyClass with invoking the constructor with an invocation handler. Proxy.newProxyInstance throws IllegalArgumentException for the same reasons that Proxy.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 class Foo, the following expression will return true:
proxy instanceof Foo
and the following cast operation will succeed (rather than throwing a 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 to Proxy.getInvocationHandler is not a proxy instance, then an IllegalArgumentException 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 type Object.
The second argument passed to invoke will be the java.lang.reflect.Method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the Method 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 as java.lang.Integer or java.lang.Boolean. The implementation of the invoke 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 by invoke 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 by invoke is null and the interface method's return type is primitive, then a NullPointerException will be thrown by the method invocation on the proxy instance. If the value returned by invoke is otherwise not compatible with the method's declared return type as described above, a ClassCastException 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 types java.lang.RuntimeException or java.lang.Error. If a checked exception is thrown by invoke that is not assignable to any of the exception types declared in the throws clause of the interface method, then an UndeclaredThrowableException will be thrown by the method invocation on the proxy instance. The UndeclaredThrowableException will be constructed with the exception that was thrown by the invoke method.
An invocation of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of the Method object passed to invoke will be java.lang.Object. Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.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, the Method 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's invoke 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, or toString methods of java.lang.Object, when such a method is invoked on a proxy instance, the Method object passed to the invocation handler will have java.lang.Object as its declaring class. In other words, the public, non-final methods of java.lang.Object logically precede all of the proxy interfaces for the determination of which Method 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 the throws clause of the method in all of the proxy interfaces that it can be invoked through. If the invoke method throws a checked exception that is not assignable to any of the exception types declared by the method in one of the proxy interfaces that it can be invoked through, then an unchecked UndeclaredThrowableException will be thrown by the invocation on the proxy instance. This restriction means that not all of the exception types returned by invoking getExceptionTypes on the Method object passed to the invoke method can necessarily be thrown successfully by the invoke method.
Serialization
Since java.lang.reflect.Proxy implements java.io.Serializable, proxy instances can be serialized, as described in this section. If a proxy instance contains an invocation handler that is not assignable to java.io.Serializable, however, then a java.io.NotSerializableException will be thrown if such an instance is written to a java.io.ObjectOutputStream. Note that for proxy classes, implementing java.io.Externalizable has the same effect with respect to serialization as implementing java.io.Serializable: the writeExternal and readExternal methods of the Externalizable interface will never be invoked on a proxy instance (or an invocation handler) as part of its serialization process. As with all Class objects, the Class object for a proxy class is always serializable.
A proxy class has no serializable fields and a serialVersionUID of 0L. In other words, when the Class object for a proxy class is passed to the static lookup method of java.io.ObjectStreamClass, the returned ObjectStreamClass instance will have the following properties:
Invoking its getSerialVersionUID method will return 0L.
Invoking its getFields method will return an array of length zero.
Invoking its getField method with any String argument will return null.
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 the java.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 proxyClassDescInfo
proxyClassDescInfo:
(int)<count> proxyInterfaceName[count] classAnnotation superClassDesc
proxyInterfaceName:
(utf)
When an ObjectOutputStream serializes the class descriptor for a class that is a proxy class, as determined by passing its Class object to the Proxy.isProxyClass method, it uses the TC_PROXYCLASSDESC type code instead of TC_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 the getInterfaces method on the Class 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 class Proxy for proxy instances.
For non-proxy classes, ObjectOutputStream calls its protected annotateClass method to allow subclasses to write custom data to the stream for a particular class. For proxy classes, instead of annotateClass, the following method in java.io.ObjectOutputStream is called with the Class object for the proxy class:
protected void annotateProxyClass(Class cl) throws IOException;
The default implementation of annotateProxyClass in ObjectOutputStream does nothing.
When an ObjectInputStream encounters the type code TC_PROXYCLASSDESC, it deserializes the class descriptor for a proxy class from the stream, formatted as described above. Instead of calling its resolveClass method to resolve the Class object for the class descriptor, the following method in java.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 to resolveProxyClass.
The default implementation of resolveProxyClass in ObjectInputStream returns the results of calling Proxy.getProxyClass with the list of Class objects for the interfaces named in the interfaces parameter. The Class object used for each interface name i is the value retuned by calling
Class.forName(i, false, loader)
where loader is the first non-null class loader up the execution stack, or null if no non-null class loaders are on the stack. This is the same class loader choice made by the default behavior of the resolveClass method. This same value of loader is also the class loader passed to Proxy.getProxyClass. If Proxy.getProxyClass throws an IllegalArgumentException, resolveClass will throw a ClassNotFoundException containing the IllegalArgumentException.
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 the Foo interface and call one of its methods:
为Foo接口的实现构建一个DebugProxy并调用其方法之一:
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:
以下是实用工具调用处理器类的示例,它为从java.lang.Object继承的方法提供默认代理行为,并根据被调用方法的接口,将某些代理方法调用委托给不同的对象:
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 override invokeNotDelegated to implement the behavior of proxy method invocations not to be directly delegated to other objects, and they can override proxyHashCode, proxyEquals, and proxyToString to override the default behavior of the methods the proxy inherits from java.lang.Object.
Delegator的子类可以重写invokeNotDelegated方法,以实现不直接委托给其他对象的代理方法调用行为。此外,它们还可以重写proxyHashCode、proxyEquals和proxyToString方法,以覆盖代理从java.lang.Object继承的方法的默认行为。
To construct a Delegator for an implementation of the Foo interface:
为Foo接口的实现构建一个委托器:
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 the Method objects for the hashCode, equals, and toString methods, it could just match them by their string names, because none of those method names are overloaded in java.lang.Object.
请注意,上面给出的Delegator类的实现更侧重于说明性而非优化性;例如,对于hashCode、equals和toString方法,它可以直接通过方法名进行匹配,而不需要缓存和比较Method对象,因为在java.lang.Object中这些方法名都没有被重载。