文章目录

  • 1.Catalina
    • 开启start
  • 2.Server启动
  • 3.Service启动
    • 3.1 Engine启动
      • 3.2 Host
        • 3.2.1 Host的初始化
        • 3.2.2 Host的启动
        • 3.2.3Pipeline启动
        • 3.2.4 HostConfig
      • 3.3 Context
        • 3.3.1 Context的启动
        • 3.3.2 ContextConfig
          • 3.3.2.1 Wrapper的启动
          • 3.3.2.2 加载静态文件
          • 3.3.2.3 设置ServletContainerInitializer
        • 3.3.3 调用ServletContainerInitializer
        • 3.3.4 启动listener
        • 3.3.5 初始化filter
        • 3.3.6 加载Wrapper
      • 3.4MapperListener启动
      • 3.5 Connector启动
        • 3.5.1 Endpoint启动

启动阶段也开始于Bootstrap的main方法里的start,我们都知道,初始化时只是到容器Engine,所以start时主要对Engine的子容器以下这一部分进行操作。

而start方法反射调用了Catalina的start方法。

daemon.start();public void start() throws Exception {if (catalinaDaemon == null) {init();}Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);method.invoke(catalinaDaemon, (Object [])null);
}

1.Catalina

开启start

public void start() {if (getServer() == null) {load();}//...long t1 = System.nanoTime();// Start the new servertry {//执行startgetServer().start();} catch (LifecycleException e) {//...}// Register shutdown hookif (useShutdownHook) {if (shutdownHook == null) {shutdownHook = new CatalinaShutdownHook();}Runtime.getRuntime().addShutdownHook(shutdownHook);}if (await) {await();stop();}
}
  1. 调用server的start,开始进行启动。
  2. 注册jvm关闭钩子,以便优雅停机。
  3. 开启shutdown端口的监听并阻塞,用于监听关闭指令。

这里的start同样会调用LifeCycleBase的start方法,start较init来说多了一种状态STARTING,对于正常流程来说,先由LifecycleBase改变当前状态值为STARTING_PREP,并发出事件通知,即执行当前事件的监听器,然后执行各个节点的start方法,在启动过程中,将状态值设置为STARTING,并执行事件监听器,启动结束后,将状态设置为初始化完成STARTED,并发出事件通知。

LifecycleBase#start

public final synchronized void start() throws LifecycleException {//...try {setStateInternal(LifecycleState.STARTING_PREP, null, false);startInternal();if (state.equals(LifecycleState.FAILED)) {// This is a 'controlled' failure. The component put itself into the// FAILED state so call stop() to complete the clean-up.stop();} else if (!state.equals(LifecycleState.STARTING)) {// Shouldn't be necessary but acts as a check that sub-classes are// doing what they are supposed to.invalidTransition(Lifecycle.AFTER_START_EVENT);} else {setStateInternal(LifecycleState.STARTED, null, false);}} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.startFail", toString());}
}

2.Server启动

StadardServer#startInternal

protected void startInternal() throws LifecycleException {fireLifecycleEvent(CONFIGURE_START_EVENT, null);setState(LifecycleState.STARTING);globalNamingResources.start();// Start our defined Services //启动services组件synchronized (servicesLock) {for (int i = 0; i < services.length; i++) {services[i].start();}}//执行生命周期事件if (periodicEventDelay > 0) {monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {startPeriodicLifecycleEvent();}}, 0, 60, TimeUnit.SECONDS);}
}

这里的Listener主要执行了NamingContextListener,做了一些JNDI资源的初始化。然后就是执行service容器的start方法。

3.Service启动

StadardService#startInternal

protected void startInternal() throws LifecycleException {if(log.isInfoEnabled())log.info(sm.getString("standardService.start.name", this.name));setState(LifecycleState.STARTING);//engine启动if (engine != null) {synchronized (engine) {engine.start();}}// 启动Executor线程池synchronized (executors) {for (Executor executor: executors) {executor.start();}}// 启动MapperListenermapperListener.start();// 启动Connectorsynchronized (connectorsLock) {for (Connector connector: connectors) {// If it has already failed, don't try and start itif (connector.getState() != LifecycleState.FAILED) {connector.start();}}}
}
  1. 启动engine
  2. 启动线程池
  3. 启动mapperListener
  4. 启动Connector

3.1 Engine启动

engine的启动和初始化一样,会调用父类ContainerBasestartInternal方法,主要分为3个步骤

  1. 使用线程池启动子容器。
  2. 启动Pipeline,并将状态设置为STARTING,发出事件通知
  3. 如果backgroundProcessorDelay参数 >= 0,则开启ContainerBackgroundProcessor线程,用于调用子容器的backgroundProcess

ContainerBase#startInternal

protected synchronized void startInternal() throws LifecycleException {// ...// 把子容器的启动放在线程池中处理Container[] children = findChildren();List<Future<Void>> results = new ArrayList<>();for (int i = 0; i < children.length; i++) {results.add(startStopExecutor.submit(new StartChild(children[i])));}MultiThrowable multiThrowable = null;// 阻塞当前线程,直到子容器start完成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);}}//...// 启用Pipelineif (pipeline instanceof Lifecycle) {((Lifecycle) pipeline).start();}setState(LifecycleState.STARTING);// 开启ContainerBackgroundProcessor线程用于调用子容器的backgroundProcess方法,默认情况下backgroundProcessorDelay=-1。if (backgroundProcessorDelay > 0) {monitorFuture = Container.getService(ContainerBase.this).getServer().getUtilityExecutor().scheduleWithFixedDelay(new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);}
}

启动子容器

startStopExecutor是在init阶段被创建的,默认情况下会创建线程池InlineExecutorService,这个线程池的coreSize=maxSize=1。而这个可以在server.xml里通过startStopThreads来配置,这里需要注意的是,startStopExecutor的创建是在ContainerBase的初始化方法调用时,意味着每个继承了ContainerBase的容器在启动子容器时都维护了一个自己的线程池,并可以分别修改线程池属性。继承了ContainerBase的如下:

ContainerBase会把StartChild任务丢给线程池处理,得到Future,并且会遍历所有的Future进行阻塞result.get(),这个操作是将异步启动转同步,子容器启动完成才会继续运行。然后再来看看submit到线程池的StartChild任务,它实现了Callable接口,在call里面完成子容器的start动作,而start方法依然是调用LifecycleBase的start方法。

private static class StartChild implements Callable<Void> {private Container child;public StartChild(Container child) {this.child = child;}@Overridepublic Void call() throws LifecycleException {child.start();return null;}
}

3.2 Host

由于Host在init阶段没有进行初始化,所以在调用start方法时,状态为NEW,需要先进行初始化。

3.2.1 Host的初始化

由于默认实现StandardHost内部没有实现initInternal,所以这里的初始化执行了ContainerBaseinitInternal方法,详见ContainerBase的initInternal

3.2.2 Host的启动

StandardHost#startInternal

 private String errorReportValveClass ="org.apache.catalina.valves.ErrorReportValve";protected synchronized void startInternal() throws LifecycleException {// Set error report valve  // errorValve默认使用org.apache.catalina.valves.ErrorReportValveString 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添加到 Pipeline 中,注意是添加到 basic valve 的前面Valve valve =(Valve) Class.forName(errorValve).getConstructor().newInstance();getPipeline().addValve(valve);}} catch (Throwable t) {//...}}// 调用父类 ContainerBasesuper.startInternal();
}

在Pipeline里寻找是否存在ErrorReportValve,如果不存在则实例化后添加到Pipeline。那么Pipline和Valve是什么呢

Pipeline是管道组件,用于封装了一组有序的Valve,便于Valve顺序地传递或者处理请求

Valve可以理解为请求拦截器。

Pipeline也是一个Lifecycle组件, 默认实现是StandardPipeline,这段被定义在ContainerBase中,也就意味着同上面的startStopExecutor一样,每个容器都维护了一个自己的拦截器链。

protected final Pipeline pipeline = new StandardPipeline(this);

而Valve的接口如下:

public interface Valve {public Valve getNext();public void setNext(Valve valve);public void backgroundProcess();public void invoke(Request request, Response response) throws IOException, ServletException;public boolean isAsyncSupported();
}

而对于Host来说,Valve在server.xml`配置了一个日志拦截器。

<Host name="localhost"  appBase="webapps"unpackWARs="true" autoDeploy="true" startStopThreads="4" ><Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"prefix="localhost_access_log" suffix=".txt"pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

另一个默认的Valve来自于StandardHost的构造方法。这里每个实现了ContainerBase的容器都会在构造方法里创建一个默认的Valve。

public StandardHost() {super();pipeline.setBasic(new StandardHostValve());}

所以StandardHost Pipeline 包含的 Valve 组件:

  1. basic:org.apache.catalina.core.StandardHostValve
  2. first:org.apache.catalina.valves.AccessLogValve

而在添加ErrorReportValve时,会将ErrorReportValve添加到basic之前,first之后。

StandardPiepline#addValve

public void addValve(Valve valve) {// Validate that we can add this Valveif (valve instanceof Contained)((Contained) valve).setContainer(this.container);//...// Add this Valve to the set associated with this Pipelineif (first == null) {first = valve;valve.setNext(basic);} else {Valve current = first;while (current != null) {if (current.getNext() == basic) {//设置到basic之前current.setNext(valve);valve.setNext(basic);break;}current = current.getNext();}}container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}

3.2.3Pipeline启动

ContainerBase#startInternal方法中,执行完子容器的启动后会启动pipeline。这里也会先执行pipeline的初始化在执行启动,不过初始化时除了状态的变更和事件通知之外,什么也没做,而在启动阶段,会遍历所有的Valve,如果当前Valve是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件。

StandardPipeline#startInternal

protected synchronized void startInternal() throws LifecycleException {// Start the Valves in our pipeline (including the basic), if anyValve current = first;if (current == null) {current = basic;}//遍历 Valve 链表,如果 Valve 是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件while (current != null) {if (current instanceof Lifecycle)((Lifecycle) current).start();current = current.getNext();}setState(LifecycleState.STARTING);
}

StandardHost的启动到这里就结束了,从代码可以看出,并没有做过多的事情。而对于Host的真正操作是在监听器HostConfig里,即每次LifeCycle状态变更时发送的事件通知。HostConfig是在解析xml阶段配置的,主要是找到webapp目录,并解压war包。

3.2.4 HostConfig

HostConfig主要负责处理start和stop事件

HostConfig#lifecycleEvent

public void lifecycleEvent(LifecycleEvent event) {//判断事件是否由 Host 发出,并且为 HostConfig 设置属性//..// Process the event that has occurredif (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();} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {stop();}
}public void start() {//...if (host.getDeployOnStartup())deployApps();
}
protected void deployApps() {File appBase = host.getAppBaseFile();File configBase = host.getConfigBaseFile();// 过滤掉hostConfig.ignorePathString[] filteredAppPaths = filterAppPaths(appBase.list());// 部署 xml 描述文件deployDescriptors(configBase, configBase.list());// 解压 war 包deployWARs(appBase, filteredAppPaths);// 处理扩展的文件deployDirectories(appBase, filteredAppPaths);}

解压war包

protected void deployWARs(File appBase, String[] files) {//...for (int i = 0; i < files.length; i++) {File war = new File(appBase, files[i]);if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") &&war.isFile() && !invalidWars.contains(files[i]) ) {//...results.add(es.submit(new DeployWar(this, cn, war)));}}}private static class DeployWar implements Runnable {private HostConfig config;private ContextName cn;private File war;@Overridepublic void run() {config.deployWAR(cn, war);}
}//部署Web应用程序时,默认情况下是否应将XML文件复制到 $CATALINA_BASE / conf / <engine> / <host>
protected boolean copyXML = false;protected String contextClass = "org.apache.catalina.core.StandardContext";protected void deployWAR(ContextName cn, File war) {//...Context context = null;//实例化contextif (deployThisXML && useXml && !copyXML) {//从扩展目录的xml解析并实例化 这个是有限制条件的context = (Context) digester.parse(xml);}else if (deployThisXML && xmlInWar) {//从META-INF/context.xml目录下的jar包里解析并实例化context = (Context) digester.parse(istream);}else if (!deployThisXML && xmlInWar) {// ...} else {//一般情况下的实例化context = (Context) Class.forName(contextClass).getConstructor().newInstance();}//...//实例化ContextConfig,并将其作为监听器add到StandardContext中Class<?> clazz = Class.forName(host.getConfigClass());LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();context.addLifecycleListener(listener);context.setName(cn.getName());context.setPath(cn.getPath());context.setWebappVersion(cn.getVersion());context.setDocBase(cn.getBaseName() + ".war");//添加并启动子容器host.addChild(context);
}

3.3 Context

Context的启动和Host类似。init阶段就不再赘述了。

3.3.1 Context的启动

StandardContext#startInternal

protected synchronized void startInternal() throws LifecycleException {//由于这个方法比较长,这里节选重点部分//调用ContextConfig 从web.xml 或者 Servlet3.0 的注解配置,读取 Servlet 相关的配置信息,比如 Filter、Servlet、Listener 等fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);// Call ServletContainerInitializers 执行ServletContainerInitializer的SPI实现类for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :initializers.entrySet()) {entry.getKey().onStartup(entry.getValue(),getServletContext());}//  初始化filterif (ok) {if (!filterStart()) {log.error(sm.getString("standardContext.filterFail"));ok = false;}}// StandardWrapper 实例化并且启动 Servlet,由于 Servlet 存在 loadOnStartup 属性// 因此使用了 TreeMap,根据 loadOnStartup 值 对 Wrapper 容器进行排序,然后依次启动 Servlet// Load and initialize all "load on startup" servletsif (ok) {if (!loadOnStartup(findChildren())){log.error(sm.getString("standardContext.servletFail"));ok = false;}}
}

由于这段代码比较复杂,这里说一下大概的内容。

  1. 实例化 WebResourceRoot,默认实现类是 StandardRoot,用于读取 webapp 的文件资源
  2. 实例化loader对象,这里默认是WebappLoader,并执行loader的start方法,将所有class缓存起来。
  3. 发布CONFIGURE_START_EVENT事件,该事件主要由ContextConfig处理。
  4. 实例化 Sesssion 管理器,默认使用 StandardManager。
  5. 初始化filter
  6. 实例化并启动servlet

针对几个重点的部分作详细研究。

3.3.2 ContextConfig

对于该事件的处理主要在ContextConfig#webConfig

  1. 首先解析web.xml
WebXml webXml = createWebXml();// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {ok = false;
}
  1. 解析web-fragment.xml,tomcat提供的jar包会忽略此xml文件
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);Set<WebXml> orderedFragments = null;
orderedFragments =WebXml.orderWebFragments(webXml, fragments, sContext);
  1. 处理 javax.servlet.ServletContainerInitializer实现类
processServletContainerInitializers();protected void processServletContainerInitializers() {List<ServletContainerInitializer> detectedScis;try {WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);detectedScis = loader.load(ServletContainerInitializer.class);} catch (IOException e) {//...}for (ServletContainerInitializer sci : detectedScis) {initializerClassMap.put(sci, new HashSet<Class<?>>());HandlesTypes ht;try {ht = sci.getClass().getAnnotation(HandlesTypes.class);} catch (Exception e) {//...continue;}if (ht == null) {continue;}Class<?>[] types = ht.value();if (types == null) {continue;}for (Class<?> type : types) {if (type.isAnnotation()) {handlesTypesAnnotations = true;} else {handlesTypesNonAnnotations = true;}Set<ServletContainerInitializer> scis =typeInitializerMap.get(type);if (scis == null) {scis = new HashSet<>();typeInitializerMap.put(type, scis);}scis.add(sci);}}
}

获取ServletContainerInitializer的实现类,并将其保存在ContextConfig的map中,ServletContainerInitializer是servlet的spi机制,可以通过 HandlesTypes 筛选出相关的 servlet 类,并对 ServletContext 进行额外处理。自定义的ServletContainerInitializer的实现类,需要将类名的项目路径配置在META-INF/services/javax.servlet.ServletContainerInitializer文件中。以下是一个例子。

@HandlesTypes( Test.class )
public class CustomServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {for ( Class<?> type : c ) {System.out.println( type.getName() );}}
}

javax.servlet.ServletContainerInitializer

com.test.CustomServletContainerInitializer
  1. 如果没有配置web.xml,则会先扫描 WEB-INF/classes 目录下面的 class 文件,然后扫描 WEB-INF/lib 目录下面的 jar 包,解析字节码读取 servlet 相关的注解配置类,将解析完的信息设置到WebXml对象中。重点代码如下:

ContextConfig#processAnnotationsStream

protected void processAnnotationsStream(InputStream is, WebXml fragment,boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)throws ClassFormatException, IOException {//对字节码文件进行解析。ClassParser parser = new ClassParser(is);JavaClass clazz = parser.parse();//处理注解@HandlesTypescheckHandlesTypes(clazz, javaClassCache);if (handlesTypesOnly) {return;}//获取其注解,并把 WebServlet、WebFilter、WebListener 注解的类添加到 WebXml 实例中processClass(fragment, clazz);
}//解析servlet3.0注解
protected void processClass(WebXml fragment, JavaClass clazz) {AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();if (annotationsEntries != null) {String className = clazz.getClassName();for (AnnotationEntry ae : annotationsEntries) {String type = ae.getAnnotationType();if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {processAnnotationWebServlet(className, ae, fragment);}else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {processAnnotationWebFilter(className, ae, fragment);}else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {fragment.addListener(className);} else {// Unknown annotation - ignore}}}
}
  1. 将解析出的filter、servlet、session、cookie等属性加载到context中
private void configureContext(WebXml webxml) {//省略部分代码...// 设置 Filterfor (FilterDef filter : webxml.getFilters().values()) {if (filter.getAsyncSupported() == null) {filter.setAsyncSupported("false");}context.addFilterDef(filter);}// 设置 FilterMapping,即 Filter 的 URL 映射for (FilterMap filterMap : webxml.getFilterMappings()) {context.addFilterMap(filterMap);}// 往 Context 中添加子容器 Wrapper,即 Servletfor (ServletDef servlet : webxml.getServlets().values()) {Wrapper wrapper = context.createWrapper();wrapper.setName(servlet.getServletName());Map<String,String> params = servlet.getParameterMap();for (Entry<String, String> entry : params.entrySet()) {wrapper.addInitParameter(entry.getKey(), entry.getValue());}wrapper.setServletClass(servlet.getServletClass());wrapper.setOverridable(servlet.isOverridable());context.addChild(wrapper);}//还有很多属性被加载,这里就不一一赘述了
}

context.addChild(wrapper)时,会调用StandardContext的addChild,然后会调用ContainerBase的addChild,最后进行wrapper的启动。Wrapper的初始化和Context没什么区别。

3.3.2.1 Wrapper的启动

StandardWrapper没有子容器,所以启动时主要完成了jmx事件通知。

StandardWrapper#startInternal

protected synchronized void startInternal() throws LifecycleException {// 发出 j2ee.state.starting 事件通知if (this.getObjectName() != null) {Notification notification = new Notification("j2ee.state.starting",this.getObjectName(),sequenceNumber++);broadcaster.sendNotification(notification);}// 调用ContainerBase的startInternal,见Engine启动的ContainerBase#startInternalsuper.startInternal();setAvailable(0L);//running 事件通知if (this.getObjectName() != null) {Notification notification =new Notification("j2ee.state.running", this.getObjectName(),sequenceNumber++);broadcaster.sendNotification(notification);}}
3.3.2.2 加载静态文件

ContextConfig#webConfig

加载所有jar包下 META-INF/resources/的静态资源文件。

processResourceJARs(resourceJars);protected void processResourceJARs(Set<WebXml> fragments) {for (WebXml fragment : fragments) {URL url = fragment.getURL();try {if ("jar".equals(url.getProtocol()) || url.toString().endsWith(".jar")) {try (Jar jar = JarFactory.newInstance(url)) {jar.nextEntry();String entryName = jar.getEntryName();while (entryName != null) {if (entryName.startsWith("META-INF/resources/")) {context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR,"/", url, "/META-INF/resources");break;}jar.nextEntry();entryName = jar.getEntryName();}}} else if ("file".equals(url.getProtocol())) {File file = new File(url.toURI());File resources = new File(file, "META-INF/resources/");if (resources.isDirectory()) {context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR,"/", resources.getAbsolutePath(), null, "/");}}} catch (IOException ioe) {//...} }
}
3.3.2.3 设置ServletContainerInitializer

最后将所有ServletContainerInitializer实现类设置到context中。

if (ok) {for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry :initializerClassMap.entrySet()) {if (entry.getValue().isEmpty()) {context.addServletContainerInitializer(entry.getKey(), null);} else {context.addServletContainerInitializer(entry.getKey(), entry.getValue());}}
}

3.3.3 调用ServletContainerInitializer

在ContextConfig处理完start事件后,会先调用ServletContainerInitializer#onStartup方法。

StandardContext#startInternal

//调用 ServletContainerInitializer#onStartup()
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :initializers.entrySet()) {try {entry.getKey().onStartup(entry.getValue(),getServletContext());} catch (ServletException e) {log.error(sm.getString("standardContext.sciFail"), e);ok = false;break;}
}

3.3.4 启动listener

public boolean listenerStart() {// 实例化所有listenerString[] listeners = findApplicationListeners();Object[] results = new Object[listeners.length];boolean ok = true;for (int i = 0; i < results.length; i++) {try {String listener = listeners[i];results[i] = getInstanceManager().newInstance(listener);} catch (Throwable t) {//...ok = false;}}// 将listener按照类型分为2个list存储List<Object> eventListeners = new ArrayList<>();List<Object> lifecycleListeners = new ArrayList<>();for (int i = 0; i < results.length; i++) {if ((results[i] instanceof ServletContextAttributeListener)|| (results[i] instanceof ServletRequestAttributeListener)|| (results[i] instanceof ServletRequestListener)|| (results[i] instanceof HttpSessionIdListener)|| (results[i] instanceof HttpSessionAttributeListener)) {eventListeners.add(results[i]);}if ((results[i] instanceof ServletContextListener)|| (results[i] instanceof HttpSessionListener)) {lifecycleListeners.add(results[i]);}}//...//调用ServletContextListener的contextInitialized方法for (int i = 0; i < instances.length; i++) {if (!(instances[i] instanceof ServletContextListener))continue;ServletContextListener listener =(ServletContextListener) instances[i];try {fireContainerEvent("beforeContextInitialized", listener);if (noPluggabilityListeners.contains(listener)) {listener.contextInitialized(tldEvent);} else {listener.contextInitialized(event);}fireContainerEvent("afterContextInitialized", listener);} catch (Throwable t) {//...ok = false;}}return ok;}
  1. 实例化所有的listener。
  2. 根据类型将listener分为eventListenerslifecycleListeners
  3. 找出lifecycleListeners中的ServletContextListener,执行它的contextInitialized方法。

3.3.5 初始化filter

public boolean filterStart() {// Instantiate and record a FilterConfig for each defined filterboolean ok = true;synchronized (filterConfigs) {filterConfigs.clear();for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {String name = entry.getKey();try {ApplicationFilterConfig filterConfig =new ApplicationFilterConfig(this, entry.getValue());filterConfigs.put(name, filterConfig);} catch (Throwable t) {ok = false;}}}return ok;
}ApplicationFilterConfig(Context context, FilterDef filterDef)throws ClassCastException, ReflectiveOperationException, ServletException,
NamingException, IllegalArgumentException, SecurityException {super();this.context = context;this.filterDef = filterDef;// Allocate a new filter instance if necessaryif (filterDef.getFilter() == null) {getFilter();} else {this.filter = filterDef.getFilter();context.getInstanceManager().newInstance(filter);initFilter();}
}

将filterDefs转换成ApplicationFilterConfig,filterDefs是ContextConfig在解析servlet注解时用来保存filter信息的对象,并在ApplicationFilterConfig的构造方法中完成调用filter.init()。举个例子。这里会调用HelloFilter的init方法。

@WebFilter(urlPatterns = {"/*"}
)
public class HelloFilter implements Filter {public HelloFilter() {}public void init(FilterConfig filterConfig) throws ServletException {System.out.println("filter is init");}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("filter is running");chain.doFilter(request, response);}public void destroy() {System.out.println("filter is destroy");}
}

3.3.6 加载Wrapper

public boolean loadOnStartup(Container[] children) {TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();for (int i = 0; i < children.length; i++) {Wrapper wrapper = (Wrapper) children[i];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);}// Load the collected "load on startup" servletsfor (ArrayList<Wrapper> list : map.values()) {for (Wrapper wrapper : list) {try {wrapper.load();} catch (ServletException e) {//...}}}return true;}
  1. 首先根据loadOnStartup大小将wrapper进行排序,loadOnStartup值相同的放在同一个list。
  2. loadOnStartup>=0的会在启动阶段被加载,而如果loadOnStartup为默认值-1的话,是在首次调用时加载,这里的load方法调用的是StandardWrapper#load()
public synchronized void load() throws ServletException {// 实例化 Servlet,并且调用 init 方法完成初始化instance = loadServlet();
}public synchronized Servlet loadServlet() throws ServletException {InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();servlet = (Servlet) instanceManager.newInstance(servletClass);//调用servlet的initinitServlet(servlet);fireContainerEvent("load", this);
}

到这里Engine的启动就结束了。

3.4MapperListener启动

public void startInternal() throws LifecycleException {setState(LifecycleState.STARTING);Engine engine = service.getContainer();if (engine == null) {return;}findDefaultHost();addListeners(engine);Container[] conHosts = engine.findChildren();for (Container conHost : conHosts) {Host host = (Host) conHost;if (!LifecycleState.NEW.equals(host.getState())) {// Registering the host will register the context and wrappersregisterHost(host);}}
}
  1. 设置当前defaultName,此测试用例为localhost
  2. MapperListener作为ContainerListenerLifecycleListener递归设置到当前容器及其所有子容器
  3. 将Engine启动时解析出来的Wrapper放到MapperListener中,以备请求时进行匹配。
private void registerHost(Host host) {String[] aliases = host.findAliases();//设置hostmapper.addHost(host.getName(), aliases, host);for (Container container : host.findChildren()) {if (container.getState().isAvailable()) {registerContext((Context) container);}}// Default host may have changedfindDefaultHost();}private void registerContext(Context context) {String contextPath = context.getPath();if ("/".equals(contextPath)) {contextPath = "";}Host host = (Host)context.getParent();WebResourceRoot resources = context.getResources();String[] welcomeFiles = context.findWelcomeFiles();List<WrapperMappingInfo> wrappers = new ArrayList<>();for (Container container : context.findChildren()) {//将wrapper封装成WrapperMappingInfoprepareWrapperMappingInfo(context, (Wrapper) container, wrappers);}//将所有wrapper设置到Mapper中mapper.addContextVersion(host.getName(), host, contextPath,context.getWebappVersion(), context, welcomeFiles, resources,wrappers);}private final Map<Context, ContextVersion> contextObjectToContextVersionMap =new ConcurrentHashMap<>();public void addContextVersion(String hostName, Host host, String path,String version, Context context, String[] welcomeResources,WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {//省略了部分代码contextObjectToContextVersionMap.put(context, newContextVersion);
}

首先将Host设置到Mapper,然后解析子容器Context,将解析出的Wrapper封装成WrapperMappingInfo设置到MapperListener的Mapper中。这里放一张截图方便理解。其中/tomcat-test是测试用例servlet,其他的是Tomcat自带。

3.5 Connector启动

StandardConnector#startInternal

protected void startInternal() throws LifecycleException {//校验端口if (getPortWithOffset() < 0) {throw new LifecycleException(sm.getString("coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));}setState(LifecycleState.STARTING);try {//protocolHandler.start();} catch (Exception e) {//...}
}

根据Connector的init阶段,这里的protocolHandler的实现是Http11NioProtocol,而Http11NioProtocol的start方法会调用父类AbstractProtocol#start(),这个方法里调用了endpoint的start。

AbstractProtocol#start()

public void start() throws Exception {endpoint.start();}

3.5.1 Endpoint启动

Connector启动的重点是在Endpoint的启动,这里会启动两个线程组,Pollers

NioEndpoint#start()

public void startInternal() throws Exception {if (!running) {running = true;paused = false;//SocketProcessor对象的缓存if (socketProperties.getProcessorCache() != 0) {processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getProcessorCache());}//缓存poller事件if (socketProperties.getEventCache() != 0) {eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getEventCache());}//字节缓冲区高速缓存if (socketProperties.getBufferPool() != 0) {nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getBufferPool());}// Create worker collectionif (getExecutor() == null) {createExecutor();}//初始化连接数计数器,默认是1000 //private int maxConnections = 10000;initializeConnectionLatch();// Start poller threadspollers = new Poller[getPollerThreadCount()];for (int i = 0; i < pollers.length; i++) {pollers[i] = new Poller();Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-" + i);pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();}startAcceptorThreads();}
}

除了配置一些缓存之外,这里创建了三种不同类型的Thread。Poller线程、Acceptor线程以及一个线程池。

  1. create线程池,

AbstractEndpoint#createExecutor

private int minSpareThreads = 10;private int maxThreads = 200;public void createExecutor() {internalExecutor = true;TaskQueue taskqueue = new TaskQueue();TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);taskqueue.setParent( (ThreadPoolExecutor) executor);
}

这里默认的coresize=10,maxsize=200,keepalive=60s,这个线程池用来处理servlet请求。

  1. start Poller Thread,该线程用于监听Socket事件,当Socket可读或者可写时,调用上面的线程池处理Socket请求。
 private int pollerThreadCount = 1;pollers = new Poller[getPollerThreadCount()];
for (int i = 0; i < pollers.length; i++) {pollers[i] = new Poller();Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-" + i);pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();
}
  1. start Acceptor Thread,用于接收新连接,并将新连接添加到Poller事件队列中。
 protected int acceptorThreadCount = 1;protected void startAcceptorThreads() {int count = getAcceptorThreadCount();acceptors = new ArrayList<>(count);for (int i = 0; i < count; i++) {Acceptor<U> acceptor = new Acceptor<>(this);String threadName = getName() + "-Acceptor-" + i;acceptor.setThreadName(threadName);acceptors.add(acceptor);Thread t = new Thread(acceptor, threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();}
}

放一张Endpoint启动完之后主要的线程状态图。

从图中可以看出Acceptor ThreadClientPoller Thread都处于running状态,而线程池则是wait状态,这里当ClientPoller Thread接收到socket请求时,会启用线程池处理servlet请求。

到这里Tomcat就成功启动完成了。

参考:

https://blog.csdn.net/Dwade_mia/article/details/79244157
https://blog.csdn.net/Dwade_mia/article/details/79328151

Tomcat源码解析:启动相关推荐

  1. Tomcat源码解析五:Tomcat请求处理过程

    前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...

  2. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  3. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/11/es-code02/ 前提 上篇文章写了 ElasticSearch 源码解析 -- ...

  4. APISIX源码解析-启动篇【ops.lua - start】

    APISIX源码解析-启动篇[ops.lua - start] 命令开始入口 ops.execute(env, arg) 命令参数校验 目前仅支持:help.version.init.init_etc ...

  5. Tomcat 源码解析一初识

      为什么我想研究Tomcat源码,我们现在都用的是SpringBoot开发项目,而SpringBoot对错Tomcat集成,导致现在基本上看不到Tomcat的身影了,但是Tomcat不存在吗?只要我 ...

  6. Tomcat源码解析:环境搭建

    下载源码 从github下载tomcat源码 git clone https://github.com/apache/tomcat.git 或者直接fork一份到自己仓库,以便后续添加注释,附上笔者自 ...

  7. Flink1.15源码解析--启动TaskManager

    文章目录 一.前言 二.TaskManagerRunner 2.1.创建 TaskManagerRunner 2.1.1.创建 TaskExecutorService, 用于创建 TaskExecut ...

  8. Tomcat源码解析一:下载源码与导入eclipse

    自从写web程序以来,web程序是如何在Tomcat中运行的一直困惑着我,不知道底层的运行机制是无法真正理解web的,所以就开始研究Tomcat源码,Tomcat是一个轻量级的java服务器,再结合& ...

  9. Tomcat源码解析三:tomcat的启动过程

    Tomcat组件生命周期管理 在Tomcat总体结构 (Tomcat源代码解析之二)中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context ...

  10. Tomcat源码解析:Jsp文件的编译、实现

    1.Jsp简介 jsp(java server page),其根本是一个简化的Servlet技术,是一种动态网页技术标准. 它是在传统的网页HTML页面中插入java代码段,从而形成jsp文件,后缀为 ...

最新文章

  1. Opencv腐蚀操作去除激光反光光斑
  2. 如何利用nginx处理DDOS进行系统优化详解
  3. 【Linux网络编程】原始套接字实例:MAC 头部报文分析
  4. 12_02_Linux软件管理之二rpm
  5. -1.#IND000 图像类型转换
  6. Teams App 如何使用设备的能力
  7. 项目管理学习总结(10)——项目经理需要具备8点管理素质
  8. 字节跳动冬令营报名启动,邀你一起备战ICPC世界总决赛!
  9. html常用标签总结
  10. mysql之explain详解(分析索引的最佳使用)
  11. 【转】NB的specify
  12. 心法利器[58] | 从长尾问题到以搜代分的机理探索
  13. 电信光猫获取超级管理员账号
  14. 1月20日在线研讨会|满足合规和项目裁剪要求的流程管理实践
  15. 电脑如何设置定时关机?
  16. 华为路由模拟器3.0参考实验8----单臂路由无法ping通问题分析
  17. 看完小区丰巢柜身上的字 我决定了
  18. js判断ipad pro设备
  19. win7正版微软给你
  20. 初识selenium--发送QQ邮件

热门文章

  1. ES7、ES8、ES9、ES10新特性大盘点
  2. python 读取和保存图片方法
  3. 关于MBR和GUID分区的问题
  4. 在MySQL中,如何给用户授予权限
  5. 【20220629】【信号处理】(平稳随机信号)自相关函数性质的证明过程
  6. 赛灵思运行linux,玩转赛灵思Zedboard开发板(6):如何在Zedboard上运行linux下的应用程序?...
  7. docker创建容器一直restart解决
  8. svn提交代码报错:svn: E175002: Unexpected HTTP status 502 ‘Bad Gateway‘
  9. npm更新包(全局单个,项目单个,全局所有,项目生产环境,项目开发环境)
  10. excel函数应用:如何写出IF函数多级嵌套公式