Charlie Lin
Charlie Lin

Reputation: 1633

Is there a way to create class(not instance) dynamically?

I have an issue about how to create class(not instance) dynamically. In my project, I need to write several similar class according to the configuration file. For exmaple, there is a JSON like this:

{                                                                                                                                                                              
    {                                                                                                                                                                          
        "lang": "python",                                                                                                                                                      
        "file": "class1.py",
        "args": ["arg1"]                                                                                                                                  
    },                                                                                                                                                                         
    {                                                                                                                                                                          
        "lang": "python",                                                                                                                                                      
        "file": "class2.py"  
        "args": ["arg2"]                                                                                                                                               
    }                                                                                                                                                                          
} 

Subsequently, I need to write two java class below:

class1:

public class Class1 extends ShellBolt implements IRichBolt {
    public Class1() {
        super("python", "class1.py");
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields(arg1));
    }

    @Override
    public Map<String, Object> getComponentConfiguration() {
        return null;
    }
}

class2:

public class Class2 extends ShellBolt implements IRichBolt {

    public Class2() {
        super("python", "class2.py");
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields(arg2));
    }

    @Override
    public Map<String, Object> getComponentConfiguration() {
        return null;
    }
}

But if the JSON file is added a new object:

{                                                                                                                                                                          
    "lang": "python",                                                                                                                                                      
    "file": "class3.py"   
    "args": ["arg3"]
}

I need to write a new Class with the similar structure. So, is there a way to create class dynamically? I know maybe cglib might works, but I hava no idea how to use it in my case. Thanks.

Upvotes: 5

Views: 270

Answers (3)

Alexey Andreev
Alexey Andreev

Reputation: 2085

Yes, there is a way. You should create your own ClassLoader implementation, override its loadClass method and there call defineClass, providing it with the bytecode. There is no built-in classes to generate bytecode, but you can always use ASM. Then you instantiate you ClassLoader and get your class from it that way:

ClassLoader myClassLoader = new MyClassLoader(ClassLoader.getSystemClassLoader());
Class<?> generatedClass = Class.forName("pkg.GeneratedClass", true, myClassLoader);
Object instance = generatedClass.newInstance();

There is an example of how your loadClass might be implemented:

@Override public void loadClass(String name) {
    if (name.equals("pkg.GeneratedClass")) {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_6, ACC_PUBLIC, "pkg/GeneratedClass", null, "java/lang/Object", null);
        // and so on, use ASM API to complete class generation,
        // see asm documentation
        byte[] bytecode = cw.toByteArray();
        return defineClass(name, bytecode, 0, bytecode.length);
    } else {
        return super.loadClass(name);
    }
}

Upvotes: 0

Alexander Tokarev
Alexander Tokarev

Reputation: 2763

If you really need to create classes in JVM dynamically, consider using dynamic languages such as Groovy.

Another way: if you need to interpret some Python code on JVM - consider using Jython. http://www.jython.org/

There's a danger on creating classes in HotSpot JVM dynamically. If you will create too many dynamic classes, you may run out of PermGen space, and your JVM will crash with OutOfMemory error. However, there's some workarounds about it, such as enabling garbage collection for PermGen via -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled jvm parameters. It is announced than PermGen will be removed in JVM 8.

Upvotes: 1

assylias
assylias

Reputation: 328568

You can use the JavaCompiler to compile any java code to a class file. You can then load the resulting class with a URLClassLoader.

An example is given in the javadocs or for a complete example you can check this question.

Applied to your Class1 it would look like the example below. Note that you need to include all the relevant imports in the code or it won't compile.

public static void main(String[] args) throws Exception {
    String packageName = "some.packagename";
    String className = packageName + ".Class1";
    String body = "package " + packageName + ";   " +
                "public class Class1 extends ShellBolt implements IRichBolt {\n" +
                "    public Class1() {\n" +
                "        super(\"python\", \"class1.py\");\n" +
                "    }\n" +
                "\n" +
                "    @Override\n" +
                "    public void declareOutputFields(OutputFieldsDeclarer declarer) {\n" +
                "        declarer.declare(new Fields(arg1));\n" +
                "    }\n" +
                "\n" +
                "    @Override\n" +
                "    public Map<String, Object> getComponentConfiguration() {\n" +
                "        return null;\n" +
                "    }\n" +
                "}";

    Path classFile = Paths.get(System.getProperty("user.home"));
    compile(className, body, classFile);
    Class<?> class1 = loadClass(className, classFile);

    Method getComponentConfiguration = class1.getDeclaredMethod("getComponentConfiguration");
    Map<String, Object> result = (Map<String, Object>) getComponentConfiguration.invoke(class1.newInstance());
}

private static Class<?> loadClass(String className, Path path) throws Exception {
    URLClassLoader loader = new URLClassLoader(new URL[]{path.toUri().toURL()}, Test1.class.getClassLoader());
    return loader.loadClass(className);
}

private static void compile(String className, String body, Path path) throws Exception {
    List<JavaSourceFromString> sourceCode = Arrays.asList(new JavaSourceFromString(className, body));

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
    fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(path.toFile()));
    boolean ok = compiler.getTask(null, fileManager, null, null, null, sourceCode).call();

    System.out.println("compilation ok = " + ok);
}

public static class JavaSourceFromString extends SimpleJavaFileObject {
    final String code;

    JavaSourceFromString(String name, String code) {
        super(URI.create("string:///" + name.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension),
                JavaFileObject.Kind.SOURCE);
        this.code = code;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return code;
    }
}

Upvotes: 2

Related Questions