簡單回顧下內嵌tomcat使用,新建一個maven項目,導入以下依賴java
<dependencies>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.12</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-annotations-api</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>9.0.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
複製代碼
新建一個servlet類,實現對應的方法。web
public class HomeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("request scheme: " + req.getScheme());
resp.getWriter().print("hello tomcat");
}
}
複製代碼
在main函數中添加以下代碼spring
public static void main(String[] args) throws Exception {
Tomcat tomcat = new Tomcat();
//設置路徑
tomcat.setBaseDir("d:tomcat/dir");
tomcat.getHost().setAutoDeploy(false);
Connector connector = new Connector();
//設置端口
connector.setPort(10086);
tomcat.getService().addConnector(connector);
Context context = new StandardContext();
//設置context路徑
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener());
tomcat.getHost().addChild(context);
//添加servlet
tomcat.addServlet("", "homeServlet", new HomeServlet());
//設置servlet路徑
context.addServletMappingDecoded("/", "homeServlet");
tomcat.start();
tomcat.getServer().await();
}
複製代碼
這樣的話一個簡單的tomcat服務器就啓動了,打開瀏覽器輸入localhost:10086,就能夠看到servlet中的返回值。apache
還記得前兩節講到springboot自動化配置裏面的配置文件麼,配置文件中有一個類,org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration ,進入這個類。api
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
複製代碼
發現上面有一個import註解,進入import註解導入的類瀏覽器
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
複製代碼
根據上一節學習的判斷條件能夠知道,import註解向spring容器中注入了一個TomcatServletWebServerFactory類,這個類咱們先標記着。tomcat
回到main函數中,順着SpringApplication.run(Application.class, args);方法進入AbstractApplicationContext的refresh方法springboot
// Initialize other special beans in specific context subclasses.
onRefresh();
複製代碼
在onRefresh方法上發現一行註釋,在子類方法中初始化特殊的bean。tomcat容器應該算是一個特殊的bean了,因此咱們進入子類的onRefresh方法。在子類ServletWebServerApplicationContext發現了這樣的代碼。bash
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
複製代碼
猜也能猜到,createWebServer方法就是tomcat初始化的地方了。因此進入方法一探究竟。服務器
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
//初始化進來,webServer和servletContext兩個對象都是null,因此進入if
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
...
}
initPropertySources();
}
複製代碼
首先看一下getWebServerFactory方法。
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory() .getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { ... } if (beanNames.length > 1) { ... } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); } 複製代碼
方法邏輯比較簡單,獲取容器中ServletWebServerFactory類型的實例,並校驗其數量,多了或者少了都不行,必須是正好1個。這個時候看一下上面經過自動化配置那邊導入spring容器的TomcatServletWebServerFactory類,這個類就是ServletWebServerFactory的子類。因此在沒有其餘配置的狀況下,getWebServerFactory方法,獲取到的就是TomcatServletWebServerFactory類。
獲取到factory實例後,就來看一下factory的getWebServer方法。
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
//設置端口
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
//配置鏈接
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
配置context
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
複製代碼
雖然比咱們一開始那個示範要複雜許多,可是大體的邏輯仍是很清晰的,不難看懂。(這個地方若是不理解的話,你須要補充一下tomcat的知識)
進入getTomcatWebServer方法。
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws WebServerException {
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
this.tomcat.start();
...
startDaemonAwaitThread();
}
...
}
}
複製代碼
在getTomcatWebServer方法中,發現了tomcat啓動相關的代碼,因此這個地方就是tomcat容器啓動的地方啦。不過若是你用debug的話,你會發現這個地方即便tomcat啓動事後,依然沒法訪問。由於在啓動前spring框架還作了一件事。
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
//移除tomcat容器的鏈接器connector
removeServiceConnectors();
}
});
複製代碼
由於這個時候做爲一個特殊的bean,tomcat容器須要優先初始化,可是此時其餘bean尚未初始化完成,鏈接進來後是沒法處理的。因此spring框架在這個地方移除了鏈接器。
那麼被移除的鏈接器在那個地方啓動的呢?在AbstractApplicationContext的refresh方法中,onRefresh方法後面還有一個方法finishRefresh方法。進入子類的這個方法(進入這個方法以前,全部的非lazy屬性的bean已經所有完成了初始化)
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
public void start() throws WebServerException {
...
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
performDeferredLoadOnStartup();
}
...
}
}
複製代碼
在這個方法中,咱們找到了被移除的connector。spring框架將剛剛移除獲得鏈接器又放到tomcat容器中,而且啓用了他,這樣的話tomcat就能夠被訪問到了。
tomcat的啓動到這兒咱們已經瞭解了,不知道你們有沒有發現一個問題,就是咱們並無看到相似示例中添加servlet和設置servlet路徑相關的代碼。那這部分代碼在哪裏呢?
回到剛剛factory的getWebServer方法。這個方法中傳入了一個參數getSelfInitializer()咱們看一下這個參數是啥。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
複製代碼
這個lambda表達式應該還很好理解吧,返回了一個ServletContextInitializer實例,該實例的onStartup方法就是調用了這邊的selfInitialize方法。這個selfInitialize方法裏,最關鍵的就是getServletContextInitializerBeans方法了。可是咱們從這邊分析代碼的話,其實不太看得出來getServletContextInitializerBeans到底獲取到了那些類,因此能夠取巧一下,使用IDEA的debug功能。藉助debug咱們看到了這邊獲取到的幾個類,關鍵的是DispatcherServletRegistrationBean。也就是這個地方會調用DispatcherServletRegistrationBean的onStartup方法。
那麼他的onStartup到底幹了那些事呢?
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description)
+ " was not registered (disabled)");
return;
}
register(description, servletContext);
}
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered "
+ "(possibly already registered?)");
return;
}
configure(registration);
}
@Override
protected ServletRegistration.Dynamic addRegistration(String description,
ServletContext servletContext) {
String name = getServletName();
//這個地方將servlet添加進了context
return servletContext.addServlet(name, this.servlet);
}
@Override
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
if (urlMapping.length == 0 && this.alwaysMapUrl) {
urlMapping = DEFAULT_MAPPINGS;
}
if (!ObjectUtils.isEmpty(urlMapping)) {
//這個方法則對servlet的路徑進行了配置
registration.addMapping(urlMapping);
}
registration.setLoadOnStartup(this.loadOnStartup);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
}
複製代碼
既然知道了ServletContextInitializer的做用,那麼咱們就追蹤一下這個ServletContextInitializer被放置到了什麼地方,什麼時候調用他的方法。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
...
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
...
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
...
configureContext(context, initializersToUse);
...
}
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
...
}
複製代碼
能夠看到ServletContextInitializer被包裝成了一個TomcatStarter放入了context中。在context的start方法裏,咱們就能夠看到initializers的啓動(這個地方涉及到tomcat容器的啓動,若是不熟悉的話能夠回顧下)。
@Override
protected synchronized void startInternal() throws LifecycleException {
...
// Call ServletContainerInitializers
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;
}
}
...
}
複製代碼
通過這幾輪的分析,從SpringApplication的啓動,到自動化配置,再到今天的tomcat容器的啓動。咱們已經窺探到了整個springboot框架的全貌。因此後面就須要對經常使用功能定點學習了。