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");