`
jackyhongvip
  • 浏览: 159651 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

分享到:
评论

相关推荐

    i-jetty 项目lib和apk源码

    开发者在使用时,需要通过Android的`ClassLoader`加载这个库,并调用相应的API来启动和配置服务器。 `i-jetty-master`则可能是i-jetty项目的源码,这对于深入理解其工作原理和进行定制开发非常有帮助。源码通常包括...

    Jetty 核心架构

    - **ClassLoader加载的资源**:包括`log4j.properties`、`springContext.xml`等配置文件。 - **ServletContext加载的资源**:包括静态资源如`*.html`、`*.jpg`等。 综上所述,Jetty不仅在启动过程、组件装配等方面...

    jetty实施手册

    同时,手册还列举了实施过程中可能遇到的问题,如classloader系列问题和其他异常情况的解决方案,这对于顺利推进项目实施至关重要。 通过上述对“jetty实施手册”的深入解析,我们可以看到,无论是技术选型、配置...

    examples-master_java_

    5. **其他辅助功能**:还包括`console`命令提供REPL环境,`thread`命令查看线程状态,`classloader`命令探索类加载器关系等。 在`embedded-jetty-websocket-examples`这个压缩包中,包含了Arthas与嵌入式Jetty ...

    应用服务架构及性能调优详解

    内容概要:该文档介绍了常见的三种Java应用服务器(JBoss, Tomcat, Jetty)的整体架构及其启动流程,并深入探讨了它们各自的特性与配置要点。此外,文中还详细阐述了应用服务器的关键组件如类加载器(ClassLoader)的...

    深入分析Java Web技术内幕高清PDF版.zip

    其次深入介绍Java技术,包括I/O技术、中文编码问题、Javac编译原理、class文件结构解析、ClassLoader工作机制及JVM的内存管理等。最后介绍Java服务端技术,主要包括Servlet、Session与Cookie、Tomcat与Jetty服务器、...

    深入分析Java Web技术内幕 修订版.pdf

    其次深入介绍了Java 技术,包括I/O 技术、中文编码问题、Javac 编译原理、class 文件结构解析、ClassLoader 工作机制及JVM 的内存管理等。最后介绍了Java 服务端技术,主要包括Servlet、Session 与Cookie、Tomcat 与...

    深入探讨 Java 类加载器.pdf

    - **Web容器**:现代Web容器(如Tomcat、Jetty等)广泛使用类加载器来支持多应用共存环境下的隔离性,每个Web应用都有自己的类加载器,这样可以避免不同应用之间的类冲突问题。 - **OSGi**:OSGi是一个Java平台的...

    在可执行jar中载入第三方jar的几个解决方法

    6. **使用容器或服务器**:在某些情况下,如Tomcat、Jetty等应用服务器或Spring Boot这样的微服务框架,它们有自己的类加载机制,可以更好地处理多JAR依赖关系。 每种解决方案都有其适用场景和优缺点,选择哪种方法...

    深入分析Java Web技术内幕 修订版

    最后介绍了Java 服务端技术,主要包括Servlet、Session 与Cookie、Tomcat 与Jetty服务器、Spring 容器、iBatis 框架和Velocity 框架等原理介绍,并介绍了服务端的一些优化技术。 《深入分析Java Web技术内幕(修订版...

    tomcat 学习与分析总结资料

    主要有三个类加载器:Bootstrap ClassLoader、Common ClassLoader和Webapp ClassLoader。Bootstrap加载JDK的类,Common加载`common.loader`指定的类,而Webapp类加载器则负责加载每个Web应用自己的类。理解类加载...

    Java中级面试题

    内置Tomcat、Jetty等容器,支持嵌入式运行。 - **Spring MVC**: 作为Spring框架的一部分,提供了强大的MVC功能,可以轻松集成Spring生态中的其他组件。 - **MyBatis**: 一个优秀的持久层框架,提供了基于SQL的查询...

    浅谈java项目与javaweb项目导入jar包的区别

    原因是JavaWeb项目不是通过本地的JRE运行的,而是部署到Web服务器(如Tomcat、Jetty)上。这些服务器都实现了自身的类加载器。以Tomcat为例,ClassLoader的加载顺序如下: 1. common.CommonClassLoader 2. server....

    J2EE面试题集锦(收集了很多公司的面试题总结)

    - `java.lang.Thread`、`java.lang.Number`和`java.lang.ClassLoader`可以被继承,因为它们不是final类。 - `java.lang.Double`、`java.lang.Math`、`java.lang.Void`和`java.lang.Class`不可被继承,因为它们是...

    基于springboot的自动更新

    SpringBoot的核心特性包括自动配置、内嵌Web服务器(如Tomcat或Jetty)以及可执行JAR包的支持。通过自动配置,开发者可以快速搭建项目结构,而无需繁琐的XML配置。 在描述中提到的文章链接已给出,但在这个平台无法...

    Apache Geronimo 2.1_ Quick Reference.pdf

    ClassLoader Viewer portlet 263 JNDI Viewer portlet 265 Dependency Viewer portlet 267 Web Server administration 268 HTTP connectors 269 HTTPS connectors 271 AJP connectors 273 Web Server Logs ...

    restful restful所需要的jar包

    * Client CLAP connector to access to the Classloader resources. * Client RIAP connector to access to the Restlet internal resources, directly inside the JVM. Available Representations * Built-in ...

    apache-maven-3.8.4.zip

    2. **boot** 目录:包含用于启动Maven内嵌的Jetty服务器的类加载器(ClassLoader)。 3. **conf** 目录:存储Maven的配置文件,比如`settings.xml`,它定义了Maven的全局配置,如本地仓库位置、远程仓库和代理设置...

    Spring boot web 访问

    Spring Boot基于约定优于配置的原则,通过内嵌的Servlet容器(如Tomcat或Jetty),我们可以快速地启动一个Web服务。在Spring Initializr中,我们可以选择web依赖来创建一个新的Spring Boot项目。 创建项目后,我们...

    J2EE面试题集锦 java

    例如,`java.lang.Math`、`java.lang.Void`和`java.lang.Class`不能被继承,而`java.lang.Thread`和`java.lang.ClassLoader`可以。 2. **抽象类与接口的区别**: - 抽象类可以包含具体实现方法,而接口只能声明...

Global site tag (gtag.js) - Google Analytics