diff --git a/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/assist/NoCocurrencyAceessObject.java b/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/assist/NoCocurrencyAceessObject.java index 98b695033..4f69939e2 100644 --- a/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/assist/NoCocurrencyAceessObject.java +++ b/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/assist/NoCocurrencyAceessObject.java @@ -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(); diff --git a/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/loader/InterceptorInstanceLoader.java b/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/loader/InterceptorInstanceLoader.java index 78addb934..4479cdb22 100644 --- a/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/loader/InterceptorInstanceLoader.java +++ b/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/plugin/interceptor/loader/InterceptorInstanceLoader.java @@ -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. - *

+ * The InterceptorInstanceLoader 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. - *

+ * + * 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 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 * @return interceptor instance. @@ -72,7 +79,8 @@ public class InterceptorInstanceLoader { * @throws IllegalAccessException * @throws InstantiationException */ - private static T loadBinary(String className, ClassLoader targetClassLoader) throws InvocationTargetException, IllegalAccessException, InstantiationException { + private static 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 * @return interceptor instance. */ - private static T findLoadedClass(String className, ClassLoader targetClassLoader) throws InvocationTargetException, IllegalAccessException, InstantiationException { + private static 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(); } } diff --git a/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/boot/ServiceManagerTest.java b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/boot/ServiceManagerTest.java new file mode 100644 index 000000000..233a77f26 --- /dev/null +++ b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/boot/ServiceManagerTest.java @@ -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); + } +} diff --git a/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/assist/NoCocurrencyAceessObjectTest.java b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/assist/NoCocurrencyAceessObjectTest.java new file mode 100644 index 000000000..bcf2304a8 --- /dev/null +++ b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/assist/NoCocurrencyAceessObjectTest.java @@ -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")); + } +} diff --git a/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/interceptor/EnhancedClassInstanceContextTest.java b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/interceptor/EnhancedClassInstanceContextTest.java new file mode 100644 index 000000000..154afe7fc --- /dev/null +++ b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/interceptor/EnhancedClassInstanceContextTest.java @@ -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)); + } +} diff --git a/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/loader/InterceptorInstanceLoaderTest.java b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/loader/InterceptorInstanceLoaderTest.java new file mode 100644 index 000000000..0c229750d --- /dev/null +++ b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/loader/InterceptorInstanceLoaderTest.java @@ -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); + } +} diff --git a/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/loader/NeverUsedTestClass.java b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/loader/NeverUsedTestClass.java new file mode 100644 index 000000000..03977d001 --- /dev/null +++ b/skywalking-sniffer/skywalking-api/src/test/java/com/a/eye/skywalking/api/plugin/loader/NeverUsedTestClass.java @@ -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 { +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/jedis-2.x-plugin/src/main/java/com/a/eye/skywalking/plugin/jedis/v2/JedisMethodInterceptor.java b/skywalking-sniffer/skywalking-sdk-plugin/jedis-2.x-plugin/src/main/java/com/a/eye/skywalking/plugin/jedis/v2/JedisMethodInterceptor.java index 01b9655c7..4cb17f016 100644 --- a/skywalking-sniffer/skywalking-sdk-plugin/jedis-2.x-plugin/src/main/java/com/a/eye/skywalking/plugin/jedis/v2/JedisMethodInterceptor.java +++ b/skywalking-sniffer/skywalking-sdk-plugin/jedis-2.x-plugin/src/main/java/com/a/eye/skywalking/plugin/jedis/v2/JedisMethodInterceptor.java @@ -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}. */