/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. Alternatively, the contents of this file may be used under * the terms of the GNU Lesser General Public License Version 2.1 or later, * or the Apache License Version 2.0. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. */ package javassist.util.proxy; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import javassist.CannotCompileException; import javassist.bytecode.AccessFlag; import javassist.bytecode.Bytecode; import javassist.bytecode.ClassFile; import javassist.bytecode.CodeAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import javassist.bytecode.DuplicateMemberException; import javassist.bytecode.ExceptionsAttribute; import javassist.bytecode.FieldInfo; import javassist.bytecode.MethodInfo; import javassist.bytecode.Opcode; import javassist.bytecode.StackMapTable; /* * This class is implemented only with the lower-level API of Javassist. * This design decision is for maximizing performance. */ /** * Factory of dynamic proxy classes. * *
This factory generates a class that extends the given super class and implements
* the given interfaces. The calls of the methods inherited from the super class are
* forwarded and then invoke()
is called on the method handler
* associated with instances of the generated class. The calls of the methods from
* the interfaces are also forwarded to the method handler.
*
*
For example, if the following code is executed, * *
* ProxyFactory f = new ProxyFactory(); * f.setSuperclass(Foo.class); * f.setFilter(new MethodFilter() { * public boolean isHandled(Method m) { * // ignore finalize() * return !m.getName().equals("finalize"); * } * }); * Class c = f.createClass(); * MethodHandler mi = new MethodHandler() { * public Object invoke(Object self, Method m, Method proceed, * Object[] args) throws Throwable { * System.out.println("Name: " + m.getName()); * return proceed.invoke(self, args); // execute the original method. * } * }; * Foo foo = (Foo)c.newInstance(); * ((Proxy)foo).setHandler(mi); ** *
Here, Method
is java.lang.reflect.Method
.
Then, the following method call will be forwarded to MethodHandler
* mi
and prints a message before executing the originally called method
* bar()
in Foo
.
*
*
* foo.bar(); ** *
The last three lines of the code shown above can be replaced with a call to
* the helper method create
, which generates a proxy class, instantiates
* it, and sets the method handler of the instance:
*
*
* : * Foo foo = (Foo)f.create(new Class[0], new Object[0], mi); ** *
To change the method handler during runtime, * execute the following code: * *
* MethodHandler mi = ... ; // alternative handler * ((Proxy)foo).setHandler(mi); ** *
If setHandler is never called for a proxy instance then it will * employ the default handler which proceeds by invoking the original method. * The behaviour of the default handler is identical to the following * handler: * *
* class EmptyHandler implements MethodHandler { * public Object invoke(Object self, Method m, * Method proceed, Object[] args) throws Exception { * return proceed.invoke(self, args); * } * } ** *
A proxy factory caches and reuses proxy classes by default. It is possible to reset * this default globally by setting static field {@link ProxyFactory#useCache} to false. * Caching may also be configured for a specific factory by calling instance method * {@link ProxyFactory#setUseCache(boolean)}. It is strongly recommended that new clients * of class ProxyFactory enable caching. Failure to do so may lead to exhaustion of * the heap memory area used to store classes. * *
Caching is automatically disabled for any given proxy factory if deprecated instance * method {@link ProxyFactory#setHandler(MethodHandler)} is called. This method was * used to specify a default handler which newly created proxy classes should install * when they create their instances. It is only retained to provide backward compatibility * with previous releases of javassist. Unfortunately,this legacy behaviour makes caching * and reuse of proxy classes impossible. The current programming model expects javassist * clients to set the handler of a proxy instance explicitly by calling method * {@link Proxy#setHandler(MethodHandler)} as shown in the sample code above. New * clients are strongly recommended to use this model rather than calling * {@link ProxyFactory#setHandler(MethodHandler)}. * *
A proxy object generated by ProxyFactory
is serializable
* if its super class or any of its interfaces implement java.io.Serializable
.
* However, a serialized proxy object may not be compatible with future releases.
* The serialization support should be used for short-term storage or RMI.
*
*
For compatibility with older releases serialization of proxy objects is implemented by * adding a writeReplace method to the proxy class. This allows a proxy to be serialized * to a conventional {@link java.io.ObjectOutputStream} and deserialized from a corresponding * {@link java.io.ObjectInputStream}. However this method suffers from several problems, the most * notable one being that it fails to serialize state inherited from the proxy's superclass. *
* An alternative method of serializing proxy objects is available which fixes these problems. It
* requires inhibiting generation of the writeReplace method and instead using instances of
* {@link javassist.util.proxy.ProxyObjectOutputStream} and {@link javassist.util.proxy.ProxyObjectInputStream}
* (which are subclasses of {@link java.io.ObjectOutputStream} and {@link java.io.ObjectInputStream})
* to serialize and deserialize, respectively, the proxy. These streams recognise javassist proxies and ensure
* that they are serialized and deserialized without the need for the proxy class to implement special methods
* such as writeReplace. Generation of the writeReplace method can be disabled globally by setting static field
* {@link ProxyFactory#useWriteReplace} to false. Alternatively, it may be
* configured per factory by calling instance method {@link ProxyFactory#setUseWriteReplace(boolean)}.
*
* @see MethodHandler
* @since 3.1
* @author Muga Nishizawa
* @author Shigeru Chiba
* @author Andrew Dinn
*/
public class ProxyFactory {
private Class> superClass;
private Class>[] interfaces;
private MethodFilter methodFilter;
private MethodHandler handler; // retained for legacy usage
private List If true, only public/protected methods are forwarded to a proxy object.
* The class for that proxy object is loaded by the {@code defineClass} method
* in {@code java.lang.invoke.MethodHandles.Lookup}, which is available in
* Java 9 or later. This works even when {@code sun.misc.Unsafe} is not
* available for some reasons (it is already deprecated in Java 9). To load a class, Javassist first tries to use {@code sun.misc.Unsafe} and,
* if not available, it uses a {@code protected} method in {@code java.lang.ClassLoader}
* via {@code PrivilegedAction}. Since the latter approach is not available
* any longer by default in Java 9 or later, the JVM argument
* {@code --add-opens java.base/java.lang=ALL-UNNAMED} must be given to the JVM
* when it is used (because of lack of {@code sun.misc.Unsafe}).
* If this argument cannot be given to the JVM, {@code onlyPublicMethods} should
* be set to {@code true}. Javassist will try to load by using
* {@code java.lang.invoke.MethodHandles.Lookup}. The default value is {@code false}. The default value is null.
*/
public String writeDirectory;
private static final Class> OBJECT_TYPE = Object.class;
private static final String HOLDER = "_methods_";
private static final String HOLDER_TYPE = "[Ljava/lang/reflect/Method;";
private static final String FILTER_SIGNATURE_FIELD = "_filter_signature";
private static final String FILTER_SIGNATURE_TYPE = "[B";
private static final String HANDLER = "handler";
private static final String NULL_INTERCEPTOR_HOLDER = "javassist.util.proxy.RuntimeSupport";
private static final String DEFAULT_INTERCEPTOR = "default_interceptor";
private static final String HANDLER_TYPE
= 'L' + MethodHandler.class.getName().replace('.', '/') + ';';
private static final String HANDLER_SETTER = "setHandler";
private static final String HANDLER_SETTER_TYPE = "(" + HANDLER_TYPE + ")V";
private static final String HANDLER_GETTER = "getHandler";
private static final String HANDLER_GETTER_TYPE = "()" + HANDLER_TYPE;
private static final String SERIAL_VERSION_UID_FIELD = "serialVersionUID";
private static final String SERIAL_VERSION_UID_TYPE = "J";
private static final long SERIAL_VERSION_UID_VALUE = -1L;
/**
* If true, a generated proxy class is cached and it will be reused
* when generating the proxy class with the same properties is requested.
* The default value is true.
*
* Note that this value merely specifies the initial setting employed by any newly created
* proxy factory. The factory setting may be overwritten by calling factory instance method
* {@link #setUseCache(boolean)}
*
* @since 3.4
*/
public static volatile boolean useCache = true;
/**
* If true, a generated proxy class will implement method writeReplace enabling
* serialization of its proxies to a conventional ObjectOutputStream. this (default)
* setting retains the old javassist behaviour which has the advantage that it
* retains compatibility with older releases and requires no extra work on the part
* of the client performing the serialization. However, it has the disadvantage that
* state inherited from the superclasses of the proxy is lost during serialization.
* if false then serialization/deserialization of the proxy instances will preserve
* all fields. However, serialization must be performed via a {@link ProxyObjectOutputStream}
* and deserialization must be via {@link ProxyObjectInputStream}. Any attempt to serialize
* proxies whose class was created with useWriteReplace set to false via a normal
* {@link java.io.ObjectOutputStream} will fail.
*
* Note that this value merely specifies the initial setting employed by any newly created
* proxy factory. The factory setting may be overwritten by calling factory instance method
* {@link #setUseWriteReplace(boolean)}
*
* @since 3.4
*/
public static volatile boolean useWriteReplace = true;
/*
* methods allowing individual factory settings for factoryUseCache and factoryWriteReplace to be reset
*/
/**
* test whether this factory uses the proxy cache
* @return true if this factory uses the proxy cache otherwise false
*/
public boolean isUseCache()
{
return factoryUseCache;
}
/**
* configure whether this factory should use the proxy cache
* @param useCache true if this factory should use the proxy cache and false if it should not use the cache
* @throws RuntimeException if a default interceptor has been set for the factory
*/
public void setUseCache(boolean useCache)
{
// we cannot allow caching to be used if the factory is configured to install a default interceptor
// field into generated classes
if (handler != null && useCache) {
throw new RuntimeException("caching cannot be enabled if the factory default interceptor has been set");
}
factoryUseCache = useCache;
}
/**
* test whether this factory installs a writeReplace method in created classes
* @return true if this factory installs a writeReplace method in created classes otherwise false
*/
public boolean isUseWriteReplace()
{
return factoryWriteReplace;
}
/**
* configure whether this factory should add a writeReplace method to created classes
* @param useWriteReplace true if this factory should add a writeReplace method to created classes and false if it
* should not add a writeReplace method
*/
public void setUseWriteReplace(boolean useWriteReplace)
{
factoryWriteReplace = useWriteReplace;
}
private static Map The value of this field can be updated for changing the default
* implementation.
*
* Example:
* "."
, then the class file is written under the current
* directory. This method is for debugging.
*
* setSuperclass()
.
*
* @since 3.4
*/
public Class> getSuperclass() { return superClass; }
/**
* Sets the interfaces of a proxy class.
*/
public void setInterfaces(Class>[] ifs) {
interfaces = ifs;
// force recompute of signature
signature = null;
}
/**
* Obtains the interfaces set by setInterfaces
.
*
* @since 3.4
*/
public Class>[] getInterfaces() { return interfaces; }
/**
* Sets a filter that selects the methods that will be controlled by a handler.
*/
public void setFilter(MethodFilter mf) {
methodFilter = mf;
// force recompute of signature
signature = null;
}
/**
* Generates a proxy class using the current filter.
*/
public Class> createClass() {
if (signature == null) {
computeSignature(methodFilter);
}
return createClass1();
}
/**
* Generates a proxy class using the supplied filter.
*/
public Class> createClass(MethodFilter filter) {
computeSignature(filter);
return createClass1();
}
/**
* Generates a proxy class with a specific signature.
* access is package local so ProxyObjectInputStream can use this
* @param signature
* @return
*/
Class> createClass(byte[] signature)
{
installSignature(signature);
return createClass1();
}
private Class> createClass1() {
Class> result = thisClass;
if (result == null) {
ClassLoader cl = getClassLoader();
synchronized (proxyCache) {
if (factoryUseCache)
createClass2(cl);
else
createClass3(cl);
result = thisClass;
// don't retain any unwanted references
thisClass = null;
}
}
return result;
}
private static char[] hexDigits =
{ '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
public String getKey(Class> superClass, Class>[] interfaces, byte[] signature, boolean useWriteReplace)
{
StringBuffer sbuf = new StringBuffer();
if (superClass != null){
sbuf.append(superClass.getName());
}
sbuf.append(":");
for (int i = 0; i < interfaces.length; i++) {
sbuf.append(interfaces[i].getName());
sbuf.append(":");
}
for (int i = 0; i < signature.length; i++) {
byte b = signature[i];
int lo = b & 0xf;
int hi = (b >> 4) & 0xf;
sbuf.append(hexDigits[lo]);
sbuf.append(hexDigits[hi]);
}
if (useWriteReplace) {
sbuf.append(":w");
}
return sbuf.toString();
}
private void createClass2(ClassLoader cl) {
String key = getKey(superClass, interfaces, signature, factoryWriteReplace);
/*
* Excessive concurrency causes a large memory footprint and slows the
* execution speed down (with JDK 1.5). Thus, we use a jumbo lock for
* reducing concrrency.
*/
// synchronized (proxyCache) {
MapcreateClass()
for obtaining
* a class loader.
* get()
on this ClassLoaderProvider
object
* is called to obtain a class loader.
*
*
* ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() {
* public ClassLoader get(ProxyFactory pf) {
* return Thread.currentThread().getContextClassLoader();
* }
* };
*
*
* @since 3.4
*/
public static ClassLoaderProvider classLoaderProvider =
new ClassLoaderProvider() {
@Override
public ClassLoader get(ProxyFactory pf) {
return pf.getClassLoader0();
}
};
protected ClassLoader getClassLoader() {
return classLoaderProvider.get(this);
}
protected ClassLoader getClassLoader0() {
ClassLoader loader = null;
if (superClass != null && !superClass.getName().equals("java.lang.Object"))
loader = superClass.getClassLoader();
else if (interfaces != null && interfaces.length > 0)
loader = interfaces[0].getClassLoader();
if (loader == null) {
loader = getClass().getClassLoader();
// In case javassist is in the endorsed dir
if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
if (loader == null)
loader = ClassLoader.getSystemClassLoader();
}
}
return loader;
}
protected ProtectionDomain getDomain() {
Class> clazz;
if (superClass != null && !superClass.getName().equals("java.lang.Object"))
clazz = superClass;
else if (interfaces != null && interfaces.length > 0)
clazz = interfaces[0];
else
clazz = this.getClass();
return clazz.getProtectionDomain();
}
/**
* Creates a proxy class and returns an instance of that class.
*
* @param paramTypes parameter types for a constructor.
* @param args arguments passed to a constructor.
* @param mh the method handler for the proxy class.
* @since 3.4
*/
public Object create(Class>[] paramTypes, Object[] args, MethodHandler mh)
throws NoSuchMethodException, IllegalArgumentException,
InstantiationException, IllegalAccessException, InvocationTargetException
{
Object obj = create(paramTypes, args);
((Proxy)obj).setHandler(mh);
return obj;
}
/**
* Creates a proxy class and returns an instance of that class.
*
* @param paramTypes parameter types for a constructor.
* @param args arguments passed to a constructor.
*/
public Object create(Class>[] paramTypes, Object[] args)
throws NoSuchMethodException, IllegalArgumentException,
InstantiationException, IllegalAccessException, InvocationTargetException
{
Class> c = createClass();
Constructor> cons = c.getConstructor(paramTypes);
return cons.newInstance(args);
}
/**
* Sets the default invocation handler. This invocation handler is shared
* among all the instances of a proxy class unless another is explicitly
* specified.
* @deprecated since 3.12
* use of this method is incompatible with proxy class caching.
* instead clients should call method {@link Proxy#setHandler(MethodHandler)} to set the handler
* for each newly created proxy instance.
* calling this method will automatically disable caching of classes created by the proxy factory.
*/
@Deprecated
public void setHandler(MethodHandler mi) {
// if we were using the cache and the handler is non-null then we must stop caching
if (factoryUseCache && mi != null) {
factoryUseCache = false;
// clear any currently held class so we don't try to reuse it or set its handler field
thisClass = null;
}
handler = mi;
// this retains the behaviour of the old code which resets any class we were holding on to
// this is probably not what is wanted
setField(DEFAULT_INTERCEPTOR, handler);
}
/**
* A unique class name generator.
*/
public static interface UniqueName {
/**
* Returns a unique class name.
*
* @param classname the super class name of the proxy class.
*/
String get(String classname);
}
/**
* A unique class name generator.
* Replacing this generator changes the algorithm to generate a
* unique name. The get
method does not have to be
* a synchronized
method since the access to this field
* is mutually exclusive and thus thread safe.
*/
public static UniqueName nameGenerator = new UniqueName() {
private final String sep = "_$$_jvst" + Integer.toHexString(this.hashCode() & 0xfff) + "_";
private int counter = 0;
@Override
public String get(String classname) {
return classname + sep + Integer.toHexString(counter++);
}
};
private static String makeProxyName(String classname) {
synchronized (nameGenerator) {
return nameGenerator.get(classname);
}
}
private ClassFile make() throws CannotCompileException {
ClassFile cf = new ClassFile(false, classname, superName);
cf.setAccessFlags(AccessFlag.PUBLIC);
setInterfaces(cf, interfaces, hasGetHandler ? Proxy.class : ProxyObject.class);
ConstPool pool = cf.getConstPool();
// legacy: we only add the static field for the default interceptor if caching is disabled
if (!factoryUseCache) {
FieldInfo finfo = new FieldInfo(pool, DEFAULT_INTERCEPTOR, HANDLER_TYPE);
finfo.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
cf.addField(finfo);
}
// handler is per instance
FieldInfo finfo2 = new FieldInfo(pool, HANDLER, HANDLER_TYPE);
finfo2.setAccessFlags(AccessFlag.PRIVATE);
cf.addField(finfo2);
// filter signature is per class
FieldInfo finfo3 = new FieldInfo(pool, FILTER_SIGNATURE_FIELD, FILTER_SIGNATURE_TYPE);
finfo3.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
cf.addField(finfo3);
// the proxy class serial uid must always be a fixed value
FieldInfo finfo4 = new FieldInfo(pool, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE);
finfo4.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC| AccessFlag.FINAL);
cf.addField(finfo4);
// HashMap allMethods = getMethods(superClass, interfaces);
// int size = allMethods.size();
makeConstructors(classname, cf, pool, classname);
List