之前在WebAppContext章节有提到过Jetty的类加载器WebAppClassLoader,不过讲的不是很深入,这里再重新梳理一次
类加载器的重要性不言而喻,而对于像Jetty这样的Web容器来说,更是非常特别,它最特殊的地方就是破坏了双亲委派原则,实现了一套特殊的类加载机制,接下来我们就来分析
通常的情况下对于双亲委派原则是具备很多优点的,它要求所有的类加载都先委托父类加载器加载,这样保证同一个类名在内存中只存在一份定义,这样可以既可以防止重复加载,还能保证系统类(如:java. , javax. 等)的安全性。但是在某些场景下我们双亲委派就不是那么灵活了,我们需要破坏这种规则
类隔离
对于Web容器而言,它里面会集成很多Web应用,例如我们可以用Jetty的同一个进程下面部署多个网站,每个网站具备不同的url前缀来区分,而对于不同的网站应用它们可能依赖了同一个jar包库的不同版本。如果使用双亲委派,那整个Jetty容器就只存在一份库中类的定义,那就可能导致其中某些的网站无法使用,因此对于Jetty而言需要做 类隔离 ,类隔离就是破坏双亲委派最核心的一个因素,它允许在同一个Web进程下面存在多份同名类的定义
热加载(热部署)
对于Web容器来说,热加载也是一个比较实用的技术,例如可以实现应用的热替换,我们可以在网站运行的时候直接替换里面类定义,而不需要重启Web容器
首先来看官方的一份文档
从文档中可以看出:
那在Jetty里面核心的类加载器自然就是WebAppClassLoader
如果程序没有显式指定类加载器,在使用WebAppContext的时候会默认创建WebAppClassLoader作为类加载器
上图构造方法,首先super初始化父类(WebAppClassLoader继承自URLClassLoader),获取父类加载器,然后是添加jar、zip两种格式的文件后缀
通常我们使用类加载器就是调用loadClass方法,如上图,最终我们调用了图中的第二个方法,resolve=false,表示不解析该类
同常规的类加载一样,都需要加一把锁,防止并发问题,然后调用如下图findLoadedClass看当前是否已经加载过该类,在利用WebAppContext里面存放的系统类名、服务端类名,来判断是否是系统、服务端的类,如果既是系统类又是服务端类,则返回null,否则当前如果该类没有加载过,并且父类加载器存在,并且配置的是父类加载器优先或是系统类,并且不是服务端类,就使用父类加载器加载,从这里可以看到:如果设置了父类加载器优先,只能加载系统类、应用类,但不能加载容器的服务端类。通常情况下,父类加载器优先是关闭的,也就是使用容器类加载器优先。如果上面都没加载成功,调用WebAppClassLoader自身的findClass来加载(后面讲)。如果容器类加载器也加载不了,如果没使用过父类加载器加载就使用父类加载器加载(前提是这个类不是服务端类),这样其实可以看到,默认这个WebAppClassLoader是自己优先加载,最后才委托父类加载器加载。最后如果设置了resolve,就调用resolveClass解析该类
接下来继续分析findClass方法
如果定义了类转换器,就会将当前类的字节数据传入转换器进行转换,并定义class,否则直接调用父类的findClass查找,很简单,就不多讲了
其实前面讲了都是关于一个Web容器应该完成的多应用部署,所以必须自定义类加载器,并且还得打破双亲委派原则,但是还有一种情况,就是我们部署的时候就是一个单应用,不存在多应用,也不需要热部署,那就是通常我们用的Spring Boot嵌套式Web容器,一个进程就一个服务,那其实我们就不用这个WebAppClassLoader了,只需要使用双亲委派的这种加载模式,从Spring Boot的实现里面就能看到,如下图
Spring Boot在集成内嵌Jetty的时候,会给WebAppContext设置一个类加载器,而这个类加载器是this.resourceLoader.getClassLoader(),可以看到是sun.misc.Launcher$AppClassLoader,即AppClassLoader,这个是Java自带的双亲委派模式的类加载器
所以我们可以看到Spring Boot默认使用的不一定就是WebAppClassLoader
Java类加载器是比较重要的一个概念,很多时候我们面试的时候会经常被问到,为什么要自定义类加载器,为什么要破坏双亲委派原则,其实从这篇文章我们可以比较清楚的看到是什么原因了,希望对大家有所帮助~