user1793318
user1793318

Reputation: 223

Custom ClassLoader with JBoss

I am trying to compile and load a class dynamically during runtime using the JavaCompiler API. I store the compiled bytecode in memory. So I use a custom class loader to load the class.

public class CompilerAPITest {

    static String sourceCode = "package in.test;" +
    "public class DynamicCompilationHelloWorld implements TestInterface{" +
        "public void test (){" +
            "System.out.println (\"Hello, dynamic compilation world!\");" +
            "new in.test.another.SomeClass().fun();" + 
        "}" +
    "}" ;

    public void doCompilation (){
    SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject ("in.test.DynamicCompilationHelloWorld", sourceCode) ;
    JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject} ;

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    JavaFileManager stdFileManager = new
            CompilerAPITest.ClassFileManager(compiler
                .getStandardFileManager(null, null, null));

    Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

            List<String> options = Arrays.asList("-cp", System.getProperty("java.class.path")
            + ":" + getPath(CompilerAPITest.class));

    CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, options, null, compilationUnits) ;
    boolean status = compilerTask.call();

    if (!status){//If compilation error occurs
        /*Iterate through each compilation problem and print it*/
        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()){
            System.out.format("Error on line %d in %s", diagnostic.getLineNumber(), diagnostic);
        }
    }
    try {
        stdFileManager.close() ;//Close the file manager
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        stdFileManager.getClassLoader(null)
                .loadClass("in.test.DynamicCompilationHelloWorld").asSubclass(TestInterface.class).newInstance().test();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        //This does nothing.
    } catch (InstantiationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

public static void main(String args[]){
    new CompilerAPITest ().doCompilation() ;
}

class DynamicJavaSourceCodeObject extends SimpleJavaFileObject{
    private String qualifiedName ;
    private String sourceCode ;

    protected DynamicJavaSourceCodeObject(String name, String code) {
        super(URI.create("string:///" +name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
        this.qualifiedName = name ;
        this.sourceCode = code ;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors)
            throws IOException {
        return sourceCode ;
    }

    public String getQualifiedName() {
        return qualifiedName;
    }

    public void setQualifiedName(String qualifiedName) {
        this.qualifiedName = qualifiedName;
    }

    public String getSourceCode() {
        return sourceCode;
    }

    public void setSourceCode(String sourceCode) {
        this.sourceCode = sourceCode;
    }

}

private static class ClassFileManager extends
ForwardingJavaFileManager<JavaFileManager> {
    private JavaClassObject jclassObject;
    public ClassFileManager(StandardJavaFileManager
            standardManager) {
        super(standardManager);
    }
    @Override
    public ClassLoader getClassLoader(Location location) {
        return new java.security.SecureClassLoader() {
            @Override
            protected Class<?> findClass(String name)
                    throws ClassNotFoundException {
                byte[] b = jclassObject.getBytes();
                return super.defineClass(name, jclassObject
                        .getBytes(), 0, b.length);
            }
        };
    }

    @Override
    public JavaFileObject getJavaFileForOutput(Location location,
            String className, Kind kind, FileObject sibling)
                    throws IOException {
        jclassObject = new JavaClassObject(className, kind);
        return jclassObject;
    }
}

private static class JavaClassObject extends SimpleJavaFileObject {
    protected final ByteArrayOutputStream bos =
            new ByteArrayOutputStream();
    public JavaClassObject(String name, Kind kind) {
        super(URI.create("string:///" + name.replace('.', '/')
                + kind.extension), kind);
    }
    public byte[] getBytes() {
        return bos.toByteArray();
    }
    @Override
    public OutputStream openOutputStream() throws IOException {
        return bos;
    }
  }
}

This works fine when run in a standalone setup. However when i call doCompilation() in my production setup, which runs on JBoss, i get the following exception.

java.lang.NoClassDefFoundError: 
    in/test/TestInterface(wrong name: 
    in/test/DynamicCompilationHelloWorld)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:621)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:466)
    at in.test.CompilerAPITest$ClassFileManager$1.findClass(CompilerAPITest.java:126)

What could be the problem here?

Upvotes: 0

Views: 869

Answers (1)

user1793318
user1793318

Reputation: 223

Found the issue after some googling.

Parent class loader needs to be set for the custom class loader used.

Class loading hierarchy is slightly different in JBoss. Here UnifiedClassLoader is used, which is used for checking even the peer class loaders, in addition to the parents, before throwing ClassNotFoundException. So when a custom class loader is used, it needs to delegate the defineClass calls to the UnifiedClassLoader, when it can not load the class.

Here is an example code snippet for a custom class loader.

private static class ByteClassLoader extends ClassLoader{
    private Map<String, JavaFileObject> store = new HashMap<String, JavaFileObject>();
    public ByteClassLoader(Map<String, JavaFileObject> str)
     {
     super( ByteClassLoader.class.getClassLoader() ); // set parent
     store = str;
    }

    protected Class<?> findClass(String name)
            throws ClassNotFoundException{
        JavaFileObject jfo = store.get(name);
        if (jfo == null){
            throw new ClassNotFoundException(name);
        }

        byte[] bytes = ((JavaClassObject)jfo).getBytes();
        Class<?> cl = defineClass(name, bytes, 0, bytes.length);
        if (cl == null){
            throw new ClassNotFoundException(name);
        }
        return cl;
    }
}

And for loading the class,

ByteClassLoader cl = new ByteClassLoader(store);
cl.loadClass(className);

needs to be used.

Here is an excellent link on dynamic compilation and class loading. http://fivedots.coe.psu.ac.th/~ad/jg/javaArt1/onTheFlyArt1.pdf

Upvotes: 1

Related Questions