Fix interceptor instance singleton bug, and add some test cases.
This commit is contained in:
parent
5c369aefb4
commit
14fc924cc4
|
|
@ -5,14 +5,15 @@ import com.a.eye.skywalking.api.plugin.interceptor.InterceptorException;
|
|||
import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
|
||||
|
||||
/**
|
||||
* {@link NoCocurrencyAceessObject} is an abstract class,
|
||||
* works for class's methods call each others, which these methods should be intercepted.
|
||||
* {@link NoCocurrencyAceessObject} is method invocation counter,
|
||||
* when {@link #whenEnter(EnhancedClassInstanceContext, Runnable)}, counter + 1;
|
||||
* and when {@link #whenExist(EnhancedClassInstanceContext, Runnable)}, counter -1;
|
||||
*
|
||||
* At this scenario, only the first access should be intercepted.
|
||||
* When, and only when, the first enter and last exist, also meaning first access, the Runnable is called.
|
||||
*
|
||||
* @author wusheng
|
||||
*/
|
||||
public abstract class NoCocurrencyAceessObject implements InstanceMethodsAroundInterceptor {
|
||||
public class NoCocurrencyAceessObject {
|
||||
protected String invokeCounterKey = "__$invokeCounterKey";
|
||||
|
||||
protected Object invokeCounterInstLock = new Object();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.a.eye.skywalking.api.plugin.interceptor.loader;
|
||||
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceConstructorInterceptor;
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.enhance.StaticMethodsAroundInterceptor;
|
||||
import com.a.eye.skywalking.logging.ILog;
|
||||
import com.a.eye.skywalking.logging.LogManager;
|
||||
import java.io.BufferedInputStream;
|
||||
|
|
@ -12,12 +15,16 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* The Classloader controller.
|
||||
* 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 <code>InterceptorInstanceLoader</code> is a classes finder and container.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Created by wusheng on 16/8/2.
|
||||
*/
|
||||
public class InterceptorInstanceLoader {
|
||||
|
|
@ -28,43 +35,43 @@ public class InterceptorInstanceLoader {
|
|||
private static ReentrantLock instanceLoadLock = new ReentrantLock();
|
||||
|
||||
public static <T> T load(String className, ClassLoader targetClassLoader)
|
||||
throws InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
|
||||
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) {
|
||||
return (T) inst;
|
||||
}
|
||||
|
||||
if (InterceptorInstanceLoader.class.getClassLoader().equals(targetClassLoader)) {
|
||||
return (T) targetClassLoader.loadClass(className).newInstance();
|
||||
}
|
||||
|
||||
instanceLoadLock.lock();
|
||||
try {
|
||||
try {
|
||||
inst = findLoadedClass(className, targetClassLoader);
|
||||
if (inst == null) {
|
||||
inst = loadBinary(className, targetClassLoader);
|
||||
if (inst == null) {
|
||||
if (InterceptorInstanceLoader.class.getClassLoader().equals(targetClassLoader)) {
|
||||
inst = targetClassLoader.loadClass(className).newInstance();
|
||||
} else {
|
||||
instanceLoadLock.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 {
|
||||
instanceLoadLock.unlock();
|
||||
}
|
||||
if (inst == null) {
|
||||
throw new ClassNotFoundException(targetClassLoader.toString() + " load interceptor class:" + className + " failure.");
|
||||
}
|
||||
INSTANCE_CACHE.put(instanceKey, inst);
|
||||
return (T) inst;
|
||||
} catch (Exception e) {
|
||||
throw new ClassNotFoundException(targetClassLoader.toString() + " load interceptor class:" + className + " failure.", e);
|
||||
}
|
||||
} finally {
|
||||
instanceLoadLock.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 className interceptor class name.
|
||||
* @param targetClassLoader the classloader, which should load the interceptor.
|
||||
* @param <T>
|
||||
* @return interceptor instance.
|
||||
|
|
@ -72,7 +79,8 @@ public class InterceptorInstanceLoader {
|
|||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
private static <T> T loadBinary(String className, ClassLoader targetClassLoader) throws InvocationTargetException, IllegalAccessException, 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;
|
||||
|
|
@ -111,21 +119,22 @@ public class InterceptorInstanceLoader {
|
|||
}
|
||||
}
|
||||
defineClassMethod.setAccessible(true);
|
||||
logger.debug("load binary code of {} to classload {}", className, targetClassLoader);
|
||||
Class<?> type = (Class<?>) defineClassMethod.invoke(targetClassLoader, className, data, 0, data.length, null);
|
||||
return (T) type.newInstance();
|
||||
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 reasons.s
|
||||
* Just in case some classes have already been loaded for some reason.
|
||||
*
|
||||
* @param className interceptor class name.
|
||||
* @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 {
|
||||
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) {
|
||||
|
|
@ -136,10 +145,10 @@ public class InterceptorInstanceLoader {
|
|||
}
|
||||
}
|
||||
defineClassMethod.setAccessible(true);
|
||||
Class<?> type = (Class<?>) defineClassMethod.invoke(targetClassLoader, className);
|
||||
Class<?> type = (Class<?>)defineClassMethod.invoke(targetClassLoader, className);
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
return (T) type.newInstance();
|
||||
return (T)type.newInstance();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.a.eye.skywalking.api.boot;
|
||||
|
||||
import com.a.eye.skywalking.api.context.ContextManager;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author wusheng
|
||||
*/
|
||||
public class ServiceManagerTest {
|
||||
@Test
|
||||
public void testBoot() {
|
||||
ServiceManager.INSTANCE.boot();
|
||||
ContextManager manager = ServiceManager.INSTANCE.findService(ContextManager.class);
|
||||
Assert.assertNotNull(manager);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.a.eye.skywalking.api.plugin.assist;
|
||||
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext;
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.assist.NoCocurrencyAceessObject;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author wusheng
|
||||
*/
|
||||
public class NoCocurrencyAceessObjectTest {
|
||||
@Test
|
||||
public void testEntraExitCounter(){
|
||||
NoCocurrencyAceessObject object = new NoCocurrencyAceessObject();
|
||||
final EnhancedClassInstanceContext context = new EnhancedClassInstanceContext();
|
||||
object.whenEnter(context, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
context.set("firstEntrance", true);
|
||||
}
|
||||
});
|
||||
object.whenEnter(context, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
context.set("secondEntrance", true);
|
||||
}
|
||||
});
|
||||
object.whenExist(context, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
context.set("firstExit", true);
|
||||
}
|
||||
});
|
||||
object.whenExist(context, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
context.set("lastEntrance", true);
|
||||
}
|
||||
});
|
||||
|
||||
Assert.assertTrue(!context.isContain("secondEntrance"));
|
||||
Assert.assertTrue(!context.isContain("firstExit"));
|
||||
Assert.assertTrue(context.isContain("firstEntrance"));
|
||||
Assert.assertTrue(context.isContain("lastEntrance"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.a.eye.skywalking.api.plugin.interceptor;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author wusheng
|
||||
*/
|
||||
public class EnhancedClassInstanceContextTest {
|
||||
@Test
|
||||
public void test(){
|
||||
EnhancedClassInstanceContext context = new EnhancedClassInstanceContext();
|
||||
context.set("key", "value");
|
||||
Assert.assertTrue(context.isContain("key"));
|
||||
Assert.assertEquals("value", context.get("key"));
|
||||
Assert.assertEquals("value", context.get("key", String.class));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.a.eye.skywalking.api.plugin.loader;
|
||||
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.loader.InterceptorInstanceLoader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author wusheng
|
||||
*/
|
||||
public class InterceptorInstanceLoaderTest {
|
||||
@Test
|
||||
public void load() throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
ClassLoader mockClassLoader = new ClassLoader() {
|
||||
@Override public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
return super.loadClass(name);
|
||||
}
|
||||
};
|
||||
Object obj = InterceptorInstanceLoader.load("com.a.eye.skywalking.api.plugin.loader.NeverUsedTestClass", mockClassLoader);
|
||||
Assert.assertTrue(obj != null);
|
||||
|
||||
Object obj2 = InterceptorInstanceLoader.load("com.a.eye.skywalking.api.plugin.loader.NeverUsedTestClass", mockClassLoader);
|
||||
Assert.assertTrue(obj != null);
|
||||
Assert.assertEquals(obj, obj2);
|
||||
|
||||
Object obj3 = InterceptorInstanceLoader.load("com.a.eye.skywalking.api.plugin.loader.NeverUsedTestClass", InterceptorInstanceLoaderTest.class.getClassLoader());
|
||||
Assert.assertTrue(obj3 != null);
|
||||
|
||||
Object obj4 = InterceptorInstanceLoader.load("com.a.eye.skywalking.api.plugin.loader.NeverUsedTestClass", InterceptorInstanceLoaderTest.class.getClassLoader());
|
||||
Assert.assertTrue(obj4 != null);
|
||||
Assert.assertEquals(obj3, obj4);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.a.eye.skywalking.api.plugin.loader;
|
||||
|
||||
/**
|
||||
* This class is only used in {@link InterceptorInstanceLoaderTest},
|
||||
* never be created manually.
|
||||
*
|
||||
* @author wusheng
|
||||
*/
|
||||
public class NeverUsedTestClass {
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.a.eye.skywalking.api.context.ContextManager;
|
|||
import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext;
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.assist.NoCocurrencyAceessObject;
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext;
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
|
||||
import com.a.eye.skywalking.api.plugin.interceptor.enhance.MethodInterceptResult;
|
||||
import com.a.eye.skywalking.api.util.StringUtil;
|
||||
import com.a.eye.skywalking.trace.Span;
|
||||
|
|
@ -16,7 +17,7 @@ import com.a.eye.skywalking.trace.tag.Tags;
|
|||
*
|
||||
* @author zhangxin
|
||||
*/
|
||||
public class JedisMethodInterceptor extends NoCocurrencyAceessObject {
|
||||
public class JedisMethodInterceptor extends NoCocurrencyAceessObject implements InstanceMethodsAroundInterceptor {
|
||||
/**
|
||||
* The key name that redis connection information in {@link EnhancedClassInstanceContext#context}.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue