java - 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标准类加载器。
我在这里做错了哪一部分?我对这个类加载东西很陌生,我不确定我真的理解如何使用它。
问候
解决方案
默认情况下,ClassLoaders 使用父优先策略。这意味着通过序列搜索和加载类
Bootstrap 类加载器 -> Ext 类加载器 -> 系统类加载器 -> 自定义类加载器
因此,通过这种方法,虚拟类是使用系统类加载器加载的。现在,通过 ClassLoader 加载的类仅对来自 Parent ClassLoader 的类具有可见性,反之亦然。因此,HikariConfig 类对 Dummy 类不可见。因此,例外。
但是,您应该能够使用 ServletContext 类加载器以这种方式加载一个类,在您的情况下是自定义类加载器。在 Dummy 类中注入 Servlet 上下文,然后
servletContext.getClassLoader().loadClass("com.zaxxer.hikari.HikariConfig");
推荐阅读
- android - Android 应用程序 64 位建议 - 接收矛盾信息
- angular - 如何直接从服务调用组件中的函数?
- python - 找不到“监督嵌入”的组件类。未知组件名称
- sql - 将 Oracle 查询迁移到 PostgreSQL
- python - 找不到指定的文件'''FileNotFoundError: [Errno 2] 没有这样的文件或目录''':
- azure - 在为 Starburst Presto 创建 Azure HDInsight 群集时,我可以创建 Spark 群集吗?
- excel - 在 VBA 中将 Excel 工作表转换为 PDF 时,是否可以在指定的分页符之前进行缩放?
- javafx - javafx如何在TextField中实现图标搜索
- c# - 我们如何处理不知道前缀/命名空间的问题?
- javascript - 如何在javascript中的另一个函数中使用函数作为对象的属性