Engine即为全局引擎容器,它的标准实现是StandardEngine。
Host在整个Servlet引擎中抽象出Host容器用于表示虚拟主机,它是根据URL地址中的主机部分抽象的,一个Servlet引擎可以包含若干个Host容器,而一个Host容器可以包含若干个Context容器。在Tomcat中Host的标准实现是StandardHost,它从虚拟主机级别对请求和响应进行处理。
一个Context对应一个Web应用程序,但Web项目的组成比较复杂,它包含很多组件。对于Web容器,需要将Web应用程序包含的组件转换成容器的组件。
文章目录
- 1. Container
- 1.1 ContainerBase
- 2. Engine接口
- 3. StandardEngine
- 4. Host接口
- 5. StandardHost
- 5.1 HostConfig
- 6. Context接口
- 6.1 Context相关的配置文件
- 6.2 StandardContext
- 6.3 Tomcat的类加载机制
1. Container
下面有段注释十分重要:
/** * A Container是一个对象,它可以执行从 *客户端,并返回基于这些请求的响应。一个容器可以 *可选地支持一个管道阀门处理请求 *通过实现管道接口,在运行时配置顺序 *。*容器将存在于Catalina的几个概念层次上。的 *以下是常见的例子:* <ul>* <li><b>Engine</b> - 整个Catalina servlet引擎的表示, 引擎 -整个Catalina servlet引擎的表示,* <li><b>Host</b> -包含数字的虚拟主机的表示形式的上下文。* <li><b>Context</b> - 表示单个ServletContext,它将 *通常为受支持的servlet包含一个或多个包装器。* <li><b>Wrapper</b> -单个servlet定义的表示* (如果servlet本身支持多个servlet实例) *实现SingleThreadModel)。* </ul> *给定的卡特琳娜部署不需要包括所有的容器 *以上描述的级别。例如,管理应用程序 *嵌入式网络设备(如路由器)可能只包含 *一个上下文和几个包装器,甚至一个包装器如果 应用程序相对较小。因此,容器实现 *需要进行设计,使它们能在不在场的情况下正常运行 *指定部署中的父容器。* <p> *容器还可以与许多支持组件相关联 *提供可能被共享的功能(通过附加到 *父容器)或单独定制。以下支持 *目前认可的组件:* <ul>* <li><b>Loader</b> - 用于集成新Java类的类装入器 将此容器放入运行Catalina的JVM中。* <li><b>Logger</b> -实现log()方法* ServletContext接口的属性。* <li><b>Manager</b> -相关联的会话池的管理这个容器。* <li><b>Realm</b> - 安全域的只读接口,用于验证用户身份及其对应角色。* <li><b>Resources</b> - 支持访问静态的JNDI目录上下文 *资源,启用自定义链接到现有的服务器组件时 ,Catalina被嵌入到一个更大的服务器中。* </ul>*/
public interface Container extends Lifecycle {
1.1 ContainerBase
ContainerBase实现Container,定义了容器所需要的一些共有组件与方法。
public abstract class ContainerBase extends LifecycleMBeanBaseimplements Container {/*** 属于此容器的子容器,按名称键入。*/protected final HashMap<String, Container> children = new HashMap<>();/*** 处理器延迟这个组件。*/protected int backgroundProcessorDelay = -1;/***此容器的容器事件监听器。实现为一个* CopyOnWriteArrayList,因为监听器可能会调用方法来添加/删除*自己或其他听众,并与读写洛克,将触发*死锁。*/protected final List<ContainerListener> listeners = new CopyOnWriteArrayList<>();/**与此容器相关联的日志程序实现。*/protected Log logger = null;/***关联的日志记录器名称。*/protected String logName = null;/**与此容器相关联的集群。*/protected Cluster cluster = null;private final ReadWriteLock clusterLock = new ReentrantReadWriteLock();/***容器名称。*/protected String name = null;/*** 此容器是其子容器的父容器。*/protected Container parent = null;/***安装加载器时要配置的父类加载器。*/protected ClassLoader parentClassLoader = null;/*** 与此容器相关联的管道对象。*/protected final Pipeline pipeline = new StandardPipeline(this);/*** 与此容器相关联的领域。*/private volatile Realm realm = null;/*** 用于控制对领域的访问的锁。*/private final ReadWriteLock realmLock = new ReentrantReadWriteLock();/*** 这个包的字符串管理器。*/protected static final StringManager sm =StringManager.getManager(Constants.Package);/*** 当添加子节点时是否会自动启动。*/protected boolean startChildren = true;/***此组件的属性更改支持。*/protected final PropertyChangeSupport support =new PropertyChangeSupport(this);/***后台线程。*/private Thread thread = null;/***后台线程完成信号。*/private volatile boolean threadDone = false;/***该容器通常处理的请求使用的访问日志*在处理链中较早处理的。*/protected volatile AccessLog accessLog = null;private volatile boolean accessLogScanComplete = false;/***任何可用于处理启动和停止事件的线程数*与此容器关联的子容器。*/private int startStopThreads = 1;protected ThreadPoolExecutor startStopExecutor;
2. Engine接口
注意Engine接口与Host接口都继承了Container,其共有的组件都继承自ContainerBase类。
/*** An <b>Engine</b> 是容器是否表示整个Catalina servlet* engine. 它在以下类型的场景中非常有用:*您希望使用拦截器来查看每个被处理的请求整个引擎。您希望使用独立的HTTP连接器运行Catalina,但仍然是这样希望支持多个虚拟主机。*一般来说,你不会使用引擎部署卡特琳娜连接 *连接到web服务器(如Apache),因为连接器将具有 *利用web服务器的功能来确定上下文(或 *甚至可能是哪个包装)应该被用来处理这个请求。* 附加到引擎的子容器通常是实现 主机(表示虚拟主机)或上下文(表示个体)的* *一个单独的servlet上下文),取决于引擎实现。*如果使用引擎,引擎总是卡特琳娜的顶级容器 *层次结构。因此,实现的setParent()方法 *应该抛出IllegalArgumentException。*/
public interface Engine extends Container {/*** @return 获取此引擎的默认主机名。*/public String getDefaultHost();/*** 设置此引擎的默认主机名。** @param defaultHost The new default host*/public void setDefaultHost(String defaultHost);/*** @return 此引擎的JvmRouteId。*/public String getJvmRoute();/*** 设置此引擎的JvmRouteId。** @param jvmRouteId(新的)JVM路由ID。集群中的每个引擎* 必须有唯一的JVM路由ID。*/public void setJvmRoute(String jvmRouteId);public Service getService();/***设置与我们关联的service (如果有的话)。** @param 拥有这个引擎的服务*/public void setService(Service service);
}
3. StandardEngine
Engine标准实现是StandardEngine,包含的主要组件有Host组件、AccessLog组件、Pipeline组件、Cluster组件、Realm组件、LifecycleListener组件和Log组件。
组件主要继承自org.apache.catalina.core.ContainerBase:
-
Host
Host组件是Engine容器的子容器,它表示一个虚拟主机。Host也包含很多其他的组件。 -
AccessLog
Engine容器里的AccessLog组件负责客户端请求访问日志的记录。因为Engine容器是一个全局的Servlet容器,所以这里的访问日志作用的范围是所有客户端的请求访问,不管访问哪个虚拟主机都会被该日志组件记录。 -
Pipeline
Pipeline其实属于一种设计模式,在Tomcat中可以认为它是将不同容器级别串联起来的通道,当请求进来时就可以通过管道进行流转处理。Tomcat中有4个级别的容器,每个容器都会有一个属于自己的Pipeline。 -
Cluster
Tomcat中有Engine和Host两个级别的集群,而这里的集群组件正是属于全局引擎容器。它主要把不同JVM上的全局引擎容器内的所有应用都抽象成集群,让它们能在不同的JVM之间互相通信,使会话同步、集群部署得以实现。 -
2.5 Realm
Realm对象其实就是一个存储了用户、密码及权限等的数据对象,它的存储方式可能是内存、xml文件或数据库等。它的作用主要是配合Tomcat实现资源认证模块。
在配置文件的<Engine>节点下配置Realm,则在启动时对应的域会添加到Engine容器中。 -
LifeCycleListener
Engine容器内的生命周期监听器是为了监听Tomcat从启动到关闭整个过程的某些事件,然后根据这些事件做不同的逻辑处理。 -
Log
日志组件负责的事情就是不同级别的日志输出,几乎所有系统都有日志组件。
4. Host接口
/** * A Host是一个容器,它表示一个虚拟主机在 * Catalina servlet引擎。它在下列情况下是有用的:* <ul>* <li>您希望使用拦截器来查看每个被处理的请求*由这个特定的虚拟主机。* <li>您希望使用独立的HTTP连接器运行Catalina,但仍然是这样*希望支持多个虚拟主机。* </ul>*一般来说,你不会在部署Catalina connected时使用主机 *连接到web服务器(如Apache),因为连接器将具有 *利用web服务器的功能来确定上下文(或 *甚至可能是哪个包装)应该被用来处理这个请求。* <p> *连接到主机的父容器通常是一个引擎,但也可能是 *是一些其他的实现,或者可能被省略,如果没有必要。* <p> 附加到主机的子容器通常是实现 上下文的*(表示单个servlet上下文)。*/
public interface Host extends Container {
接口内方法如下:
5. StandardHost
Host容器包含了若干Context容器、AccessLog组件、Pipeline组件、Cluster组件、Realm组件、HostConfig组件和Log组件。
其中的AccessLog组件、Pipeline组件、Cluster组件、Realm组件和Log组件,继承自ContainerBase。
- Context
每个Host容器包含若干个Web应用(Context)。对于Web项目来说,其结构相对比较复杂,而且包含很多机制,Tomcat需要对它的结构进行解析,同时还要具体实现各种功能和机制,这些复杂的工作就交给了Context容器。Context容器对应实现了Web应用包含的语义,实现了Servlet和JSP的规范。
/*** The Java class name of the default Context implementation class for* deployed web applications.*/private String contextClass ="org.apache.catalina.core.StandardContext";
Context也是比较复杂的一块。
- AccessLog
Host容器的访问日志作用的范围是该虚拟主机的所有客户端的请求访问,不管访问哪个应用都会被该日志组件记录。 - Pipeline
不同级别容器的管道完成的工作都不一样,每个管道要搭配阀门(Valve)才能工作。Host容器的Pipeline默认以StandardHostValve作为基础阀门,这个阀门主要的处理逻辑是先将当前线程上下文类加载器设置成Context容器的类加载器,让后面Context容器处理时使用该类加载器,然后调用子容器Context的管道。 - Cluster略
- Realm略
- HostConfig
见下一小节。
5.1 HostConfig
Host作为虚拟主机容器用于放置Context级别容器,而Context其实对应的就是Web应用,实际上每个虚拟主机可能会对应部署多个应用,每个应用都有自己的属性。
当Tomcat启动时,必须把对应Web应用的属性设置到对应的Context中,根据Web项目生成Context,并将Context添加到Host容器中。另外,当我们把这些Web应用程序复制到指定目录后,还有一个重要的步骤就是加载,把Web项目加载到对应的Host容器内。
当Tomcat启动时,它必须把所有Web项目都加载到对应的Host容器内,完成这些任务的就是HostConfig监听器。
HostConfig实现了Lifecycle接口,当START_EVENT事件发生时则会执行Web应用部署加载动作。
Web应用有三种部署类型:
Descriptor描述符、WAR包以及目录。所以部署时也要根据不同的类型做不同的处理。
Descriptor描述符略过,感兴趣的可以自行搜索。
WAR包类型如下:
WAR包类型的部署是直接读取%CATALINA_HOME%/webapps目录下所有以war包形式打包的Web项目,然后根据war包的内容生成Tomcat内部需要的各种对象。
为了优化多个应用项目部署时间,使用了线程池和Future机制。
目录类型如下:
目录类型的部署是直接读取%CATALINA_HOME%/webapps目录下所有目录形式的Web项目。
优化同上。
6. Context接口
/** * Context是一个表示servlet上下文的容器 因此,一个独立的web应用程序,在Catalina servlet引擎中。 因此,它在几乎所有的部署Catalina(即使a 连接到web服务器(如Apache)的连接器使用web服务器的 *识别处理此请求的适当包装器的功能。 *它还提供了一种方便的机制来使用see的拦截器 每个请求由这个特定的web应用程序处理。* <p> 附加到上下文的父容器通常是宿主,但也可能是宿主 *是一些其他的实现,或者可能被省略,如果没有必要。* <p> 附加到上下文的子容器通常是实现 包装器的*(表示单独的servlet定义)。* <p>*/
public interface Context extends Container, ContextBind {
下面以StandardContext为例
6.1 Context相关的配置文件
- Tomcat的server.xml配置文件中的节点可用于配置Context,它直接在Tomcat解析server.xml时就完成Context对象的创建,而不用交给HostConfig监听器创建。
- Web应用的/META-INF/context.xml文件可用于配置Context,此配置文件用于配置该Web应用对应的Context属性。
- 用%CATALINA_HOME%/conf/[EngineName]/[HostName]/[WebName].xml文件声明创建一个Context。
- Tomcat全局配置为conf/context.xml,此文件配置的属性会设置到所有Context中。
- Tomcat的Host级别配置文件为/conf/[EngineName]/[HostName]/context.xml.default文件,它配置的属性会设置到某Host下面的所有Context中。
6.2 StandardContext
public class StandardContext extends ContainerBaseimplements Context, NotificationEmitter {
其继承的ContainerBase的组件就不过多介绍。
- Wrapper
Context容器会包含若干个子容器,这些子容器就叫Wrapper容器。它属于Tomcat中最小级别的容器,它不能再包含其他子容器,而且它的父容器必须为Context容器。每个Wrapper其实就对应一个Servlet, Servlet的各种定义在Tomcat中就Wrapper的形式存在。
/***将要添加的LifecycleListeners的类名集*通过createWrapper()来创建每个新创建的包装器。*/private String wrapperLifecycles[] = new String[0];
- ErrorPage
每个Context容器都拥有各自的错误页面对象,它用于定义在Web容器处理过程中出现问题后向客户端展示错误信息的页面,这也是Servlet规范中规定的内容。它可以在Web部署描述文件中配置。
private final ErrorPageSupport errorPageSupport = new ErrorPageSupport();
- Manager
Context容器的会话管理器用于管理对应Web容器的会话,维护会话的生成、更新和销毁。每个Context都会有自己的会话管理器,如果显式在配置文件中配置了会话管理器,则Context容器会使用该会话管理器;否则,Tomcat会分配默认的标准会话管理器(StandardManager)。
/*** 与此容器关联的管理器实现。*/protected Manager manager = null;
- DirContext
DirContext接口其实是属于JNDI的标准接口,实现此接口即可实现目录对象相关属性的操作。
例如:通过“/META-INF/context.xml”获取到context.xml的文件。 - JarScanner
/*** Jar扫描器用于搜索可能包含的Jar*配置信息,如tld或web-fragment.xml文件。*/private JarScanner jarScanner = null;
从JarScanner的名字上已经知道它的作用了,它一般包含在Context容器中,专门用于扫描Context对应的Web应用的Jar包。每个Web应用初始化时,在对TLD文件和web-fragment.xml文件处理时都需要对该Web应用下的Jar包进行扫描,因为Jar包可能包含这些配置文件,Web容器需要对它们进行处理。
JarScanner在设计上采用了回调机制,每扫描到一个Jar包时都会调用回调对象进行处理。回调对象需要实现JarScannerCallback接口。
-
过滤器
过滤器提供了为某个Web应用的所有请求和响应做统一逻辑处理的功能。也就是我们意义上理解的过滤器。 -
NamingResource
将配置文件中声明的不同的资源及其属性映射到内存中,这些映射统一由NamingResource对象封装
命名资源的配置有两个地方,分别为Tomcat容器的server.xml文件和每个Web项目的context.xml文件。 -
Mapper
Context容器包含了一个请求路由映射器(Mapper)组件,它属于局部路由映射器,它只能负责本Context容器内的路由导航。即每个Web应用包含若干Servlet,而当对请求使用请求分发器RequestDispatcher以分发到不同的Servlet上处理时,就用了此映射器。 -
WebappLoader(重点部分)
每个Web应用都有各自的Class类和Jar包。一般来说,在Tomcat启动时要准备好相应的类加载器,包括加载策略及Class文件的查找,方便后面对Web应用实例化Servlet对象时通过类加载器加载相关类。因为每个Web应用不仅要达到资源的互相隔离,还要能支持重加载,所以这里需要为每个Web应用安排不同的类加载器对象加载,重加载时可直接将旧的类加载器对象丢弃而使用新的。
每个Web应用对应一个WebappLoader,每个WebappLoader互相隔离,各自包含的类互相不可见。
- ApplicationContext
/*** 与此上下文关联的ServletContext实现。*/protected ApplicationContext context = null;
在Servlet的规范中规定了一个ServletContext接口,它提供了Web应用所有Servlet的视图,通过它可以对某个Web应用的各种资源和功能进行访问。
首先来看ServletContext,包属于JDK的包:
package javax.servlet;
等等方法。。。
对于Tomcat容器,Context容器才是其运行时真正的环境。为了满足Servlet规范,它必须要包含一个ServletContext接口的实现,这个实现就是ApplicationContext。ApplicationContext是ServletContext的标准实现,用它表示某个Web应用的运行环境,每个Tomcat的Context容器都会包含一个ApplicationContext。
public class ApplicationContext implements ServletContext {
ApplicationContext对ServletContext接口的所有方法都进行了实现,所以Web开发人员可以在Servlet中通过getServletContext()方法获得该上下文,进而再对上下文进行操作或获取上下文中的各种资源。但实际上getServletContext()获取到的并非ApplicationContext对象,而是一个ApplicationContext的门面对象ApplicationContextFacade。
门面模式的作用就是提供一个类似代理的访问模式,把ApplicationContext里面不该暴露的方法和属性屏蔽掉,不让Web开发人员访问。
ApplicationContext就是为了满足Servlet标准的ServletContext接口而实现的一个类,它按Servlet的规范要求提供了各种实现方法。
-
InstanceManager
Context容器中包含了一个实例管理器,它主要的作用就是实现对Context容器中监听器、过滤器以及Servlet等实例的管理。其中包括根据监听器Class对其进行实例化,对它们的Class的注解进行解析并处理,对它们的Class实例化的访问权限的限制,销毁前统一调用preDestroy方法等。 -
ServletContainerInitializer
在Web容器启动时为让第三方组件机做一些初始化工作,例如注册Servlet或者Filters等,Servlet规范中通过ServletContainerInitializer实现此功能。
/***方法中的一个条目注册了ServletContainerInitializers (SCIs)* 文件meta - inf / services / javax.servlet。ServletContainerInitializer必须是包含在包含SCI实现的JAR文件中。* <p>* SCI处理的执行与元数据完成的设置无关。 * SCI处理可以通过片段排序来控制每个JAR文件。如果 *定义了绝对顺序,然后只将jar包含在顺序中 *将为科学资讯系统处理。若要完全禁用SCI处理,请使用空 *可以定义绝对顺序。* <p>*SCIs注册对注释(类、方法或字段)和/或感兴趣 *通过{@link javax.servlet.annotation类型。HandlesTypes}注释,被添加到类中。*/
public interface ServletContainerInitializer {/*** Receives notification during startup of a web application of the classes* within the web application that matched the criteria defined via the* {@link javax.servlet.annotation.HandlesTypes} annotation.** @param c The (possibly null) set of classes that met the specified* criteria* @param ctx The ServletContext of the web application in which the* classes were discovered** @throws ServletException If an error occurs*/void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
每个框架要使用ServletContainerInitializer就必须在对应的Jar包的META-INF/services目录中创建一个名为javax.servlet.ServletContainerInitializer的文件。文件内容指定具体的ServletContainerInitializer实现类,于是,当Web容器启动时,就会运行这个初始化器做一些组件内的初始化工作。
Tomcat容器的ServletContainerInitializer机制,主要交由Context容器和ContextConfig监听器共同实现。ContextConfig监听器首先负责在容器启动时读取每个Web应用的WEB-INF/lib目录下包含的Jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及Web根目录下的META-INF/services/javax.servlet.ServletContainerInitializer,通过反射完成这些Servlet ContainerInitializer的实例化,然后再设置到Context容器中。最后,Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法,并将感兴趣的类作为参数传入。
- Context容器的监听器
Tomcat启动过程中一般默认会在Context容器中添加4个监听器,分别为ContextConfig、TldConfig、NamingContextListener以及MemoryLeakTrackingListener。
ContextConfig监听器主要负责在适当的阶段对Web项目的配置文件进行相关处理;TldConfig监听器主要负责对TLD标签配置文件的相关处理;NamingContextListener监听器主要负责对命名资源的创建、组织、绑定等相关的处理工作,使之符合JNDI标准;MemoryLeakTrackingListener监听器主要用于跟踪重加载可能导致内存泄漏的相关处理。
6.3 Tomcat的类加载机制
贴一篇写得很不错的博客
org.apache.catalina.core.StandardContext#startInternal
if (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader);}
WebappLoader的核心工作其实交给其内部的WebappClassLoader,它才是真正完成类加载工作的加载器,它是一个自定义的类加载器。
那么我们进入WebappLoader看看:
/*** 由此加载器组件管理的类加载器。*/private WebappClassLoaderBase classLoader = null;
/*** 创建相关的类加载器。*/private WebappClassLoaderBase createClassLoader()throws Exception {Class<?> clazz = Class.forName(loaderClass);WebappClassLoaderBase classLoader = null;if (parentClassLoader == null) {parentClassLoader = context.getParentClassLoader();}Class<?>[] argTypes = { ClassLoader.class };Object[] args = { parentClassLoader };Constructor<?> constr = clazz.getConstructor(argTypes);classLoader = (WebappClassLoaderBase) constr.newInstance(args);return classLoader;}
public class WebappClassLoader extends WebappClassLoaderBase {
public abstract class WebappClassLoaderBase extends URLClassLoader {
WebappClassLoader间接继承了URLClassLoader,只需要把/WEB-INF/lib和/WEB-INF/classes目录下的类和Jar包以URL形式添加到URLClassLoader中即可,后面就可以用该类加载器对类进行加载。
WebappClassLoader并没有遵循双亲委派机制,而是按自己的策略顺序加载类。根据委托标识,加载分为两种方式。
org.apache.catalina.loader.WebappClassLoaderBase#getResources(java.lang.String)
org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)
boolean delegateFirst = delegate || filter(name, false);
- 当委托标识delegate为false时,WebappClassLoader类加载器首先先尝试从本地缓存中查找加载该类,然后用System类加载器尝试加载类,接着由自己尝试加载类,最后才由父类加载(Common)器尝试加载。所以此时它搜索的目录顺序是:
<JAVA_HOME>/jre/lib→<JAVA_HOME>/jre/lib/ext→CLASSPATH→/WEB-INF/classes→/WEB-INF/lib→$CATALINA_BASE/lib和$CATALINA_HOME/lib。 - 当委托标识delegate为true时,WebappClassLoader类加载器首先先尝试从本地缓存中查找加载该类,然后用System类加载器尝试加载类,接着由父类加载器(Common)尝试加载类,最后才由自己尝试加载。所以此时它的搜索的目录顺序是<JAVA_HOME>/jre/lib→<JAVA_HOME>/jre/lib/ext→CLASSPATH→$CATALINA_BASE/lib和$CATALINA_HOME/lib→/WEB-INF/classes→/WEB-INF/lib。
对于公共资源可以共享,而属于Web应用的资源则通过类加载器进行了隔离。对于重加载的实现,也比较清晰,只需要重新实例化一个WebappClassLoader对象并把原来WebappLoader中旧的置换掉即可完成重加载功能,置换掉的将被GC回收。