该帖已经被评为良好帖
|
|||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||||||||
发表时间:2010-11-28
最后修改:2010-11-28
背景前段时间一直在做应用容器的迁移,将公司的应用容器从jboss,tomcat统一迁移到jetty。在整个迁移过程中遇到最多的潜在问题还是在classloader机制上,这里记录一下希望能对大家有所帮助,避免重复走弯路。
啥都不说,先来看下遇到的几个问题,比较纠结的问题。 问题1: (jar sealed问题)Caused by: java.lang.SecurityException: sealing violation: package com.sun.media.jai.util is sealed at java.net.URLClassLoader.defineClass(URLClassLoader.java:234) at java.net.URLClassLoader.access$000(URLClassLoader.java:58) at java.net.URLClassLoader$1.run(URLClassLoader.java:197) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:419) at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:381) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
说明: jboss容器运行正常 , jetty容器运行出错
问题2: (xml解析)Caused by: java.lang.NoSuchMethodError: javax.xml.parsers.SAXParserFactory.newInstance(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljavax/xml/parsers/SAXParserFactory; 说明: jetty容器运行正常 , tomcat容器运行正常,jboss容器运行异常
问题3: (xml解析)java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.eclipse.jetty.start.Main.invokeMain(Main.java:490) at org.eclipse.jetty.start.Main.start(Main.java:634) at org.eclipse.jetty.start.Main.parseCommandLine(Main.java:280) at org.eclipse.jetty.start.Main.main(Main.java:82) Caused by: javax.xml.parsers.FactoryConfigurationError: Provider org.apache.xerces.jaxp.SAXParserFactoryImpl not found at javax.xml.parsers.SAXParserFactory.newInstance(SAXParserFactory.java:134) at org.eclipse.jetty.xml.XmlParser.<init>(XmlParser.java:68) at org.eclipse.jetty.xml.XmlConfiguration.initParser(XmlConfiguration.java:79) at org.eclipse.jetty.xml.XmlConfiguration.<init>(XmlConfiguration.java:112) at org.eclipse.jetty.xml.XmlConfiguration$1.run(XmlConfiguration.java:1028) at java.security.AccessController.doPrivileged(Native Method) at org.eclipse.jetty.xml.XmlConfiguration.main(XmlConfiguration.java:983) ... 8 more 说明: jboss容器运行正常 , jetty容器运行异常
问题4:(mail问题)Caused by: java.lang.ClassNotFoundException: javax.mail.event.TransportListener at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:419) at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:381) ... 78 more 说明: jboss容器运行正常 , jetty容器运行异常
可以说基本都是对应的class , method等找不到,或者类冲突等问题,一看就是比较典型的classloader引发的问题。 |
容器 | jboss(4.05) | tomcat(6.0.30) | jetty(7.1.20) |
支持child/parent first设置(默认值) | Java2ClassLoadingCompliance=false | delegate=false | _parentLoaderPriority=false |
过滤package配置 | FilteredPackages 默认值: javax.servlet,org.apache.commons.logging |
packageTriggers 默认配置:org.apache.commons.logging |
systemClasses 默认配置:java. javax. org.xml. org.w3c. org.apache.commons.logging. org.eclipse.jetty.continuation. org.eclipse.jetty.jndi. org.eclipse.jetty.plus.jaas. org.eclipse.jetty.websocket. org.eclipse.jetty.servlet.DefaultServlet. |
特殊性 |
1. UseJBossWebLoader=false时,过滤packages才能生效 2. UseJBossWebLoader=true时,不支持过滤packages 3. jboss 5.0以后UseJBossWebLoader参数将不支持 |
1. 在执行child/parent判断之前,会委托system classloader装载系统class,比如jdk的lib库 |
1. 多了一个serverclass配置,如果是serverclass优先采用child first 2. systemclass默认的配置,多了javax,org.xml,org.w3c配置。 |
相关文档 | svn url : http://anonsvn.jboss.org/repos/jbossas/tags/JBoss_4_0_5_GA_CP18 jboss社区classloader文档: http://community.jboss.org/wiki/ClassLoadingConfiguration |
svn url : http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk 官方classloader机制: http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html |
svn url : http://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/tags/jetty-7.2.0.v20101020/ classloader 官方文档: http://docs.codehaus.org/display/JETTY/Classloading |
最后(相关问题分析)
问题1:
是一个jar sealed问题, 官方说明: http://download.oracle.com/javase/tutorial/deployment/jar/sealman.html
Package Sealing: A package within a JAR file can be optionally sealed, which means that all classes defined in that package must be archived in the same JAR file. A package might be sealed to ensure version consistency among the classes in your software or as a security measure. To seal a package, a Name header needs to be added for the package, followed by a Sealed header, similar to this: Name: myCompany/myPackage/ Sealed: true The Name header's value is the package's relative pathname. Note that it ends with a '/' to distinguish it from a filename. Any headers following a Name header, without any intervening blank lines, apply to the file or package specified in the Name header. In the above example, because the Sealed header occurs after the Name: myCompany/myPackage header, with no blank lines between, the Sealed header will be interpreted as applying (only) to the package myCompany/myPackage. This code doesn't work.
说白了就是jdk在jar层面上做的一层控制,避免出现两个不同版本的实现类同时在应用中被装载。
理清了sealed的含义,再看一下出错的堆栈:com.sun.media.jai.util,这个类是jai相关处理
- jai_core.jar
- jai_codec.jar
- jai_imageio.jar
几个jar包的META-INF/MANIFEST.MF中都定义了sealed :true。而我们的应用中刚好有两个jar包,那为什么在jboss运行中没问题,jetty运行却出了问题。
最后的原因:
1. 我们使用的是javax.media.jai.JAI.create(String opName,ParameterBlock args),该类刚好依赖了com.sun.media.jai.util.ImagingListenerImpl等相关类。
2. 注意看jetty的特殊性,针对javax.media.jai.JAI是属于systemclass,所以会优先使用jdk去装载,而com.sun则通过应用容器装载,很巧的是公司的jdk下的jre/lib/ext下刚好增加了jai的相关jar,最终导致了sealed冲突。
解决方案:
处理起来相对暴力,增加配置systemclass配置,添加jai的相关package,所有的jai都采取parent first加载。
<!--for jai_code.jar , jai_codec.jar --> <Item>com.sun.media.jai.</Item> <!--for jai_imageio.jar --> <Item>com.sun.media.imageio.</Item> <Item>com.sun.media.imageioimpl.</Item> <Item>jj2000.j2k.</Item>
问题2
xml加载的问题,原因也跟jetty的特殊性相关。大家都知道xml解析类有很多种
- xerces
- crimson
- jdk rt.jar
- j2ee.jar
真TMD令人纠结啊,应用容器中一旦同时依赖了这几个xml类库,那麻烦问题就来了。这个SAXParserFactory.newInstance(String factoryClassName, ClassLoader classLoader)在jdk1.6.18版本中有,而在其他的几个包中却没有该接口方法。
该问题的原因:
- 原先应用运行的是tomca,也因为tomcat容器的特殊性,会优先通过system classloader优先装载,正好SAXParserFactory类属于jdk的库,所以也正好装载了jdk的类
- jetty容器因为特殊systemclass配置,针对javax.打头的package采用parent first,所以这时装载了jdk SAXParserFactory
- 最后jboss运行时,因为我们使用的是UseJbossWebLoader=true,所以会优先装载应用中的lib,至于用哪个那就看概率了,一般按classpath装载lib的顺序。所以这时没有该方法,就抛出了对应的错误
解决方案:
处理方式也比较粗暴,如果该方法存在歧义,那干脆不用这方法不就得了,最后换用了无参的newInstance()方法。但这时得注意了,不同xml类库,对应的xml impl默认配置不一样。比如jdk的配置:就会以他自己的为默认值
return (SAXParserFactory) FactoryFinder.find( /* The default property name according to the JAXP spec */ "javax.xml.parsers.SAXParserFactory", /* The fallback implementation class name */ "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
所以最后为了统一使用,通过设置环境变量:
-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl
问题3:
该问题是由问题2引出的,正因为设置了xml解析器为xerces,导致在jetty启动的时候出现了一些问题。这次主要的原因就跟共享lib库有关了。
最终原因分析:
- 因为通过环境变量设置了xml解析器的实现类,所以不同的xml类库在创建xml parse时,都会尝试去载入对应的lib库。
- jetty启动时,其自身需要解析xml文件,这时候就出现问题了,jetty默认是没有xerces xml的解析类的,所以启动就出错了。
解决方案:
- 参照jboss的共享lib的配置,在jetty的ext库里添加了xercesImpl-2.9.1.jar,xml-apis-1.3.04.jar,xml-resolver-1.2.jar
- 因为我使用的是外部ext库,不想放到jetty软件的lib库下,所以需要通过手工指定,在start.ini中添加:
lib=${jettyserverhome}/ext
问题4:
是一个mail邮件发送时发现的问题,从堆栈信息描述看也很见到,对应的javax.mail.event.TransportListener没找到
mail的lib库也是挺让人纠结的
1. xml一样有多个lib库:j2ee.jar,javamail。
2. 但有一点又不同的是j2ee.jar中只有接口申明没有对应的实现类
3. 更纠结的是j2ee-1.4新版本和老的javamail版本接口上还存在差异性(这个是别人告诉我的,个人没仔细验证)
看到这,各位看官需要多淡定一下,真是很让人纠结啊
最终原先分析:
- 原先jboss容器运行没问题,主要是和其默认的lib库有关,jboss 4.05在默认的server/default/lib下有个jboss-j2ee.jar,所以即使容器中没有javamail的包也能正常运行。
- 换成jetty以后,因默认在jetty下没有j2ee这个lib库,所以很悲惨,抛异常了。
解决方案:
1. 没必要参合j2ee.jar,直接在原先的工程里添加javamail的依赖,最后在应用中引入javamail的包。
启动速度。。。。
启动速度。。。。
不是单纯为了启动速度吧,难道这不是用在生产环境。。。
多方面的原因
1. 嵌入式 (目前未上嵌入式,以后是一种趋势,以后应用真的是可以无状态化,随便一个包扔服务器上就能跑)
2. 轻便 (支持options参数,加载最少的模块)
3. 性能 (相比于jboss4.05有一定的提升,30%左右,使用的是jetty NIO connector,不过目前测试在线上测试性比tomcat略差一点)
也说说它的缺点:
1. 代码。 你会看到它的release版本会非常频繁,也说明它的小问题bug比较多。有时你会在代码里看到system.err(message)的代码。
2. 使用nio,系统负载比正常的阻塞io的jboss,tomcat略高一些,比如load,cpu利用率等。