package org.skywalking.apm.agent.core.plugin.interceptor.loader;
import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsAroundInterceptor;
import org.skywalking.apm.logging.ILog;
import org.skywalking.apm.logging.LogManager;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* The <code>InterceptorInstanceLoader</code> is a classes finder and container.
* <p>
* This is a very important class in sky-walking's auto-instrumentation mechanism. If you want to fully understand why
* need this, and how it works, you need have knowledge about Classloader appointment mechanism.
* <p>
* The loader will load a class, and focus the target class loader (be intercepted class's classloader) loads it.
* <p>
* If the target class and target class loader are same, the loaded classes( {@link InstanceConstructorInterceptor},
* {@link InstanceMethodsAroundInterceptor} and {@link StaticMethodsAroundInterceptor} implementations) stay in
* singleton.
* <p>
* Created by wusheng on 16/8/2.
*/
public class InterceptorInstanceLoader {
private static final ILog logger = LogManager.getLogger(InterceptorInstanceLoader.class);
private static ConcurrentHashMap<String, Object> INSTANCE_CACHE = new ConcurrentHashMap<String, Object>();
private static ReentrantLock INSTANCE_LOAD_LOCK = new ReentrantLock();
public static <T> T load(String className, ClassLoader targetClassLoader)
throws InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
String instanceKey = className + "_OF_" + targetClassLoader.getClass().getName() + "@" + Integer.toHexString(targetClassLoader.hashCode());
Object inst = INSTANCE_CACHE.get(instanceKey);
if (inst == null) {
if (InterceptorInstanceLoader.class.getClassLoader().equals(targetClassLoader)) {
inst = targetClassLoader.loadClass(className).newInstance();
} else {
INSTANCE_LOAD_LOCK.lock();
try {
try {
inst = findLoadedClass(className, targetClassLoader);
if (inst == null) {
inst = loadBinary(className, targetClassLoader);
}
if (inst == null) {
throw new ClassNotFoundException(targetClassLoader.toString() + " load interceptor class:" + className + " failure.");
}
} catch (Exception e) {
throw new ClassNotFoundException(targetClassLoader.toString() + " load interceptor class:" + className + " failure.", e);
}
} finally {
INSTANCE_LOAD_LOCK.unlock();
}
}
if (inst != null) {
INSTANCE_CACHE.put(instanceKey, inst);
}
}
return (T) inst;
}
/**
* load class from class binary files.
* Most likely all the interceptor implementations should be loaded by this.
*
* @param className interceptor class name.
* @param targetClassLoader the classloader, which should load the interceptor.
* @param <T>
* @return interceptor instance.
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private static <T> T loadBinary(String className,
ClassLoader targetClassLoader) throws InvocationTargetException, IllegalAccessException, InstantiationException {
String path = "/" + className.replace('.', '/').concat(".class");
byte[] data = null;
BufferedInputStream is = null;
ByteArrayOutputStream baos = null;
try {
logger.debug("Read binary code of {} using classload {}", className, InterceptorInstanceLoader.class.getClassLoader());
is = new BufferedInputStream(InterceptorInstanceLoader.class.getResourceAsStream(path));
baos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = is.read()) != -1) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
if (is != null)
try {
is.close();
} catch (IOException ignored) {
}
if (baos != null)
try {
baos.close();
} catch (IOException ignored) {
}
}
Method defineClassMethod = null;
Class<?> targetClassLoaderType = targetClassLoader.getClass();
while (defineClassMethod == null && targetClassLoaderType != null) {
try {
defineClassMethod = targetClassLoaderType.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class);
} catch (NoSuchMethodException e) {
targetClassLoaderType = targetClassLoaderType.getSuperclass();
}
}
defineClassMethod.setAccessible(true);
logger.debug("load binary code of {} to classloader {}", className, targetClassLoader);
Class<?> type = (Class<?>) defineClassMethod.invoke(targetClassLoader, className, data, 0, data.length, null);
return (T) type.newInstance();
}
/**
* Find loaded class in the current classloader.
* Just in case some classes have already been loaded for some reason.
*
* @param className interceptor class name.
* @param targetClassLoader the classloader, which should load the interceptor.
* @param <T>
* @return interceptor instance.
*/
private static <T> T findLoadedClass(String className,
ClassLoader targetClassLoader) throws InvocationTargetException, IllegalAccessException, InstantiationException {
Method defineClassMethod = null;
Class<?> targetClassLoaderType = targetClassLoader.getClass();
while (defineClassMethod == null && targetClassLoaderType != null) {
try {
defineClassMethod = targetClassLoaderType.getDeclaredMethod("findLoadedClass", String.class);
} catch (NoSuchMethodException e) {
targetClassLoaderType = targetClassLoaderType.getSuperclass();
}
}
defineClassMethod.setAccessible(true);
Class<?> type = (Class<?>) defineClassMethod.invoke(targetClassLoader, className);
if (type == null) {
return null;
}
return (T) type.newInstance();
}
}