Tomcat源码解析:启动
文章目录
- 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();}
}
- 调用server的start,开始进行启动。
- 注册jvm关闭钩子,以便优雅停机。
- 开启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();}}}
}
- 启动engine
- 启动线程池
- 启动mapperListener
- 启动Connector
3.1 Engine启动
engine的启动和初始化一样,会调用父类ContainerBase
的startInternal
方法,主要分为3个步骤
- 使用线程池启动子容器。
- 启动Pipeline,并将状态设置为STARTING,发出事件通知
- 如果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
,所以这里的初始化执行了ContainerBase
的initInternal
方法,详见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 "%r" %s %b" />
</Host>
另一个默认的Valve来自于StandardHost
的构造方法。这里每个实现了ContainerBase
的容器都会在构造方法里创建一个默认的Valve。
public StandardHost() {super();pipeline.setBasic(new StandardHostValve());}
所以StandardHost Pipeline 包含的 Valve 组件:
- basic:org.apache.catalina.core.StandardHostValve
- 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;}}
}
由于这段代码比较复杂,这里说一下大概的内容。
- 实例化 WebResourceRoot,默认实现类是 StandardRoot,用于读取 webapp 的文件资源
- 实例化loader对象,这里默认是WebappLoader,并执行loader的start方法,将所有class缓存起来。
- 发布
CONFIGURE_START_EVENT
事件,该事件主要由ContextConfig处理。 - 实例化 Sesssion 管理器,默认使用 StandardManager。
- 初始化filter
- 实例化并启动servlet
针对几个重点的部分作详细研究。
3.3.2 ContextConfig
对于该事件的处理主要在ContextConfig#webConfig
。
- 首先解析
web.xml
。
WebXml webXml = createWebXml();// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {ok = false;
}
- 解析
web-fragment.xml
,tomcat提供的jar包会忽略此xml文件
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);Set<WebXml> orderedFragments = null;
orderedFragments =WebXml.orderWebFragments(webXml, fragments, sContext);
- 处理 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
- 如果没有配置
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}}}
}
- 将解析出的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;}
- 实例化所有的listener。
- 根据类型将listener分为
eventListeners
和lifecycleListeners
。 - 找出
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;}
- 首先根据
loadOnStartup
大小将wrapper进行排序,loadOnStartup
值相同的放在同一个list。 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);}}
}
- 设置当前defaultName,此测试用例为
localhost
- 将
MapperListener
作为ContainerListener
和LifecycleListener
递归设置到当前容器及其所有子容器 - 将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的启动,这里会启动两个线程组,Poller
和s
。
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
线程以及一个线程池。
- 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请求。
- 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();
}
- 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 Thread
和ClientPoller 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源码解析:启动相关推荐
- Tomcat源码解析五:Tomcat请求处理过程
前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...
- Tomcat源码解析系列二:Tomcat总体架构
Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...
- 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)
关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/11/es-code02/ 前提 上篇文章写了 ElasticSearch 源码解析 -- ...
- APISIX源码解析-启动篇【ops.lua - start】
APISIX源码解析-启动篇[ops.lua - start] 命令开始入口 ops.execute(env, arg) 命令参数校验 目前仅支持:help.version.init.init_etc ...
- Tomcat 源码解析一初识
为什么我想研究Tomcat源码,我们现在都用的是SpringBoot开发项目,而SpringBoot对错Tomcat集成,导致现在基本上看不到Tomcat的身影了,但是Tomcat不存在吗?只要我 ...
- Tomcat源码解析:环境搭建
下载源码 从github下载tomcat源码 git clone https://github.com/apache/tomcat.git 或者直接fork一份到自己仓库,以便后续添加注释,附上笔者自 ...
- Flink1.15源码解析--启动TaskManager
文章目录 一.前言 二.TaskManagerRunner 2.1.创建 TaskManagerRunner 2.1.1.创建 TaskExecutorService, 用于创建 TaskExecut ...
- Tomcat源码解析一:下载源码与导入eclipse
自从写web程序以来,web程序是如何在Tomcat中运行的一直困惑着我,不知道底层的运行机制是无法真正理解web的,所以就开始研究Tomcat源码,Tomcat是一个轻量级的java服务器,再结合& ...
- Tomcat源码解析三:tomcat的启动过程
Tomcat组件生命周期管理 在Tomcat总体结构 (Tomcat源代码解析之二)中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context ...
- Tomcat源码解析:Jsp文件的编译、实现
1.Jsp简介 jsp(java server page),其根本是一个简化的Servlet技术,是一种动态网页技术标准. 它是在传统的网页HTML页面中插入java代码段,从而形成jsp文件,后缀为 ...
最新文章
- Opencv腐蚀操作去除激光反光光斑
- 如何利用nginx处理DDOS进行系统优化详解
- 【Linux网络编程】原始套接字实例:MAC 头部报文分析
- 12_02_Linux软件管理之二rpm
- -1.#IND000 图像类型转换
- Teams App 如何使用设备的能力
- 项目管理学习总结(10)——项目经理需要具备8点管理素质
- 字节跳动冬令营报名启动,邀你一起备战ICPC世界总决赛!
- html常用标签总结
- mysql之explain详解(分析索引的最佳使用)
- 【转】NB的specify
- 心法利器[58] | 从长尾问题到以搜代分的机理探索
- 电信光猫获取超级管理员账号
- 1月20日在线研讨会|满足合规和项目裁剪要求的流程管理实践
- 电脑如何设置定时关机?
- 华为路由模拟器3.0参考实验8----单臂路由无法ping通问题分析
- 看完小区丰巢柜身上的字 我决定了
- js判断ipad pro设备
- win7正版微软给你
- 初识selenium--发送QQ邮件
热门文章
- ES7、ES8、ES9、ES10新特性大盘点
- python 读取和保存图片方法
- 关于MBR和GUID分区的问题
- 在MySQL中,如何给用户授予权限
- 【20220629】【信号处理】(平稳随机信号)自相关函数性质的证明过程
- 赛灵思运行linux,玩转赛灵思Zedboard开发板(6):如何在Zedboard上运行linux下的应用程序?...
- docker创建容器一直restart解决
- svn提交代码报错:svn: E175002: Unexpected HTTP status 502 ‘Bad Gateway‘
- npm更新包(全局单个,项目单个,全局所有,项目生产环境,项目开发环境)
- excel函数应用:如何写出IF函数多级嵌套公式