前段时间一直在做应用容器的迁移,将公司的应用容器从jboss,tomcat统一迁移到jetty。在整个迁移过程中遇到最多的潜在问题还是在classloader机制上,这里记录一下希望能对大家有所帮助,避免重复走弯路。
啥都不说,先来看下遇到的几个问题,比较纠结的问题。
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)
Caused by: java.lang.NoSuchMethodError: javax.xml.parsers.SAXParserFactory.newInstance(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljavax/xml/parsers/SAXParserFactory;
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.(XmlParser.java:68)
at org.eclipse.jetty.xml.XmlConfiguration.initParser(XmlConfiguration.java:79)
at org.eclipse.jetty.xml.XmlConfiguration.(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
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
可以说基本都是对应的class , method等找不到,或者类冲突等问题,一看就是比较典型的classloader引发的问题。
这里早期对jboss classloader几点配置做了下说明,可以参见: http://agapple.iteye.com/blog/791940
为了和tomcat,jetty有更明显的对比,这里就主要介绍三个参数代码层面上的实现:
...... false true javax.servlet,org.apache.commons.logging .....
相信这几个参数大家都应该比较熟知,配置项在 deploy/jbossweb-tomcat55.sar/META-INF/jboss-service.xml中
下面循着代码来看一下jboss的相关实现:
if (ctxPath.equals("/") || ctxPath.equals("/ROOT") || ctxPath.equals(""))
{
log.debug("deploy root context=" + ctxPath);
ctxPath = "/";
metaData.setContextRoot(ctxPath);
}
URL url = new URL(warUrl);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
/* If we are using the jboss class loader we need to augment its path
to include the WEB-INF/{lib,classes} dirs or else scoped class loading
does not see the war level overrides. The call to setWarURL adds these
paths to the deployment UCL.
*/
Loader webLoader = null;
if (config.isUseJBossWebLoader()) // 这里对useJbossWebLoader进行判断,进行不同的classloader处理
{
WebCtxLoader jbossLoader = new WebCtxLoader(loader);
jbossLoader.setWarURL(url);
webLoader = jbossLoader;
}
else
{
String[] pkgs = config.getFilteredPackages();
WebAppLoader jbossLoader = new WebAppLoader(loader, pkgs);
jbossLoader.setDelegate(getJava2ClassLoadingCompliance());
webLoader = jbossLoader;
}
if (webLoader != null)
{
server.setAttribute(objectName, new Attribute("loader", webLoader));
}
else
{
server.setAttribute(objectName, new Attribute("parentClassLoader", loader));
}
server.setAttribute(objectName, new Attribute("delegate", new Boolean(getJava2ClassLoadingCompliance()))); // 设置deletegate属性
说明:
WebCtxLoader(ClassLoader encLoader)
{
this.encLoader = encLoader;
this.ctxLoader = new ENCLoader(encLoader);
ClassLoader parent = encLoader;
while ((parent instanceof RepositoryClassLoader) == false && parent != null)
parent = parent.getParent();
this.delegate = (RepositoryClassLoader) parent; //delegate对象设置
}
public void setWarURL(URL warURL) throws MalformedURLException
{
this.warURL = warURL;
String path = warURL.getFile();
File classesDir = new File(path, "WEB-INF/classes");
if (classesDir.exists())
{
delegate.addURL(classesDir.toURL()); //无非都是委托给delegate loader
ctxLoader.addURLInternal(classesDir.toURL());
}
File libDir = new File(path, "WEB-INF/lib");
if (libDir.exists())
{
File[] jars = libDir.listFiles();
int length = jars != null ? jars.length : 0;
for (int j = 0; j < length; j++)
{
delegate.addURL(jars[j].toURL()); //无非都是委托给delegate loader
ctxLoader.addURLInternal(jars[j].toURL());
}
}
}
public class WebAppLoader extends org.apache.catalina.loader.WebappLoader
{
private String[] filteredPackages = {
"org.apache.commons.logging"
};
public WebAppLoader()
{
super();
setLoaderClass(WebAppClassLoader.class.getName());
}
.....
}
tomcat相比于jboss4.05概念上简介了很多,不过tomcat 6版本相比于tomcat 5有一些变化,少了一些shared lib库的概念。
Bootstrap | System | Common / \ Webapp1 Webapp2 ...
一个树状结构,相信大家差不多都知道tomcat默认是以child first装载class,优先载入web中的class类,找不到才会去装载公用类。
下面就来看一下代码的一些实现:
public synchronized void start() {
.....
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
.....
}
// Construct a class loader based on our current repositories list
try {
classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
.....
for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}
}
// (0.1) Check our previously loaded class cache
clazz = findLoadedClass(name);
// (0.2) Try loading the class with the system class loader, to prevent the webapp from overriding J2SE classes
clazz = system.loadClass(name);
boolean delegateLoad = delegate || filter(name);
// (1) Delegate to our parent if requested
if (delegateLoad) {
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
....
}
.....
}
// (2) Search local repositories
clazz = findClass(name);
// (3) Delegate to parent unconditionally
if (!delegateLoad) {
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
....
}
.....
}
jetty与tomcat相比,没有太大上的区别,也是有parent first/child first的概念,filter package等。但还是有些小区别,那先看一下代码:
public void preConfigure() throws Exception
{
// Setup configurations
loadConfigurations();
// Setup system classes
loadSystemClasses();
// Setup server classes
loadServerClasses();
// Configure classloader
_ownClassLoader=false;
if (getClassLoader()==null)
{
WebAppClassLoader classLoader = new WebAppClassLoader(this);
setClassLoader(classLoader);
_ownClassLoader=true;
}
.....
}
(_context.isParentLoaderPriority() || system_class) && !server_class)
public final static String[] __dftServerClasses =
{
"-org.eclipse.jetty.continuation.", // don't hide continuation classes
"-org.eclipse.jetty.jndi.", // don't hide naming classes
"-org.eclipse.jetty.plus.jaas.", // don't hide jaas classes
"-org.eclipse.jetty.websocket.", // don't hide websocket extension
"-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
"org.eclipse.jetty." // hide other jetty classes
} ;
public final static String[] __dftSystemClasses = { "java.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) "javax.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) "org.xml.", // needed by javax.xml "org.w3c.", // needed by javax.xml "org.apache.commons.logging.", // TODO: review if special case still needed "org.eclipse.jetty.continuation.", // webapp cannot change continuation classes "org.eclipse.jetty.jndi.", // webapp cannot change naming classes "org.eclipse.jetty.plus.jaas.", // webapp cannot change jaas classes "org.eclipse.jetty.websocket.", // WebSocket is a jetty extension "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets } ;
org.eclipse.jetty.webapp.systemClasses - java.
......
容器 | 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 |
是一个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相关处理
几个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加载。
- com.sun.media.jai.
- com.sun.media.imageio.
- com.sun.media.imageioimpl.
- jj2000.j2k.
xml加载的问题,原因也跟jetty的特殊性相关。大家都知道xml解析类有很多种
真TMD令人纠结啊,应用容器中一旦同时依赖了这几个xml类库,那麻烦问题就来了。这个SAXParserFactory.newInstance(String factoryClassName, ClassLoader classLoader)在jdk1.6.18版本中有,而在其他的几个包中却没有该接口方法。
该问题的原因:
解决方案:
处理方式也比较粗暴,如果该方法存在歧义,那干脆不用这方法不就得了,最后换用了无参的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
该问题是由问题2引出的,正因为设置了xml解析器为xerces,导致在jetty启动的时候出现了一些问题。这次主要的原因就跟共享lib库有关了。
最终原因分析:
解决方案:
lib=${jettyserverhome}/ext
是一个mail邮件发送时发现的问题,从堆栈信息描述看也很见到,对应的javax.mail.event.TransportListener没找到
mail的lib库也是挺让人纠结的
1. xml一样有多个lib库:j2ee.jar,javamail。
2. 但有一点又不同的是j2ee.jar中只有接口申明没有对应的实现类
3. 更纠结的是j2ee-1.4新版本和老的javamail版本接口上还存在差异性(这个是别人告诉我的,个人没仔细验证)
看到这,各位看官需要多淡定一下,真是很让人纠结啊
最终原先分析:
解决方案:
1. 没必要参合j2ee.jar,直接在原先的工程里添加javamail的依赖,最后在应用中引入javamail的包。