I am developing a wicket application which is running on jetty. My application should be able to load plugins from jar-files.
Thus I created a URLClassLoader which should load the plugin classes.
As a standard Java-application everything works, but when I want to load plugins on the server I get following errors: http://pastebin.com/e2JRAYTr
The Module interface that can not be found is defined in another project, but if I output the class-name in Wickets init() method like this
System.out.println(Module.class.getName())`
I get no error. So I think the other project is loaded correctly by Maven but there are issues with my custom jar-classloader.
The classloaders constructor where the action happens looks like this:
public WorkableLoader(final File jarFile) throws IOException, ClassNotFoundException, InstantiationException,
IllegalAccessException {
final URL[] urls = new URL[] { jarFile.toURI().toURL() };
classLoader = new URLClassLoader(urls);
final Class<?> cls = loadWorkableClass(jarFile);
workable = (Workable) cls.newInstance();
}
private Class<?> loadWorkableClass(final File jar) throws ClassNotFoundException, IOException {
try (final JarFile jarFile = new JarFile(jar)) {
final Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
final JarEntry jarEntry = jarEntries.nextElement();
if (isClassFile(jarEntry.getName())) {
final String className = jarEntry.getName().replace("/", ".").replace(".class", "");
final Class<?> cls = classLoader.loadClass(className);
for (final Class<?> iface : cls.getInterfaces()) {
if (iface.equals(Module.class) || iface.equals(Bundle.class)) {
return cls;
}
}
}
}
} catch (final IOException e) {
throw e;
}
throw new ClassNotFoundException("Es konnte keine Workable-Klasse gefunden werden.");
}
The method isClass() just checks if the file ends with .class.
The last error of my code is at com.siemens.importer.workables.WorkableLoader.loadWorkableClass(WorkableLoader.java:137). Line 137 is the line
final Class<?> cls = classLoader.loadClass(className); of the loadWorkableClass() method. So how can I load classes with a custom classloader on a
jetty server?
Sorry for the huge output and thanks in advance!
Related
I tried to develop multiple webservices using RESTEasy and Jetty. Im planning to make each of the webservice to have its own set of JAR files which will be loaded from certain directory.
What I have done is I create custom ClassLoader like this
public class AppClassLoader extends ClassLoader{
static Logger log = Logger.getLogger(AppClassLoader.class.getName());
String libPath = "";
public AppClassLoader(String libPath) {
this.libPath = libPath;
}
#Override
public Class loadClass(String name) throws ClassNotFoundException {
Class clazz = findLoadedClass(name);
if(clazz == null) {
try {
clazz = ClassLoader.getSystemClassLoader().loadClass(name);
if(clazz == null) {
clazz = getClass(name);
if(clazz == null) {
throw new ClassNotFoundException();
}
}
return clazz;
}catch (ClassNotFoundException e) {
// TODO: handle exception
throw new ClassNotFoundException();
}
}else {
return getSystemClassLoader().loadClass(name);
}
}
private Class<?> getClass(String name) throws ClassNotFoundException {
try {
File dir = new File(this.libPath);
if(dir.isDirectory()) {
for(File jar : dir.listFiles()) {
JarFile jarFile = new JarFile(jar.getPath());
Enumeration<JarEntry> e = jarFile.entries();
URL[] urls = { new URL("jar:file:" + jar.getPath()+"!/") };
URLClassLoader cl = URLClassLoader.newInstance(urls);
while (e.hasMoreElements()) {
JarEntry je = e.nextElement();
if(je.isDirectory() || !je.getName().endsWith(".class")){
continue;
}
String className = je.getName().substring(0,je.getName().length()-6);
className = className.replace('/', '.');
if(className.equals(name)) {
return cl.loadClass(className);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
return null;
}
And then what I did is assign this custom class loader into ServletContextHandler when I initialized Jetty and RestEasy to start the server, like below
final int port = 8080;
final Server server = new Server(port);
// Setup the basic Application "context" at "/".
// This is also known as the handler tree (in Jetty speak).
final ServletContextHandler context = new ServletContextHandler(server, CONTEXT_ROOT);
AppClassLoader classLoader = new AppClassLoader("../apps/dummy/lib");
context.setClassLoader(classLoader);
// Setup RESTEasy's HttpServletDispatcher at "/api/*".
final ServletHolder restEasyServlet = new ServletHolder(new HttpServletDispatcher());
restEasyServlet.setInitParameter("resteasy.servlet.mapping.prefix",APPLICATION_PATH);
restEasyServlet.setInitParameter("javax.ws.rs.Application",App.class.getCanonicalName());
final ServletHolder servlet = new ServletHolder(new HttpServletDispatcher());
context.addServlet(restEasyServlet, APPLICATION_PATH + "/*");
server.start();
server.join();
And then in the jax-rs endpoint I have this code
#Path("/")
public class Dummy {
Logger log = Logger.getLogger(Dummy.class.getName());
#GET
#Path("dummy")
#Produces(MediaType.TEXT_PLAIN)
public String test() {
HikariConfig src = new HikariConfig();
JwtMap jw = new JwtMap();
return "This is DUMMY service : "+src.getClass().getName().toString()+" ### "+jw.getClass().getName();
}}
I managed to start the server just fine, but when I tried to call the webservice, it return
java.lang.NoClassDefFoundError: com/zaxxer/hikari/HikariConfig
And then I see the classLoader used in the thread is not my custom class loader but the java standard class loader.
Which part that I did wrong here? Im so new to this classs loading stuff and I am not sure I literally understand how to used it.
Regards
By Default, ClassLoaders works on Parent first strategy. That means that Classes are searched and loaded through the sequence
Bootstrap Class Loader -> Ext Class Loader -> System Class Loader ->
Custom Class Loader
So, with that approach Dummy Class is loaded using System Class Loader. Now, classes loaded through a ClassLoader only has the visibility to the classes from Parent ClassLoader and not vice versa. So, HikariConfig class is not visible to the Dummy Class. Hence, the exception.
But, you should be able to load a class this way using the ServletContext Classloader which in your case is the Custom ClassLoader.
Inject the Servlet Context in your Dummy class and then
servletContext.getClassLoader().loadClass("com.zaxxer.hikari.HikariConfig");
I have two java projects MASTER and PLUGIN. PLUGIN has dependencies to MASTER and its intent is to extend a class found in MASTER, called SCRIPT.
Once I have declared a SCRIPT (myScript), I want to move the .class file to a folder that MASTER can access. I want MASTER to dynamically load and instantiate that class as a SCRIPT.
I've looked for quite a bit and tried different solutions, but I always get a ClassNotFoundException exception.
I would prefer to do this without passing arguments to the JVM at startup.
Is it even possible? This is my current solution: "currentPath" is "etc/etc/myScript.class
try {
OUT.ln("initiating script " + currentPath);
File file = new File(currentPath);
File parent = file.getParentFile();
String name = file.getName().split(".class")[0];
// Convert File to a URL
URL url = parent.toURI().toURL();
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
#SuppressWarnings("resource")
ClassLoader cl = new URLClassLoader(urls);
current = (SCRIPT) cl.loadClass("main.script." + name).newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script " + currentPath);
}
if the class you want to load is defined within a package like:
main.script.myScript
and you want to load this class from a folder like c:/myclasses,
then you have to put this class to c:/myclasses/main/script/myScript.class
and then instantate the classloader with the basefolder like:
URL[] urls = new URL[]{new URL("file://c:/myclasses")};
ClassLoader cl = new URLClassLoader(urls);
then the class can be loaded by using the qualified class name:
cl.loadClass("main.script.myScript").getDeclaredConstructor().newInstance()
if you want to keep the class at a specific folder without considering the package structure you could do something like this:
public static void main(String[] args) {
try {
File file = new File("etc/etc/myScript.class");
String className = file.getName().split(".class")[0];
String packageName = "main.script.";
byte[] bytes = Files.readAllBytes(Path.of(file.getPath()));
MyClassLoader myClassLoader = new MyClassLoader(Thread.currentThread().getContextClassLoader());
Object o = myClassLoader.getClass(packageName+className, bytes).getDeclaredConstructor().newInstance();
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script ");
}
}
public static class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> getClass(String name, byte[] code) {
return defineClass(name, code, 0, code.length);
}
}
So, I have a main application that should load a jar that contains code and other resources (i.e.: jar files, text files, java properties etc.). I use:
JarFile jar = new JarFile(jar);
Enumeration<JarEntry> entries = jar.entries();
int URLsize = 1;
while (entries.hasMoreElements())
if (entries.nextElement().getName().startsWith("foo/bar/foobar"))
URLsize++;
entries = jar.entries();
URL[] urls = new URL[URLsize];
urls[0] = patch.toURI().toURL();
int count = 1;
while (entries.hasMoreElements())
{
JarEntry nextElement = entries.nextElement();
if (nextElement.getName().startsWith("foo/bar/foobar"))
{
urls[count] = new URL("jar:file:/"+ jar.getAbsolutePath() + "!/" + nextElement.getName());
count++;
}
}
to load the resources of the jar, and an URLClassLoader plus some reflection to get all resources together and execute the jar's main class, like this:
URLClassLoader loader = new URLClassLoader (urls);
Thread.currentThread().setContextClassLoader(loader);
Class<?> jarC = Class.forName ("foo.bar.barfoo.Main", true, loader);
Constructor<?> cons = jarC.getConstructor(String.class, String.class, Properties.class, Properties.class, String[].class);
Object instance = cons.newInstance (systemPath, programPath, config, some_data, args);
Method method = jarC.getMethod ("Main");
method.invoke (instance);
Now the problem is that inside the loaded jar's code when I try to load a bunch of files (resources) from a package inside the jar (e.g.: /foo/bar/foobar) it throws a NullPointerException.
private static InputStream getResourceAsStream(String resource) {
try {
return Thread.currentThread().getContextClassLoader().getResource(resource).openStream();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
That's how I try to get the package that than gets parsed with a BufferedReader and an InputStreamReader to get the names of each resource inside the package.
Okay, maybe a bit too detailed (this is just one way I use the getResourceAsStream method), but I hope I made myself understood, the ContextClassLoader doesn't contain the resources I loaded in the application that runs this jar within itself, so what do I need to do to get those from within the loaded jar?
EDIT: Calling the getResourceAsStream method:
private static List<String> getResourceFiles(String path) throws IOException {
List<String> filenames = new ArrayList<>();
try (
InputStream in = getResourceAsStream(path);
BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
String resource;
while ((resource = br.readLine()) != null) {
filenames.add(resource);
}
}
return filenames;
}
And where the getResourceFiles method is called:
List<String> names = Foo.getResourceFiles("/foo/bar/foobar");
Why do you even do all this? Why not just add URL to JAR to the URLClassLoader?
E.g.
URLClassLoader loader = new URLClassLoader(new File(jar).toURI().toURL());
Also you should probably make that URLClassLoader have your current classloader as parent, e.g.
URLClassLoader loader = new URLClassLoader(new File(jar).toURI().toURL(), Thread.currentThread().getContextClassLoader());
I need to read a Spring Boot jar and load all the clases on a ClassLoader.
My problem,in spring boot classes are on "/BOOT-INF/classes" directory and not on the root directory.
Anybody knows how i can load this classes in my ClassLoader?
I try to do this:
private URLClassLoader getURLClassLoaderFromJar(Path jarPath) throws MalformedURLException {
return URLClassLoader
.newInstance(new URL[] { new URL("jar:file:" + jarPath.toAbsolutePath().toString() + "!/") });
}
This load the jar, but no the classes inside /BOOT-INF/classes
Investigating a bit how spring boot loads third party(Liquibase) classes, i would go like this:
Given that you know the package name you want to load
Resource[] scan(ClassLoader loader, String packageName) throws IOException {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
loader);
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class";
Resource[] resources = resolver.getResources(pattern);
return resources;
}
void findAllClasses(String packageName, ClassLoader loader) throws ClassNotFoundException {
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
loader);
try {
Resource[] resources = scan(loader, packageName);
for (Resource resource : resources) {
MetadataReader reader = metadataReaderFactory.getMetadataReader(resource);
ClassUtils.forName(reader.getClassMetadata().getClassName(), loader)
}
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
Also use your class loader with loaded jar:
findAllClasses("com.package", getURLClassLoaderFromJar(pathToJar));
This variant is safe to use with Spring Boot packaged
executable JARs
I finally opted for decompress de jar on a temporary directory and create a URLClassloader with this entries:
One to the root directory.
One to the BOOT-INF/classes
And one for every jar in BOOT-INT/lib
Path warDirectory = decompressWar(absolutePathFile);
File rootDir = new File(warDirectory.toAbsolutePath().toString());
File springBootDir = new File(warDirectory.toAbsolutePath().toString() + "/BOOT-INF/classes/");
List<URL> listaURL = new ArrayList<URL>();
listaURL.add(rootDir.toURI().toURL());
listaURL.add(springBootDir.toURI().toURL());
//This scan the BOOT-INF/lib folder and return a List<URL> with all the libraries.
listaURL.addAll(getURLfromSpringBootJar(warDirectory));
URL[] urls = new URL[listaURL.size()];
urls = listaURL.toArray(urls);
cl = new URLClassLoader(urls);
//This explore the JAR and load all the .class fulies to get the className.
resultClassesBean = loadJars(Collections.singletonList(pathJarFile), cl);
if(resultClassesBean != null && resultClassesBean.getListResultClasses() != null && !resultClassesBean.getListResultClasses().isEmpty()) {
for(String clazz : resultClassesBean.getListResultClasses()) {
cl.loadClass(clazz);
}
}
It's a project downloaded from Web and I just changed the path to find the class which I want to make it load dynamically.Here are the codes in which I try to load the class on runtime.But I got a ClassNotFoundException eventually:
private static IExample newInstanceWithThrows() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
URLClassLoader tmp =
new URLClassLoader(new URL[] {getClassPath()}) {
public Class<?> loadClass(String name)
throws ClassNotFoundException {
if ("example.Example".equals(name)
|| "example.Leak".equals(name))
return findClass(name);
return super.loadClass(name);
}
};
return (IExample) tmp.loadClass("example.Example")
.newInstance();
}
private static URL getClassPath() {
String dir = "/Users/longtuan/develop/rjc2011/classes/";
try {
//return new URL(dir);
File path = new File(dir);
return path.toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
And the path to class example.Example is:
/Users/longtuan/develop/java/rjc2011/classes/example
And the run command I used is:
java -classpath ./bin example.Main
Current directory is:
/Users/longtuan/develop/java/rjc2011
All the things of current directory are like this:
Example is the directory in which I put all the java files.I place the compiled class file in directory bin except the class example.Example which is put in classes lonely.
Above are all the informations I can give, thanks for any help.