简体   繁体   中英

Loading external classes that depend on internal classes

I am trying to write a simple plugin architecture for Java (it is extending some existing functionality we have, which is why I'm not using a proper plugin library)

I have a base class in my war (deployed using JBoss):

package org.example.components;

public abstract class ComponentBase {
    // Base code
}

I have a jar custom-components.jar containing some custom component code:

package org.example.components.custom;

public class CustomComponent extends ComponentBase {
    // Custom code
}

Through some magic the plugin code in my war gets the name of the CustomComponent class and does:

Class classToRegister = Class.forName("org.example.components.custom.CustomComponent");

However, this throws a NoClassDefFoundError , saying it can't find org.example.components.ComponentBase .

Caused by: java.lang.NoClassDefFoundError: org/example/components/ComponentBase
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at org.jboss.mx.loading.RepositoryClassLoader.findClassLocally(RepositoryClassLoader.java:690)
    at org.jboss.mx.loading.RepositoryClassLoader.findClass(RepositoryClassLoader.java:670)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassLocally(RepositoryClassLoader.java:200)
    at org.jboss.mx.loading.ClassLoadingTask$ThreadTask.run(ClassLoadingTask.java:131)
    at org.jboss.mx.loading.LoadMgr3.nextTask(LoadMgr3.java:399)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassImpl(RepositoryClassLoader.java:527)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClass(RepositoryClassLoader.java:415)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:295)
    at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:627)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1345)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1204)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:169)
    ... 259 more
Caused by: java.lang.ClassNotFoundException: No ClassLoaders found for: org/example/components/ComponentBase
    at org.jboss.mx.loading.LoadMgr3.beginLoadTask(LoadMgr3.java:212)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClassImpl(RepositoryClassLoader.java:521)
    at org.jboss.mx.loading.RepositoryClassLoader.loadClass(RepositoryClassLoader.java:415)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    ... 297 more

ComponentBase has loaded fine, because I can do Class.forName('org.example.components.ComponentBase') without error. Is it using a different class loader when initializing CustomComponent ? I tried explicitly passing in a class loader but I got the same error.

Since your plugin base class is in your WAR file (presumably in in WEB-INF/classes/...) you need to remove custom-components.jar from somewhere on the classpath and move it into the 'WEB-INF/lib' folder of your war file.

This way both classes are loaded by the same class loader.

The way you have it now, when you execute:

Class.forName('org.example.components.ComponentBase')

it succeeds because you are loading ComponentBase into your war classloader. The forName method first checks the war's WEB-INF/lib and WEB-INF/classes and finds it.

However when you execute

Class.forName("org.example.components.custom.CustomComponent");

the war classloader first checks WEB-INF/lib, WEB-INF/classes as before but does not find it there. Then it starts asking the "parent" classloader. The parent classloader (maybe a JBoss specific classloader, or the boot classloader?) finds the class and attempts to load it.

All goes well until it detects that CustomComponent extends BaseComponent. So the parent classloader attempts to load BaseComponent. However the parent classloader cannot find BaseComponent because it is not allowed to look in your war file. You war classloader is a child classloader and classloaders can only delegate to parent class loaders.

Consider what would happen if you undeployed your war and your base class went away. Or if you deployed two wars, both with your base class. The idea of a plugin class on the boot class path depending on a base class that might vanish or multiply would make no sense.

For now, just put all the code in your WAR file and keep the classloading logic as simple as possible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM