背景
前段时间一直在做应用容器的迁移,将公司的应用容器从jboss,tomcat统一迁移到jetty。在整个迁移过程中遇到最多的潜在问题还是在classloader机制上,这里记录一下希望能对大家有所帮助,避免重复走弯路。
啥都不说,先来看下遇到的几个问题,比较纠结的问题。
问题1: (jar sealed问题)
1.Caused by: java.lang.SecurityException: sealing violation: package com.sun.media.jai.util is sealed
2. at java.net.URLClassLoader.defineClass(URLClassLoader.java:234)
3. at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
4. at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
5. at java.security.AccessController.doPrivileged(Native Method)
6. at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
7. at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:419)
8. at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:381)
9. at java.lang.ClassLoader.defineClass1(Native Method)
10. at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
说明: jboss容器运行正常 , jetty容器运行出错
问题2: (xml解析)
1.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解析)
1.java.lang.reflect.InvocationTargetException
2. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
3. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
4. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
5. at java.lang.reflect.Method.invoke(Method.java:597)
6. at org.eclipse.jetty.start.Main.invokeMain(Main.java:490)
7. at org.eclipse.jetty.start.Main.start(Main.java:634)
8. at org.eclipse.jetty.start.Main.parseCommandLine(Main.java:280)
9. at org.eclipse.jetty.start.Main.main(Main.java:82)
10.Caused by: javax.xml.parsers.FactoryConfigurationError: Provider org.apache.xerces.jaxp.SAXParserFactoryImpl not found
11. at javax.xml.parsers.SAXParserFactory.newInstance(SAXParserFactory.java:134)
12. at org.eclipse.jetty.xml.XmlParser.<init>(XmlParser.java:68)
13. at org.eclipse.jetty.xml.XmlConfiguration.initParser(XmlConfiguration.java:79)
14. at org.eclipse.jetty.xml.XmlConfiguration.<init>(XmlConfiguration.java:112)
15. at org.eclipse.jetty.xml.XmlConfiguration$1.run(XmlConfiguration.java:1028)
16. at java.security.AccessController.doPrivileged(Native Method)
17. at org.eclipse.jetty.xml.XmlConfiguration.main(XmlConfiguration.java:983)
18. ... 8 more
说明: jboss容器运行正常 , jetty容器运行异常
问题4:(mail问题)
1.Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl
1.Caused by: java.lang.ClassNotFoundException: javax.mail.event.TransportListener
2. at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
3. at java.security.AccessController.doPrivileged(Native Method)
4. at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
5. at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:419)
6. at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:381)
7. ... 78 more
说明: jboss容器运行正常 , jetty容器运行异常
可以说基本都是对应的class , method等找不到,或者类冲突等问题,一看就是比较典型的classloader引发的问题。
下面就来看看对容器classloader机制的分析和对比,相信大家了解了相关classloader机制微妙的区别后,基本也能解析这一类问题了
jboss4.05 classloader机制
这里早期对jboss classloader几点配置做了下说明,可以参见: http://agapple.iteye.com/blog/791940
为了和tomcat,jetty有更明显的对比,这里就主要介绍三个参数代码层面上的实现:
1.<server>
2.
3. <!-- Tomcat 5 Service-->
4. <mbean code="org.jboss.web.tomcat.tc5.Tomcat5"
5. name="jboss.web:service=WebServer" xmbean-dd="META-INF/webserver-xmbean.xml">
6.......
7.
8.<!-- Get the flag indicating if the normal Java2 parent first class
9. loading model should be used over the servlet 2.3 web container first
10. model.
11. -->
12. <attribute name="Java2ClassLoadingCompliance">false</attribute>
13. <!-- A flag indicating if the JBoss Loader should be used. This loader
14. uses a unified class loader as the class loader rather than the tomcat
15. specific class loader.
16. The default is false to ensure that wars have isolated class loading
17. for duplicate jars and jsp files.
18. -->
19. <attribute name="UseJBossWebLoader">true</attribute>
20. <!-- The list of package prefixes that should not be loaded without
21. delegating to the parent class loader before trying the web app
22. class loader. The packages listed here are those tha are used by
23. the web container implementation and cannot be overriden. The format
24. is a comma separated list of the package names. There cannot be any
25. whitespace between the package prefixes.
26. This setting only applies when UseJBossWebLoader=false.
27. -->
28. <attribute name="FilteredPackages">javax.servlet,org.apache.commons.logging</attribute>
29.
30......
31.
32.</server>
相信这几个参数大家都应该比较熟知,配置项在 deploy/jbossweb-tomcat55.sar/META-INF/jboss-service.xml中
下面循着代码来看一下jboss的相关实现:
- 代码入口: org.jboss.web.tomcat.tc5.Tomcat5, 这里的三个配置都对应于Tomcat5类的属性,默认值就是当前配置的值。
- Tomcat5会创建一个Deploy进行war包的装载,TomcatDeployer(继承于AbstractWebDeployer)
1.if (ctxPath.equals("/") || ctxPath.equals("/ROOT") || ctxPath.equals(""))
2. {
3. log.debug("deploy root context=" + ctxPath);
4. ctxPath = "/";
5. metaData.setContextRoot(ctxPath);
6. }
7.
8. URL url = new URL(warUrl);
9.
10. ClassLoader loader = Thread.currentThread().getContextClassLoader();
11. /* If we are using the jboss class loader we need to augment its path
12. to include the WEB-INF/{lib,classes} dirs or else scoped class loading
13. does not see the war level overrides. The call to setWarURL adds these
14. paths to the deployment UCL.
15. */
16. Loader webLoader = null;
17. if (config.isUseJBossWebLoader()) // 这里对useJbossWebLoader进行判断,进行不同的classloader处理
18. {
19. WebCtxLoader jbossLoader = new WebCtxLoader(loader);
20. jbossLoader.setWarURL(url);
21. webLoader = jbossLoader;
22. }
23. else
24. {
25. String[] pkgs = config.getFilteredPackages();
26. WebAppLoader jbossLoader = new WebAppLoader(loader, pkgs);
27. jbossLoader.setDelegate(getJava2ClassLoadingCompliance());
28. webLoader = jbossLoader;
29. }
- 最后通过MBean调用,将classloader设置给对应的tomcat上下文对象: "org.apache.catalina.core.StandardContext";
1.if (webLoader != null)
2. {
3. server.setAttribute(objectName, new Attribute("loader", webLoader));
4. }
5. else
6. {
7. server.setAttribute(objectName, new Attribute("parentClassLoader", loader));
8. }
9.
10. server.setAttribute(objectName, new Attribute("delegate", new Boolean(getJava2ClassLoadingCompliance()))); // 设置deletegate属性
说明:
- WebCtxLoader: 一个对jboss UCL classloader的一个代理而已,setWarUrl也只是将war资源加入到当前jboss的UCL classloader去装载
1.WebCtxLoader(ClassLoader encLoader)
2. {
3. this.encLoader = encLoader;
4. this.ctxLoader = new ENCLoader(encLoader);
5. ClassLoader parent = encLoader;
6. while ((parent instanceof RepositoryClassLoader) == false && parent != null)
7. parent = parent.getParent();
8. this.delegate = (RepositoryClassLoader) parent; //delegate对象设置
9. }
10.
11.
12.public void setWarURL(URL warURL) throws MalformedURLException
13. {
14. this.warURL = warURL;
15. String path = warURL.getFile();
16. File classesDir = new File(path, "WEB-INF/classes");
17. if (classesDir.exists())
18. {
19. delegate.addURL(classesDir.toURL()); //无非都是委托给delegate loader
20. ctxLoader.addURLInternal(classesDir.toURL());
21. }
22. File libDir = new File(path, "WEB-INF/lib");
23. if (libDir.exists())
24. {
25. File[] jars = libDir.listFiles();
26. int length = jars != null ? jars.length : 0;
27. for (int j = 0; j < length; j++)
28. {
29. delegate.addURL(jars[j].toURL()); //无非都是委托给delegate loader
30. ctxLoader.addURLInternal(jars[j].toURL());
31. }
32. }
33. }
- WebAppLoader: 对tomcat WebappLoader的一个封装, 同时设置filteredPackages给tomcat WebappLoader进行class控制。
1.public class WebAppLoader extends org.apache.catalina.loader.WebappLoader
2.{
3. private String[] filteredPackages = {
4. "org.apache.commons.logging"
5. };
6.
7. public WebAppLoader()
8. {
9. super();
10. setLoaderClass(WebAppClassLoader.class.getName());
11. }
12......
13.}
看到这里大家相信应该差不多了解了,总结一下:
- java2ClassLoadingCompliance是针对useJbossWebLoader=false时而言,是通过设置tomcat WebappClassloader的是否delegate进行控制classloader,实现child first/parent first。
- java2ClassLoadingCompliance在useJBossWebLoader=true时,不会生效,会被强制设置为false,具体可看WebCtxLoaders,实现了Loader接口,getClassLoader()返回的是一个ctxLoader对jboss WebLoader的一个包装。
- filteredPackages目前是通过tomcat的classloader机制上进行控制,所以只能是在useJbossWebLoader=false时有效,因为依赖了tomcat的实现。
不过需要注意两点:
- 目前在jboss4.05上不支持在在war包下WEB-INF/jboss-web.xml的配置,在jboss4.20以后才支持,可见https://jira.jboss.org/browse/JBAS-3047?subTaskView=all
- jboss 5.0以后,就没有UseJbossWebLoader这一配置项了,实现方式也不是delegate到tomcat,而是独立的一个classloader,统一处理。
jboss classloader机制大家也不必太刻意的去学习,只要适当的了解基本使用。只能说一切都是浮云浮云,随时都可能被改变。
tomcat6.0.30 classloader机制
tomcat相比于jboss4.05概念上简介了很多,不过tomcat 6版本相比于tomcat 5有一些变化,少了一些shared lib库的概念。
Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ...
一个树状结构,相信大家差不多都知道tomcat默认是以child first装载class,优先载入web中的class类,找不到才会去装载公用类。
下面就来看一下代码的一些实现:
- tomcat启动入口,通过分析StandardContext.start()
1.public synchronized void start() { 2...... 3. 4.if (getLoader() == null) { 5. WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); 6. webappLoader.setDelegate(getDelegate()); 7. setLoader(webappLoader); 8. } 9...... 10.}
public synchronized void start() { ..... if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } ..... }
- 如果getLoader为空,则创建一个新的WebappLoader,注意这也是jboss设置tomcat loader的入口,从而替换默认的tomcat classloader。
- WebappLoader通过在startInternal启动了一个新的WebappClassLoader
- 最后就是WebappClassLoader的loadClass方法了,具体的类装载策略。
下面来看一下,loadClass的相关逻辑:
- (0.1)先检查class是否已经被装载过,findLoadedClass
- (0.2)通过system classloader装载系统class,所以这里会优先装载jdk的相关代码,这个很重要,也很特别。
- 如果是delegate=true并且不是filterPackage列表,则采用parent first,否则采用child first。
相关代码: