Jo_Nathan
Jo_Nathan

Reputation: 35

Tomcat update java file at runtime

I am building a dynamic Web Project (in Eclipse with Tomcat as server) using servlets and JSPs. The general purpose of the project is to let the user solve small code problems. In order to do that I take a method written by the user and use it to build a java file that I can run via Reflection. The problem I can't seem to figure out is that Tomcat (or Eclipse?) does not update the file at runtime. So when I create the file using the code of the current user and try to compile it, my program always executes the file as it was when I started the server using the code of the previous user. How can I tell it to update the file before running it?

Edit: That's how I create the file:

public boolean writeFile() {
    try {
        PrintWriter writer = new PrintWriter(relativePath + "src\\testfiles\\TestFile.java");
        writer.print(content);
        writer.close();
        return true; }...

Here I call the writer and try running the file:

FileWriter writer = new FileWriter(content);        
    if(writer.writeFile()){
        Class<?> TestFile;
        Method m;
        try {
            TestFile = cl.loadClass("testfiles.TestFile");
            m = TestFile.getDeclaredMethod("testSolution");
            m.invoke(null);

Thanks in advance!

Upvotes: 0

Views: 404

Answers (1)

kaqqao
kaqqao

Reputation: 15489

Ok, it's now clear what the issue is. Your issue is not with Tomcat not reloading the file, but with the classloader not reloading the class.

Normal classloaders will only load a class once, and keep it cached forever. The only way for a class to get unloaded is by its classloader being garbage collected. To reload a class you either have to use a different classloader each time (with the previous one getting garbage collected or you'll run out of memory), or to have a custom loader thar doesn't cache.

See this article for an implementation of a custom classloader that does what you want.

You could theoretically just have a new class each time (by changing its name on each save) but you'd eventually run out of memory.

Maybe the easiest way is to instantiate a new classloader in the method itself, load a class, run the method on it, and don't keep any other references to the loader or the class. That way, you'll get a freshly loaded class each time, and the old instances of both classes and loaders will get garbage collected.

UPDATE: I was operating under the assumption that you already know how to compile a class at runtime but, based on the comments, that's not the case. A classloader can, of course, only load a compiled class, so a source directly is not very useful.

Java internally provides a a compiler interface under javax.tools.JavaCompiler, but it's not easy to use and requires a different handling of Java versions before and after Java 9. So it is much easier to use a library like jOOR that hides the gory parts:

Class clazz = Reflect.compile("com.example.Test",
                "package com.example;" +
                "public class Test {\n" +
                "        public String hello() {\n" +
                "            return \"hello\";\n" +
                "        }\n" +
                "    }")
              .type();

Instead of type() to simply get the class, you can actually keep using jOOR's fluent reflection API to trigger the methods on the generated class or whatever it is you'd normally do via regular reflection.

For direct JavaCompiler usage, see e.g. this or, even better, jOOR's source code.

Upvotes: 1

Related Questions