Jetty ServletContextHandler setClassLoader 不在每个请求线程上工作

Jetty ServletContextHandler setClassLoader not working on every request thread

我尝试使用 RESTEasy 和 Jetty 开发多个网络服务。我打算让每个 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 以启动服务器时将此自定义 class 加载器分配给 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();
 }}

我成功地启动了服务器,但是当我尝试调用网络服务时,它 return

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

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

我哪里做错了?我对这个 class 的加载内容很陌生,我不确定我真的理解如何使用它。

此致

默认情况下,Class加载程序采用父优先策略。这意味着 Classes 通过序列

被搜索和加载

Bootstrap Class Loader -> Ext Class Loader -> System Class Loader -> Custom Class Loader

因此,通过这种方法,使用系统 Class 加载程序加载 Dummy Class。现在,通过 ClassLoader 加载的 classes 只能看到来自父 ClassLoader 的 classes,反之亦然。因此,HikariConfig class 对 Dummy Class 不可见。因此,异常。

但是,您应该能够使用 ServletContext Classloader 以这种方式加载 class,在您的情况下是 Custom ClassLoader。 在你的 Dummy class 中注入 Servlet 上下文,然后

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