From 4173828d4f4b728372769dd517b086ace97086d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Fri, 23 Oct 2020 21:17:48 +0800 Subject: [PATCH] no more EnableTestable annotation in agent --- .../testable/agent/constant/ConstPool.java | 1 - .../agent/handler/TestClassHandler.java | 4 +- .../agent/tool/ComparableWeakRef.java | 33 ++++++++++++++++ .../transformer/TestableClassTransformer.java | 27 ++++++++----- .../testable/agent/util/ClassUtil.java | 35 +++++++++++------ .../agent/tool/ComparableWeakRefTest.java | 38 +++++++++++++++++++ .../testable/agent/util/ClassUtilTest.java | 10 +++-- 7 files changed, 121 insertions(+), 27 deletions(-) create mode 100644 testable-agent/src/main/java/com/alibaba/testable/agent/tool/ComparableWeakRef.java create mode 100644 testable-agent/src/test/java/com/alibaba/testable/agent/tool/ComparableWeakRefTest.java diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java index 4f3a7ad..cb9bdda 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java @@ -12,6 +12,5 @@ public class ConstPool { public static final String TEST_POSTFIX = "Test"; public static final String TESTABLE_INJECT_REF = "_testableInternalRef"; - public static final String ENABLE_TESTABLE = "com.alibaba.testable.core.annotation.EnableTestable"; public static final String TESTABLE_INJECT = "com.alibaba.testable.core.annotation.TestableInject"; } diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java index aead738..fea1799 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java @@ -41,7 +41,7 @@ public class TestClassHandler extends BaseClassHandler { private void transformMethod(ClassNode cn, MethodNode mn) { handleAnnotation(cn, mn); - handleInstruction(cn, mn); + handleInstruction(mn); } private void handleAnnotation(ClassNode cn, MethodNode mn) { @@ -61,7 +61,7 @@ public class TestClassHandler extends BaseClassHandler { } } - private void handleInstruction(ClassNode cn, MethodNode mn) { + private void handleInstruction(MethodNode mn) { AbstractInsnNode[] instructions = mn.instructions.toArray(); int i = 0; do { diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/tool/ComparableWeakRef.java b/testable-agent/src/main/java/com/alibaba/testable/agent/tool/ComparableWeakRef.java new file mode 100644 index 0000000..81c5060 --- /dev/null +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/tool/ComparableWeakRef.java @@ -0,0 +1,33 @@ +package com.alibaba.testable.agent.tool; + +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * @author flin + */ +public class ComparableWeakRef extends WeakReference { + + public ComparableWeakRef(T referent) { + super(referent); + } + + static public Set> getWeekHashSet() { + return Collections.newSetFromMap(new WeakHashMap, Boolean>()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WeakReference) { + return this.get().equals(((WeakReference)obj).get()); + } + return false; + } + + @Override + public int hashCode() { + return this.get().hashCode(); + } +} diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java index 96842bd..c7bd6ec 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java @@ -5,6 +5,7 @@ import com.alibaba.testable.agent.handler.SourceClassHandler; import com.alibaba.testable.agent.handler.TestClassHandler; import com.alibaba.testable.agent.model.ImmutablePair; import com.alibaba.testable.agent.model.MethodInfo; +import com.alibaba.testable.agent.tool.ComparableWeakRef; import com.alibaba.testable.agent.util.ClassUtil; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Type; @@ -17,7 +18,6 @@ import java.lang.instrument.ClassFileTransformer; import java.net.URLClassLoader; import java.security.ProtectionDomain; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -29,28 +29,26 @@ import static com.alibaba.testable.agent.util.ClassUtil.toSlashSeparatedName; */ public class TestableClassTransformer implements ClassFileTransformer { - private final Set loadedClassNames = new HashSet(); + private final Set> loadedClassNames = ComparableWeakRef.getWeekHashSet(); private static final String TARGET_CLASS = "targetClass"; private static final String TARGET_METHOD = "targetMethod"; + @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) { - if (isSystemClass(loader, className) || loadedClassNames.contains(className)) { + if (isSystemClass(loader, className) || loadedClassNames.contains(new ComparableWeakRef(className))) { // Ignore system class and reloaded class return null; } - - List annotations = ClassUtil.getAnnotations(className); - List testAnnotations = ClassUtil.getAnnotations(ClassUtil.getTestClassName(className)); try { - if (testAnnotations.contains(ConstPool.ENABLE_TESTABLE)) { + if (shouldTransformAsSourceClass(className)) { // it's a source class with testable enabled - loadedClassNames.add(className); + loadedClassNames.add(new ComparableWeakRef(className)); List injectMethods = getTestableInjectMethods(ClassUtil.getTestClassName(className)); return new SourceClassHandler(injectMethods).getBytes(classFileBuffer); - } else if (annotations.contains(ConstPool.ENABLE_TESTABLE)) { + } else if (shouldTransformAsTestClass(className)) { // it's a test class with testable enabled - loadedClassNames.add(className); + loadedClassNames.add(new ComparableWeakRef(className)); return new TestClassHandler().getBytes(classFileBuffer); } } catch (IOException e) { @@ -59,6 +57,15 @@ public class TestableClassTransformer implements ClassFileTransformer { return null; } + private boolean shouldTransformAsSourceClass(String className) { + return ClassUtil.anyMethodHasAnnotation(ClassUtil.getTestClassName(className), ConstPool.TESTABLE_INJECT); + } + + private boolean shouldTransformAsTestClass(String className) { + return className.endsWith(ConstPool.TEST_POSTFIX) && + ClassUtil.anyMethodHasAnnotation(className, ConstPool.TESTABLE_INJECT); + } + private boolean isSystemClass(ClassLoader loader, String className) { return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/"); } diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java b/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java index e339182..9f00b9d 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java @@ -1,14 +1,13 @@ package com.alibaba.testable.agent.util; import com.alibaba.testable.agent.constant.ConstPool; +import com.alibaba.testable.agent.tool.ComparableWeakRef; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author flin @@ -29,6 +28,8 @@ public class ClassUtil { private static final char TYPE_ARRAY = '['; private static final Map TYPE_MAPPING = new HashMap(); + private static final Map, Boolean> loadedClass = + new WeakHashMap, Boolean>(); static { TYPE_MAPPING.put(TYPE_BYTE, "java/lang/Byte"); @@ -42,21 +43,33 @@ public class ClassUtil { } /** - * Get annotation on class definition + * Check whether any method in specified class has specified annotation * @param className class that need to explore + * @param annotationName annotation to look for */ - public static List getAnnotations(String className) { + public static boolean anyMethodHasAnnotation(String className, String annotationName) { + Boolean found = loadedClass.get(new ComparableWeakRef(className)); + if (found != null) { + return found; + } try { - List annotations = new ArrayList(); ClassNode cn = new ClassNode(); new ClassReader(className).accept(cn, 0); - for (AnnotationNode an : cn.visibleAnnotations) { - annotations.add(toDotSeparateFullClassName(an.desc)); + for (MethodNode mn : cn.methods) { + if (mn.visibleAnnotations != null) { + for (AnnotationNode an : mn.visibleAnnotations) { + if (toDotSeparateFullClassName(an.desc).equals(annotationName)) { + loadedClass.put(new ComparableWeakRef(className), true); + return true; + } + } + } } - return annotations; } catch (Exception e) { - return new ArrayList(); + // ignore } + loadedClass.put(new ComparableWeakRef(className), false); + return false; } /** diff --git a/testable-agent/src/test/java/com/alibaba/testable/agent/tool/ComparableWeakRefTest.java b/testable-agent/src/test/java/com/alibaba/testable/agent/tool/ComparableWeakRefTest.java new file mode 100644 index 0000000..fd2c27b --- /dev/null +++ b/testable-agent/src/test/java/com/alibaba/testable/agent/tool/ComparableWeakRefTest.java @@ -0,0 +1,38 @@ +package com.alibaba.testable.agent.tool; + +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class ComparableWeakRefTest { + + @Test + void should_works_with_weak_hash_map() throws Exception { + Map, String> map = new WeakHashMap, String>(); + map.put(new ComparableWeakRef("a"), "bc"); + map.put(new ComparableWeakRef("ab"), "ac"); + map.put(new ComparableWeakRef("abc"), "bc"); + assertEquals(3, map.size()); + assertEquals("bc", map.get(new ComparableWeakRef("abc"))); + System.gc(); + Thread.sleep(100); + assertEquals(0, map.size()); + assertNull(map.get(new ComparableWeakRef("abc"))); + } + + @Test + void should_works_with_hash_set() throws Exception { + Set> set = ComparableWeakRef.getWeekHashSet(); + set.add(new ComparableWeakRef("a")); + set.add(new ComparableWeakRef("ab")); + set.add(new ComparableWeakRef("abc")); + assertEquals(3, set.size()); + assertTrue(set.contains(new ComparableWeakRef("ab"))); + System.gc(); + Thread.sleep(100); + assertEquals(0, set.size()); + assertFalse(set.contains(new ComparableWeakRef("ab"))); + } +} diff --git a/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java b/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java index 5949119..7cd9456 100644 --- a/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java +++ b/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java @@ -2,15 +2,19 @@ package com.alibaba.testable.agent.util; import org.junit.jupiter.api.Test; +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.WeakHashMap; + import static org.junit.jupiter.api.Assertions.*; class ClassUtilTest { @Test void should_able_to_get_annotation() { - assertEquals(0, ClassUtil.getAnnotations("class.not.exist").size()); - assertEquals(0, ClassUtil.getAnnotations("com.alibaba.testable.agent.util.ClassUtil").size()); - assertEquals("org.apiguardian.api.API", ClassUtil.getAnnotations("org.junit.jupiter.api.Assertions").get(0)); + assertEquals(false, ClassUtil.anyMethodHasAnnotation("class.not.exist", "")); + assertEquals(false, ClassUtil.anyMethodHasAnnotation("org.junit.jupiter.api.Assertions", "annotation.not.exist")); + assertEquals(true, ClassUtil.anyMethodHasAnnotation("org.junit.jupiter.api.Assertions", "org.apiguardian.api.API")); } @Test