1.现象
在从jboss迁移到jetty后,有一个应用页面报了如下异常:
1 2 3 4 5 6 |
net.sf.json.JSONException: java.lang.ClassCastException: com.ali.martini.biz.marketing.time.Parser$PeriodType cannot be cast to java.lang.String at com.ali.martini.web.marketing.SendTimeDtoUtil$1.setProperty(SendTimeDtoUtil.java:210) at net.sf.json.JSONObject.setProperty(JSONObject.java:1497) at net.sf.json.JSONObject.toBean(JSONObject.java:387) at com.ali.martini.common.JsonUtil.JSONStringToBean(JsonUtil.java:44) at com.ali.martini.web.marketing.SendTimeDtoUtil.JSONStringToBean(SendTimeDtoUtil.java:216) |
看了下代码大概是这么一个操作:
1 2 3 4 5 6 7 |
JsonConfig config = new JsonConfig(); config.setPropertySetStrategy(new PropertySetStrategy() { public void setProperty(Object bean, String key, Object value) throws JSONException { if("periodType".equals(key)){ Object val = PeriodType.valueOf((String)value); } } |
2.原因
换了容器报错,第一能想到的就是jar包的问题,是否这个类加载了不同版本的Class,导致以前传入的value是一个String,现在不是了。
为了求证这个问题,在该类的调用处使用了如下方式:
1 2 3 4 5 6 7 8 9 10 |
try { Enumeration<URL> urls = this.getClass().getClassLoader().getResources("net/sf/json/JSONObject.class"); while(urls.hasMoreElements()) { URL url = urls.nextElement(); System.out.println("url!!="+url); logger.info("url!!="+url); } } catch (IOException e) { e.printStackTrace(); } |
然后观察发现,jboss的顺序是这样的:
1 2 |
jar:file:/D:/alibaba/jboss-4.2.2.GA/server/default/deploy/eve.war/WEB-INF/lib/ajax.json__json-lib-2.2-jdk15.jar-2.2.jar!/net/sf/json/JSONObject.class jar:file:/D:/alibaba/jboss-4.2.2.GA/server/default/deploy/eve.war/WEB-INF/lib/sourceforge.json-lib-2.2.3.jar!/net/sf/json/JSONObject.class |
到jetty下顺序就反过来了,sourceforge.json-lib-2.2.3.jar!/net/sf/json/JSONObject.class排到了前面,所以会出现这个问题。
用eclipse maven插件或者 mvn dependency:tree可以看到,以上两个jar包分别是在两个二方库引入的依赖(shy2和aranda)
考察jetty的jar包加载顺序:
jetty在启动的过程中,使用WebAppContext的configure来加载lib的路径,具体方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Override public void configure(WebAppContext context) throws Exception { //cannot configure if the context is already started if (context.isStarted()) { if (Log.isDebugEnabled()){Log.debug("Cannot configure webapp "+context+" after it is started");} return; } Resource web_inf = context.getWebInf(); // Add WEB-INF classes and lib classpaths if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader) { // Look for classes directory Resource classes= web_inf.addPath("classes/"); if (classes.exists()) ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes); // Look for jars Resource lib= web_inf.addPath("lib/"); if (lib.exists() || lib.isDirectory()) ((WebAppClassLoader)context.getClassLoader()).addJars(lib); } ... } |
而调用的classloader具体的addJars方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/* ------------------------------------------------------------ */ /** Add elements to the class path for the context from the jar and zip files found * in the specified resource. * @param lib the resource that contains the jar and/or zip files. */ public void addJars(Resource lib) { if (lib.exists() && lib.isDirectory()) { String[] files=lib.list(); for (int f=0;files!=null && f<files.length;f++) { try { Resource fn=lib.addPath(files[f]); String fnlc=fn.getName().toLowerCase(); if (!fn.isDirectory() && isFileSupported(fnlc)) { String jar=fn.toString(); jar=StringUtil.replace(jar, ",", "%2C"); jar=StringUtil.replace(jar, ";", "%3B"); addClassPath(jar); } } catch (Exception ex) { Log.warn(Log.EXCEPTION,ex); } } } } |
addClassPath最终调用父类URLClassLoader的addURL方法把
1 2 3 |
protected void addURL(URL url) { ucp.addURL(url); } |
加入到classloader的路径中。在类加载的时候,就是根据这个顺序一一查找path,看是否能找到对应的jar包。
因此,addClassPath的顺序就决定了那个jar包中的类被加载的问题。
而从上面的程序可以看到,file被list的顺序是由 String[] files=lib.list();决定的,因此查看lib.list
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public String[] list() { String[] list =_file.list(); if (list==null) return null; for (int i=list.length;i-->0;) { if (new File(_file,list[i]).isDirectory() && !list[i].endsWith("/")) list[i]+="/"; } return list; } |
实际使用的是java.io.file的list方法:
1 2 3 4 5 6 7 |
public String[] list() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(path); } return fs.list(this); } |
最终调用的是 FileSystem的抽象方法
public abstract String[] list(File f);
在往下就是平台相关的代码了.
因为jdk源码查询不熟,所以写了一个简单代码,用外科的方式来考察:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class ListFileTest { public static void main(String[] args) { if (args.length == 0 || args[0] == null) { System.out.println("no args"); System.exit(0); } File file = new File(args[0]); String [] files = file.list(); for (String f :files) { System.out.println(f); } } } |
通过strace检查系统调用,得到如下有用信息:
15381 open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY) = 9 15381 fstat(9, {st_mode=S_IFDIR|0777, st_size=20480, ...}) = 0 15381 fcntl(9, F_SETFD, FD_CLOEXEC) = 0 15381 getdents(9, /* 75 entries */, 4096) = 4072 15381 getdents(9, /* 74 entries */, 4096) = 4080 15381 getdents(9, /* 76 entries */, 4096) = 4080 15381 getdents(9, /* 72 entries */, 4096) = 4064
可以发现最终调用的是getdents(最终好像是调用readdir),然后这个系统函数list的文件是什么顺序,目前我也没有搞懂,
有说法是按inode号,试试下好像也不是,总是,顺序是操作系统相关的且不能保证的。
3.解决方案:
1.复写jetty的webAppClassloader,将list出来的文件排序,甚至可以配置指定几个包的顺序在前。
2.通过maven配置exclude一个依赖,但要保证兼容,如果不兼容,需要沟通两方二方库人员解决
3.山寨办法,打包时对jar包重命名,不是很靠谱。
相关推荐
jetty_8.1.17_API.chm ,从jetty-distribution-8.1.17.v20150415.zip编译
jetty_7.6.17_API.chm ,从jetty-distribution-7.6.17.v20150415.zip中编译
eclipse插件,jetty_svn。使用links方式安装,直接解压在eclipse 根目录下就行。
maven jetty插件配置指南。 看看就知道。
jetty socket java c# 互相调用 jetty socket java c# 互相调用 jetty socket java c# 互相调用
jetty开通jmx的方式,亲测有效。
支持web接口的批处理框架 在eclipse中导出为可执行的jar,无需部署到任何web容器中。直接通过bat或shell启动即可。...mybatis3.4.1 druid1.0.17 smg3(决策引擎) jetty8.1.5 fastjson1.2.7 springjdbc3.2.14
jetty-distribution-7.6.16.v20140903.zip中的doc编译的chm
通过android studio倒入i-jetty-ui工程,会发现无法运行,问题的原因一个是没有引入jetty相关的jar,另一个就是没有合并i-jetty-server代码,当jetty相关jar引入成功,i-jetty-server代码合并完成以后就可以运行了
eclipse 插件,包括 SVN中文,jetty 和 mybatipse 以及打开文件夹位置的插件。 直接解压到eclipse的根目录就行
利用该资源能有效的搭建起web项目开发平台,能顺利有效地进行资源开发。
Jetty6_指南书 Jetty6_指南书 Jetty6_指南书Jetty6_指南书 Jetty6_指南书 Jetty6_指南书
正在维护项目中的一个中间件之一,使得在嵌入式程序中快速搭建WEB服务,或使用HTTP Client服务抑或Apache MINA的推送服务变得容易。
把${jetty_home}/lib/jsp-2.1目录复制到${project_home}/jetty/lib目录下(如果不复制jsp-2.1或jsp-2.0也可以正常启动,只是不能解析jsp,打开主页时提示 JSP not support)。 同样把jetty-6.1.14.jar、jetty-util-...
runjettyrun.jetty8_1.3.2.jar
角色变量jetty_version 将要安装的 Jetty 版本。 (默认为9.2.3.v20140905 。) jetty_host 听什么主持人。 默认情况下,它侦听所有接口。 jetty_port 要侦听的 HTTP 端口。 默认情况下,它侦听端口8080 。 jetty_...
java运行依赖jar包
jsf_primefaces_app_with_embedded_jetty_9_container 真是一件很酷的事情。 我设法使用嵌入式 Jetty9 容器运行带有 primefaces 框架的 JSF 应用程序。
最新官网下载的runjettyrun.jetty9_1.3.4.jar,里面包含runjettyrun.jetty7_1.3.4.jar,runjettyrun.jetty8_1.3.4.jar,runjettyrun.jetty9_1.3.4.jar,runjettyrun_1.3.4.jar四个文件,解压后放在eclipse目录下的...
需要安装runjettyrun.jetty9_1.3.5的, runjettyrun.jetty9_1.3.5.201802101211.jar