前文介绍了双亲委派模型,以及我们在何时需要打破双亲委派模型。本文我们聊一下Tomcat作为一个Web容器,是如何加载类的,是否可以用默认的类加载器机制。
Web容器的类加载需求
一个Web容器可能部署不止一个应用程序,不同的应用程序可能会依赖同一个类库的不同版本,在同一个Tomcat服务器上可能有多份相同类库。因此各个应用程序的类库需要保证相互隔离。另外Web容器与应用程序也可能用了不同版本的类库,为避混淆,容器的类库和程序的类库也需要隔离开。
如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器是不管什么版本的,只要全限定类名一样,就只加载一份。
Tomcat为了实现Web容器下各个应用之间的类库隔离,也打破了双亲委派模型的约定。
Tomcat的类加载器
当Tomcat启动时,会创建多种类加载器。除了JVM自带的启动类加载器、扩展类加载器、应用程序类加载器以外,Tomcat自定义的类加载器包括:Common类加载器、Catalina类加载器、Shared类加载器、WebApp类加载器、JSP类加载器等。
其中Common类加载器、Catalina类加载器、Shared类加载器原先分别负责加载Tomcat目录下 /common/*、 /server/*、 /shared/* 下的类库,不过Tomcat6这三个目录都已经合并到 CATALINA_HOME/lib 目录中。WebApp类加载器负责加载 /WebApp/WEB-INF/* 中的Java类库。
Tomcat自定义类加载器的区别:
Common类加载器:加载路径的class可以被Tomcat容器本身以及各个Webapp访问;
Catalina类加载器:加载路径的class对于Tomcat容器可见,对于Webapp不可见;
Shared类加载器:加载路径的class对于所有Webapp可见,但对于Tomcat容器不可见;
Webapp类加载器:加载路径中的class只对当前Webapp可见。
Tomcat类加载器
类加载器的工作机制
类加载器的关系图
启动类加载器 和 扩展类加载器 负责加载JVM启动所需的核心类(jre/lib/*.jar)以及标准扩展类(jre/lib/ext/*.jar)。
应用程序类加载器
负责加载Tomcat启动所需的类,如bootstrap.jar,位于CATALINA_HOME/bin下。通常在 catalina.bat 或者 catalina.sh 中指定。
Common 通用类加载器
加载Tomcat使用的类以及Web应用的通用类,位于 CATALINA_HOME/lib 下,比如servlet-api.jar等。Common类加载器、Catalina类加载器、Shared类加载器在默认的配置中,它们其实都是同一个对象,即commonLoader。
WebApp 类加载器
每个应用在部署后,都会创建一个唯一的WebApp类加载器。该类加载器会加载位于 WEB-INF/lib下的jar文件中的类 和 WEB-INF/classes下的class文件。
当应用需要到某个类时,则会按照以下顺序进行类加载:
使用启动类加载器加载
使用应用程序类加载器加载
使用WebApp类加载器在WEB-INF/classes中加载
使用WebApp类加载器在WEB-INF/lib中加载
使用Common类加载器在CATALINA_HOME/lib中加载
Tomcat 为了实现不同应用之间的隔离性,没有遵守双亲委派的约定,每个WebAppClassLoader加载自己目录下的class文件,不会传递给父类加载器。
JSP页面的类加载
每一个JSP文件最终都要编译成class文件才能在虚拟机中运行,每一个JSP页面也对应一个JSP类加载器。
Web容器需要支持JSP文件修改后不用重启。JSP文件如果修改了,但类名还是一样的,类加载器会直接取方法区中已经存在的,如何加载修改后的JSP文件呢?
因为每个JSP文件对应一个唯一的类加载器,当这个JSP文件修改了,那么就直接卸载掉这个jsp页面对应的类加载器,重新创建一个新的类加载器,重新加载JSP文件。
我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注,谢谢。