Search code examples
javaspring-bootdynamicclassloaderjava-compiler-api

How can I change classloader of getSystemJavaCompiler


I am dynamically compiling Java sources using the Java compiler API. My generated source files inherit from com.example.BaseClass, which is just a normal class, not dynamically generated. The generated Java sources look like this:

public class Foo implements com.example.BaseClass
{
    @Override
    public Integer getAnswer(com.example.Context context) throws Exception
    {
        return ...;
    }
}

All works fine when running in IDE, but after packaging into a Springboot jar, my com.example.BaseClass is moved to BOOT-INF/classes/com.example.BaseClass. When dynamically compiling I now get:

/Foo.java:1: error: package com.example does not exist 
public class Foo implements com.example.BaseClass
                                       ^

I try to change the classloader of the compiler so that the compiler will search in BOOT-INF/classes.

    ClassLoader before = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(new CustomClassloader(before));
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    Thread.currentThread().setContextClassLoader(before);

However, debugging shows that my CustomClassloader.loadClass(String name) method is never called. More debugging showed that compiler.getClass().getClassloader() returns

java.net.FactoryURLClassLoader@39a5ae48

So, the CustomClassloader is not used by the Compiler instance. How can I get the Compiler to use my CustomClassloader? Better solutions for solving the compiling issue are also welcome ofcourse :-).


Solution

  • There are some oddities about how the java standard compiler does lookups and it doesn't always resolve out of the running class path correctly. Anyway, it does that resolution using the JavaFileManager.list call.

    It will call it at least 4 times in the process of trying to look up your base class. Override a ForwardingJavaFileManager and pass that into getTask and have it lookup the resource and return it.

    Alternately, you could use the Janino in-momeory compiler library which sets up a fake in memory file system ( no compiling to disk ) and still uses the plaform compiler and sorts out all this classpath nonsense for you.