diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 544ab55..ba8fecd 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -25,16 +25,52 @@ jobs:
distribution: temurin
# only first java setup need enable cache
cache: maven
- - name: build and test with Java 17
+ - name: build and test with Java 17 without agents
run: ./mvnw -V --no-transfer-progress clean install
+ - name: test under hello and world agents
+ run: ./mvnw dependency:properties surefire:test
+ env:
+ STUDY_AGENT_RUN_MODE: hello-and-world-agents
+ - name: test under hello agent
+ run: ./mvnw dependency:properties surefire:test
+ env:
+ STUDY_AGENT_RUN_MODE: only-hello-agent
+ - name: run Main without agents
+ run: ./mvnw dependency:properties exec:exec -pl main-runner
+ - name: run Main under hello and world agents
+ run: ./mvnw dependency:properties exec:exec -pl main-runner
+ env:
+ STUDY_AGENT_RUN_MODE: hello-and-world-agents
+ - name: run Main under hello agent
+ run: ./mvnw dependency:properties exec:exec -pl main-runner
+ env:
+ STUDY_AGENT_RUN_MODE: only-hello-agent
- name: setup Java 8
uses: actions/setup-java@v3
with:
java-version: 8
distribution: zulu
- - name: test with Java 8
+ - name: test with Java 8 without agents
run: ./mvnw -V --no-transfer-progress dependency:properties surefire:test
+ - name: test under hello and world agents
+ run: ./mvnw dependency:properties surefire:test
+ env:
+ STUDY_AGENT_RUN_MODE: hello-and-world-agents
+ - name: test under hello agent
+ run: ./mvnw dependency:properties surefire:test
+ env:
+ STUDY_AGENT_RUN_MODE: only-hello-agent
+ - name: run Main without agents
+ run: ./mvnw dependency:properties exec:exec -pl main-runner
+ - name: run Main under hello and world agents
+ run: ./mvnw dependency:properties exec:exec -pl main-runner
+ env:
+ STUDY_AGENT_RUN_MODE: hello-and-world-agents
+ - name: run Main under hello agent
+ run: ./mvnw dependency:properties exec:exec -pl main-runner
+ env:
+ STUDY_AGENT_RUN_MODE: only-hello-agent
- name: 'remove self maven install files(OS: *nix)'
run: rm -rf $HOME/.m2/repository/io/foldright/study*
diff --git a/hello-agent/pom.xml b/hello-agent/pom.xml
index 53fc339..94ef71f 100644
--- a/hello-agent/pom.xml
+++ b/hello-agent/pom.xml
@@ -10,7 +10,16 @@
hello-agent
+
+ io.foldright.study.hello.agent
+
+
+
+ io.foldright.study
+ utils
+ ${project.version}
+
org.javassist
javassist
@@ -25,10 +34,10 @@
- io.foldright.study.hello.agent.HelloAgent
+ ${project.root.package}.HelloAgent
${project.build.finalName}.jar
- false
true
+ true
false
@@ -46,17 +55,28 @@
+
+ io.foldright.study.agent.utils
+ ${project.root.package}.internal.utils
+
javassist
- io.foldright.study.hello.agent.internal.javassist
+ ${project.root.package}.internal.javassist
+ io.foldright.study:utils
org.javassist:javassist
+
+ io.foldright.study:utils
+
+ META-INF/MANIFEST.MF
+
+
org.javassist:javassist
diff --git a/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java b/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java
index 573ea11..a8a9909 100644
--- a/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java
+++ b/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java
@@ -1,12 +1,32 @@
package io.foldright.study.hello.agent;
import edu.umd.cs.findbugs.annotations.NonNull;
+import io.foldright.study.agent.utils.transform.DispatchTransformer;
+import io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet;
import java.lang.instrument.Instrumentation;
+import java.util.Collections;
+import java.util.List;
+import static io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet.THREAD_POOL_EXECUTOR_CLASS_NAME;
+import static io.foldright.study.agent.utils.Utils.isClassLoaded;
+
+/**
+ * a demo agent.
+ */
public class HelloAgent {
+ private static final String NAME = "Hello";
+
public static void premain(final String agentArgs, @NonNull final Instrumentation inst) {
- System.out.println("Hello Agent!");
+ System.out.println("[" + NAME + "Agent] Enter premain!");
+
+ if (isClassLoaded(inst, THREAD_POOL_EXECUTOR_CLASS_NAME))
+ throw new IllegalStateException("class " + THREAD_POOL_EXECUTOR_CLASS_NAME + " already loaded");
+
+ List transformlets = Collections.singletonList(
+ new ThreadPoolExecutorTransformlet(NAME)
+ );
+ inst.addTransformer(new DispatchTransformer(NAME, transformlets));
}
}
diff --git a/main-runner/pom.xml b/main-runner/pom.xml
index a8b632b..ca88215 100644
--- a/main-runner/pom.xml
+++ b/main-runner/pom.xml
@@ -13,10 +13,8 @@
io.foldright.study.main.Main
- -javaagent:${io.foldright.study:hello-agent:jar}
- -Denable.hello.agent=true
- -javaagent:${io.foldright.study:world-agent:jar}
- -Denable.world.agent=true
+ -javaagent:${io.foldright.study:hello-agent:jar}
+ -javaagent:${io.foldright.study:world-agent:jar}
@@ -60,17 +58,20 @@
- enable-hello-world-agents
+ enable-hello-and-world-agents-mode
+
+
+ env.STUDY_AGENT_RUN_MODE
+ hello-and-world-agents
+
+
org.apache.maven.plugins
maven-surefire-plugin
- @{argLine}
- ${agent.opt.hello} ${dopt.enable.agent.hello}
- ${agent.opt.world} ${dopt.enable.agent.world}
-
+ @{argLine} ${hello.agent.opt} ${world.agent.opt}
@@ -83,10 +84,55 @@
-Duser.language=en
-Duser.country=US
- ${agent.opt.hello}
- ${dopt.enable.agent.hello}
- ${agent.opt.world}
- ${dopt.enable.agent.world}
+
+
+ ${hello.agent.opt}
+ ${world.agent.opt}
+
+ -classpath
+
+
+ ${exec.mainClass}
+
+
+
+
+
+
+
+ enable-only-hello-agent-mode
+
+
+ env.STUDY_AGENT_RUN_MODE
+ only-hello-agent
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ @{argLine} ${hello.agent.opt}
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ -Xms256m
+ -ea
+ -Duser.language=en
+ -Duser.country=US
+
+
+
+ ${hello.agent.opt}
-classpath
diff --git a/main-runner/src/main/java/io/foldright/study/main/Main.java b/main-runner/src/main/java/io/foldright/study/main/Main.java
index 6ce5d73..e3cc3b4 100644
--- a/main-runner/src/main/java/io/foldright/study/main/Main.java
+++ b/main-runner/src/main/java/io/foldright/study/main/Main.java
@@ -1,7 +1,15 @@
package io.foldright.study.main;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
public class Main {
public static void main(String[] args) {
+ ExecutorService executor = Executors.newCachedThreadPool();
+ System.out.println("========================================");
System.out.println("Hello World!");
+ System.out.println(executor);
+ System.out.println("========================================");
}
}
diff --git a/main-runner/src/main/java/io/foldright/study/main/StudyAgentMode.java b/main-runner/src/main/java/io/foldright/study/main/StudyAgentMode.java
new file mode 100644
index 0000000..89c8cae
--- /dev/null
+++ b/main-runner/src/main/java/io/foldright/study/main/StudyAgentMode.java
@@ -0,0 +1,23 @@
+package io.foldright.study.main;
+
+public enum StudyAgentMode {
+ NO_AGENTS,
+ HELLO_AND_WORLD_AGENTS,
+ ONLY_HELLO_AGENT,
+
+ ;
+
+ public static StudyAgentMode getStudyAgentMode() {
+ final String envVarName = "STUDY_AGENT_RUN_MODE";
+ final String value = System.getenv(envVarName);
+ if (value == null || "".equals(value)) {
+ return NO_AGENTS;
+ } else if ("hello-and-world-agents".equals(value)) {
+ return HELLO_AND_WORLD_AGENTS;
+ } else if ("only-hello-agent".equals(value)) {
+ return ONLY_HELLO_AGENT;
+ } else {
+ throw new IllegalStateException("Illegal value of env var(" + envVarName + "): " + value);
+ }
+ }
+}
diff --git a/main-runner/src/test/java/io/foldright/study/main/MainTest.java b/main-runner/src/test/java/io/foldright/study/main/MainTest.java
deleted file mode 100644
index d348011..0000000
--- a/main-runner/src/test/java/io/foldright/study/main/MainTest.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.foldright.study.main;
-
-import org.junit.jupiter.api.Test;
-
-
-class MainTest {
- @Test
- void hello() {
- System.out.println("Hello world!");
- }
-}
diff --git a/main-runner/src/test/java/io/foldright/study/main/MainTest.kt b/main-runner/src/test/java/io/foldright/study/main/MainTest.kt
new file mode 100644
index 0000000..b5bcf02
--- /dev/null
+++ b/main-runner/src/test/java/io/foldright/study/main/MainTest.kt
@@ -0,0 +1,36 @@
+package io.foldright.study.main
+
+import io.kotest.assertions.fail
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.string.shouldContainOnlyOnce
+import io.kotest.matchers.string.shouldNotContain
+import java.util.concurrent.Executors
+
+
+class MainTest : FunSpec({
+ test("agent hacked toString of ThreadPoolExecutor") {
+ val executor = Executors.newCachedThreadPool() as java.util.concurrent.ThreadPoolExecutor
+ val addedByHelloTransformlet = "[[[AddedByHelloTransformlet]]]"
+ val addedByWorldTransformlet = "[[[AddedByWorldTransformlet]]]"
+
+ val executorString = executor.toString()
+ when (val studyAgentMode = StudyAgentMode.getStudyAgentMode()) {
+ StudyAgentMode.NO_AGENTS -> {
+ executorString.shouldNotContain(addedByHelloTransformlet)
+ executorString.shouldNotContain(addedByWorldTransformlet)
+ }
+
+ StudyAgentMode.HELLO_AND_WORLD_AGENTS -> {
+ executorString.shouldContainOnlyOnce(addedByHelloTransformlet)
+ executorString.shouldContainOnlyOnce(addedByWorldTransformlet)
+ }
+
+ StudyAgentMode.ONLY_HELLO_AGENT -> {
+ executorString.shouldContainOnlyOnce(addedByHelloTransformlet)
+ executorString.shouldNotContain(addedByWorldTransformlet)
+ }
+
+ else -> fail("Unknown StudyAgentMode: $studyAgentMode")
+ }
+ }
+})
diff --git a/pom.xml b/pom.xml
index adc69b9..eb1b0b5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,7 @@
+ utils
hello-agent
world-agent
main-runner
diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh
index a7906c5..3990bfd 100755
--- a/scripts/integration_test.sh
+++ b/scripts/integration_test.sh
@@ -52,10 +52,14 @@ jvb::mvn_cmd clean install
# test by multiply version jdks
########################################
-for jdk_version in "${JDK_VERSIONS[@]}"; do
- # skip default jdk, already tested above
- [ "$jdk_version" = "$default_build_jdk_version" ] && continue
+# about CI env var
+# https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
+if [ "${CI:-}" = true ]; then
+ CI_MORE_BEGIN_OPTS=jacoco:prepare-agent
+ CI_MORE_END_OPTS=jacoco:report
+fi
+for jdk_version in "${JDK_VERSIONS[@]}"; do
jh_var_name="JAVA${jdk_version}_HOME"
[ -d "${!jh_var_name:-}" ] || cu::die "\$${jh_var_name}(${!jh_var_name:-}) dir is not existed!"
export JAVA_HOME="${!jh_var_name}"
@@ -63,11 +67,25 @@ for jdk_version in "${JDK_VERSIONS[@]}"; do
# just test without build
cu::head_line_echo "test with Java $jdk_version: $JAVA_HOME"
- # about CI env var
- # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
- if [ "${CI:-}" = true ]; then
- jvb::mvn_cmd jacoco:prepare-agent dependency:properties surefire:test jacoco:report
- else
- jvb::mvn_cmd dependency:properties surefire:test
+ # skip default jdk, already tested above
+ if [ "$jdk_version" != "$default_build_jdk_version" ]; then
+ # just test without build
+ cu::head_line_echo "test without agents: $JAVA_HOME"
+ jvb::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} dependency:properties surefire:test ${CI_MORE_END_OPTS:-}
fi
+
+ cu::head_line_echo test under hello and world agents
+ STUDY_AGENT_RUN_MODE=hello-and-world-agents jvb::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} dependency:properties surefire:test ${CI_MORE_END_OPTS:-}
+
+ cu::head_line_echo test under hello agent
+ STUDY_AGENT_RUN_MODE=only-hello-agent jvb::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} dependency:properties surefire:test ${CI_MORE_END_OPTS:-}
+
+ cu::head_line_echo run Main without agents
+ jvb::mvn_cmd dependency:properties exec:exec -pl main-runner
+
+ cu::head_line_echo run Main under hello and world agents
+ STUDY_AGENT_RUN_MODE=hello-and-world-agents jvb::mvn_cmd dependency:properties exec:exec -pl main-runner
+
+ cu::head_line_echo run Main under hello agent
+ STUDY_AGENT_RUN_MODE=only-hello-agent jvb::mvn_cmd dependency:properties exec:exec -pl main-runner
done
diff --git a/utils/pom.xml b/utils/pom.xml
new file mode 100644
index 0000000..678c95b
--- /dev/null
+++ b/utils/pom.xml
@@ -0,0 +1,19 @@
+
+ 4.0.0
+
+ io.foldright.study
+ java-agent-study
+ 0.1.0-SNAPSHOT
+ ../pom.xml
+
+
+ utils
+
+
+
+ org.javassist
+ javassist
+
+
+
diff --git a/utils/src/main/java/io/foldright/study/agent/utils/Utils.java b/utils/src/main/java/io/foldright/study/agent/utils/Utils.java
new file mode 100644
index 0000000..51d5652
--- /dev/null
+++ b/utils/src/main/java/io/foldright/study/agent/utils/Utils.java
@@ -0,0 +1,46 @@
+package io.foldright.study.agent.utils;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.instrument.Instrumentation;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+import static java.security.AccessController.doPrivileged;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toSet;
+
+
+public class Utils {
+ public static boolean isClassLoaded(@NonNull final Instrumentation inst, @NonNull String className) {
+ return getLoadedClasses(inst).containsKey(className);
+ }
+
+ public static boolean isClassLoadedByClassLoader(@NonNull final Instrumentation inst, @NonNull String className, ClassLoader classLoader) {
+ Map>>> loadedClasses = getLoadedClasses(inst);
+ if (!loadedClasses.containsKey(className)) return false;
+
+ classLoader = classLoader == null ? NULL_CLASS_LOADER : classLoader;
+ return loadedClasses.get(className).containsKey(classLoader);
+ }
+
+ @NotNull
+ private static Map>>> getLoadedClasses(@NotNull Instrumentation inst) {
+ return Arrays.stream((Class>[]) inst.getAllLoadedClasses())
+ .collect(groupingBy(Class::getName, groupingBy(clazz -> {
+ ClassLoader classLoader = clazz.getClassLoader();
+ return classLoader == null ? NULL_CLASS_LOADER : classLoader;
+ }, toSet())));
+ }
+
+ @SuppressWarnings("removal")
+ private static final ClassLoader NULL_CLASS_LOADER = doPrivileged((PrivilegedAction) () -> new ClassLoader() {
+ });
+
+ public static String classFileToName(@NonNull final String classFile) {
+ return classFile.replace('/', '.');
+ }
+}
diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/ClassInfo.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/ClassInfo.java
new file mode 100644
index 0000000..77f9e79
--- /dev/null
+++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/ClassInfo.java
@@ -0,0 +1,70 @@
+package io.foldright.study.agent.utils.transform;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.LoaderClassPath;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+
+class ClassInfo {
+ private final String className;
+ private final byte[] classFileBuffer;
+ private final ClassLoader loader;
+
+ // SuppressFBWarnings for classFileBuffer/loader parameter:
+ // SuppressFBWarnings for classFileBuffer parameter:
+ // [ERROR] new com.alibaba.ttl.threadpool.agent.internal.transformlet.ClassInfo(String, byte[], ClassLoader)
+ // may expose internal representation by storing an externally mutable object
+ // into ClassInfo.classFileBuffer/loader
+ public ClassInfo(@NonNull String className,
+ @NonNull @SuppressFBWarnings({"EI_EXPOSE_REP2"}) byte[] classFileBuffer,
+ @Nullable @SuppressFBWarnings({"EI_EXPOSE_REP2"}) ClassLoader loader) {
+ this.className = className;
+ this.classFileBuffer = classFileBuffer;
+ this.loader = loader;
+ }
+
+ @NonNull
+ public String getClassName() {
+ return className;
+ }
+
+ private CtClass ctClass;
+
+ @NonNull
+ @SuppressFBWarnings({"EI_EXPOSE_REP"})
+ // [ERROR] Medium: com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo.getCtClass()
+ // may expose internal representation
+ // by returning ClassInfo.ctClass [com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo]
+ public CtClass getCtClass() throws IOException {
+ if (ctClass != null) return ctClass;
+
+ final ClassPool classPool = new ClassPool(true);
+ if (loader == null) {
+ classPool.appendClassPath(new LoaderClassPath(ClassLoader.getSystemClassLoader()));
+ } else {
+ classPool.appendClassPath(new LoaderClassPath(loader));
+ }
+
+ final CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classFileBuffer), false);
+ clazz.defrost();
+
+ this.ctClass = clazz;
+ return clazz;
+ }
+
+ private boolean modified = false;
+
+ public boolean isModified() {
+ return modified;
+ }
+
+ public void setModified() {
+ this.modified = true;
+ }
+}
diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/DispatchTransformer.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/DispatchTransformer.java
new file mode 100644
index 0000000..4cadc6a
--- /dev/null
+++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/DispatchTransformer.java
@@ -0,0 +1,59 @@
+package io.foldright.study.agent.utils.transform;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.security.ProtectionDomain;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static io.foldright.study.agent.utils.Utils.classFileToName;
+
+
+public final class DispatchTransformer implements ClassFileTransformer {
+ /**
+ * "null
if no transform is performed",
+ * see {@code @return} of {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])}
+ */
+ @SuppressFBWarnings("MS_PKGPROTECT")
+ // [ERROR] com.alibaba.ttl.threadpool.agent.TtlTransformer.transform(ClassLoader, String, Class, ProtectionDomain, byte[])
+ // may expose internal representation by returning TtlTransformer.NO_TRANSFORM
+ // the value is null, so there is NO "EI_EXPOSE_REP" problem actually.
+ public static final byte[] NO_TRANSFORM = null;
+
+ private final String name;
+ private final Map transformlets;
+
+ public DispatchTransformer(String name, Collection extends Transformlet> transformlets) {
+ Map transformletMap = new HashMap<>();
+ for (Transformlet transformlet : transformlets) {
+ for (String className : transformlet.targetClassNames()) {
+ transformletMap.put(className, transformlet);
+ }
+ }
+ this.name = name;
+ this.transformlets = transformletMap;
+ }
+
+ @Override
+ public byte[] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class> classBeingRedefined,
+ final ProtectionDomain protectionDomain, @NonNull final byte[] classFileBuffer) {
+ try {
+ // Lambda has no class file, no need to transform, just return.
+ if (classFile == null) return NO_TRANSFORM;
+ System.out.println("[" + name + "Transformer] try transform " + classFile);
+
+ final String className = classFileToName(classFile);
+ final Transformlet tr = transformlets.get(className);
+ if (tr == null) return NO_TRANSFORM;
+ return tr.transform(loader, classFile, classBeingRedefined, protectionDomain, classFileBuffer);
+ } catch (Exception e) {
+ System.err.println(e);
+ e.printStackTrace();
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/ThreadPoolExecutorTransformlet.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/ThreadPoolExecutorTransformlet.java
new file mode 100644
index 0000000..daf1c51
--- /dev/null
+++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/ThreadPoolExecutorTransformlet.java
@@ -0,0 +1,41 @@
+package io.foldright.study.agent.utils.transform;
+
+import javassist.CtMethod;
+
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+
+public class ThreadPoolExecutorTransformlet implements Transformlet {
+ public static final String THREAD_POOL_EXECUTOR_CLASS_NAME = "java.util.concurrent.ThreadPoolExecutor";
+
+ private final String name;
+
+ public ThreadPoolExecutorTransformlet(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws Exception {
+ final ClassInfo classInfo = new ClassInfo(THREAD_POOL_EXECUTOR_CLASS_NAME, classFileBuffer, loader);
+
+ // HACK toString method
+ final CtMethod toString = classInfo.getCtClass().getDeclaredMethod("toString");
+ final String code = "$_ = $_ + \"[[[AddedBy" + name + "Transformlet]]]\";";
+ toString.insertAfter(code);
+ log("insertAfter toString method of " + THREAD_POOL_EXECUTOR_CLASS_NAME + ": " + code);
+
+ return classInfo.getCtClass().toBytecode();
+ }
+
+ @Override
+ public Set targetClassNames() {
+ return new HashSet<>(Collections.singletonList(THREAD_POOL_EXECUTOR_CLASS_NAME));
+ }
+
+ private void log(String msg) {
+ System.out.println("[" + name + "Transformlet] " + msg);
+ }
+}
diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/Transformlet.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/Transformlet.java
new file mode 100644
index 0000000..bb887da
--- /dev/null
+++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/Transformlet.java
@@ -0,0 +1,11 @@
+package io.foldright.study.agent.utils.transform;
+
+import java.security.ProtectionDomain;
+import java.util.Set;
+
+
+public interface Transformlet {
+ byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws Exception;
+
+ Set targetClassNames();
+}
diff --git a/world-agent/pom.xml b/world-agent/pom.xml
index 85996e1..fbe6106 100644
--- a/world-agent/pom.xml
+++ b/world-agent/pom.xml
@@ -10,7 +10,16 @@
world-agent
+
+ io.foldright.study.world.agent
+
+
+
+ io.foldright.study
+ utils
+ ${project.version}
+
org.javassist
javassist
@@ -25,10 +34,10 @@
- io.foldright.study.world.agent.WorldAgent
+ ${project.root.package}.WorldAgent
${project.build.finalName}.jar
- false
true
+ true
false
@@ -46,17 +55,28 @@
+
+ io.foldright.study.agent.utils
+ ${project.root.package}.internal.utils
+
javassist
- io.foldright.study.world.agent.internal.javassist
+ ${project.root.package}.internal.javassist
+ io.foldright.study:utils
org.javassist:javassist
+
+ io.foldright.study:utils
+
+ META-INF/MANIFEST.MF
+
+
org.javassist:javassist
diff --git a/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java b/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java
index 383acd8..4548613 100644
--- a/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java
+++ b/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java
@@ -1,12 +1,32 @@
package io.foldright.study.world.agent;
import edu.umd.cs.findbugs.annotations.NonNull;
+import io.foldright.study.agent.utils.transform.DispatchTransformer;
+import io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet;
import java.lang.instrument.Instrumentation;
+import java.util.Collections;
+import java.util.List;
+import static io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet.THREAD_POOL_EXECUTOR_CLASS_NAME;
+import static io.foldright.study.agent.utils.Utils.isClassLoaded;
+
+/**
+ * a demo agent.
+ */
public class WorldAgent {
+ private static final String NAME = "World";
+
public static void premain(final String agentArgs, @NonNull final Instrumentation inst) {
- System.out.println("World Agent!");
+ System.out.println("[" + NAME + "Agent] Enter premain!");
+
+ if (isClassLoaded(inst, THREAD_POOL_EXECUTOR_CLASS_NAME))
+ throw new IllegalStateException("class " + THREAD_POOL_EXECUTOR_CLASS_NAME + " already loaded");
+
+ List transformlets = Collections.singletonList(
+ new ThreadPoolExecutorTransformlet(NAME)
+ );
+ inst.addTransformer(new DispatchTransformer(NAME, transformlets));
}
}