raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.28k stars 807 forks source link

Issue while loading custom class to Bootstrap class loader #1442

Closed Chaho12 closed 1 month ago

Chaho12 commented 1 year ago

HI, I have issue with class loading where it cannot find certain libraries.

2023-05-22T06:14:15.771Z    ERROR   dispatcher-query-0      io.trino.dispatcher.LocalDispatchQuery  query submitter threw exception
java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper
    at com.naver.trino.hook.esSender.<clinit>(esSender.java:24)
    at io.trino.plugin.hive.HivePartitionManager.canPartitionsBeLoaded(HivePartitionManager.java)
    at io.trino.plugin.hive.HivePartitionManager.applyPartitionResult(HivePartitionManager.java:165)
public class HookAgent {
    public static void premain(String agentArguments, Instrumentation instrumentation) {
        File configFile = new File(agentArguments);
        configFile = configFile.getAbsoluteFile();
        Map<String, String> properties = loadEventListenerProperties(configFile);
        String props = Joiner.on(",").withKeyValueSeparator("=").join(properties);

        File temp = null;
        try {
            temp = Files.createTempDirectory("tmp").toFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ClassInjector.UsingInstrumentation
                .of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation)
                .injectRaw(ImmutableMap.<String, byte[]>builder()
                        .put(HivePartitionManagerHookAdvice.class.getName(), ClassFileLocator.ForClassLoader.read(HivePartitionManagerHookAdvice.class))
                        .put(esSender.class.getName(), ClassFileLocator.ForClassLoader.read(esSender.class))
                        .build()
          );
          new AgentBuilder.Default()
                .type(ElementMatchers.named("io.trino.plugin.hive.HivePartitionManager"))
                .transform((builder, typeDescription, classLoader, module, protectionDomain) -> builder
                        .method(ElementMatchers.named("canPartitionsBeLoaded"))
                        .intercept(Advice.withCustomMapping().bind(AgentArguments.class, props).to(HivePartitionManagerHookAdvice.class)))
                .installOn(instrumentation);

Where esSender.java:24 has error at private static final ObjectMapper jackson = new ObjectMapper();.

Any ideas or comments would be helpful on finding out why it saids NoClassDefFoundError

raphw commented 1 year ago

You would need to inject every single linked class. That is not normally recommended. What are you trying to do?

Chaho12 commented 1 year ago

You would need to inject every single linked class. That is not normally recommended. What are you trying to do?

Yeah that was the issue. I had to add every single linked class, which is seemly impossible as Jackson is too big.

What I wanted to do was, create a separate class file(esSender.java) that sends data to elastic search. Within esSender, we use Jackson to create message string to send to ES.

raphw commented 1 year ago

create an abstract class with a public static field of itself. set that field with a subclass that is not injected. only use jvm types in your abstract class.

Chaho12 commented 1 year ago

only use jvm types in your abstract class.

Hmm.. I don't quite understand this part. is there any links that you can share what you mean by jvm types?

raphw commented 1 year ago

types the the java package.

tirerocket commented 1 year ago

create an abstract class with a public static field of itself. set that field with a subclass that is not injected. only use jvm types in your abstract class.

@raphw, do you mind describing this design pattern in more detail? I'm trying to solve the same problem.

raphw commented 1 year ago

You would create a class like this:

public abstract class Dispatcher {
  public static Dispatcher dispatcher;
  public abstract void doDispatch();
}

Once on the boot loader, you can implement this dispatcher from your own class loader and set an instance as the field value. You then callback the method via this field.

tirerocket commented 1 year ago

@raphw thanks for the quick response. How would I access my own class loader from the Agent to create the Dispatcher implementation?

This seems incorrect:

Dispatcher.dispatcher = (Dispatcher) Class.forName("agent.DispatcherImpl", false, customClassLoader) .newInstance();

tirerocket commented 1 year ago

This is what I receive when I do the above:

java.lang.ClassCastException: class agent.DispatcherImpl cannot be cast to class agent.Dispatcher (agent.DispatcherImpl is in unnamed module of loader 'app'; agent.Dispatcher is in unnamed module of loader 'bootstrap')

tirerocket commented 1 year ago

Nevermind - I see what you mean now. Just gotta play with the classloaders. Thanks!

xuhuanzy commented 1 month ago

Nevermind - I see what you mean now. Just gotta play with the classloaders. Thanks!

Could you share it? How do I inject into the dispatcher? I tried to implement my own classloader Dispatcher, but when setting it up, I got an error saying their class loaders are different.