tomcat学习步骤,附带打破双亲委派模型企业应用实战
1. tomcat入门
入门模块仅做学习大纲梳理,忽略了具体操作指引。
Tomcat的三种部署模式:
简单架构模型
连接器的非阻塞模式(NIO)
通道(Channel)、缓冲区(Buffer)、选择器(Selector)
容器container的责任链模式:
1.请求被Connector组件接收,创建Request和Response对象。
2.Connector将Request和Response交给Container,先通过Engine的pipeline组件流经内部的每个Valve。
3.请求流转到Host的pipeline组件中,并且经过内部Valve的过。
4.请求流转到Context的pipeline组件中,并且经过内部的Valve的过滤。
5.请求流转到Wrapper的pipeline组件中,并且经过内部的Valve的过滤。
6.Wrapper内部的WrapperValve创建FilterChain实例,调用指定的Servlet实例处理请求。
7.返回。
2.tomcat架构进阶
tomcat顶层结构
Server:服务器的意思,代表整个tomcat服务器,一个tomcat只有一个Server;
Service:Server中的一个逻辑功能层, 一个Server可以包含多个Service;
Connector:称作连接器,是Service的核心组件之一,一个Service可以有多个Connector,主要是连接客户端请求;
Container:Service的另一个核心组件,按照层级有Engine,Host,Context,Wrapper四种,一个Service只有一个Engine,其主要作用是执
行业务逻辑;
Jasper:JSP引擎;
Session:会话管理;
Connector解析
Connector使用ProtocolHandler来处理请求的Endpoint用来处理底层Socket的网络连接。
Processor用于将Endpoint接收到的Socket封装成Request。
Adapter充当适配器,用于将Request转换为ServletRequest交给Container进行具体的处理。
ProtocolHandler由包含了三个部件:Endpoint、Processor,Adapter
Container解析
service中的name是catalina,engine中的name也是catalina,这个就证明一个service就只有一个engine, 一个engine可以有多个host主机
host,站点,虚拟主机。
Engine:引擎、只有一个定义了一个名为Catalina的Engine
Host:站点、虚拟主机一个Engine包含多个Host的设计,使得一个服务器实例可以承担多个域名的服务,是很灵活的设计
Context:一个应用默认配置下webapps下的每个目录都是一个应用
Wrapper:一个Servlet
结合tomcat目录结构理解上面的4个容器之间的关系
3.tomcat类加载机制
引用自:https://www.cnblogs.com/ghoster/p/7602158.html
1.主流的Java Web服务器,如Tomcat、Jetty、WebLogic、WebSphere等都实现了自己定义的类加载器(一般都不止一个)。因为一个功能健全的web服务器要解决一下几个问题:
1)部署在一个服务器上的两个web应用程序所使用的Java类库可以实现互相隔离。这是最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相独立使用
2)部署在同一个服务器上的两个Web应用程序所使用的Java类库可以互相共享。这个需求也非常很常见,如,用户可能有10各使用Spring组织的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费-这主要不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到服务器内存,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险
3)服务器需要尽可能地保证自身的安全不受部署的Web应用程序影响。目前,有许多主流的Java Web服务器自身也是使用Java语言来实现的。因此,服务器本身也有类库依赖的问题,一般来说,基于安全考虑,服务器所使用的类库应该与应用程序的类库相互独立
4)支持jsp应用的Web服务器,大多数都需要支持HotSwap功能。我们知道,jsp文件最终要编译成Java Class才能由虚拟机执行,但jsp文件由于其纯文本存储的特性,运行时修改的概率远远大于第三方类库或程序自身的Class文件。而且ASP、PHP和JSP这些网页应用也把修改后无需重启作为一个很大的“优势”来看待,因此,“主流”的Web服务器都会支持JSP的热替换,当然也有“非主流”的,如运行在生产模式下的WebLogic服务器默认就不会处理JSP文件的变化
由于存在上述问题,在部署Web应用时,单独的一个ClassPath就无法满足需求了,所以各种Web服务器都“不约而同”地提供了好几个ClassPath路径供用户存放第三方类库,这些路径一般都以lib或classes命名,被放置到不同路径中的类库,具备不同的访问范围和服务对象,通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库。
2.Tomcat对用户类库与类加载器的规划
在其目录结构下有三组目录(“/common/*”、“/server/*”、“/shared/*”)可以存放Java类库,另外还可以加上Web应用程序本身的目录“/WEB-INF/*”,一共4组,把Java类库放置在这些目录中的含义分别如下:
1)放置在/commom目录中:类库可被Tomcat和所有的Web应用程序共同使用
2)放置在/server目录中:类库可被Tomcat使用,对所有的Web应用程序都不可见
3)放置在/shared目录中:类库可被所有的Web应用程序所共同使用,但对Tomcat自己不可见
4)放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见
为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,所下图:
最上面的三个类加载器是JDK默认提供的类加载器,这三个加载器的的作用之前也说过,这里不再赘述了,可以在http://www.cnblogs.com/ghoster/p/7594224.html简单了解。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebAppClassLoader则是Tomcat自己定义的类加载器,他们分别加载/common/*、/server/*、/shared/*和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个jsp文件对应一个Jsp类加载器
从上图的委派关系可以看出,CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的哪一个Class,它出现的目的就是为了被丢弃:当服务器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过在建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能
4.打破双亲委派模型企业级应用实战
需求背景:在使用多数据源的情况下,同时连接某大厂魔改的数据库mppdb和pgsql数据库的时候。出现了无法同时加载mppdb和pgsql的驱动的问题。
原因分析:spring在启动时会调用应用类加载器加载org目录下的.class文件,同时spring自定义类加载器分别加载BOOT-INF,META-INF 目录下的文件,mppdb 与pgsql驱动的本质都是.jar文件,所以会放在META-INF目录下的lib目录下。
又因为mppdb是某国内大厂以pgsql为蓝本魔改的,遗留下一个问题,mppdb与mysql驱动的类名是完全一致的。而两驱动都放在lib目录下,由同一个类加载器来加载,就会出现以下情况:当第一个驱动pgsql加载时,使用双亲机制正常加载,
当第二个驱动mppdb再次加载时,会判断该类是否加载过,此时,由于类加载器一致,类名一致,类加载器会误认为mppdb驱动已经加载过了,实际是之前加载的pgsql驱动,所以spring不会再去加载mppdb,而是从缓冲中把pgsql的驱动抛出,
从而导致了,spring在启动时无法同时加载mppdn驱动和pgsql驱动。
解决方案:
方案一:遵循双亲委派模型
在spring容器启动后(此时spring已经加载到其中一个驱动,假设是pgsql驱动),新开一个jvm进程,在该进程中使用扩展类加载器加载mppdb驱动,根据双亲委派模型,扩展类加载器是应用类加载器的父类,当需要加载驱动时会优先委派父类加载,
所以同一个类在父类加载器和子类加载器都能加载的情况下,父类加载器总能优先加载的。这样就保证了,新起的进程总是能加载到扩展类加载器加载的mppdb驱动。
方案二:打破双亲委派模型
在spring容器启动后(此时spring已经加载到其中一个驱动,假设是pgsql驱动),不必新开进程,还是在该spring的进程内,使用应用类加载器XMLClassLoader去加载mppdb驱动,如果此时使用双亲委派模型,会加载到spring初始化时加载到的驱动,即pgsql驱动,
故而 需要将XMLClassLoader 的父加载器设置为null,禁止其父加载器加载,而由其自己加载,即可以成功加载mppdb驱动,具体实现涉及公司信息安全,在此仅提供问题解决思路。