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? :)