Jetty ServletContextHandler setClassLoader не работает для каждого потока запроса - PullRequest
0 голосов
/ 12 июля 2020

Я пытался разработать несколько веб-сервисов с помощью RESTEasy и Jetty. Я планирую сделать так, чтобы каждый веб-сервис имел свой собственный набор файлов 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;
}

И затем я назначил этот пользовательский загрузчик классов в ServletContextHandler, когда я инициализировал Jetty и RestEasy для запуска сервера, как показано ниже

 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 загрузчик классов.

Какую часть я сделал здесь неправильно? Я так новичок в этой загрузке классов, и я не уверен, что буквально понимаю, как ее использовать.

С уважением

1 Ответ

0 голосов
/ 12 июля 2020

По умолчанию ClassLoaders работает по стратегии Parent first. Это означает, что классы ищутся и загружаются в последовательности

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

Итак , при таком подходе фиктивный класс загружается с помощью загрузчика системного класса. Теперь классы, загруженные через ClassLoader, видны только классам из Parent ClassLoader, а не наоборот. Итак, класс HikariConfig не виден фиктивному классу. Следовательно, исключение.

Но вы должны иметь возможность загрузить класс таким образом, используя ServletContext Classloader, который в вашем случае является Custom ClassLoader. Вставьте контекст сервлета в ваш фиктивный класс, а затем

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