设为首页 收藏本站
查看: 716|回复: 0

[经验分享] Jetty类加载机制

[复制链接]

尚未签到

发表于 2017-2-26 11:06:38 | 显示全部楼层 |阅读模式
问题导出

关于主流的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类加载器

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-347378-1-1.html 上篇帖子: jetty的下载,编译,安装等 下篇帖子: eclipse jee配置jetty的两种方法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表