jetty迁移的classload一例

1.现象

在从jboss迁移到jetty后,有一个应用页面报了如下异常:

[html]  view plain copy
  1. net.sf.json.JSONException: java.lang.ClassCastException: com.ali.martini.biz.marketing.time.Parser$PeriodType cannot be cast to java.lang.String  
  2.  at com.ali.martini.web.marketing.SendTimeDtoUtil$1.setProperty(SendTimeDtoUtil.java:210)  
  3.  at net.sf.json.JSONObject.setProperty(JSONObject.java:1497)  
  4.  at net.sf.json.JSONObject.toBean(JSONObject.java:387)  
  5.  at com.ali.martini.common.JsonUtil.JSONStringToBean(JsonUtil.java:44)  
  6.  at com.ali.martini.web.marketing.SendTimeDtoUtil.JSONStringToBean(SendTimeDtoUtil.java:216)  

看了下代码大概是这么一个操作:

[java]  view plain copy
  1. JsonConfig config = new JsonConfig();  
  2. config.setPropertySetStrategy(new PropertySetStrategy() {  
  3.   public void setProperty(Object bean, String key, Object value) throws JSONException {  
  4.  if("periodType".equals(key)){  
  5.      Object val = PeriodType.valueOf((String)value);  
  6.        }  
  7.         }  

 

2.原因


换了容器报错,第一能想到的就是jar包的问题,是否这个类加载了不同版本的Class,导致以前传入的value是一个String,现在不是了。
为了求证这个问题,在该类的调用处使用了如下方式:

[java]  view plain copy
  1. try {  
  2.         Enumeration<URL> urls = this.getClass().getClassLoader().getResources("net/sf/json/JSONObject.class");  
  3.         while(urls.hasMoreElements()) {  
  4.             URL url = urls.nextElement();  
  5.             System.out.println("url!!="+url);  
  6.             logger.info("url!!="+url);  
  7.         }  
  8.     } catch (IOException e) {  
  9.         e.printStackTrace();  
  10.     }  


然后观察发现,jboss的顺序是这样的:

[html]  view plain copy
  1. 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  
  2. 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的路径,具体方法如下:

[java]  view plain copy
  1. @Override  
  2.     public void configure(WebAppContext context) throws Exception  
  3.     {  
  4.         //cannot configure if the context is already started  
  5.         if (context.isStarted())  
  6.         {  
  7.             if (Log.isDebugEnabled()){Log.debug("Cannot configure webapp "+context+" after it is started");}  
  8.             return;  
  9.         }  
  10.   
  11.         Resource web_inf = context.getWebInf();  
  12.   
  13.         // Add WEB-INF classes and lib classpaths  
  14.         if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)  
  15.         {  
  16.             // Look for classes directory  
  17.             Resource classes= web_inf.addPath("classes/");  
  18.             if (classes.exists())  
  19.                 ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);  
  20.   
  21.             // Look for jars  
  22.             Resource lib= web_inf.addPath("lib/");  
  23.             if (lib.exists() || lib.isDirectory())  
  24.                 ((WebAppClassLoader)context.getClassLoader()).addJars(lib);  
  25.         }  
  26.         ...  
  27. }  

 而调用的classloader具体的addJars方法如下:

 

[java]  view plain copy
  1. /* ------------------------------------------------------------ */  
  2.     /** Add elements to the class path for the context from the jar and zip files found 
  3.      *  in the specified resource. 
  4.      * @param lib the resource that contains the jar and/or zip files. 
  5.      */  
  6.     public void addJars(Resource lib)  
  7.     {  
  8.         if (lib.exists() && lib.isDirectory())  
  9.         {  
  10.             String[] files=lib.list();  
  11.             for (int f=0;files!=null && f<files.length;f++)  
  12.             {  
  13.                 try   
  14.                 {  
  15.                     Resource fn=lib.addPath(files[f]);  
  16.                     String fnlc=fn.getName().toLowerCase();  
  17.                     if (!fn.isDirectory() && isFileSupported(fnlc))  
  18.                     {  
  19.                         String jar=fn.toString();  
  20.                         jar=StringUtil.replace(jar, ",""%2C");  
  21.                         jar=StringUtil.replace(jar, ";""%3B");  
  22.                         addClassPath(jar);  
  23.                     }  
  24.                 }  
  25.                 catch (Exception ex)  
  26.                 {  
  27.                     Log.warn(Log.EXCEPTION,ex);  
  28.                 }  
  29.             }  
  30.         }  
  31.     }  


addClassPath最终调用父类URLClassLoader的addURL方法把

[java]  view plain copy
  1. protected void addURL(URL url) {  
  2.     ucp.addURL(url);  
  3.  }  


加入到classloader的路径中。在类加载的时候,就是根据这个顺序一一查找path,看是否能找到对应的jar包。
因此,addClassPath的顺序就决定了那个jar包中的类被加载的问题。

而从上面的程序可以看到,file被list的顺序是由 String[] files=lib.list();决定的,因此查看lib.list

[java]  view plain copy
  1. public String[] list()  
  2.    {  
  3.        String[] list =_file.list();  
  4.        if (list==null)  
  5.            return null;  
  6.        for (int i=list.length;i-->0;)  
  7.        {  
  8.            if (new File(_file,list[i]).isDirectory() &&  
  9.                !list[i].endsWith("/"))  
  10.                list[i]+="/";  
  11.        }  
  12.        return list;  
  13.    }  



实际使用的是java.io.file的list方法:

[java]  view plain copy
  1.    public String[] list() {  
  2. SecurityManager security = System.getSecurityManager();  
  3. if (security != null) {  
  4.     security.checkRead(path);  
  5. }  
  6. return fs.list(this);  
  7.    }  


最终调用的是 FileSystem的抽象方法
public abstract String[] list(File f);
在往下就是平台相关的代码了.

 

因为jdk源码查询不熟,所以写了一个简单代码,用外科的方式来考察:

[java]  view plain copy
  1. public class ListFileTest {  
  2.     public static void main(String[] args) {  
  3.         if (args.length == 0 || args[0] == null) {  
  4.             System.out.println("no args");  
  5.             System.exit(0);  
  6.         }  
  7.   
  8.         File file = new File(args[0]);  
  9.         String [] files = file.list();  
  10.         for (String f :files) {  
  11.             System.out.println(f);  
  12.         }  
  13.     }  
  14. }  

通过strace检查系统调用,得到如下有用信息:

[cpp]  view plain copy
  1. 15381 open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY) = 9  
  2. 15381 fstat(9, {st_mode=S_IFDIR|0777, st_size=20480, ...}) = 0  
  3. 15381 fcntl(9, F_SETFD, FD_CLOEXEC)     = 0  
  4. 15381 getdents(9, /* 75 entries */, 4096) = 4072  
  5. 15381 getdents(9, /* 74 entries */, 4096) = 4080  
  6. 15381 getdents(9, /* 76 entries */, 4096) = 4080  
  7. 15381 getdents(9, /* 72 entries */, 4096) = 4064  


可以发现最终调用的是getdents(最终好像是调用readdir),然后这个系统函数list的文件是什么顺序,目前我也没有搞懂,
有说法是按inode号,试试下好像也不是,总是,顺序是操作系统相关的且不能保证的。

 

3.解决方案:

1.复写jetty的webAppClassloader,将list出来的文件排序,甚至可以配置指定几个包的顺序在前。

2.通过maven配置exclude一个依赖,但要保证兼容,如果不兼容,需要沟通两方二方库人员解决

3.山寨办法,打包时对jar包重命名,不是很靠谱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值