首页 > 解决方案 > Jetty ServletContextHandler setClassLoader 不适用于每个请求线程

问题描述

我尝试使用 RESTEasy 和 Jetty 开发多个 Web 服务。我计划让每个 web 服务都有自己的一组 JAR 文件,这些文件将从某个目录加载。

我所做的是我像这样创建自定义 ClassLoader

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;
}

然后我所做的就是在初始化 Jetty 和 RestEasy 以启动服务器时将这个自定义类加载器分配给 ServletContextHandler,如下所示

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

然后在 jax-rs 端点我有这段代码

 @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();
 }}

我设法启动服务器就好了,但是当我尝试调用网络服务时,它返回

java.lang.NoClassDefFoundError: com/zaxxer/hikari/HikariConfig

然后我看到线程中使用的classLoader不是我的自定义类加载器,而是java标准类加载器。

我在这里做错了哪一部分?我对这个类加载东西很陌生,我不确定我真的理解如何使用它。

问候

标签: javajax-rsjettyclassloaderresteasy

解决方案


默认情况下,ClassLoaders 使用父优先策略。这意味着通过序列搜索和加载类

Bootstrap 类加载器 -> Ext 类加载器 -> 系统类加载器 -> 自定义类加载器

因此,通过这种方法,虚拟类是使用系统类加载器加载的。现在,通过 ClassLoader 加载的类仅对来自 Parent ClassLoader 的类具有可见性,反之亦然。因此,HikariConfig 类对 Dummy 类不可见。因此,例外。

但是,您应该能够使用 ServletContext 类加载器以这种方式加载一个类,在您的情况下是自定义类加载器。在 Dummy 类中注入 Servlet 上下文,然后

servletContext.getClassLoader().loadClass("com.zaxxer.hikari.HikariConfig");

推荐阅读