-
-
Save raphw/34c0e2fffe2ee7b4f02f to your computer and use it in GitHub Desktop.
package net.bytebuddy; | |
import net.bytebuddy.agent.ByteBuddyAgent; | |
import net.bytebuddy.agent.builder.AgentBuilder; | |
import net.bytebuddy.description.type.TypeDescription; | |
import net.bytebuddy.dynamic.ClassFileLocator; | |
import net.bytebuddy.dynamic.DynamicType; | |
import net.bytebuddy.dynamic.loading.ClassInjector; | |
import net.bytebuddy.implementation.MethodDelegation; | |
import net.bytebuddy.implementation.bind.annotation.SuperCall; | |
import net.bytebuddy.matcher.ElementMatchers; | |
import java.io.File; | |
import java.lang.instrument.Instrumentation; | |
import java.net.HttpURLConnection; | |
import java.net.URL; | |
import java.nio.file.Files; | |
import java.util.Collections; | |
import java.util.concurrent.Callable; | |
public class BootstrapAgent { | |
public static void main(String[] args) throws Exception { | |
premain(null, ByteBuddyAgent.install()); | |
HttpURLConnection urlConnection = (HttpURLConnection) new URL("http://www.google.com").openConnection(); | |
System.out.println(urlConnection.getRequestMethod()); | |
} | |
public static void premain(String arg, Instrumentation inst) throws Exception { | |
File temp = Files.createTempDirectory("tmp").toFile(); | |
ClassInjector.UsingInstrumentation.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, inst).inject(Collections.singletonMap( | |
new TypeDescription.ForLoadedType(MyInterceptor.class), | |
ClassFileLocator.ForClassLoader.read(MyInterceptor.class).resolve())); | |
new AgentBuilder.Default() | |
.ignore(ElementMatchers.nameStartsWith("net.bytebuddy.")) | |
.enableBootstrapInjection(temp, inst) | |
.type(ElementMatchers.nameEndsWith(".HttpURLConnection")) | |
.transform(new AgentBuilder.Transformer() { | |
@Override | |
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) { | |
return builder.method(ElementMatchers.named("getRequestMethod")).intercept(MethodDelegation.to(MyInterceptor.class)); | |
} | |
}).installOn(inst); | |
} | |
public static class MyInterceptor { | |
public static String intercept(@SuperCall Callable<String> zuper) throws Exception { | |
System.out.println("Intercepted!"); | |
return zuper.call(); | |
} | |
} | |
} |
There is the tutorial but the documentation needs improvement, indeed.
You would probably want to use .disableClassFormatChanges()
rather then .with(Implementation.Context.Disabled.Factory.INSTANCE)
.
Actually, I test this casecase1, but it don't work. so, I use another onecase2.
In my question, threadlocal is also loaded by a separate classloaders, which is bootstrap class loader, before ByteBuddyAgent.install.
I know case1 did work in follow example, but it does not work with threadlocal, maybe id loaded by bootstrap class loader.
public void test(){
Foo foo = new Foo();
ByteBuddyAgent.install();
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.type(is(Foo.class))
.transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
return builder.visit(Advice.to(MyAdvice.class).on(named("m")));
}
})
.installOnByteBuddyAgent();
foo.m();
}
class Foo {
String m() { return "foo"; }
}
print:
interceptor foo
Hi I was trying this sample but with the mentioned version I am getitng a compilation error in the below line
ClassInjector.UsingInstrumentation.Target.BOOTSTRAP
As it seems like Target is private but in the documentation its stil saying its public. So I am confused here. Can you please help me here.
It's public and it's supposed to work. What does the compiler say?
You are likely referencing the lower-case field target, not the Target class with a capital T.
No I am using the Target with Capital T as given below as given in this agent class example; the code given below
ClassInjector.UsingInstrumentation
.of(tempDirectory, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation)
.inject(Collections.singletonMap(new TypeDescription.ForLoadedType(MyInterceptor.class),
ClassFileLocator.ForClassLoader.read(MyInterceptor.class)));
Eclipse is showing Target cannot be resolved or is not a field. Not sure what I am missing.
It works in my IDE, from any package: ClassInjector.UsingInstrumentation.Target.BOOTSTRAP
.
I am using eclipse and facing there. Anyway, @raphw thanks a lot for your time and reply. I appreciate that. Started using bytebuddy so was exploring sample programs. Do we have any tutorial or any git code examples for byte buddy to try out or play around? that will really help all the new users to get familiar with the library easily.
There's a tutorial on bytebuddy.net. It's slighly outdated but still describes the general idea. Otherwise, there's plenty of material on YouTube.
nice! it's works good!
niubi a, da xiong dei!
Hey @raphw, it's really a good example!
I have meet a problem when I try to intercepts a method of the bootstrap class loader with not bootstrap class, I have reproduce the problem with a demo as below, it seems that the Context
class was loaded by the system class loader again at line 110 and cause that it diff from the one at line 73. how can I get the Context
class loaded by bootstrap loader at line 110?
I am using byte-buddy and byte-buddy-agent v1.10.22
package me.i36.bytebuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
public class Main {
public static void main(String[] args) throws Exception {
premain(null, ByteBuddyAgent.install());
HttpURLConnection urlConnection = (HttpURLConnection) new URL("http://www.github.com").openConnection();
System.out.println(urlConnection.getRequestMethod());
new SystemClassLoaderClass().testMethod();
}
public static void premain(String arg, Instrumentation inst) throws Exception {
File temp = Files.createTempDirectory("tmp").toFile();
Map<TypeDescription.ForLoadedType, byte[]> injectMap = new HashMap<>(2);
injectMap.put(new TypeDescription.ForLoadedType(MyInterceptor.class),
ClassFileLocator.ForClassLoader.read(MyInterceptor.class));
// If I don't inject the Context into the bootsrap loader it cause a
// java.lang.NoClassDefFoundError: me/i36/bytebuddy/Main$Context exception at line 73
injectMap.put(new TypeDescription.ForLoadedType(Context.class),
ClassFileLocator.ForClassLoader.read(Context.class));
ClassInjector.UsingInstrumentation.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, inst).inject(injectMap);
new AgentBuilder.Default()
.ignore(ElementMatchers.nameStartsWith("net.bytebuddy."))
.with(new AgentBuilder.InjectionStrategy.UsingInstrumentation(inst, temp))
.type(ElementMatchers.nameEndsWith(".HttpURLConnection"))
.transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder.method(ElementMatchers.named("getRequestMethod")).intercept(MethodDelegation.to(MyInterceptor.class));
}
})
// intercepts a method of the system class loader.
.type(ElementMatchers.named("me.i36.bytebuddy.Main$SystemClassLoaderClass"))
.transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder.method(ElementMatchers.named("testMethod")).intercept(MethodDelegation.to(SystemClassLoaderClassInterceptor.class));
}
})
.installOn(inst);
}
public static class MyInterceptor {
private MyInterceptor() {}
public static String intercept(@SuperCall Callable<String> zuper) throws Exception {
//Context was loaded by bootstrap class loader
Context.set("X"); // line 73
System.out.println(Thread.currentThread().getName() +
" - Intercepted! set context value to X");
return zuper.call();
}
}
public static class Context {
private Context() {}
private static final ThreadLocal<String> CONTEXT_VALUE = new ThreadLocal<>();
public static void set(String value) {
CONTEXT_VALUE.set(value);
}
public static String get() {
return CONTEXT_VALUE.get();
}
public static void remove() {
CONTEXT_VALUE.remove();
}
}
public static class SystemClassLoaderClass {
public void testMethod() {
System.out.println(Thread.currentThread().getName() +
" - I'm in testMethod");
}
}
public static class SystemClassLoaderClassInterceptor {
public static String intercept(@SuperCall Callable<String> zuper) throws Exception {
//Context was loaded by system class loader cause the Context.get() to null
System.out.println(Thread.currentThread().getName() +
" - Intercepted! context value : " + Context.get()); // line 110
return zuper.call();
}
}
}
Do you have a stack trace? If you are using later then Java 8, you probably have to add adjustReadEdge
to your agent builder API to read the interceptor's module from the boot module as the module system otherwise prevents the access.
@raphw I'am using jdk1.8.0_51
here is the stack when suspend at line 73, the value of evaluate Context.class.getClassLoader()
is null
(the bootstrap class loader)
intercept:73, Main$MyInterceptor (me.i36.bytebuddy) [2]
getRequestMethod:-1, HttpURLConnection (java.net)
getRequestMethod$accessor$3jEUJIja:-1, HttpURLConnection (sun.net.www.protocol.http)
call:-1, HttpURLConnection$auxiliary$USarPSaH (sun.net.www.protocol.http)
intercept:77, Main$MyInterceptor (me.i36.bytebuddy) [1]
getRequestMethod:-1, HttpURLConnection (sun.net.www.protocol.http)
main:28, Main (me.i36.bytebuddy)
and below is the stack when suspend at line 110, the value of Context.class.getClassLoader().toString()
is sun.misc.Launcher$AppClassLoader@14dad5dc
( the system class loader)
intercept:110, Main$SystemClassLoaderClassInterceptor (me.i36.bytebuddy)
testMethod:-1, Main$SystemClassLoaderClass (me.i36.bytebuddy)
main:30, Main (me.i36.bytebuddy)
Ah, the Context
class must be injected into the boot loader, too. Otherwise, it is not available to boot classes.
But I have already injected the Context
class into the boot loader.
Why can't I get the Context
class loaded by the boot loader in the SystemClassLoaderClassInterceptor class ?
(on line 110, the class loader of Context
class is not the boot loader)
Ah, I figure it out, I can get the Context value now, : )
public static class SystemClassLoaderClassInterceptor {
public static String intercept(@SuperCall Callable<String> zuper) throws Exception {
Class<?> contextClazz = Class.forName("me.i36.bytebuddy.Main$Context", true, null);
Method method = contextClazz.getMethod("get");
method.setAccessible(true);
System.out.println(Thread.currentThread().getName() +
" - Intercepted! context value : " + method.invoke(contextClazz)); // the value X
return zuper.call();
}
}
You probably have the class included in your agent jar and also attach it to the boot loader. This way it will be loaded in the system loader before it is attached to boot and therefore be loaded twice. With different class loaders, the two classes are different. Try not including it as a ".class" file in the boot loader.
You probably have the class included in your agent jar and also attach it to the boot loader. This way it will be loaded in the system loader before it is attached to boot and therefore be loaded twice. With different class loaders, the two classes are different. Try not including it as a ".class" file in the boot loader.
how can I try not to including it as a ".class" file in the boot loader ?
Have a look at ClassInjector
and Instrumentation
which both offer APIs for that.
Have a look at
ClassInjector
andInstrumentation
which both offer APIs for that.
OK, thanks raphw.
Hello I tested the following code for JDK22 and the intercepted method didn`t work, is there anything missing to work on jdk 22?
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.concurrent.Callable;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.AgentBuilder.InjectionStrategy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
public class BootstrapAgent {
public static void main(String[] args) throws Exception {
premain(null, ByteBuddyAgent.install());
HttpURLConnection urlConnection = (HttpURLConnection) new URL("http://www.google.com").openConnection();
System.out.println(urlConnection.getRequestMethod());
}
public static void premain(String arg, Instrumentation inst) throws Exception {
File temp = Files.createTempDirectory("tmp").toFile();
ClassInjector.UsingInstrumentation.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, inst).inject(Collections.singletonMap(
new TypeDescription.ForLoadedType(MyInterceptor.class),
ClassFileLocator.ForClassLoader.read(MyInterceptor.class)));
new AgentBuilder.Default()
.ignore(ElementMatchers.nameStartsWith("net.bytebuddy."))
.with(new InjectionStrategy.UsingInstrumentation(inst,temp))
.type(ElementMatchers.nameEndsWith(".HttpURLConnection"))
.transform(new AgentBuilder.Transformer() {
@Override
public Builder<?> transform(Builder<?> arg0, TypeDescription arg1, ClassLoader arg2,
JavaModule arg3, ProtectionDomain arg4) {
return arg0.method(ElementMatchers.named("getRequestMethod")).intercept(MethodDelegation.to(MyInterceptor.class));
}
}).installOn(inst);
}
public static class MyInterceptor {
public static String intercept(@SuperCall Callable<String> zuper) throws Exception {
System.out.println("Intercepted!");
return zuper.call();
}
}
}
changing the code for another class that is not on jdk base modules the interceptor worked fine.
public static void main(String[] args) throws Exception {
premain(null, ByteBuddyAgent.install());
var urlConnection = new Clazze();
urlConnection.printValue("hello");
}
public static void premain(String arg, Instrumentation inst) throws Exception {
File temp = Files.createTempDirectory("tmp").toFile();
ClassInjector.UsingInstrumentation.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, inst).inject(Collections.singletonMap(
new TypeDescription.ForLoadedType(MyInterceptor.class),
ClassFileLocator.ForClassLoader.read(MyInterceptor.class)));
new AgentBuilder.Default()
.ignore(ElementMatchers.nameStartsWith("net.bytebuddy."))
.with(new InjectionStrategy.UsingInstrumentation(inst,temp))
.type(ElementMatchers.nameEndsWith("Clazze"))
.transform(new AgentBuilder.Transformer() {
@Override
public Builder<?> transform(Builder<?> arg0, TypeDescription arg1, ClassLoader arg2,
JavaModule arg3, ProtectionDomain arg4) {
return arg0.method(ElementMatchers.named("printValue")).intercept(MethodDelegation.to(MyInterceptor.class));
}
}).installOn(inst);
}
public static class Clazze {
public void printValue(String value) {
System.err.println(value);
}
}
public static class MyInterceptor {
public static String intercept(@SuperCall Callable<String> zuper) throws Exception {
System.out.println("Intercepted!");
return zuper.call();
}
}
The boot class cannot see your interceptor class, as it is a parent loader to your agent.
Have a look at Advice instead.
Thanks for the advice,
My main issue now regards to SpringBoot and classes that I defined using bybuddy, I trying to mitigate the problem,
SpringData uses a class to scan the class for Resources to locate the classes that extends the Repository Interface,
Using the classloader getResource i locate the bytebuddy defined class so, on my understanding from class construction perspectiveall is fine.
I`ve already did some experiments (with sucess) using byte-buddy to construct an EntityModel for Hibernate and a Repository Model for spring data with success, but i had to define the SpringData beans manually because the AutoConfiguration does not locate the classes defined by ByteByddy. I can locate the Classes using Class.forName(bytebuddyClass) but Spring does not locate the classes and both Spring classes and ByteBuddy defined classes are in the classloader.. Anyhow if i don't find the solution will continue to work with what is already working.
Regards.
Found the problem
The Class.forName works fine.
but both methods bellow don`t
Object clazz = org.nanotek.config.spring.BootstrapAgent.class.getClassLoader()
.getResourceAsStream("org/nanotek/config/spring/repositories/SimpleTableRepository.class");
System.err.println("clazz "+clazz);
Object resource = org.nanotek.config.spring.BootstrapAgent.class.getClassLoader()
.getResource("org/nanotek/config/spring/repositories/SimpleTableRepository.class");
System.err.println("resource "+resource);
Now, I understood that a "ClassLoader" need to be used to override the methods that SpringData uses to scan the packages,
and also "register" the instances of DynamicTypes manually but, using the sequence of registration used by the framework.
yes,I solve it. My code is as follows:
print:
I am not very clear how to do it, but it does work.
could you recommend a few books or papers about how to use bytebuddy, I want to learn more about it, but source code is too hard to read. Thank you.