-
Notifications
You must be signed in to change notification settings - Fork 288
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support spring-boot-devtoools reloadable classlaoder
- Loading branch information
Showing
7 changed files
with
213 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 47 additions & 26 deletions
73
dd-java-agent/instrumentation/spring-boot/gradle.lockfile
Large diffs are not rendered by default.
Oops, something went wrong.
78 changes: 78 additions & 0 deletions
78
...main/java/datadog/trace/instrumentation/springboot/RestartClassLoaderInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package datadog.trace.instrumentation.springboot; | ||
|
||
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; | ||
import static datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter.ExcludeType.RUNNABLE; | ||
import static net.bytebuddy.matcher.ElementMatchers.isConstructor; | ||
import static net.bytebuddy.matcher.ElementMatchers.isMethod; | ||
|
||
import com.google.auto.service.AutoService; | ||
import datadog.trace.agent.tooling.ExcludeFilterProvider; | ||
import datadog.trace.agent.tooling.Instrumenter; | ||
import datadog.trace.agent.tooling.InstrumenterModule; | ||
import datadog.trace.agent.tooling.bytebuddy.memoize.Memoizer; | ||
import datadog.trace.api.InstrumenterConfig; | ||
import datadog.trace.bootstrap.InstrumentationContext; | ||
import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import net.bytebuddy.asm.Advice; | ||
import org.springframework.boot.devtools.restart.classloader.RestartClassLoader; | ||
|
||
@AutoService(InstrumenterModule.class) | ||
public class RestartClassLoaderInstrumentation extends InstrumenterModule.Tracing | ||
implements Instrumenter.ForSingleType, ExcludeFilterProvider { | ||
|
||
public RestartClassLoaderInstrumentation() { | ||
super("spring-boot-devtools", "spring-boot"); | ||
} | ||
|
||
@Override | ||
public boolean isEnabled() { | ||
return super.isEnabled() && InstrumenterConfig.get().isResolverMemoizingEnabled(); | ||
} | ||
|
||
@Override | ||
public Map<String, String> contextStore() { | ||
return Collections.singletonMap( | ||
"org.springframework.boot.devtools.restart.classloader.RestartClassLoader", | ||
"java.lang.Boolean"); | ||
} | ||
|
||
@Override | ||
public String instrumentedType() { | ||
return "org.springframework.boot.devtools.restart.classloader.RestartClassLoader"; | ||
} | ||
|
||
@Override | ||
public void methodAdvice(MethodTransformer transformer) { | ||
transformer.applyAdvice(isConstructor(), getClass().getName() + "$RecordNewClassloaderAdvice"); | ||
transformer.applyAdvice( | ||
isMethod().and(named("findClass")), getClass().getName() + "$ResetMemoizerAdvice"); | ||
} | ||
|
||
@Override | ||
public Map<ExcludeFilter.ExcludeType, ? extends Collection<String>> excludedClasses() { | ||
return Collections.singletonMap( | ||
RUNNABLE, | ||
Collections.singleton( | ||
"org.springframework.boot.devtools.restart.Restarter$LeakSafeThread")); | ||
} | ||
|
||
public static class RecordNewClassloaderAdvice { | ||
@Advice.OnMethodExit(suppress = Throwable.class) | ||
public static void after(@Advice.This RestartClassLoader self) { | ||
InstrumentationContext.get(RestartClassLoader.class, Boolean.class).putIfAbsent(self, true); | ||
} | ||
} | ||
|
||
public static class ResetMemoizerAdvice { | ||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static void before(@Advice.This RestartClassLoader self) { | ||
if (Boolean.TRUE.equals( | ||
InstrumentationContext.get(RestartClassLoader.class, Boolean.class).remove(self))) { | ||
Memoizer.resetState(); | ||
} | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
dd-java-agent/instrumentation/spring-boot/src/test/groovy/RestartClassLoaderTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import datadog.trace.agent.test.AgentTestRunner | ||
import datadog.trace.agent.test.utils.TraceUtils | ||
import org.apache.commons.io.IOUtils | ||
import org.springframework.asm.ClassReader | ||
import org.springframework.asm.ClassVisitor | ||
import org.springframework.asm.ClassWriter | ||
import org.springframework.asm.MethodVisitor | ||
import org.springframework.asm.Opcodes | ||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile | ||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles | ||
import org.springframework.boot.devtools.restart.classloader.RestartClassLoader | ||
|
||
class RestartClassLoaderTest extends AgentTestRunner { | ||
def 'should instrument reloaded classes'() { | ||
given: | ||
def repository = new ClassLoaderFiles() | ||
def parent = new URLClassLoader(new URL[]{ | ||
GroovyObject.class.getProtectionDomain().getCodeSource().getLocation() | ||
}, (ClassLoader)null) | ||
def cl = new RestartClassLoader(parent, | ||
new URL[] { | ||
TestBean.class.getProtectionDomain().getCodeSource().getLocation() | ||
}, | ||
repository) | ||
when: | ||
TestBean.test() | ||
then: | ||
assertTraces(0, {}) | ||
when: | ||
def content = IOUtils.toByteArray(new URL(TracingBean.class.getProtectionDomain().getCodeSource().getLocation().toString() + "TracingBean.class")) | ||
// We need to inject a different bytecode (the one of TracingBean) by emulating it was in TestBean like if TestBean has been recompiled on the fly and swapped. | ||
// One option is to kind of cheat is to quickly manipulate the TracingBean bytecode to have a type name change | ||
ClassReader reader = new ClassReader(content) | ||
ClassWriter writer = new ClassWriter(0) | ||
ClassVisitor transformer = new ClassVisitor(Opcodes.ASM5, writer) { | ||
@Override | ||
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
super.visit(version, access, "TestBean", signature, superName, interfaces) | ||
} | ||
|
||
@Override | ||
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { | ||
return super.visitMethod(access, name, desc, signature, exceptions) | ||
} | ||
} | ||
reader.accept( transformer, 0) | ||
repository.addFile("TestBean.class", new ClassLoaderFile(ClassLoaderFile.Kind.MODIFIED, writer.toByteArray())) | ||
cl.loadClass(TestBean.class.getName()).getMethod("test").invoke(null) | ||
then: | ||
assertTraces(1, { | ||
trace(1) { | ||
TraceUtils.basicSpan(it, "trace.annotation","TestBean.test",null, null, ["component":"trace"] ) | ||
} | ||
}) | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
dd-java-agent/instrumentation/spring-boot/src/test/java/TestBean.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
public class TestBean { | ||
|
||
public static void test() {} | ||
} |
6 changes: 6 additions & 0 deletions
6
dd-java-agent/instrumentation/spring-boot/src/test/java/TracingBean.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import datadog.trace.api.Trace; | ||
|
||
public class TracingBean { | ||
@Trace | ||
public static void test() {} | ||
} |