嵌入式Jetty Web应用部署中的类加载器问题
解决跨类加载器的ClassCastException问题
嘿,这个坑我踩过好多次了!核心原因其实很简单:JVM判定两个类是否为同一个类型,不仅看全限定类名,还要看加载它们的类加载器是否相同。你遇到的两种情况,本质都是系统类加载器(main方法用的)和Web应用类加载器(Servlet用的)各自加载了SomeClass,导致两个类型看似相同实则完全不兼容。
为什么会抛出ClassCastException?
当你在main方法里用系统类加载器实例化SomeClass,并把它放进Servlet上下文后,Servlet里的代码会尝试用Web应用类加载器去加载SomeClass来做类型转换。这时候JVM会认为:“这两个SomeClass来自不同的类加载器,是完全不同的类型”,直接抛出ClassCastException——哪怕它们的代码一模一样。
用WebAppContext.addSystemClasses怎么解决?
WebAppContext.addSystemClasses这个方法的作用,就是告诉Web应用类加载器:遇到指定的类时,不要自己加载,委托给系统类加载器去加载。这样一来,Servlet里拿到的SomeClass类型,和main方法里实例化的对象所属的类型,就来自同一个类加载器了,强转自然就没问题。
具体操作步骤
- 配置类加载委托规则:在启动Web容器(比如Jetty,因为
WebAppContext是Jetty的核心类)的时候,添加如下代码:WebAppContext webAppContext = new WebAppContext(); // 让Web应用类加载器委托系统类加载器加载SomeClass webAppContext.addSystemClasses(SomeClass.class.getName()); // 如果SomeClass还有依赖的其他类,也要一起加进来,比如: // webAppContext.addSystemClasses(SomeDependency.class.getName()); - 确保类的存放位置正确:一定要把
SomeClass的字节码放在系统类加载器能加载到的路径下(比如JVM的classpath),绝对不能放在Web应用的WEB-INF/classes或者WEB-INF/lib里——不然Web应用类加载器会优先加载自己路径下的类,忽略你设置的委托规则,问题还会重现。
代码示例
- main方法里放入上下文:
public static void main(String[] args) throws Exception { Server server = new Server(8080); WebAppContext webAppContext = new WebAppContext(); webAppContext.addSystemClasses(SomeClass.class.getName()); server.setHandler(webAppContext); // 实例化对象(系统类加载器加载) SomeClass obj = new SomeClass(); // 放入Servlet上下文 webAppContext.setAttribute("myObj", obj); server.start(); server.join(); } - Servlet里获取并使用:
@WebServlet("/test") public class TestServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 这里拿到的SomeClass是系统类加载器加载的,和main里的对象类型一致 SomeClass obj = (SomeClass) getServletContext().getAttribute("myObj"); // 正常使用obj } }
额外注意事项
- 如果
SomeClass有继承或实现的接口,也要确保接口被同一个类加载器加载——要么一起添加到addSystemClasses,要么也放在系统类路径下。 - 不要滥用
addSystemClasses,只添加确实需要跨类加载器共享的类,不然可能导致Web应用之间的类隔离失效,引发其他问题。
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

