Skip to main content
1 of 2
Hawk
  • 216
  • 1
  • 6

DynamicClass - loads and instantiates classes at run-time

I needed to dynamically load and instantiate some classes from a generated jar and a generated .class file which depends on that jar. So I came up with this:

package ytm.yajco.translation.util;

import org.jetbrains.annotations.NotNull;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;

import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;

public class DynamicClass {
    protected static final StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
    protected final        Class<?>    loadedClass;

    /**
     * <b>WARNING:</b> Do not call this constructor if {@code jar} has already been loaded.
     * This will avoid multiple class loading.
     * <p>
     * Creates a new class loader which can load classes from {@code jar}.
     * The new classloader inherits the classpath from the class loader of the caller of this constructor.
     * Finally, the sought class is loaded from the given {@code jar}.
     *
     * @param classFQN fully qualified name of the class to be loaded
     * @param jar      path to the jar or root directory where {@code classFQN} is to be found
     * @see StackWalker#getCallerClass()
     */
    public DynamicClass(String classFQN, @NotNull Path jar) throws MalformedURLException, ReflectiveOperationException {
        this(classFQN, jar, walker.getCallerClass().getClassLoader());
    }

    /**
     * <b>WARNING:</b> Do not call this constructor if {@code jar} has already been loaded by {@code parentCL}
     * or its ancestors. This will avoid multiple class loading.
     * <p>
     * Creates a new class loader which can load classes from {@code jar}.
     * The new classloader inherits the classpath from {@code parentCL}.
     * Finally, the sought class is loaded from the given {@code jar}.
     *
     * @param classFQN fully qualified name of the class to be loaded
     * @param jar      path to the jar or root directory where {@code classFQN} is to be found
     * @param parentCL classloader to use as a parent
     */
    public DynamicClass(String classFQN, @NotNull Path jar, ClassLoader parentCL) throws MalformedURLException, ClassNotFoundException {
        final URL[] jarLocation = {jar.toUri().toURL()};
        final URLClassLoader classLoader = URLClassLoader.newInstance(jarLocation, parentCL);
        loadedClass = Class.forName(classFQN, true, classLoader);
    }

    /**
     * Finds the sought class using the given class loader.
     *
     * @param classFQN    fully qualified name of the class to be loaded
     * @param classLoader class loader to reuse
     * @throws ClassNotFoundException if the class cannot be located by the specified class loader
     */
    public DynamicClass(String classFQN, ClassLoader classLoader) throws ClassNotFoundException {
        loadedClass = Class.forName(classFQN, true, classLoader);
    }

    public Class<?> getLoadedClass() {
        return loadedClass;
    }

    public ClassLoader getClassLoader() {
        return loadedClass.getClassLoader();
    }
}

In one of the cases I know the interface statically so in my DynamicSupplier subclass:

public Supplier<?> newSupplierInstance() throws ReflectiveOperationException {
    return (Supplier<?>) loadedClass.getDeclaredConstructor().newInstance();
}

In the other case I know the method signatures, so I have provided methods to which hide the reflective access. Also I wrap the new instance in a small class (maybe an adapter?) just to put an abstract layer when working with a bare Object.

package ytm.yajco.translation.language;

import org.jetbrains.annotations.NotNull;
import ytm.yajco.translation.util.DynamicClass;

import java.io.Reader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.nio.file.Path;

public class DynamicParser extends DynamicClass {

// skipped JavaDoc for brevity in this review
    public DynamicParser(String parserFQN, @NotNull Path parserJar) throws MalformedURLException, ReflectiveOperationException {
        this(parserFQN, parserJar, walker.getCallerClass().getClassLoader());
    }

// skipped other constructors matching super for brevity in this review


    public static Object parseSentence(@NotNull Object parser, String sentence) throws ReflectiveOperationException {
        final Method parse = parser.getClass().getMethod("parse", String.class);
        return parse.invoke(parser, sentence);
    }

    public static Object parseSentence(@NotNull Object parser, Reader sentence) throws ReflectiveOperationException {
        final Method parse = parser.getClass().getMethod("parse", Reader.class);
        return parse.invoke(parser, sentence);
    }


    public Parser newParserInstance() throws ReflectiveOperationException {
        final Object parser = loadedClass.getDeclaredConstructor().newInstance();
        return new Parser(parser);
    }


    public static class Parser {
        private final Object parser;

        private Parser(Object parser) {
            this.parser = parser;
        }

        public Object parseSentence(String sentence) throws ReflectiveOperationException {
            return DynamicParser.parseSentence(parser, sentence);
        }

        public Object parseSentence(Reader sentence) throws ReflectiveOperationException {
            return DynamicParser.parseSentence(parser, sentence);
        }

        public Object getRealParser() {
            return parser;
        }
    }

}

So what do you think about my creation? :)

Hawk
  • 216
  • 1
  • 6