Jetty类加载机制

问题导出

关于主流的Java Web服务器Tomcat、Jetty、WebLogic、WebSphere等,先提出几个问题:

  • 部署在同一个服务器上的两个Web应用程序所使用的Java类库是如何实现相互独立?
  • 部署在同一个服务器上的两个Web应用程序所使用的Java类库是如何实现相互共享?
  • 服务器如何保证自身的安全不受部署的Web应用程序的影响?
  • 支持JSP应用的Web服务器,如何支持HotSwap功能的?

上述问题,可以由Web服务器的类加载系统来实现。例如,Tomcat的ClassLoader体系结构如下所示:

首先回顾一下Jvm标准的类加载体系。

Jvm ClassLoader

1) ClassLoader
Java程序并不是一个原生的可执行文件,而是由许多独立的类文件组成,每一个文件对应一个Java类。这些类文件并非立即全部装入内存的,而是根据程序需要装入内存。ClassLoader专门负责类文件装入到内存。 获取类加载器的方式如下: this.getClass().getClassLoader() 得到当前的类加载器 this.getClass().getClassLoader() .getParent() 得到当前类的父类加载器

 

Java类装载器在Java安全体系结构中起着最关重要的作用,是Java安全沙箱的第一道防线。类装载器体系结构在三个方面对Java的沙箱起作用:

  • 它防止恶意代码去干涉善意的代码
  • 它守护了被信任的类库的边界
  • 它将代码归入某类(称为保护域),该类确定了代码可以进行哪些操作

类装载器体系结构可以防止恶意代码去干涉善意的代码,这是通过为不同的类装载器装入的类提供不同的命名空间来实现的。

 

Btw: 另外一种加载类的方法:Class.forName,Class.forName的一个很常见的用法用来加载数据库驱动。

2) ClassLoader体系结构

  • 启动类加载器(BootStrap ClassLoader): 是最顶层的类加载器,由C++编写而成,并不继承自 java.lang.ClassLoader,并且已经内嵌到JVM中了。主要用来读取Java的核心类库jre/lib/rt.jar
  • 扩展类加载器(Extension ClassLoader): 是用来读取Java的扩展类库,读取jre/lib/ext/*.jar
  • 系统类加载器(App ClassLoader): 它根据Java应用的类路径(classpath)来加载Java类。一般来说,Java应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  • 自定义类加载器(Custom ClassLoader): 开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
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 /**   * User: niyong@meituan.com   * Date: 14-8-19   * Time: 下午5:42   *   第一行结果表示:ClassLoaderTest的类加载器是AppClassLoader   第二行结果表示:AppClassLoader的类加载器是ExtClassLoder   第三行结果表示:null表示ExtClassLoader的类加载器是Bootstrap ClassLoader   *   */ public  class  ParentClassLoaderTest {      public  static  void  main(String[] args) {          ClassLoader loader = ParentClassLoaderTest. class .getClassLoader();          while  (loader !=  null ) {              System.out.println(loader.getClass().getName());              loader = loader.getParent();          }          System.out.println(loader);      } }   result:   sun.misc.Launcher$AppClassLoader sun.misc.Launcher$ExtClassLoader null
3) 双亲委派模型

从1.2版本开始,Java引入了双亲委派模型,从而更好的保证Java平台的安全。

 在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象,若parent不能装载,则由parent的请求者去装载,如果此时自己也不能加载,则产生java.lang.NoClassDefFoundError。具体委托逻辑在java.lang.ClassLoader的loadClass(String name,boolean resolve)方法中:

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 32 33 34 35 36 protected  Class loadClass(String name,  boolean  resolve)      throws  ClassNotFoundException {      synchronized  (getClassLoadingLock(name)) {          // 先检查,该类是否已经被加载过          Class c = findLoadedClass(name);          if  (c ==  null ) {              long  t0 = System.nanoTime();              try  {                  if  (parent !=  null ) {                      //递归调用,委托给父类类加载器                      c = parent.loadClass(name,  false );                  else  {                      c = findBootstrapClassOrNull(name);                  }              catch  (ClassNotFoundException e) {                  // ClassNotFoundException thrown if class not found                  // from the non-null parent class loader              }              if  (c ==  null ) {                  // If still not found, then invoke findClass in order                  // to find the class.                  long  t1 = System.nanoTime();                  c = findClass(name);                  // this is the defining class loader; record the stats                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);                  sun.misc.PerfCounter.getFindClasses().increment();              }          }          if  (resolve) {              resolveClass(c);          }          return  c;      } }

      例如,java.lang.String这个类是由Bootstrap ClassLoader加载的,它是最终的父加载器,如果用户自己写一个java.lang.String从自定义的类加载器加载,那么根据双亲委派模型,真正的String由Bootstrap ClassLoader加载,而自定义的java.lang.String永远加载不进来。

      双亲委派模型有一个缺陷,如果父ClassLoader想加载子ClassLoader中的类比较困难,而在有的应用中这种加载方式是需要的,比如JNDI,Servlet.

4) 全盘负责

所谓全盘负责,即当一个ClassLoader加载一个Class的时候,这个Class所依赖和引用的所有Class也由这个ClassLoader负责载入,除非显示的使用另外一个ClassLoader载入。例如,由于java.lang.String是由Bootstrap ClassLoader载入的,那么String中引用的类如CharSequence等默认都是使用Bootstrap ClassLoader载入。 

5) 命名空间

由不同的类装载器装载的类将被放在虚拟机内部的不同命名空间。命名空间由一系列唯一的名称组成,每一个被装载的类有一个名字。JAVA虚拟机为每一个类装载器维护一个名字空间。例如,一旦JAVA虚拟机将一个名为Volcano的类装入一个特定的命名空间,它就不能再装载名为Valcano的其他类到相同的命名空间了。可以把多个Valcano类装入一个JAVA虚拟机中,因为可以通过创建多个类装载器从而在一个JAVA应用程序中创建多个命名空间。

 

Jetty类加载器

Jetty的ClassLoader架构 

Jetty,Tomcat等web容器通常都会对ClassLoader做扩展,因为一个正常的容器至少要保证其内部运行的多个webapp之间:私有的类库不受影响,并且公有的类库可以共享。这正好发挥ClassLoader的层级划分优势。 Jetty中有一个org.mortbay.jetty.webapp.WebAppClassLoader,负责加载一个webapp context中的应用类,WebAppClassLoader以系统类加载器作为parent,用于加载系统类。不过servlet规范使得web容器的ClassLoader比正常的ClassLoader委托模型稍稍复杂,servlet规范要求:

  • WEB-INF/lib和WEB-INF/classes优先于父容器中的类加载,比如WEB-INF/classes下有个XYZ类,classpath下也有个XYZ类,jetty中优先加载的是WEB-INF/classes下的,这与正常的父加载器优先相反(childfirst)。
  •  系统类比如java.lang.String不遵循第一条, WEB-INF/classes或WEB-INF/lib下的类不能替换系统类。不过规范中没有明确规定哪些是系统类,jetty中的实现是按照类的全路径名判断。
  •  Server的实现类不被应用中的类引用,即Server的实现类不能被人和应用类加载器加载。不过,同样的,规范里没有明确规定哪些是Server的实现类,jetty中同样是按照类的全路径名判断。

为了处理上述三个问题,jetty的应用类加载器(org.mortbay.jetty.webapp.WebAppClassLoader)做了些特殊处理。

Jetty的ClassLoader体系结构如下所示:

 

WebAppClassLoader的实现

WebAppClassLoader的构造器

1 2 3 4 5 6 7 8 9 public  WebAppClassLoader(ClassLoader parent, WebAppContext context)      throws  IOException {      super ( new  URL[]{},parent!= null ?parent              :(Thread.currentThread().getContextClassLoader()!= null ?Thread.currentThread().getContextClassLoader()                      :(WebAppClassLoader. class .getClassLoader()!= null ?WebAppClassLoader. class .getClassLoader()                              :ClassLoader.getSystemClassLoader())));      ...... }

WebAppClassLoader还是按照正常的范式设置parent ClassLoader,如果当前线程上下文中设定了ClassLoader就以当前线程上下文类加载器为父ClassLoader,否则使用WebAppClassLoader的加载器,如果还没有,就采用系统类加载器。

 

下面看一下WebAppClassLoader的loadClass()方法:

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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 protected  synchronized  Class loadClass(String name,  boolean  resolve)  throws  ClassNotFoundException {      //检查类是否已经加载      Class c= findLoadedClass(name);      ClassNotFoundException ex=  null ;      boolean  tried_parent=  false ;            //判断该类是否为系统类或server类      //如果该类未加载且父加载器不为空且设置了父加载器优先或类类为系统类,则尝试使用父加载器加载该类      if  (c ==  null  && _parent!= null  && (_context.isParentLoaderPriority() || isSystemPath(name)) )      {          tried_parent=  true ;          try          {              c= _parent.loadClass(name);              if  (Log.isDebugEnabled())                  Log.debug( "loaded "  + c);          }          catch  (ClassNotFoundException e)          {              ex= e;          }      }      //如果不是父加载器优先或者父加载器未加载到该类,使用WebAppClassLoader加载该类      if  (c ==  null )      {          try          {              c=  this .findClass(name);          }          catch  (ClassNotFoundException e)          {              ex= e;          }      }      //如果是不是父加载器优先,并且WebAppClassLoader未加载到该类,且该类不是server类, 尝试使用父加载器加载该类      if  (c ==  null  && _parent!= null  && !tried_parent && !isServerPath(name) )          c= _parent.loadClass(name);      //找到则返回,否则抛出ClassNotFoundException      if  (c ==  null )          throw  ex;      if  (resolve)          resolveClass(c);      if  (Log.isDebugEnabled())          Log.debug( "loaded "  + c+  " from " +c.getClassLoader());            return  c; }
ClassLoader Priority

上述过程涉及一个加载器优先级的概念,这也是针对前述第一条规范中WEB-INF/lib和WEB-INF/classes类优先的处理。jetty中父加载器优先的配置项可以通过环境变量

   org.eclipse.jetty.server.webapp.parentLoaderPriority=false(默认)/true来设置

也可以通过

   org.eclipse.jetty.webapp.WebAppContext.setParentLoaderPriority(boolean)方法来设置

优于该配置默认是false,因此在load class过程中优先使用WebAppClassLoader加载WEB-INF/lib和WEB-INF/classes中的类。 当将该配置项设为true时需要确认类加载顺序没有问题。

小结:

       如果是parentfirst或者system_class并且不是server_class,则采用parentfirst策略加载;

       如果是childfirst,则加载顺序为:WebAppClassLoader-->bootstraploader-->ExtClassLoader-->AppClassLoader

设置系统类

规范2中约定系统类不能被应用类覆盖,但是没有明确规定哪些时系统类,jetty中以类的package路径名来区分,当类的package路径名位包含于以下路径时,会被认为是系统类。WebAppContext中配置如下:

1 2 3 4 5 6 7 8 9 10 private  String[] _systemClasses = {      "java." ,      "javax." ,      "org.mortbay." ,      "org.xml." ,      "org.w3c." ,      "org.apache.commons.logging." ,      "org.apache.log4j." };

因此,我们可以通过 org.eclipse.jetty.webapp.WebAppContext.setSystemClasses(String Array)或者org.eclipse.jetty.webapp.WebAppContext.addSystemClass(String)来设置系统类。 系统类是对多有应用都可见。

设置Server类

规范3中约定Server类不对任何应用可见。Jetty同样是用package路径名来区分哪些是Server类。WebAppContext中配置如下:

1 2 3 4 5 6 7 8 9 10 private  String[] _serverClasses = {      "-org.mortbay.jetty.plus.annotation." ,        // don't hide      "-org.mortbay.jetty.plus.jaas." ,              // don't hide      "-org.mortbay.jetty.plus.naming." ,            // don't hide      "-org.mortbay.jetty.plus.jaas." ,              // don't hide      "-org.mortbay.jetty.servlet.DefaultServlet" // don't hide      "org.mortbay.jetty." ,      "org.slf4j." };

我们可以通过, org.eclipse.jetty.webapp.WebAppContext.setServerClasses(String Array) 或org.eclipse.jetty.webapp.WebAppContext.addServerClass(String)方法设置Server类。 注意,Server类是对所有应用都不可见的,但是WEB-INF/lib下的类可以替换Server类。

自定义WebApp ClassLoader

当默认的WebAppClassLoader不能满足需求时,可以自定义WebApp ClassLoader,不过Jetty建议自定义的ClassLoader要扩展于默认的WebAppClassLoader实现。

 

参考

 http://www.ibm.com/developerworks/cn/java/j-lo-classloader/  深入探讨Java类加载器


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部