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");
我尝试使用 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");