Skip to content

Instantly share code, notes, and snippets.

@raphw
Last active March 6, 2025 18:06
Show Gist options
  • Save raphw/34c0e2fffe2ee7b4f02f to your computer and use it in GitHub Desktop.
Save raphw/34c0e2fffe2ee7b4f02f to your computer and use it in GitHub Desktop.
An example agent that intercepts a method of the bootstrap class loader.
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();
}
}
}
@raphw
Copy link
Author

raphw commented May 22, 2022

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.

@i36lib
Copy link

i36lib commented Jun 18, 2022

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 ?

@raphw
Copy link
Author

raphw commented Jun 19, 2022

Have a look at ClassInjector and Instrumentation which both offer APIs for that.

@i36lib
Copy link

i36lib commented Jun 20, 2022

Have a look at ClassInjector and Instrumentation which both offer APIs for that.

OK, thanks raphw.

@JoseCanova
Copy link

JoseCanova commented Mar 5, 2025

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();
        }
    }
}

@JoseCanova
Copy link

the method have thrown the following exception on debug mode.

image

@JoseCanova
Copy link

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();
        }
    }

@raphw
Copy link
Author

raphw commented Mar 5, 2025

The boot class cannot see your interceptor class, as it is a parent loader to your agent.

Have a look at Advice instead.

@JoseCanova
Copy link

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.

@JoseCanova
Copy link

JoseCanova commented Mar 6, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment