1.測試代碼,一個簡單的springboot web項目:地址:https://gitee.com/yangxioahui/demo_mybatis.git
一:tomcat的主要架構:1.如果我們下載了tomcat的安裝包,可以在解壓后的文件夾看到server.xml文件:
內容如下:
<?xml version='1.0' encoding='utf-8'?> <Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html --> <Listener className="org.apache.catalina.core.JasperListener" /> <!-- Prevent memory leaks due to use of particular java/javax APIs--> <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> <GlobalNamingResources> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> </GlobalNamingResources> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> <Context path="/shop" docBase="D:\shop\demo\web" debug="0" reloadable="true"> </Host> </Engine> </Service> </Server>
從server.xml中我們可以分析出如下關系:
上圖wrapper是我根據tomcat源碼補上去的,在server.xml中並沒有體現出來,通過上圖,大概分析下他們的關系,記得不要在意每個節點的意思:
由於在java中一切皆對象,所以結合上圖我們分析:
一個tomcat對象中會包含一個server對象,一個server對象可以包含多個service對象,一個service對象,可以包含多個connector對象(用於接收TCP連接)和一個Engine對象;
一個Engine可以包含多個Host對象,而每個Host可以包含多個context(我們寫的每個項目就是一個context),每個Context會包含多個Wrapper對象,而每個Wrapper都會包含一個servlet;
從Engine節點往下包含它自己,都稱為容器Container
偽代碼實現如下:
Tomcat tomcat=new Tomcat();
Server server=new Server();
tomcat.setServer(server);
Service service=new Service();
server.addService(service); //存到集合中
Connector connector=new Connector();
service.setConnector(connector);
Engine engine=new Engine();
service.setEngine(engine);
..... 省略
在tomcat中,命名上面的節點都喜歡使用StandardXX,如StandardServer,StandardService;
通過這些節點的類,我們發現Engine節點和其包含的子節點都是繼承了ContainerBase這個類,所以為何說Engine這個節點和其子節點都叫做容器的道理了:
我們仔細查看上面的節點類,發現他們都繼承了生命周期相關的類:LifecycleMBeanBase:
所以他們都會有對應的生命周期相關方法,這些節點通過生命周期的方法進行串聯起來,如tomcat.start()->server.start()->servece.start()->engine.start().........
二.springboot內嵌tomcat源碼分析:
有了上面的基礎,我們分析springboot內嵌tomcat的源碼就比較方便了: 啟動項目,debug調試,可以跟蹤到內嵌tomcat的源碼:
//下面從createWebServer() 開始就是我們要分析的源碼了,它在ServletWebServerApplicationContext這個類中
private void createWebServer() { WebServer webServer = this.webServer; //第一次過來,這個為null ServletContext servletContext = getServletContext(); //這個也是null if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory();//獲取tomcat工廠 this.webServer = factory.getWebServer(getSelfInitializer());//通過工廠類獲取服務器對象。這個是核心 getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
上面代碼主要是通過web工廠,創建webserver對象,那web工廠有哪些呢?
這里我們用的是tomcat,所以webserver工廠就自然是TomcatServletWebServerFactory,之后就看它是如何創建tomcat的
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } Tomcat tomcat = new Tomcat(); //創建tomcat對象了 File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); //創建connector了,根據之前tomcat架構分析,這里的connector最終會包含在service里 connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); //這里獲取service,獲取不到,就會創建,然后設置到Connector到service中 customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false);//獲取Host節點,獲取不到就會創建Host configureEngine(tomcat.getEngine());//創建Engine,在里面會將Host設置到Engine里面 for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers);//創建Connext,在里面會將Context設置到host里面 return getTomcatWebServer(tomcat);//啟動各個組件的生命周期的方法 }
上面的方法,主要是創建tomcat的架構的各種主件,並且將它們的關系串聯起來,之后就調用組件的生命周期方法:
分析幾個重要的方法,在tomcat中,通過tomcat.getXX主件,獲取不到,內部會創建1. tomcat.getService().addConnector(connector); //由於創建service之前需要創建server,因為server節點是包含service的:
public Service getService() { return getServer().findServices()[0]; } public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); server = new StandardServer(); //創建server節點了 initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); server.setPort( -1 ); Service service = new StandardService(); //創建service節點了 service.setName("Tomcat"); server.addService(service); //將service節點加到server節點中 return server; }
2.tomcat.getHost().setAutoDeploy(false); //根據tomcat架構分析,創建Host前一定會創建Engine
3.prepareContext(tomcat.getHost(), initializers); 這里開始創建Context節點了,並將其加到Host中
protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); TomcatEmbeddedContext context = new TomcatEmbeddedContext();//繼承於StandardContext if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.5.39. Continue. } configureTldSkipPatterns(context); WebappLoader loader = new WebappLoader(); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); //context中添加servelet,這里添加的並不是我們業務代碼創建的servlet,而是默認的 } if (shouldRegisterJspServlet()) { addJspServlet(context); // addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); host.addChild(context); //將Context添加到Host節點 configureContext(context, initializersToUse); postProcessContext(context); }
4.我們看看servlet如何添加到Context中的
至此,tomcat架構的主要主件已經被串起來了,接下來要分析的是主件的生命周期啟動過程:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); } public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null; initialize(); //開始初始化 } private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); //開始啟動主件的生命周期方法 // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
從這里開始this.tomcat.start();將會是我們要重點關注的邏輯了:
我們知道tomcat架構第一個主件是server,所以肯定接下來是server.start()
我們繼續跟進,server.start(); //這里開始要注意了,主件的start()方法是在父類LifecycleBase 中的,因此,后續的主件調用也是調用父類的start方法
@Override public final synchronized void start() throws LifecycleException { if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) || LifecycleState.STARTED.equals(state)) { if (log.isDebugEnabled()) { Exception e = new LifecycleException(); log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e); } else if (log.isInfoEnabled()) { log.info(sm.getString("lifecycleBase.alreadyStarted", toString())); } return; } if (state.equals(LifecycleState.NEW)) { init(); //第一次進來先調用初始化,運用多態的思想,具體邏輯由子類實現 } else if (state.equals(LifecycleState.FAILED)) { stop(); } else if (!state.equals(LifecycleState.INITIALIZED) && !state.equals(LifecycleState.STOPPED)) { invalidTransition(Lifecycle.BEFORE_START_EVENT); } try { setStateInternal(LifecycleState.STARTING_PREP, null, false); startInternal(); //初始化完,開始調用內部的start方法 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) { // This is an 'uncontrolled' failure so put the component into the // FAILED state and throw an exception. handleSubClassException(t, "lifecycleBase.startFail", toString()); } }
從上面方法可知,所有主件都會先調用int()-> startInternal();
我們繼續跟進startInternal();
service.start(); 最終也是調用父類的對應方法,跟進去看看

我們只要看startInternal();方法即可
protected void startInternal() throws LifecycleException { if(log.isInfoEnabled()) log.info(sm.getString("standardService.start.name", this.name)); setState(LifecycleState.STARTING); // Start our defined Container first if (engine != null) { synchronized (engine) { engine.start(); //這里啟動engine->啟動host->context } } synchronized (executors) { for (Executor executor: executors) { executor.start(); } } //----- 走到下面的代碼說明上面的容器節點都執行完生命周期了------------------ mapperListener.start(); //啟動監聽器,這個是重點,因為servlet交給tomcat是在這里做的 // Start our defined Connectors second synchronized (connectorsLock) { for (Connector connector: connectors) { // If it has already failed, don't try and start it if (connector.getState() != LifecycleState.FAILED) { connector.start(); //啟動連接器,這里也是重點,這個是初始化NIO的服務端,開始監聽客戶端請求 } } } }
通過上面的service的start()方法,我們發現其會調用容器的start方法,之后調用mapperListener的start方法,最后調用Connector的start()方法,開始監聽服務。我們一個個看:
1.engine.start(),之后也是調用父類的方法,所以只要看startInternal(); 從這里開始都是容器的啟動,實現
@Override protected synchronized void startInternal() throws LifecycleException { // Start our subordinate components, if any logger = null; getLogger(); Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).start(); } Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Start our child containers, if any Container children[] = findChildren(); //獲取子容器,這里的子容器 List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { results.add(startStopExecutor.submit(new StartChild(child))); //使用線程池啟動子容器StartChild實現了Callable接口 } 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); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } setState(LifecycleState.STARTING); // Start our thread if (backgroundProcessorDelay > 0) { monitorFuture = Container.getService(ContainerBase.this).getServer() .getUtilityExecutor().scheduleWithFixedDelay( new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } }
startStopExecutor.submit(new StartChild(child));//容器的啟動會調用該方法

Host的啟動,會調用Context的啟動,邏輯跟前面一樣

Context的啟動會調用Wrapper的啟動
之后的Wrapper的start();后續就不分析了,現在小結生命周期的流程:
1.問題:tomcat接收到Http請求后,如何通過url找到對應的servlet的?
回答上述問題,先看看service的生命周期調用中,調用了mapperListener.start();我們看看它做了啥? 因為mapperListener也是實現生命周期接口的,所以我們看其:
startInternal();方法:
@Override 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 wrappers registerHost(host); //根進這個方法 } } }
private void registerHost(Host host) { String[] aliases = host.findAliases(); mapper.addHost(host.getName(), aliases, host); for (Container container : host.findChildren()) { if (container.getState().isAvailable()) { registerContext((Context) container); //跟進這里,因為Context包含了wrapper,而Wrapper包含了Servlet,servlet是有對應的映射路徑的 } } // Default host may have changed findDefaultHost(); if(log.isDebugEnabled()) { log.debug(sm.getString("mapperListener.registerHost", host.getName(), domain, service)); } }
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()) { prepareWrapperMappingInfo(context, (Wrapper) container, wrappers); //將wrapper封裝成WrapperMappingInfo } mapper.addContextVersion(host.getName(), host, contextPath, context.getWebappVersion(), context, welcomeFiles, resources, wrappers); //加到Mapper對象中 }
通過上圖可知,經過prepareWrapperMappingInfo()方法,將Context容器中的所有Wrapper都封裝成了WrapperMappingInfo對象了:
之后我們根據mapper.addContextVersion() 方法: 省略部分代碼后:下面的代碼存在Mapper這個類中
protected void addWrapper(ContextVersion context, String path, Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) { synchronized (context) { if (path.endsWith("/*")) { // Wildcard wrapper //如果匹配的路徑是通配符 String name = path.substring(0, path.length() - 2); MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly); MappedWrapper[] oldWrappers = context.wildcardWrappers; MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.wildcardWrappers = newWrappers; //加到ContextVersion 的WildcardWrapper數組中 int slashCount = slashCount(newWrapper.name); if (slashCount > context.nesting) { context.nesting = slashCount; } } } else if (path.startsWith("*.")) { // Extension wrapper //如果是拓展名結尾的映射路徑 String name = path.substring(2); MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly); MappedWrapper[] oldWrappers = context.extensionWrappers; MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.extensionWrappers = newWrappers; //加到ContextVersion 的extensionWrappers數組中 } } else if (path.equals("/")) { // Default wrapper MappedWrapper newWrapper = new MappedWrapper("", wrapper, jspWildCard, resourceOnly); context.defaultWrapper = newWrapper; //默認的,我們這個的是默認的 } else { // Exact wrapper final String name; if (path.length() == 0) { // Special case for the Context Root mapping which is // treated as an exact match name = "/"; } else { name = path; } MappedWrapper newWrapper = new MappedWrapper(name, wrapper, jspWildCard, resourceOnly); MappedWrapper[] oldWrappers = context.exactWrappers; MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.exactWrappers = newWrappers; //ContextVersion 的精確匹配的exactWrappers 數組中 } } } }
通過上面的分析,我們可以清晰的看到,Servlet的路徑不一樣,最后存到ContextVersion中的數組也是不一樣,分為:通配符匹配,拓展名匹配,默認匹配,精確匹配,我們看看ContextVersion對象:
我們已經知道Mapper的內部類存有所有的servlet包裝類,並且也根據不同的路徑分類了,那接下來,我們分析下請求過來時,如何查找對應的servlet: 還是在Mapper這個類
private final void internalMapWrapper(ContextVersion contextVersion, CharChunk path, MappingData mappingData) throws IOException { int pathOffset = path.getOffset(); int pathEnd = path.getEnd(); boolean noServletPath = false; int length = contextVersion.path.length(); if (length == (pathEnd - pathOffset)) { noServletPath = true; } int servletPath = pathOffset + length; path.setOffset(servletPath); // Rule 1 -- Exact Match //精確匹配 匹配到的wrapper會存到mappingData MappedWrapper[] exactWrappers = contextVersion.exactWrappers; internalMapExactWrapper(exactWrappers, path, mappingData); // Rule 2 -- Prefix Match //通配符匹配 匹配到的wrapper會存到mappingData boolean checkJspWelcomeFiles = false; MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers; if (mappingData.wrapper == null) { internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting, path, mappingData); if (mappingData.wrapper != null && mappingData.jspWildCard) { char[] buf = path.getBuffer(); if (buf[pathEnd - 1] == '/') { /* * Path ending in '/' was mapped to JSP servlet based on * wildcard match (e.g., as specified in url-pattern of a * jsp-property-group. * Force the context's welcome files, which are interpreted * as JSP files (since they match the url-pattern), to be * considered. See Bugzilla 27664. */ mappingData.wrapper = null; checkJspWelcomeFiles = true; } else { // See Bugzilla 27704 mappingData.wrapperPath.setChars(buf, path.getStart(), path.getLength()); mappingData.pathInfo.recycle(); } } } if(mappingData.wrapper == null && noServletPath && contextVersion.object.getMapperContextRootRedirectEnabled()) { // The path is empty, redirect to "/" path.append('/'); pathEnd = path.getEnd(); mappingData.redirectPath.setChars (path.getBuffer(), pathOffset, pathEnd - pathOffset); path.setEnd(pathEnd - 1); return; } // Rule 3 -- Extension Match //拓展名匹配 匹配到的wrapper會存到mappingData MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers; if (mappingData.wrapper == null && !checkJspWelcomeFiles) { internalMapExtensionWrapper(extensionWrappers, path, mappingData, true); } // Rule 4 -- Welcome resources processing for servlets //歡迎頁匹配 if (mappingData.wrapper == null) { boolean checkWelcomeFiles = checkJspWelcomeFiles; if (!checkWelcomeFiles) { char[] buf = path.getBuffer(); checkWelcomeFiles = (buf[pathEnd - 1] == '/'); } if (checkWelcomeFiles) { for (int i = 0; (i < contextVersion.welcomeResources.length) && (mappingData.wrapper == null); i++) { path.setOffset(pathOffset); path.setEnd(pathEnd); path.append(contextVersion.welcomeResources[i], 0, contextVersion.welcomeResources[i].length()); path.setOffset(servletPath); // Rule 4a -- Welcome resources processing for exact macth internalMapExactWrapper(exactWrappers, path, mappingData); // Rule 4b -- Welcome resources processing for prefix match if (mappingData.wrapper == null) { internalMapWildcardWrapper (wildcardWrappers, contextVersion.nesting, path, mappingData); } // Rule 4c -- Welcome resources processing // for physical folder if (mappingData.wrapper == null && contextVersion.resources != null) { String pathStr = path.toString(); WebResource file = contextVersion.resources.getResource(pathStr); if (file != null && file.isFile()) { internalMapExtensionWrapper(extensionWrappers, path, mappingData, true); if (mappingData.wrapper == null && contextVersion.defaultWrapper != null) { mappingData.wrapper = contextVersion.defaultWrapper.object; mappingData.requestPath.setChars (path.getBuffer(), path.getStart(), path.getLength()); mappingData.wrapperPath.setChars (path.getBuffer(), path.getStart(), path.getLength()); mappingData.requestPath.setString(pathStr); mappingData.wrapperPath.setString(pathStr); } } } } path.setOffset(servletPath); path.setEnd(pathEnd); } } /* welcome file processing - take 2 * Now that we have looked for welcome files with a physical * backing, now look for an extension mapping listed * but may not have a physical backing to it. This is for * the case of index.jsf, index.do, etc. * A watered down version of rule 4 */ if (mappingData.wrapper == null) { boolean checkWelcomeFiles = checkJspWelcomeFiles; if (!checkWelcomeFiles) { char[] buf = path.getBuffer(); checkWelcomeFiles = (buf[pathEnd - 1] == '/'); } if (checkWelcomeFiles) { for (int i = 0; (i < contextVersion.welcomeResources.length) && (mappingData.wrapper == null); i++) { path.setOffset(pathOffset); path.setEnd(pathEnd); path.append(contextVersion.welcomeResources[i], 0, contextVersion.welcomeResources[i].length()); path.setOffset(servletPath); internalMapExtensionWrapper(extensionWrappers, path, mappingData, false); } path.setOffset(servletPath); path.setEnd(pathEnd); } } // Rule 7 -- Default servlet //默認servlet匹配,“/” 開頭的匹配,springmvc的dispatcherServlet就是這種 if (mappingData.wrapper == null && !checkJspWelcomeFiles) { if (contextVersion.defaultWrapper != null) { mappingData.wrapper = contextVersion.defaultWrapper.object; mappingData.requestPath.setChars (path.getBuffer(), path.getStart(), path.getLength()); mappingData.wrapperPath.setChars (path.getBuffer(), path.getStart(), path.getLength()); mappingData.matchType = MappingMatch.DEFAULT; } // Redirection to a folder char[] buf = path.getBuffer(); if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') { String pathStr = path.toString(); // Note: Check redirect first to save unnecessary getResource() // call. See BZ 62968. if (contextVersion.object.getMapperDirectoryRedirectEnabled()) { WebResource file; // Handle context root if (pathStr.length() == 0) { file = contextVersion.resources.getResource("/"); } else { file = contextVersion.resources.getResource(pathStr); } if (file != null && file.isDirectory()) { // Note: this mutates the path: do not do any processing // after this (since we set the redirectPath, there // shouldn't be any) path.setOffset(pathOffset); path.append('/'); mappingData.redirectPath.setChars (path.getBuffer(), path.getStart(), path.getLength()); } else { mappingData.requestPath.setString(pathStr); mappingData.wrapperPath.setString(pathStr); } } else { mappingData.requestPath.setString(pathStr); mappingData.wrapperPath.setString(pathStr); } } } path.setOffset(pathOffset); path.setEnd(pathEnd); }
至此,我們分析完了,servlet如何存到tomcat中,同時tomcat如何通過url查詢對應的servlet:
最后我們回去看看Connector的啟動流程,看看它又干了啥?
debug發現,並不是在上面圖所示進行Connector進行start(),我們打斷點到Connector這個類的startInternal():
繼承根進:
public final void start() throws Exception { if (bindState == BindState.UNBOUND) { bindWithCleanup(); bindState = BindState.BOUND_ON_START; } startInternal(); } @Override public void startInternal() throws Exception { if (!running) { running = true; paused = false; if (socketProperties.getProcessorCache() != 0) { processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getProcessorCache()); } 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 collection if (getExecutor() == null) { createExecutor(); } initializeConnectionLatch(); // Start poller thread poller = new Poller(); // 這里是重點,該類實現了Runnable Thread pollerThread = new Thread(poller, getName() + "-ClientPoller"); pollerThread.setPriority(threadPriority); pollerThread.setDaemon(true); pollerThread.start(); //通過線程啟動poller這個Runnable startAcceptorThread(); //這里也是重點 } }
//我們先看看Poller這個類:
先來補充下NIO的幾個組件知識: Selector選擇器,會監聽注冊到該監聽器的SocketChannel的各種事件
我們回到Poller來,先看其構造器,然后看其run()方法:
public class Poller implements Runnable { private Selector selector; private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();//棧結構,用於存儲selector監聽到的事件 private volatile boolean close = false; // Optimize expiration handling private long nextExpiration = 0; private AtomicLong wakeupCounter = new AtomicLong(0); private volatile int keyCount = 0; public Poller() throws IOException { this.selector = Selector.open(); //創建選擇器,NIO的重要主件之一
}
//省略了部分代碼 public void run() { // Loop until destroy() is called while (true) { boolean hasEvents = false; try { if (!close) { hasEvents = events();//判斷是否有注冊事件 if (wakeupCounter.getAndSet(-1) > 0) { // If we are here, means we have other stuff to do // Do a non blocking select keyCount = selector.selectNow(); } else { keyCount = selector.select(selectorTimeout); } wakeupCounter.set(0); } if (close) { events(); timeout(0, false); try { selector.close(); } catch (IOException ioe) { log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe); } break; } } catch (Throwable x) { ExceptionUtils.handleThrowable(x); log.error(sm.getString("endpoint.nio.selectorLoopError"), x); continue; } // Either we timed out or we woke up, process events first if (keyCount == 0) { hasEvents = (hasEvents | events()); } Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null; //選擇器開始監聽事件 // Walk through the collection of ready keys and dispatch // any active event. while (iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment(); // Attachment may be null if another thread has called // cancelledKey() if (socketWrapper == null) { iterator.remove(); } else { iterator.remove(); processKey(sk, socketWrapper);//如果有事件過來就會 這個是重點,服務器接收到客戶端連接事件后就會調用該方法處理客戶端的連接 } } // Process timeouts timeout(keyCount,hasEvents); } getStopLatch().countDown(); }
//根據看看events():下圖當envents這個棧有事件時,其會調用events.poll()方法彈出一個事件,如果是注冊事件,就會注冊到Selector中:
小結:poller實現了Runnable 接口,其run()方法會進行客戶端各種連接事件,而其evens這個棧結構用於存儲ServerSocketChannel.accept()接收到的SocketChannel封裝成的事件:
那么:到底是誰將客戶端的連接壓入Poller的events棧的呢?我們回到下面的代碼:
這里有個acceptor主件,跟poller一樣,它也實現了Runnable:看看其源碼:
acceptor = new Acceptor<>(this); //該方法傳入了this(AbstractEndpoint),而this是包含了Poller的,所以Acceptor接收的客戶端才能交給Poller;
我們分析下其run方法:
@Override public void run() { int errorDelay = 0; // Loop until we receive a shutdown command while (endpoint.isRunning()) { // Loop if endpoint is paused while (endpoint.isPaused() && endpoint.isRunning()) { state = AcceptorState.PAUSED; try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } if (!endpoint.isRunning()) { break; } state = AcceptorState.RUNNING; try { //if we have reached max connections, wait endpoint.countUpOrAwaitConnection(); // Endpoint might have been paused while waiting for latch // If that is the case, don't accept new connections if (endpoint.isPaused()) { continue; } U socket = null; try { // Accept the next incoming connection from the server // socket socket = endpoint.serverSocketAccept(); } catch (Exception ioe) { // We didn't get a socket endpoint.countDownConnection(); if (endpoint.isRunning()) { // Introduce delay if necessary errorDelay = handleExceptionWithDelay(errorDelay); // re-throw throw ioe; } else { break; } } // Successful accept, reset the error delay errorDelay = 0; // Configure the socket if (endpoint.isRunning() && !endpoint.isPaused()) { // setSocketOptions() will hand the socket off to // an appropriate processor if successful if (!endpoint.setSocketOptions(socket)) { endpoint.closeSocket(socket); } } else { endpoint.destroySocket(socket); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); String msg = sm.getString("endpoint.accept.fail"); // APR specific. // Could push this down but not sure it is worth the trouble. if (t instanceof Error) { Error e = (Error) t; if (e.getError() == 233) { // Not an error on HP-UX so log as a warning // so it can be filtered out on that platform // See bug 50273 log.warn(msg, t); } else { log.error(msg, t); } } else { log.error(msg, t); } } } state = AcceptorState.ENDED; }
@Override public void run() { int errorDelay = 0; // Loop until we receive a shutdown command while (endpoint.isRunning()) { // Loop if endpoint is paused while (endpoint.isPaused() && endpoint.isRunning()) { state = AcceptorState.PAUSED; try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } if (!endpoint.isRunning()) { break; } state = AcceptorState.RUNNING; try { //if we have reached max connections, wait endpoint.countUpOrAwaitConnection(); // Endpoint might have been paused while waiting for latch // If that is the case, don't accept new connections if (endpoint.isPaused()) { continue; } U socket = null; try { // Accept the next incoming connection from the server // socket socket = endpoint.serverSocketAccept(); //接收客戶端的請求,后續會根據,這個方法會一直阻塞,這也就是為何我們啟動springweb項目,不會自動關掉的原因了 } catch (Exception ioe) { // We didn't get a socket endpoint.countDownConnection(); if (endpoint.isRunning()) { // Introduce delay if necessary errorDelay = handleExceptionWithDelay(errorDelay); // re-throw throw ioe; } else { break; } } // Successful accept, reset the error delay errorDelay = 0; // Configure the socket if (endpoint.isRunning() && !endpoint.isPaused()) { // setSocketOptions() will hand the socket off to // an appropriate processor if successful if (!endpoint.setSocketOptions(socket)) {//如果接收到了客戶端請求,那么就會調用該方法 endpoint.closeSocket(socket); } } else { endpoint.destroySocket(socket); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); String msg = sm.getString("endpoint.accept.fail"); // APR specific. // Could push this down but not sure it is worth the trouble. if (t instanceof Error) { Error e = (Error) t; if (e.getError() == 233) { // Not an error on HP-UX so log as a warning // so it can be filtered out on that platform // See bug 50273 log.warn(msg, t); } else { log.error(msg, t); } } else { log.error(msg, t); } } } state = AcceptorState.ENDED; }
我們根進看看: endpoint.setSocketOptions(socket)
@Override protected boolean setSocketOptions(SocketChannel socket) { NioSocketWrapper socketWrapper = null; try { // Allocate channel and wrapper NioChannel channel = null; if (nioChannels != null) { channel = nioChannels.pop(); } if (channel == null) { SocketBufferHandler bufhandler = new SocketBufferHandler( socketProperties.getAppReadBufSize(), socketProperties.getAppWriteBufSize(), socketProperties.getDirectBuffer()); if (isSSLEnabled()) { channel = new SecureNioChannel(bufhandler, selectorPool, this); } else { channel = new NioChannel(bufhandler); } } NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this); channel.reset(socket, newWrapper); connections.put(socket, newWrapper); socketWrapper = newWrapper; // Set socket properties // Disable blocking, polling will be used socket.configureBlocking(false); socketProperties.setProperties(socket.socket()); socketWrapper.setReadTimeout(getConnectionTimeout()); socketWrapper.setWriteTimeout(getConnectionTimeout()); socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests()); socketWrapper.setSecure(isSSLEnabled()); poller.register(channel, socketWrapper);//在這里將會將接收到的客戶端封裝成事件,存到Poller的envens棧中 return true; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); try { log.error(sm.getString("endpoint.socketOptionsError"), t); } catch (Throwable tt) { ExceptionUtils.handleThrowable(tt); } if (socketWrapper == null) { destroySocket(socket); } } // Tell to close the socket if needed return false; }
//小結上面的流程:
//最后通過調用Controller的方法,看看整個調用鏈條:
前面分析過,Poller的run方法會處理監聽到的事件,所以,我們打個斷點在該方法里:
根進去:
public boolean processSocket(SocketWrapperBase<S> socketWrapper, SocketEvent event, boolean dispatch) { try { if (socketWrapper == null) { return false; } SocketProcessorBase<S> sc = null; if (processorCache != null) { sc = processorCache.pop(); } if (sc == null) { sc = createSocketProcessor(socketWrapper, event); //將客戶端封裝成sc,sc實現了Runnable接口 } else { sc.reset(socketWrapper, event); } Executor executor = getExecutor(); if (dispatch && executor != null) { executor.execute(sc); //最終交給了一個線程池處理了 } else { sc.run(); } } catch (RejectedExecutionException ree) { getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree); return false; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); // This means we got an OOM or similar creating a thread, or that // the pool and its queue are full getLog().error(sm.getString("endpoint.process.fail"), t); return false; } return true; }
所以接下來看看:SocketProcessorBase的run方法:
@Override public final void run() { synchronized (socketWrapper) { // It is possible that processing may be triggered for read and // write at the same time. The sync above makes sure that processing // does not occur in parallel. The test below ensures that if the // first event to be processed results in the socket being closed, // the subsequent events are not processed. if (socketWrapper.isClosed()) { return; } doRun(); } }
繼續跟蹤doRun();
一直到這里,我們可以看到,最終會調用Mapper的方法去找到對應的Servlet的,前面已經分析過Mapper有個根據url獲取Servelet包裝類的放法,並將其存到MappingData中,我們跟進去:
Pipeline最后一個基本都是xxxValue:
@Override public final void invoke(Request request, Response response) throws IOException, ServletException { // Disallow any direct access to resources under WEB-INF or META-INF MessageBytes requestPathMB = request.getRequestPathMB(); if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0)) //這些判斷就說明了,為何前端不能直接訪問WEB-INF的內容了 || (requestPathMB.equalsIgnoreCase("/META-INF")) || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0)) || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // Select the Wrapper to be used for this Request Wrapper wrapper = request.getWrapper();//前面分析過了,Servlet存到了Request中,這里獲取出來 if (wrapper == null || wrapper.isUnavailable()) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // Acknowledge the request try { response.sendAcknowledgement(); } catch (IOException ioe) { container.getLogger().error(sm.getString( "standardContextValve.acknowledgeException"), ioe); request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } if (request.isAsyncSupported()) { request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported()); } wrapper.getPipeline().getFirst().invoke(request, response);//調用Servlet處理請求 }
至此分析完畢整個調用流程