jetty_classloader

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包重命名,不是很靠谱。

你可能感兴趣的:(ClassLoader)