Fix interceptor instance singleton bug, and add some test cases.

This commit is contained in:
wusheng 2017-03-16 18:45:33 +08:00
parent 5c369aefb4
commit 14fc924cc4
8 changed files with 179 additions and 44 deletions

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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"));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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 {
}

View File

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