package org.googlecode.perftrace.javaagent.util; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.util.Map; import java.util.Set; import org.googlecode.perftrace.javassist.CannotCompileException; import org.googlecode.perftrace.javassist.ClassPath; import org.googlecode.perftrace.javassist.ClassPool; import org.googlecode.perftrace.javassist.CtClass; import org.googlecode.perftrace.javassist.LoaderClassPath; import org.googlecode.perftrace.javassist.NotFoundException; /** * From Tapestry 5 * Web Server下 classpool下需要添加loader对应的classpath; * Used to ensure that {@link org.googlecode.perftrace.javassist.ClassPool#appendClassPath(org.googlecode.perftrace.javassist.ClassPath)} is invoked within a synchronized * lock, and also handles tricky class loading issues (caused by the creation of classes, and class loaders, at * runtime). */ @SuppressWarnings("unchecked") public class ClassFactoryClassPool extends ClassPool { // Kind of duplicating some logic from ClassPool to avoid a deadlock-producing synchronized block. private static final Method defineClass = findMethod("defineClass", String.class, byte[].class, int.class, int.class); private static final Method defineClassWithProtectionDomain = findMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class); private static Method findMethod(final String methodName, final Class... parameterTypes) { try { return AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() { public Method run() throws Exception { Class cl = Class.forName("java.lang.ClassLoader"); Method result = cl.getDeclaredMethod(methodName, parameterTypes); // Just make it accessible; no particular reason to make it unaccessible again. result.setAccessible(true); return result; } }); } catch (PrivilegedActionException ex) { throw new RuntimeException(String.format("Unable to initialize ClassFactoryClassPool: %s", toMessage(ex)), ex); } } /** * Used to identify which class loaders have already been integrated into the pool. */ private final Set<ClassLoader> allLoaders = CollectionFactory.newSet(); private final Map<ClassLoader, ClassPath> leafLoaders = CollectionFactory.newMap(); public ClassFactoryClassPool(ClassLoader contextClassLoader) { super(null); addClassLoaderIfNeeded(contextClassLoader); } /** * Returns the nearest super-class of the provided class that can be converted to a {@link CtClass}. This is used to * filter out Hibernate-style proxies (created as subclasses of oridnary classes). This will automatically add the * class' classLoader to the pool's class path. * * @param clazz class to import * @return clazz, or a super-class of clazz */ public Class importClass(Class clazz) { addClassLoaderIfNeeded(clazz.getClassLoader()); while (true) { try { String name = ClassFabUtils.toJavaClassName(clazz); get(name); break; } catch (NotFoundException ex) { clazz = clazz.getSuperclass(); } } return clazz; } /** * Convienience method for adding to the ClassPath for a particular class loader. * <p/> * * @param loader the class loader to add (derived from a loaded class, and may be null for some system classes) */ public synchronized void addClassLoaderIfNeeded(ClassLoader loader) { Set<ClassLoader> leaves = leafLoaders.keySet(); if (loader == null || leaves.contains(loader) || allLoaders.contains(loader)) return; // Work out if this loader is a child of a loader we have already. ClassLoader existingLeaf = loader; while (existingLeaf != null && !leaves.contains(existingLeaf)) { existingLeaf = existingLeaf.getParent(); } if (existingLeaf != null) { // The new loader is a child of an existing leaf. // So we remove the old leaf before we add the new loader ClassPath priorPath = leafLoaders.get(existingLeaf); removeClassPath(priorPath); leafLoaders.remove(existingLeaf); } ClassPath path = new LoaderClassPath(loader); leafLoaders.put(loader, path); insertClassPath(path); ClassLoader l = loader; while (l != null) { allLoaders.add(l); l = l.getParent(); } } /** * Overriden to remove a deadlock producing synchronized block. We expect that the defineClass() methods will have * been marked as accessible statically (by this class), so there's no need to set them accessible again. */ @Override public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain) throws CannotCompileException { Throwable failure; try { byte[] b = ct.toBytecode(); boolean hasDomain = domain != null; Method method = hasDomain ? defineClassWithProtectionDomain : defineClass; Object[] args = hasDomain ? new Object[] {ct.getName(), b, 0, b.length, domain} : new Object[] {ct.getName(), b, 0, b.length}; return (Class) method.invoke(loader, args); } catch (InvocationTargetException ite) { failure = ite.getTargetException(); } catch (Exception ex) { failure = ex; } throw new CannotCompileException( String.format("Failure defining new class %s: %s", ct.getName(), toMessage(failure)), failure); } /** * Extracts the message from an exception. If the exception's message is null, returns the exceptions class name. * * @param exception * to extract message from * @return message or class name */ public static String toMessage(Throwable exception) { String message = exception.getMessage(); if (message != null) return message; return exception.getClass().getName(); } }