All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.feilong.lib.javassist.util.proxy.ProxyFactory Maven / Gradle / Ivy

Go to download

feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.

There is a newer version: 4.0.8
Show newest version
/*
 * 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 com.feilong.lib.javassist.util.proxy;

import java.lang.invoke.MethodHandles.Lookup;
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 com.feilong.lib.javassist.CannotCompileException;
import com.feilong.lib.javassist.bytecode.AccessFlag;
import com.feilong.lib.javassist.bytecode.Bytecode;
import com.feilong.lib.javassist.bytecode.ClassFile;
import com.feilong.lib.javassist.bytecode.CodeAttribute;
import com.feilong.lib.javassist.bytecode.ConstPool;
import com.feilong.lib.javassist.bytecode.Descriptor;
import com.feilong.lib.javassist.bytecode.DuplicateMemberException;
import com.feilong.lib.javassist.bytecode.ExceptionsAttribute;
import com.feilong.lib.javassist.bytecode.FieldInfo;
import com.feilong.lib.javassist.bytecode.MethodInfo;
import com.feilong.lib.javassist.bytecode.Opcode;
import com.feilong.lib.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 com.feilong.lib.javassist.util.proxy.ProxyObjectOutputStream} and * {@link com.feilong.lib.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> signatureMethods; private boolean hasGetHandler; private byte[] signature; private String classname; private String basename; private String superName; private Class thisClass; /** * per factory setting initialised from current setting for useCache but able to be reset before each create call */ private boolean factoryUseCache; /** * per factory setting initialised from current setting for useWriteReplace but able to be reset before each create call */ private boolean factoryWriteReplace; /** *

* 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}. *

* * @see DefineClassHelper#toClass(String, Class, ClassLoader, ProtectionDomain, byte[]) * @since 3.22 */ public static boolean onlyPublicMethods = false; /** * If the value of this variable is not null, the class file of * the generated proxy class is written under the directory specified * by this variable. For example, if the value is * ".", then the class file is written under the current * directory. This method is for debugging. * *

* 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 = com.feilong.lib.javassist.util.proxy.RuntimeSupport.class.getName(); 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> proxyCache = new WeakHashMap<>(); /** * determine if a class is a javassist proxy class * * @param cl * @return true if the class is a javassist proxy class otherwise false */ public static boolean isProxyClass(Class cl){ // all proxies implement Proxy or ProxyObject. nothing else should. return (Proxy.class.isAssignableFrom(cl)); } /** * used to store details of a specific proxy class in the second tier of the proxy cache. this entry * will be located in a hashmap keyed by the unique identifying name of the proxy class. the hashmap is * located in a weak hashmap keyed by the classloader common to all proxy classes in the second tier map. */ static class ProxyDetails{ /** * the unique signature of any method filter whose behaviour will be met by this class. each bit in * the byte array is set if the filter redirects the corresponding super or interface method and clear * if it does not redirect it. */ byte[] signature; /** * a hexadecimal string representation of the signature bit sequence. this string also forms part * of the proxy class name. */ Reference> proxyClass; /** * a flag which is true this class employs writeReplace to perform serialization of its instances * and false if serialization must employ of a ProxyObjectOutputStream and ProxyObjectInputStream */ boolean isUseWriteReplace; ProxyDetails(byte[] signature, Class proxyClass, boolean isUseWriteReplace){ this.signature = signature; this.proxyClass = new WeakReference<>(proxyClass); this.isUseWriteReplace = isUseWriteReplace; } } /** * Constructs a factory of proxy class. */ public ProxyFactory(){ superClass = null; interfaces = null; methodFilter = null; handler = null; signature = null; signatureMethods = null; hasGetHandler = false; thisClass = null; writeDirectory = null; factoryUseCache = useCache; factoryWriteReplace = useWriteReplace; } /** * Sets the super class of a proxy class. */ public void setSuperclass(Class clazz){ superClass = clazz; // force recompute of signature signature = null; } /** * Obtains the super class set by 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. * The module or package where a proxy class is created * has to be opened to this package or the Javassist module. * * @see #createClass(Lookup) */ public Class createClass(){ if (signature == null){ computeSignature(methodFilter); } return createClass1(null); } /** * Generates a proxy class using the supplied filter. * The module or package where a proxy class is created * has to be opened to this package or the Javassist module. */ public Class createClass(MethodFilter filter){ computeSignature(filter); return createClass1(null); } /** * Generates a proxy class with a specific signature. * access is package local so ProxyObjectInputStream can use this * * @param signature */ Class createClass(byte[] signature){ installSignature(signature); return createClass1(null); } /** * Generates a proxy class using the current filter. * * @param lookup * used for loading the proxy class. * It needs an appropriate right to invoke {@code defineClass} * for the proxy class. * @since 3.24 */ public Class createClass(Lookup lookup){ if (signature == null){ computeSignature(methodFilter); } return createClass1(lookup); } /** * Generates a proxy class using the supplied filter. * * @param lookup * used for loading the proxy class. * It needs an appropriate right to invoke {@code defineClass} * for the proxy class. * @param filter * the filter. * @since 3.24 */ public Class createClass(Lookup lookup,MethodFilter filter){ computeSignature(filter); return createClass1(lookup); } /** * Generates a proxy class with a specific signature. * access is package local so ProxyObjectInputStream can use this. * * @param lookup * used for loading the proxy class. * It needs an appropriate right to invoke {@code defineClass} * for the proxy class. * @param signature * the signature. */ Class createClass(Lookup lookup,byte[] signature){ installSignature(signature); return createClass1(lookup); } private Class createClass1(Lookup lookup){ Class result = thisClass; if (result == null){ ClassLoader cl = getClassLoader(); synchronized (proxyCache){ if (factoryUseCache){ createClass2(cl, lookup); }else{ createClass3(cl, lookup); } 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 (Class interface1 : interfaces){ sbuf.append(interface1.getName()); sbuf.append(":"); } for (byte b : signature){ 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,Lookup lookup){ 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) { Map cacheForTheLoader = proxyCache.get(cl); ProxyDetails details; if (cacheForTheLoader == null){ cacheForTheLoader = new HashMap<>(); proxyCache.put(cl, cacheForTheLoader); } details = cacheForTheLoader.get(key); if (details != null){ Reference> reference = details.proxyClass; thisClass = reference.get(); if (thisClass != null){ return; } } createClass3(cl, lookup); details = new ProxyDetails(signature, thisClass, factoryWriteReplace); cacheForTheLoader.put(key, details); // } } private void createClass3(ClassLoader cl,Lookup lookup){ // we need a new class so we need a new class name allocateClassName(); try{ ClassFile cf = make(); if (writeDirectory != null){ FactoryHelper.writeFile(cf, writeDirectory); } if (lookup == null){ thisClass = FactoryHelper.toClass(cf, getClassInTheSamePackage(), cl, getDomain()); }else{ setField(FILTER_SIGNATURE_FIELD, signature); } // legacy behaviour : we only set the default interceptor static field if we are not using the cache if (!factoryUseCache){ setField(DEFAULT_INTERCEPTOR, handler); } }catch (CannotCompileException e){ throw new RuntimeException(e.getMessage(), e); } } /** * Obtains a class belonging to the same package that the created * proxy class belongs to. It is used to obtain an appropriate * {@code java.lang.invoke.MethodHandles.Lookup}. */ private Class getClassInTheSamePackage(){ if (basename.startsWith(packageForJavaBase)){ return this.getClass(); }else if (superClass != null && superClass != OBJECT_TYPE){ return superClass; }else if (interfaces != null && interfaces.length > 0){ return interfaces[0]; }else{ return this.getClass(); // maybe wrong? } } private void setField(String fieldName,Object value){ if (thisClass != null && value != null){ try{ Field f = thisClass.getField(fieldName); SecurityActions.setAccessible(f, true); f.set(null, value); SecurityActions.setAccessible(f, false); }catch (Exception e){ throw new RuntimeException(e); } } } static byte[] getFilterSignature(Class clazz){ return (byte[]) getField(clazz, FILTER_SIGNATURE_FIELD); } private static Object getField(Class clazz,String fieldName){ try{ Field f = clazz.getField(fieldName); f.setAccessible(true); Object value = f.get(null); f.setAccessible(false); return value; }catch (Exception e){ throw new RuntimeException(e); } } /** * Obtains the method handler of the given proxy object. * * @param p * a proxy object. * @return the method handler. * @since 3.16 */ public static MethodHandler getHandler(Proxy p){ try{ Field f = p.getClass().getDeclaredField(HANDLER); f.setAccessible(true); Object value = f.get(p); f.setAccessible(false); return (MethodHandler) value; }catch (Exception e){ throw new RuntimeException(e); } } /** * A provider of class loaders. * * @see #classLoaderProvider * @since 3.4 */ public static interface ClassLoaderProvider{ /** * Returns a class loader. * * @param pf * a proxy factory that is going to obtain a class loader. */ public ClassLoader get(ProxyFactory pf); } /** * A provider used by createClass() for obtaining * a class loader. * get() on this ClassLoaderProvider object * is called to obtain a class loader. * *

* The value of this field can be updated for changing the default * implementation. * *

* Example: * *

     * ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider(){
     * 
     *     public ClassLoader get(ProxyFactory pf){
     *         return Thread.currentThread().getContextClassLoader();
     *     }
     * };
     * 
* * @since 3.4 */ public static ClassLoaderProvider classLoaderProvider = pf -> 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 forwarders = new ArrayList<>(); int s = overrideMethods(cf, pool, classname, forwarders); addClassInitializer(cf, pool, classname, s, forwarders); addSetter(classname, cf, pool); if (!hasGetHandler){ addGetter(classname, cf, pool); } if (factoryWriteReplace){ try{ cf.addMethod(makeWriteReplace(pool)); }catch (DuplicateMemberException e){ // writeReplace() is already declared in the super class/interfaces. } } thisClass = null; return cf; } private void checkClassAndSuperName(){ if (interfaces == null){ interfaces = new Class[0]; } if (superClass == null){ superClass = OBJECT_TYPE; superName = superClass.getName(); basename = interfaces.length == 0 ? superName : interfaces[0].getName(); }else{ superName = superClass.getName(); basename = superName; } if (Modifier.isFinal(superClass.getModifiers())){ throw new RuntimeException(superName + " is final"); } // Since java.base module is not opened, its proxy class should be // in a different (open) module. Otherwise, it could not be created // by reflection. if (basename.startsWith("java.") || basename.startsWith("jdk.") || onlyPublicMethods){ basename = packageForJavaBase + basename.replace('.', '_'); } } private static final String packageForJavaBase = "com.feilong.lib.javassist.util.proxy"; private void allocateClassName(){ classname = makeProxyName(basename); } private static Comparator> sorter = (e1,e2) -> e1.getKey().compareTo(e2.getKey()); private void makeSortedMethodList(){ checkClassAndSuperName(); hasGetHandler = false; // getMethods() may set this to true. Map allMethods = getMethods(superClass, interfaces); signatureMethods = new ArrayList<>(allMethods.entrySet()); Collections.sort(signatureMethods, sorter); } private void computeSignature(MethodFilter filter) // throws CannotCompileException { makeSortedMethodList(); int l = signatureMethods.size(); int maxBytes = ((l + 7) >> 3); signature = new byte[maxBytes]; for (int idx = 0; idx < l; idx++){ Method m = signatureMethods.get(idx).getValue(); int mod = m.getModifiers(); if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && isVisible(mod, basename, m) && (filter == null || filter.isHandled(m))){ setBit(signature, idx); } } } private void installSignature(byte[] signature) // throws CannotCompileException { makeSortedMethodList(); int l = signatureMethods.size(); int maxBytes = ((l + 7) >> 3); if (signature.length != maxBytes){ throw new RuntimeException("invalid filter signature length for deserialized proxy class"); } this.signature = signature; } private boolean testBit(byte[] signature,int idx){ int byteIdx = idx >> 3; if (byteIdx > signature.length){ return false; } int bitIdx = idx & 0x7; int mask = 0x1 << bitIdx; int sigByte = signature[byteIdx]; return ((sigByte & mask) != 0); } private void setBit(byte[] signature,int idx){ int byteIdx = idx >> 3; if (byteIdx < signature.length){ int bitIdx = idx & 0x7; int mask = 0x1 << bitIdx; int sigByte = signature[byteIdx]; signature[byteIdx] = (byte) (sigByte | mask); } } private static void setInterfaces(ClassFile cf,Class[] interfaces,Class proxyClass){ String setterIntf = proxyClass.getName(); String[] list; if (interfaces == null || interfaces.length == 0){ list = new String[] { setterIntf }; }else{ list = new String[interfaces.length + 1]; for (int i = 0; i < interfaces.length; i++){ list[i] = interfaces[i].getName(); } list[interfaces.length] = setterIntf; } cf.setInterfaces(list); } private static void addClassInitializer(ClassFile cf,ConstPool cp,String classname,int size,List forwarders) throws CannotCompileException{ FieldInfo finfo = new FieldInfo(cp, HOLDER, HOLDER_TYPE); finfo.setAccessFlags(AccessFlag.PRIVATE | AccessFlag.STATIC); cf.addField(finfo); MethodInfo minfo = new MethodInfo(cp, "", "()V"); minfo.setAccessFlags(AccessFlag.STATIC); setThrows(minfo, cp, new Class[] { ClassNotFoundException.class }); Bytecode code = new Bytecode(cp, 0, 2); code.addIconst(size * 2); code.addAnewarray("java.lang.reflect.Method"); final int varArray = 0; code.addAstore(varArray); // forName() must be called here. Otherwise, the class might be // invisible. code.addLdc(classname); code.addInvokestatic("java.lang.Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); final int varClass = 1; code.addAstore(varClass); for (Find2MethodsArgs args : forwarders){ callFind2Methods(code, args.methodName, args.delegatorName, args.origIndex, args.descriptor, varClass, varArray); } code.addAload(varArray); code.addPutstatic(classname, HOLDER, HOLDER_TYPE); code.addLconst(SERIAL_VERSION_UID_VALUE); code.addPutstatic(classname, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE); code.addOpcode(Opcode.RETURN); minfo.setCodeAttribute(code.toCodeAttribute()); cf.addMethod(minfo); } /** * @param thisMethod * might be null. */ private static void callFind2Methods( Bytecode code, String superMethod, String thisMethod, int index, String desc, int classVar, int arrayVar){ String findClass = RuntimeSupport.class.getName(); String findDesc = "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;[Ljava/lang/reflect/Method;)V"; code.addAload(classVar); code.addLdc(superMethod); if (thisMethod == null){ code.addOpcode(Opcode.ACONST_NULL); }else{ code.addLdc(thisMethod); } code.addIconst(index); code.addLdc(desc); code.addAload(arrayVar); code.addInvokestatic(findClass, "find2Methods", findDesc); } private static void addSetter(String classname,ClassFile cf,ConstPool cp) throws CannotCompileException{ MethodInfo minfo = new MethodInfo(cp, HANDLER_SETTER, HANDLER_SETTER_TYPE); minfo.setAccessFlags(AccessFlag.PUBLIC); Bytecode code = new Bytecode(cp, 2, 2); code.addAload(0); code.addAload(1); code.addPutfield(classname, HANDLER, HANDLER_TYPE); code.addOpcode(Opcode.RETURN); minfo.setCodeAttribute(code.toCodeAttribute()); cf.addMethod(minfo); } private static void addGetter(String classname,ClassFile cf,ConstPool cp) throws CannotCompileException{ MethodInfo minfo = new MethodInfo(cp, HANDLER_GETTER, HANDLER_GETTER_TYPE); minfo.setAccessFlags(AccessFlag.PUBLIC); Bytecode code = new Bytecode(cp, 1, 1); code.addAload(0); code.addGetfield(classname, HANDLER, HANDLER_TYPE); code.addOpcode(Opcode.ARETURN); minfo.setCodeAttribute(code.toCodeAttribute()); cf.addMethod(minfo); } private int overrideMethods(ClassFile cf,ConstPool cp,String className,List forwarders) throws CannotCompileException{ String prefix = makeUniqueName("_d", signatureMethods); Iterator> it = signatureMethods.iterator(); int index = 0; while (it.hasNext()){ Map.Entry e = it.next(); if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_5 || !isBridge(e.getValue())){ if (testBit(signature, index)){ override(className, e.getValue(), prefix, index, keyToDesc(e.getKey(), e.getValue()), cf, cp, forwarders); } } index++; } return index; } private static boolean isBridge(Method m){ return m.isBridge(); } private void override( String thisClassname, Method meth, String prefix, int index, String desc, ClassFile cf, ConstPool cp, List forwarders) throws CannotCompileException{ Class declClass = meth.getDeclaringClass(); String delegatorName = prefix + index + meth.getName(); if (Modifier.isAbstract(meth.getModifiers())){ delegatorName = null; }else{ MethodInfo delegator = makeDelegator(meth, desc, cp, declClass, delegatorName); // delegator is not a bridge method. See Sec. 15.12.4.5 of JLS 3rd Ed. delegator.setAccessFlags(delegator.getAccessFlags() & ~AccessFlag.BRIDGE); cf.addMethod(delegator); } MethodInfo forwarder = makeForwarder(thisClassname, meth, desc, cp, declClass, delegatorName, index, forwarders); cf.addMethod(forwarder); } private void makeConstructors(String thisClassName,ClassFile cf,ConstPool cp,String classname) throws CannotCompileException{ Constructor[] cons = SecurityActions.getDeclaredConstructors(superClass); // legacy: if we are not caching then we need to initialise the default handler boolean doHandlerInit = !factoryUseCache; for (Constructor c : cons){ int mod = c.getModifiers(); if (!Modifier.isFinal(mod) && !Modifier.isPrivate(mod) && isVisible(mod, basename, c)){ MethodInfo m = makeConstructor(thisClassName, c, cp, superClass, doHandlerInit); cf.addMethod(m); } } } private static String makeUniqueName(String name,List> sortedMethods){ if (makeUniqueName0(name, sortedMethods.iterator())){ return name; } for (int i = 100; i < 999; i++){ String s = name + i; if (makeUniqueName0(s, sortedMethods.iterator())){ return s; } } throw new RuntimeException("cannot make a unique method name"); } private static boolean makeUniqueName0(String name,Iterator> it){ while (it.hasNext()){ if (it.next().getKey().startsWith(name)){ return false; } } return true; } /** * Returns true if the method is visible from the package. * * @param mod * the modifiers of the method. */ private static boolean isVisible(int mod,String from,Member meth){ if ((mod & Modifier.PRIVATE) != 0){ return false; }else if ((mod & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0){ return true; }else{ String p = getPackageName(from); String q = getPackageName(meth.getDeclaringClass().getName()); if (p == null){ return q == null; } return p.equals(q); } } private static String getPackageName(String name){ int i = name.lastIndexOf('.'); if (i < 0){ return null; } return name.substring(0, i); } /* * getMethods() may set hasGetHandler to true. */ private Map getMethods(Class superClass,Class[] interfaceTypes){ Map hash = new HashMap<>(); Set> set = new HashSet<>(); for (Class interfaceType : interfaceTypes){ getMethods(hash, interfaceType, set); } getMethods(hash, superClass, set); return hash; } private void getMethods(Map hash,Class clazz,Set> visitedClasses){ // This both speeds up scanning by avoiding duplicate interfaces and is needed to // ensure that superinterfaces are always scanned before subinterfaces. if (!visitedClasses.add(clazz)){ return; } Class[] ifs = clazz.getInterfaces(); for (Class if1 : ifs){ getMethods(hash, if1, visitedClasses); } Class parent = clazz.getSuperclass(); if (parent != null){ getMethods(hash, parent, visitedClasses); } /* * Java 5 or later allows covariant return types. * It also allows contra-variant parameter types * if a super class is a generics with concrete type arguments * such as Foo. So the method-overriding rule is complex. */ Method[] methods = SecurityActions.getDeclaredMethods(clazz); for (int i = 0; i < methods.length; i++){ if (!Modifier.isPrivate(methods[i].getModifiers())){ Method m = methods[i]; String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m); // see keyToDesc(). if (key.startsWith(HANDLER_GETTER_KEY)){ hasGetHandler = true; } // JIRA JASSIST-85 // put the method to the cache, retrieve previous definition (if any) Method oldMethod = hash.put(key, m); // JIRA JASSIST-244, 267 // ignore a bridge method to a method declared in a non-public class. if (null != oldMethod && isBridge(m) && !Modifier.isPublic(oldMethod.getDeclaringClass().getModifiers()) && !Modifier.isAbstract(oldMethod.getModifiers()) && !isDuplicated(i, methods)){ hash.put(key, oldMethod); } // check if visibility has been reduced if (null != oldMethod && Modifier.isPublic(oldMethod.getModifiers()) && !Modifier.isPublic(m.getModifiers())){ // we tried to overwrite a public definition with a non-public definition, // use the old definition instead. hash.put(key, oldMethod); } } } } private static boolean isDuplicated(int index,Method[] methods){ String name = methods[index].getName(); for (int i = 0; i < methods.length; i++){ if (i != index){ if (name.equals(methods[i].getName()) && areParametersSame(methods[index], methods[i])){ return true; } } } return false; } private static boolean areParametersSame(Method method,Method targetMethod){ Class[] methodTypes = method.getParameterTypes(); Class[] targetMethodTypes = targetMethod.getParameterTypes(); if (methodTypes.length == targetMethodTypes.length){ for (int i = 0; i < methodTypes.length; i++){ if (methodTypes[i].getName().equals(targetMethodTypes[i].getName())){ continue; }else{ return false; } } return true; } return false; } private static final String HANDLER_GETTER_KEY = HANDLER_GETTER + ":()"; private static String keyToDesc(String key,Method m){ return key.substring(key.indexOf(':') + 1); } private static MethodInfo makeConstructor( String thisClassName, Constructor cons, ConstPool cp, Class superClass, boolean doHandlerInit){ String desc = RuntimeSupport.makeDescriptor(cons.getParameterTypes(), Void.TYPE); MethodInfo minfo = new MethodInfo(cp, "", desc); minfo.setAccessFlags(Modifier.PUBLIC); // cons.getModifiers() & ~Modifier.NATIVE setThrows(minfo, cp, cons.getExceptionTypes()); Bytecode code = new Bytecode(cp, 0, 0); // legacy: if we are not using caching then we initialise the instance's handler // from the class's static default interceptor and skip the next few instructions if // it is non-null if (doHandlerInit){ code.addAload(0); code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE); code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE); code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE); code.addOpcode(Opcode.IFNONNULL); code.addIndex(10); } // if caching is enabled then we don't have a handler to initialise so this else branch will install // the handler located in the static field of class RuntimeSupport. code.addAload(0); code.addGetstatic(NULL_INTERCEPTOR_HOLDER, DEFAULT_INTERCEPTOR, HANDLER_TYPE); code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE); int pc = code.currentPc(); code.addAload(0); int s = addLoadParameters(code, cons.getParameterTypes(), 1); code.addInvokespecial(superClass.getName(), "", desc); code.addOpcode(Opcode.RETURN); code.setMaxLocals(s + 1); CodeAttribute ca = code.toCodeAttribute(); minfo.setCodeAttribute(ca); StackMapTable.Writer writer = new StackMapTable.Writer(32); writer.sameFrame(pc); ca.setAttribute(writer.toStackMapTable(cp)); return minfo; } private MethodInfo makeDelegator(Method meth,String desc,ConstPool cp,Class declClass,String delegatorName){ MethodInfo delegator = new MethodInfo(cp, delegatorName, desc); delegator.setAccessFlags( Modifier.FINAL | Modifier.PUBLIC | (meth.getModifiers() & ~(Modifier.PRIVATE | Modifier.PROTECTED | Modifier.ABSTRACT | Modifier.NATIVE | Modifier.SYNCHRONIZED))); setThrows(delegator, cp, meth); Bytecode code = new Bytecode(cp, 0, 0); code.addAload(0); int s = addLoadParameters(code, meth.getParameterTypes(), 1); Class targetClass = invokespecialTarget(declClass); code.addInvokespecial(targetClass.isInterface(), cp.addClassInfo(targetClass.getName()), meth.getName(), desc); addReturn(code, meth.getReturnType()); code.setMaxLocals(++s); delegator.setCodeAttribute(code.toCodeAttribute()); return delegator; } /* * Suppose that the receiver type is S, the invoked method * is declared in T, and U is the immediate super class of S * (or its interface). If S <: U <: T (S <: T reads "S extends T"), * the target type of invokespecial has to be not T but U. */ private Class invokespecialTarget(Class declClass){ if (declClass.isInterface()){ for (Class i : interfaces){ if (declClass.isAssignableFrom(i)){ return i; } } } return superClass; } /** * @param delegatorName * null if the original method is abstract. */ private static MethodInfo makeForwarder( String thisClassName, Method meth, String desc, ConstPool cp, Class declClass, String delegatorName, int index, List forwarders){ MethodInfo forwarder = new MethodInfo(cp, meth.getName(), desc); forwarder.setAccessFlags(Modifier.FINAL | (meth.getModifiers() & ~(Modifier.ABSTRACT | Modifier.NATIVE | Modifier.SYNCHRONIZED))); setThrows(forwarder, cp, meth); int args = Descriptor.paramSize(desc); Bytecode code = new Bytecode(cp, 0, args + 2); /* * static { * methods[index * 2] * = RuntimeSupport.findSuperMethod(this, , ); * methods[index * 2 + 1] * = RuntimeSupport.findMethod(this, , ); * or = null // the original method is abstract. * } * : * return ($r)handler.invoke(this, methods[index * 2], * methods[index * 2 + 1], $args); */ int origIndex = index * 2; int delIndex = index * 2 + 1; int arrayVar = args + 1; code.addGetstatic(thisClassName, HOLDER, HOLDER_TYPE); code.addAstore(arrayVar); forwarders.add(new Find2MethodsArgs(meth.getName(), delegatorName, desc, origIndex)); code.addAload(0); code.addGetfield(thisClassName, HANDLER, HANDLER_TYPE); code.addAload(0); code.addAload(arrayVar); code.addIconst(origIndex); code.addOpcode(Opcode.AALOAD); code.addAload(arrayVar); code.addIconst(delIndex); code.addOpcode(Opcode.AALOAD); makeParameterList(code, meth.getParameterTypes()); code.addInvokeinterface( MethodHandler.class.getName(), "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", 5); Class retType = meth.getReturnType(); addUnwrapper(code, retType); addReturn(code, retType); CodeAttribute ca = code.toCodeAttribute(); forwarder.setCodeAttribute(ca); return forwarder; } static class Find2MethodsArgs{ String methodName, delegatorName, descriptor; int origIndex; Find2MethodsArgs(String mname, String dname, String desc, int index){ methodName = mname; delegatorName = dname; descriptor = desc; origIndex = index; } } private static void setThrows(MethodInfo minfo,ConstPool cp,Method orig){ Class[] exceptions = orig.getExceptionTypes(); setThrows(minfo, cp, exceptions); } private static void setThrows(MethodInfo minfo,ConstPool cp,Class[] exceptions){ if (exceptions.length == 0){ return; } String[] list = new String[exceptions.length]; for (int i = 0; i < exceptions.length; i++){ list[i] = exceptions[i].getName(); } ExceptionsAttribute ea = new ExceptionsAttribute(cp); ea.setExceptions(list); minfo.setExceptionsAttribute(ea); } private static int addLoadParameters(Bytecode code,Class[] params,int offset){ int stacksize = 0; int n = params.length; for (int i = 0; i < n; ++i){ stacksize += addLoad(code, stacksize + offset, params[i]); } return stacksize; } private static int addLoad(Bytecode code,int n,Class type){ if (type.isPrimitive()){ if (type == Long.TYPE){ code.addLload(n); return 2; }else if (type == Float.TYPE){ code.addFload(n); }else if (type == Double.TYPE){ code.addDload(n); return 2; }else{ code.addIload(n); } }else{ code.addAload(n); } return 1; } private static int addReturn(Bytecode code,Class type){ if (type.isPrimitive()){ if (type == Long.TYPE){ code.addOpcode(Opcode.LRETURN); return 2; }else if (type == Float.TYPE){ code.addOpcode(Opcode.FRETURN); }else if (type == Double.TYPE){ code.addOpcode(Opcode.DRETURN); return 2; }else if (type == Void.TYPE){ code.addOpcode(Opcode.RETURN); return 0; }else{ code.addOpcode(Opcode.IRETURN); } }else{ code.addOpcode(Opcode.ARETURN); } return 1; } private static void makeParameterList(Bytecode code,Class[] params){ int regno = 1; int n = params.length; code.addIconst(n); code.addAnewarray("java/lang/Object"); for (int i = 0; i < n; i++){ code.addOpcode(Opcode.DUP); code.addIconst(i); Class type = params[i]; if (type.isPrimitive()){ regno = makeWrapper(code, type, regno); }else{ code.addAload(regno); regno++; } code.addOpcode(Opcode.AASTORE); } } private static int makeWrapper(Bytecode code,Class type,int regno){ int index = FactoryHelper.typeIndex(type); String wrapper = FactoryHelper.wrapperTypes[index]; code.addNew(wrapper); code.addOpcode(Opcode.DUP); addLoad(code, regno, type); code.addInvokespecial(wrapper, "", FactoryHelper.wrapperDesc[index]); return regno + FactoryHelper.dataSize[index]; } private static void addUnwrapper(Bytecode code,Class type){ if (type.isPrimitive()){ if (type == Void.TYPE){ code.addOpcode(Opcode.POP); }else{ int index = FactoryHelper.typeIndex(type); String wrapper = FactoryHelper.wrapperTypes[index]; code.addCheckcast(wrapper); code.addInvokevirtual(wrapper, FactoryHelper.unwarpMethods[index], FactoryHelper.unwrapDesc[index]); } }else{ code.addCheckcast(type.getName()); } } private static MethodInfo makeWriteReplace(ConstPool cp){ MethodInfo minfo = new MethodInfo(cp, "writeReplace", "()Ljava/lang/Object;"); String[] list = new String[1]; list[0] = "java.io.ObjectStreamException"; ExceptionsAttribute ea = new ExceptionsAttribute(cp); ea.setExceptions(list); minfo.setExceptionsAttribute(ea); Bytecode code = new Bytecode(cp, 0, 1); code.addAload(0); code.addInvokestatic( com.feilong.lib.javassist.util.proxy.RuntimeSupport.class.getName(), "makeSerializedProxy", "(Ljava/lang/Object;)Ljavassist/util/proxy/SerializedProxy;"); code.addOpcode(Opcode.ARETURN); minfo.setCodeAttribute(code.toCodeAttribute()); return minfo; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy