您的位置:首页 > 编程语言 > Java开发

JAVA2 & Jetty Class Loading

2011-12-21 13:53 363 查看
前段时间做Jetty迁移项目过程中,遇到的与ClassLoading有关的两个问题的总结与分析,不对的地方,请指正,欢迎拍砖。

问题:Jar包信赖冲突,即项目中依赖的一个类有两个实现版本,我们的JVM、Web容器到底载入的是哪一个呢?

现象:这种问题是很难发现的,也是比较随机的,可能本地编译、运行都没有问题,可是部署到服务器上就有问题,经常:MethodNotFound exception.有时你在eclipse中去查看这个方法时,又是存在的,这时候就感觉比较诡异.

解析:解决方法非常简单,你可能一下就想到了,只要装载的是我们需要的那个版本就可以了,因此你可能想到的有两种方法:

方法一:只保留一份实现,这个不用解释,只有一份当然不会冲突了。

方法二:你可能想到Jvm的classloader的特性,即:同一个类只载入一次.只要保证我们需要的类先载入不就行啦,这种想法是没错的,你可能想到:在lib下的载入顺序是按照文件名的字母顺序,通过修改jar包的名字的方式来更改载入的顺序,我也在测试环境下试过确实好用.但是依然不能下定论,我们要看看Jvm到底是如何载入的,它的载入顺序是怎样的?

  我们都知道ClassLoader委托体系,我们看一下URLClassLoader中是如何载入一类的:

1.classLoader在URLClassPath下查找资源文件:

public URL findResource(String name, boolean check) {

Loader loader;

for (int i = 0; (loader =getLoader(i)) != null; i++) {

URL url =loader.findResource(name, check);

if (url !=null) {

return url;

}

}

return null;

}

其中getLoader(i)函数是按照classPath的定义顺序来获取每一个资源的Loader,这个时候我们的问题都归结到了classPath的构建上来了。

先看看java2中的classLoader的体系是如何构建的:

ExtClassLoader的classPath构建过程:

1.获取extDirs,即系统属性:“java.ext.dirs”

2.遍历extDirs下的每一个文件,并生成对应的URL,存入urls数组。

3.用urls构建ucp(URLClassPath)。

注:在ucp中的顺序即为载入类时的顺序。这个顺序是严格按照urls中的顺序。因此,对于由ExtClassLoader来装载的类是顺序是由上面第2部来决定的,即这个URL的数组的构建,下面我们来看一下源码:

private static URL[] getExtURLs(File[] dirs) throwsIOException {

Vector urls = new Vector();

for (int i = 0; i < dirs.length; i++) {

String[] files = dirs[i].list();

if (files != null) {

for (int j = 0; j < files.length; j++) {

if (!files[j].equals("meta-index")){

File f = new File(dirs[i], files[j]);

urls.add(getFileURL(f));

}

}

}

}

URL[] ua = new URL[urls.size()];

urls.copyInto(ua);

return ua;

}

我们看到extURL的构建是dirs[i].list()产生的,但是看到这个File.list()函数,我们就要吐血了,它不保证列出的文件顺序,尤其是不保证它们按照fileName字母顺序出现。 也就是说对于在extdirs下放置的类的载入顺序是依赖于系统实现的(即File.list()),因此在ExtClassLoader中,如果我们采用方法二是行不通的。

AppClassLoader的classPath构建过程:

1.获取classPath,即系统属性:“java.class.path”

2.将classPath按分分隔符来拆分构建URL,并存储到:urls数组

3.用urls构建ucp(URLClassPath)。

注意:这里面与ext稍有不同的是第二步,这里的没有对classPath的目录进行扩展。因此在“java.class.path”中的顺序即定义了载入的顺序,可以利用这个来控制载入类顺序。

在jetty容器中的WebAppClassLoader我们的web-inf/lib目录的构建webAppClassLoader时采用的方式与ExtClassLoader相同的方式:

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);

}

}

}

}

对于文件FileResource中的定义:

public String[] list(){

String[] list =_file.list();

if (list==null)

return null;

for (int i=list.length;i-->0;){

if (newFile(_file,list[i]).isDirectory() && !list[i].endsWith("/"))

list[i]+="/";

}

return list;

}

因此在jetty应用的lib中jar包在classPath顺序也是依赖于File.list()的实现。同样因为这个File的list()方法,不保证任何顺序。

综上,在我们应用中要保证所有依赖仅保留一个版本,就不会出问题啦,否则不同的运行环境可能就会有问题。

问题:sealingviolation

现象:sealingviolation: packag is sealed

解析:这个涉及到jetty的加载机制 Jetty Classloading

Jetty做为Servlet容器,为了遵守Servlet的规范,类加载体系中重写了loadClass()方法,改变了类加载机制不再是java2中的父委托机制,先把这个loaderClass()方法贴出来:

protected synchronized Class<?> loadClass(Stringname, boolean resolve) throws ClassNotFoundException{

Class<?> c= findLoadedClass(name);

ClassNotFoundException ex= null;

boolean tried_parent= false;

booleansystem_class=_context.isSystemClass(name);

boolean server_class=_context.isServerClass(name);

if (system_class && server_class){

return null;

}

if(c == null && _parent!=null &&(_context.isParentLoaderPriority() || system_class) && !server_class){

tried_parent= true;

try {

c= _parent.loadClass(name);

if (Log.isDebugEnabled())

Log.debug("loaded " + c);

}

catch (ClassNotFoundException e) {

ex= e;

}

}

if (c == null) {

try {

c= this.findClass(name);

}

catch (ClassNotFoundException e) {

ex= e;

}

}

if (c == null && _parent!=null && !tried_parent &&!server_class )

c= _parent.loadClass(name);

if (c == null)

throw ex;

if (resolve)

resolveClass(c);

if (Log.isDebugEnabled())

Log.debug("loaded " + c+ " from "+c.getClassLoader());

return c;

}

对于这段代码,在jetty的官网关于classLoading有这样一段解读,说明了为什么jetty中的要重写这个方法:web容器的classLoader的结构与普通java应用有所不同,每一个web应用应该有自己的一个WebAppClassloader,Parent为systemClassloader,层次结构也遵守java2中的结构,但是Servlet规范对于servlet容器是有规定的,即它不是完全采用parent first机制装载Class,它是这样规定的:

1.对于WEB-INF/lib 和 WEB-INF/classes下的类优先于ParentClassLoader被加载,即child first机制.

2.系统类如:java.lang.String 在子优先的方式中是不允许被WebAppClassLoader加载的,避免了在WEB-INF/lib or WEB-INF/ 替换了系统类.但不幸的是,这个规范并没有明确定义哪些是系统类

3.Server的实现类不允许在任何的ClassLoader中访问.但也规范中也没有明确给出哪些是Server实现类.

对于上面的1,2,3都是可以进行配置的,比如:classloader的优先级child first 还是parent first;哪些是System Class,哪些是Server Class,都可以配置的,例如通过jetty-web.xml。

Jetty的这种ClassLoading结构可能会出现一种问题就是:如果应用的web-inf/lib 与 jdk下ext目录有相同的类的Jar包,并且这些jar中的类有被定义为Sealed的,则会出现jar Sealed问题。因为在同一package下有些类是在WebAppClassLoader中加载,有些是在ExtClassLoader中加载。对于这个的解决方法,就是可以利用上面的第2点,我们可以把这些类指定为System Class,采用parent first来加载,这样就不会被WebAppClassLoader加载,也就不会出现sealed
violation。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: