Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper
前文:
《Tomcat源码:启动类Bootstrap与Catalina的加载》
《Tomcat源码:容器的生命周期管理与事件监听》
《Tomcat源码:StandardServer与StandardService》
《Tomcat源码:Container接口》
写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。
前言
在前文中,我们介绍了容器组件的公共接口Container接口,这个接口的抽象实现类ContainerBase实现了initInternal、startInternal这两个生命周期,规定了子容器中的大部分行为,本文我们就来继续深入到各个子容器中进行源码的分析。
目录
前言
一、StandardEngine
1、initInternal
2、startInternal
二、StandardHost
三、HostConfig
生命周期监听器的添加
生命周期监听器的执行
start与deployApps
deployWARs
HostConfig#deployWAR
四、StandardContext
1、initInternal
2、startInternal
触发事件监听器
解析web.xml配置
wrapper的创建
启动监听器
loadOnStartup
五、StandardWrapper
1、startInternal
2、load
一、StandardEngine
Engine的实现类为StandardEngine,在前文中我们了解到StandardService在init时调用了engine.init()来初始化engine,StandardEngine没有重写Init方法,但由于StandardEngine继承了ContainerBase从而间接继承了LifecycleBase抽象类,得以复用LifecycleBase中的init方法,start方法也是如此,因此以engine为代表的这些子容器实际上只需要重写initInternal、startInternal即可。
// LifecycleBase.javapublic 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.javaprivate boolean deployOnStartup = true; // HostConfig.javapublic 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 configBasedeployDescriptors(configBase, configBase.list());// Deploy WARsdeployWARs(appBase, filteredAppPaths);// Deploy expanded foldersdeployDirectories(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;}@Overridepublic void run() {try {// 发布单个warconfig.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.resources17、创建实例管理器(InstanceManager),用于创建对象实例,如Servlet、Filter等。18、将Jar包扫描器(JarScanner)添加到ServletContext)属性,属性名为org.apache.tomcat.JarScanner19、合并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初始化e25、对于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,如没有则创建一个,类别为ApplicationContextServletContext 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中配置的servletWrapper 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);}@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}@Overridepublic 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#startInternalif (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方法。