Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper
在前文中,我们介绍了容器组件的公共接口Container接口,这个接口的抽象实现类ContainerBase实现了initInternal、startInternal这两个生命周期,规定了子容器中的大部分行为,本文我们就来继续深入到各个子容器中进行源码的分析。目录前言一、StandardEngine二、StandardHost三、HostConfig生命周期监听器的添加生命周期监听器的执行star
前文:
《Tomcat源码:启动类Bootstrap与Catalina的加载》
《Tomcat源码:StandardServer与StandardService》
写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。
前言
在前文中,我们介绍了容器组件的公共接口Container接口,这个接口的抽象实现类ContainerBase实现了initInternal、startInternal这两个生命周期,规定了子容器中的大部分行为,本文我们就来继续深入到各个子容器中进行源码的分析。
目录
一、StandardEngine
Engine的实现类为StandardEngine,在前文中我们了解到StandardService在init时调用了engine.init()来初始化engine,StandardEngine没有重写Init方法,但由于StandardEngine继承了ContainerBase从而间接继承了LifecycleBase抽象类,得以复用LifecycleBase中的init方法,start方法也是如此,因此以engine为代表的这些子容器实际上只需要重写initInternal、startInternal即可。
// LifecycleBase.java
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 子类实现initInternal方法完成不同的初始化逻辑
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
1、initInternal
StandardEngine的initInternal方法中,第一步调用了getRealm() 方法,确保 StandardEngine 的父类 StandardEngine中的 Realm 类型的属性不为空,Realm 是 Tomcat 的特性功能,这里先略过。
第二步调用了super.initInternal(),也就是ContainerBase的initInternal方法,该方法我们前文中做了介绍,简单来说就是创建一个用于启动子容器的线程池。
protected void initInternal() throws LifecycleException {
getRealm();
super.initInternal();
}
ContainerBase的initInternal方法如下
protected ThreadPoolExecutor startStopExecutor;
protected void initInternal() throws LifecycleException {
// 创建线程安全的队列
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
// 创建线程池
startStopExecutor = new ThreadPoolExecutor(getStartStopThreadsInternal(), getStartStopThreadsInternal(), 10,
TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
// 调用父类LifecycleMBeanBase的初始化方法
super.initInternal();
}
2、startInternal
startInternal也和initInternal一样,直接调用了父类ContainerBase中的该方法,其作用便是利用initInternal方法中创建的线程池启动以当前容器的子容器(比如Engine中启动Host)。
protected synchronized void startInternal() throws LifecycleException {
if (log.isInfoEnabled()) {
log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
}
super.startInternal();
}
startInternal通过线程池异步启动当前容器的子容器。
// 将要启动的子容器加入到线程池中异步启动
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
// 获取子容器启动结果
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
// ...
threadStart();
startInternal启动过程中会调用threadStart方法启动当前容器的backgroundProcess,因为StandardEngine没有重写该方法,因此还是调用的ContainerBase中的实现,前文中已经做了解析,这里就不赘述了。(《Tomcat源码:Container接口》)
二、StandardHost
Host时Engine的子容器,其结构也和StandardEngine类似,但StandardHost没有重写initInternal因此这里只分析startInternal。
Host#startInternal的逻辑就是看自己的Pipeline对象里是否包含了 org.apache.catalina.valves.ErrorReportValve这个Valve对象,如果没有,就添加一个 org.apache.catalina.valves.ErrorReportValve 到自己的Pipeline 对象里。
最后调用 ContainerBase 的 startInternal 方法,ContainerBase#startInternal 方法在上篇文章也讲到了,这里就不多讲了。
protected synchronized void startInternal() throws LifecycleException {
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if (!found) {
Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardHost.invalidErrorReportValveClass", errorValve), t);
}
}
super.startInternal();
}
三、HostConfig
生命周期监听器的添加
在Host之前的容器都属于server.xml中的配置,但context并不是其中的配置,因此context的读取方式就和之前的容器不同了,下面我们来介绍下context是如何成为host的child的。
首先回到Catalina初始化中的createStartDigester方法,其中有这样一行:
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
这是要向Digester实例中添加多个规则(RuleSet),HostRuleSet继承了RuleSetBase类,并实现了它的抽像方法:addRuleInstances。下面来看这个方法的具体实现:
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
// 其余代码
}
首先创建了LifecycleListenerRule规则,然后在模式" Server/Service/Engine/Hos"下,将这个规则加到Host之上。这个规则正是tomcat的生命周期规则,因此在Host生命周期过程中会触发这个规则。
以上归纳起来就一句话:HostConfig是一个生命周期监听器类,在tomcat加载配置文件的时候被添加到Host之上。结合我们之前介绍生命周期方法的内容可知,StandardHost启动的过程中,在其父类中改变了生命周期状态,导致这个监听器的触发。
生命周期监听器的执行
添加完了监听器,StandardHost在启动时就会触发监听器,下面我们看下监听器执行了哪些操作。
start与deployApps
首先是根据状态判断触发start方法。
public void lifecycleEvent(LifecycleEvent event) {
// 其余代码
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
// start状态时触发
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
继续调用deployApps方法,这个方法里面将会过滤出WEB应用的地址(即webapps),然后对描述符部署、war包部署、文件夹部署这三种不同的部署方式做专门处理。
注意这里获取WEB应用的webapps使用的时数组,因为一个webapps下面可能有多个web应用。
// StandardHost.java
private boolean deployOnStartup = true;
// HostConfig.java
public void start() {
// 其源代码
if (host.getDeployOnStartup()) {
deployApps();
}
}
protected void deployApps() {
// 这里的appBase会读取到StardHost中的 private String appBase = "webapps";
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
// 过滤出应用路径
String[] filteredAppPaths = filterAppPaths(appBase.list());
// 针对描述符部署、war包部署、文件夹部署这三种方式
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
deployWARs
我们以最经典的部署包方式为例来深入看下
protected void deployWARs(File appBase, String[] files) {
// initInternal时创建的用于异步启动子容器的线程池
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (String file : files) {
if (file.equalsIgnoreCase("META-INF")) {
continue;
}
if (file.equalsIgnoreCase("WEB-INF")) {
continue;
}
File war = new File(appBase, file);
if (file.toLowerCase(Locale.ENGLISH).endsWith(".war") && war.isFile() && !invalidWars.contains(file)) {
ContextName cn = new ContextName(file, true);
if (tryAddServiced(cn.getName())) {
try {
// 其余代码
// 发布war的任务提交到线程池中
results.add(es.submit(new DeployWar(this, cn, war)));
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
removeServiced(cn.getName());
throw t;
}
}
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("hostConfig.deployWar.threaded.error"), e);
}
}
}
异步发布war包调用了静态内部类DeployWar中的run方法,可以看到其实际上是调用了外部类的deployWAR。
private static class DeployWar implements Runnable {
private HostConfig config;
private ContextName cn;
private File war;
DeployWar(HostConfig config, ContextName cn, File war) {
this.config = config;
this.cn = cn;
this.war = war;
}
@Override
public void run() {
try {
// 发布单个war
config.deployWAR(cn, war);
} finally {
config.removeServiced(cn.getName());
}
}
}
HostConfig#deployWAR
可以看到这里将当前WEB应用构建为了context子容器作为child加入到了host中,并且给context添加了一个监听器ContextConfig,这个监听器将会在下文有非常重要的作用,具体内容我们会在下文介绍。
protected void deployWAR(ContextName cn, File war) {
// 其余代码
Context context = null;
boolean deployThisXML = isDeployThisXML(war, cn);
try {
// 其余代码
// 这里读取的StartHost中的configClass变量
// configClass = "org.apache.catalina.startup.ContextConfig";
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
context.addLifecycleListener(listener);
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName() + ".war");
// 添加StandardContext到当前Host中
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployWar.error", war.getAbsolutePath()), t);
}
// 其余代码
}
四、StandardContext
1、initInternal
首先调用了父类的 initInternal 方法,也就是 ContainerBase#initInternal。
然后调用了 namingResources(NamingResourcesImpl类型的属性) 的 init 方法,这个 namingResources 跟 StandardServer 里的 globalNamingResources 类似,只不过 globalNamingResources 是全局的,而这里的 namingResources 只是这个 StandardContext 的。
最后构造一个 Notification 对象并调用 broadcaster.sendNotification 方法来广播这个通知。broadcaster 是在 StandardContext 对象构造函数里初始化的。
protected void initInternal() throws LifecycleException {
super.initInternal();
if (namingResources != null) {
namingResources.init();
}
if (this.getObjectName() != null) {
// 发布正在启动的JMX通知,可以通过添加NotificationListener监听Web应用的启动
Notification notification = new Notification("j2ee.object.created", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
}
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
if (!Globals.STRICT_SERVLET_COMPLIANCE) {
resourceOnlyServlets.add("jsp");
}
}
2、startInternal
这段代码超过了三百行,因此这里直接以文字描述的方式简介一下其中的过程
1、发布正在启动的JMX通知,这样可以通过添加NotificationListener来监听Web应用的启动。
2、启动当前Context维护的NDI资源。
3、初始化当前Context使用的WebResourceRoot并启动。WebResourceRoot维护了Web应用所有的资源集合(Class文件、Jar包以及其他资源文件),主要用于类加载和按照路径查找资源文件。
4、创建Web应用类加载器(WebappLoader)。WebappLoader继承自LifecycleMBeanBase,在其启动时创建Web应用类加载器(WebappClassLoader)。此外,该类还提供了background-Process,用于Context后台处理。当检测到Web应用的类文件、Jar包发生变更时,重新加载Context。
5、如果没有设置Cookie处理器,则创建默认的Rfc6265CookieProcessor。
6、设置字符集映射(CharsetMapper),该映射主要用于根据Locale获取字符集编码。
7、初始化临时目录,默认为$CATALINA_BASE/work/<Engine名称><Host名称>/<Context名称>
8、Wb应用的依赖检测,主要检测依赖扩展,点完整性。
9、如果当前Context使用JNDI,则为其添加NamingContextListener。
10、启动Web应用类加载器(WebappLoader.start),此时才真正创建WebappClassLoader实例。
11、启动安全组件(Realm)。
12、发布CONFIGURE_START_EVENT事件,ContextConfig监听该事件以完成Servlet的创建.
13、启动Context子节点(Wrapper),在server.xml中配置的Wrapper。
14、启动Context维护的Pipeline。
15、创建会话管理器。如果配置了集群组件,则由集群组件创建,否则使用标准的会话管理器(StandardManager)。在集群环境下,需要将会话管理器注册到集群组件。
16、将Context的Web资源集合(org.apache.catalina.WebResourceRoot)添加到Servlet(ontext属性,属性名为org.apache,catalina.resources
17、创建实例管理器(InstanceManager),用于创建对象实例,如Servlet、Filter等。
18、将Jar包扫描器(JarScanner)添加到ServletContext)属性,属性名为org.apache.tomcat.JarScanner
19、合并ServletContext初始化参数和Context组件中的ApplicationParameter。合并原则:ApplicationParameter配置为可以覆盖,那么只有当ServletContext没有相关参数或者相关参数为空时添加;如果配置为不可覆盖,则强制添加,此时即使ServletContext配置了同名参数也不会生效。
20、启动添加到当前Context的ServletContainerInitializer。该类的实例具体由ContextConfig查找并添加。该类主要用于以可编程的方式添加Wb应用的配置,如Servlet、Filter等。
21、实例化应用监听器(ApplicationListener),分为事件监听器(ServletContextAttributeListener,ServletRequestAttributeListener,ServletRequestListener HttpSessionldListener,HttpSessionAttributeListener)以及生命周期监听(HttpSessionListener、ServletContextListener)。这些监听器可以通过Context部署描述文件、可编程的方式(ServletContainerInitializer)或者Web.xml添加,并且触发ServletContextListener.contextInitialized。
22、检测未覆盖的HTTP方法的安全约束。
23、启动会话管理器。
24、实例化FilterConfig(ApplicationFilterConfig)、Filter,并调用Filter.init初始化e
25、对于loadOnStartup≥O的Wrapper,调用Wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init进行初始化。
26、启动后台定时处理线程。只有当backgroundProcessorDelay>O时启动,用于监控守护文件的变更等。当backgroundProcessorDelay≤O时,表示Context的后台任务由上级容器(Host)调度。
27、发布正在运行的MX通知。
28、调用WebResourceRoot.gc()释放资源(WebResourceRoot加载资源时,为了提高性能会缓存某些信息,该方法用于清理这些资源,如关闭JAR文件)。
29、设置Context的状态,如果启动成功,设置为STARTING(其父类LifecycleBase会自动将状态转换为STARTED),否则设置为FAILED。
触发事件监听器
在启动子容器前会先触发CONFIGURE_START_EVENT事件。
// 发布CONFIGURE_START_EVENT事件,ContextConfig监听该事件完成Servlet创建
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
在上文StandardHost的最后我们讲到Host将Context添加为子容器时为其添加了一个监听器ContextConfig,下面我们来看下ContextConfig内部的操作。
可以看到在CONFIGURE_START_EVENT事件时,会调用configureStart方法,该方法最重要的步骤为webConfig(),正是这个步骤解析了web.xml。
public void lifecycleEvent(LifecycleEvent event) {
context = (Context) event.getLifecycle();
// 其余代码
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
}
}
protected synchronized void configureStart() {
// 其余代码
// 开始解析web.xml的配置
webConfig();
// 如果未配置忽略应用注解配置,那么对filter,servlet,listener进行Resource注解的搜索
// Resource配置在类,字段,方法上,会根据资源的类型进行划分资源类型,添加到不同资源集合中,比如环境变量,JNDI等等资源
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
// 将context约束的角色和wrapper中注解RunAs或配置中设置的角色添加到context容器中,重复的不会被再次添加
validateSecurityRoles();
}
// 配置验证器,如果没有Ralm或者实现了 Authenticator的管道阀,那么就会添加一个默认的NonLoginAuthenticator验证器
if (ok) {
authenticatorConfig();
}
// 其余代码
// 如果配置context时没有遇到任何问题,那么就表示配置成功
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
}
}
解析web.xml配置
Tomcat初始化Web容器的过程如下(ContextConfig.webConfig):
- 解析默认配置,生成WebXml对象(Tomcat使用该对象表示web.xml的解析结果)。先解析容器级配置,然后再解析Host级配置。这样对于同名配置,Host级将覆盖容器级。为了便于后续过程描述,我们暂且称之为“默认WebXml”。为了提升性能,ContextConfig对默认WebXml进行了缓存,以避免重复解析。
- 解析Web应用的web.xml文件。如果StandardContext的altDDName不为空,则将该属性指向的文件作为web,xml,否则使用默认路径,即WEB-NF/web.xml。解析结果同样为WebXml对象(此时创建的对象为主WebXml,其他解析结果均需要合并到该对象上)。暂时将其称为“主WebXml"。
- 扫描Web应用所有JAR包,如果包含META-INF/web-fragment.xml,则解析文件并创建WebXml对象。暂时将其称为“片段WebXml”。
- 将web-fragment.xml创建的WebXml对象按照Servlet规范进行排序,同时将排序结果对应的JAR文件名列表设置到ServletContext属性中,属性名为javax.servlet.context.orderedLibs。该排序非常重要,因为这决定了Filter等的执行顺序。
篇幅原因,这里只看下读取web应用读取web.xml的步骤,可以看到首先是调用getContextWebXmlSource方法将web.xml转换为输入流后进行的解析。
protected void webConfig() {
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal());
Set<WebXml> defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment(webXmlParser));
WebXml webXml = createWebXml();
// 读取应用下的web.xml并解析
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}
// 其余代码
// 添加wrapper为context的子容器
configureContext(webXml);
}
这里将读取当前web应用下的web.xml。
protected InputSource getContextWebXmlSource() {
InputStream stream = null;
InputSource source = null;
URL url = null;
String altDDName = null;
// 获取ServletContext,如没有则创建一个,类别为ApplicationContext
ServletContext servletContext = context.getServletContext();
try {
if (servletContext != null) {
altDDName = (String)servletContext.getAttribute(Globals.ALT_DD_ATTR);
// 其余代码
// ApplicationWebXml = "/WEB-INF/web.xml";
stream = servletContext.getResourceAsStream
(Constants.ApplicationWebXml);
try {
url = servletContext.getResource(
Constants.ApplicationWebXml);
} catch (MalformedURLException e) {
log.error(sm.getString("contextConfig.applicationUrl"));
}
}
// 其余代码
source = new InputSource(url.toExternalForm());
source.setByteStream(stream);
}
// 其余代码
return source;
}
wrapper的创建
可以看到这里是将上文中解析完的web.xml中的配置里的servlet读取出来后创建wrapper并设置属性,最后添加到context中作为子容器。
另外还有一个要注意的就是addApplicationListener,Spring启动中的关键监听器ContextLoaderListener便是这个时候被添加的。
private void configureContext(WebXml webxml) {
// 添加web.xml中的监听器
for (String listener : webxml.getListeners()) {
context.addApplicationListener(listener);
}
// 其余代码
for (ServletDef servlet : webxml.getServlets().values()) {
// 获取web.xml中配置的servlet
Wrapper wrapper = context.createWrapper();
// 启动项设置
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
wrapper.setName(servlet.getServletName());
// 配置servlet初始化参数
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setServletClass(servlet.getServletClass());
// 添加为context的子容器
context.addChild(wrapper);
}
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
// 其余代码
}
创建完后回到StandardContext的startInternal中就可以启动子容器了
// StandardContext#startInternal
// 启动Context子节点Wrapper(在server.xml中配置的)
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
启动监听器
startInternal下面会调用listenerStart方法实例化并启动应用监听器
// 实例化并启动应用监听器(ApplicationListener)
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
listenerStart方法内部找出ServletContextListener类型的监听器,调用contextInitialized方法,这个方法就是我们Spring框架的启动点。
public boolean listenerStart() {
// 其余代码
// 遍历所有监听器找出ServletContextListener类别的
for (Object instance : instances) {
if (!(instance instanceof ServletContextListener)) {
continue;
}
ServletContextListener listener = (ServletContextListener) instance;
try {
fireContainerEvent("beforeContextInitialized", listener);
// 调用其contextInitialized方法
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
}
// 其余代码
}
}
ContextLoaderListener继承了ServletContextListener,其contextInitialized将会启动Spring框架。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {}
public ContextLoaderListener(WebApplicationContext context) {super(context);}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
loadOnStartup
在web.xml中我们对于需要启动时加载的servlet会配置<load-on-startup>1</load-on-startup>,这个功能的实现也是在StandardContext#startInternal中完成的。
// StandardContext#startInternal
if (ok) {
if (!loadOnStartup(findChildren())) {
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
可以看到内部实现是交给了对应wrapper的load方法完成的,其具体内容我们下文介绍StandardWrapper时介绍。
public boolean loadOnStartup(Container children[]) {
// 对于设置了<load-on-startup>1</load-on-startup>的servlet调用wrapper的load方法
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
}
}
}
return true;
}
五、StandardWrapper
StandardWrapper 没有重载initInternal 方法,我们从startInternal看起
1、startInternal
和其他容器差不多,唯一不同的是多了一个setAvailable。
protected synchronized void startInternal() throws LifecycleException {
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting", this.getObjectName(), sequenceNumber++);
broadcaster.sendNotification(notification);
}
super.startInternal();
// 方法设置 available 属性的值。
setAvailable(0L);
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.running", this.getObjectName(), sequenceNumber++);
broadcaster.sendNotification(notification);
}
}
从 available 的注释可以看出,它的作用是表示 Servlet 的可用时间的。
/**
* The date and time at which this servlet will become available (in milliseconds since the epoch), or zero if the
* servlet is available. If this value equals Long.MAX_VALUE, the unavailability of this servlet is considered
* permanent.
*/
protected long available = 0L;
2、load
在上文中,讲到了Context的startInternal 方法中做了一件事情就是调用 Wrapper 的 load 方法(在 StandardContext#loadOnStartup 中调用的)。在 StandardContext#startInternal 中先调用 Wrapper的 start 方法,然后调用 Wrapper 的 load 方法。
load 方法逻辑很简单,先调用 loadServlet() 获取一个 Servlet 对象,就是通过 servletClass 属性指定的类名,调用 InstanceManager#newInstance(servletClass) 方法来创建一个 Servlet 对象。然后调用 initServlet(instance) 来初始化这个 Servlet 对象,也就是调用这个 Servlet 对象的 init 方法。
可以看出 Wrapper 里有一个 Servlet 属性,Wrapper 正是对 Servlet 的包装。
public synchronized void load() throws ServletException {
// 创建当前Servlet 对象
instance = loadServlet();
if (!instanceInitialized) {
// 调用Servlet对象的init方法来初始化
initServlet(instance);
}
// 其余代码
}
至此,整个容器的启动过程就介绍完了,可以看到整个流程是由Server起步直到Wrapper结束。
其中Server代表的是整个tomcat应用,Service代表的是server.xml中的service节点。而后续的Engine与Host都是service中的子节点。
再到Context代表了webapps下的每个应用,子容器Wrapper表示web应用中的每个servlet。Context中的start方法中会创建当前web应用的ServletContext,并启动ServletContextListener监听器,启动web应用(典型的如Spring容器的启动点就是ContextLoaderListener这个ServletContextListener的实现类)。并对于需要启动时加载的servlet(loadOnStartup=1)调用其对应wrapper的load方法。
最后再来回顾下前文StandardService中得Mapper 组件,Tomcat正是通过Mapper 组件来确定请求是由哪个 Wrapper 容器里的 Servlet 来处理的。
举例来说,假如有一个网购系统,有面向网站管理人员的后台管理系统,还有面向终端客户的在线购物系统。这两个系统跑在同一个 Tomcat 上,为了隔离它们的访问域名,配置了两个虚拟域名:manage.shopping.com和user.shopping.com,网站管理人员通过manage.shopping.com域名访问 Tomcat 去管理用户和商品,而用户管理和商品管理是两个单独的 Web 应用。终端客户通过user.shopping.com域名去搜索商品和下订单,搜索功能和订单管理也是两个独立的 Web 应用。如下所示,演示了 url 应声 Servlet 的处理流程。
假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?
- 首先,根据协议和端口号选定 Service 和 Engine。
- 然后,根据域名选定 Host。
- 之后,根据 URL 路径找到 Context 组件。
- 最后,根据 URL 路径找到 Wrapper(Servlet)。
更多推荐
所有评论(0)