咱們已經知道,使用SpringBoot啓動web應用並不須要配置tomcat,就能夠直接使用,實際上是springboot使用的是tomcat做爲嵌入式的servlet容器致使的,這稱做嵌入式的servlet容器,這是怎麼一回事,springboot的內部都作了些什麼呢?前端
修改server對象的值相關屬性就能夠了(ServerProperties
)。java
server.port = 8081 server.context-path=/myweb
server.tomcat.xxxx=cccc
編寫一個WebServerFactoryCustomize
類型的servlet組件,注意,我這裏是2.x版本,若是是1.x的話應該是EmbeddedServletContainerCustomizer
,教程裏是1.0的 ,不過總體差異不大,差很少是同樣的用法:web
@Configuration public class MyConfig implements WebMvcConfigurer { @Bean // 定製嵌入式的servlet容器相關規則 public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){ return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() { @Override public void customize(ConfigurableWebServerFactory factory) { factory.setPort(8085); } }; } }
咱們知道servletd的三大組件分別爲:Servlet、Filter、Listener。因爲咱們如今打包是jar形式,以jar方式啓動嵌入式的tomcat,不是標準的web目錄結構,標準目錄下有一個WEB-INF/web.xml
,咱們通常會在web.xml中註冊三大組件,而jar形式該怎麼註冊呢?spring
要註冊Servlet,只需在SpringBoot容器中註冊一個名爲ServletRegistrationBean
的組件便可,查看源碼,其某個構造函數以下所示,分別表明咱們須要傳入的servlet,以及映射的路徑。apache
public ServletRegistrationBean(T servlet, String... urlMappings) { this(servlet, true, urlMappings); }
所以,咱們能夠這樣作:tomcat
package com.zhaoyi.springboot.restweb.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("I'm Myservlet!"); } } 而後,再將該Servlet綁定到ServletRegistrationBean組件並添加到容器中。
package com.zhaoyi.springboot.restweb.config; import com.zhaoyi.springboot.restweb.servlet.MyServlet; import org.springframework.boot.web.server.ConfigurableWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyServerConfig { @Bean // 配置servlet容器 public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){ return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() { @Override public void customize(ConfigurableWebServerFactory factory) { factory.setPort(8085); } }; } /** * 配置自定義Servlet組件 * @return */ @Bean public ServletRegistrationBean myServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet"); return registrationBean; } }
訪問地址:localhost:8085/myServlet,便可獲得反饋springboot
I'm Myservlet!
上一節有一個配置容器的配置(將內嵌容器的啓動端口號修改成8085),我將其移動到了MyServerConfig.class中,留意一下。服務器
以後的兩大組件的註冊方式其實就和Servlet註冊的方式大同小異了,咱們看看怎麼作就好了。先來自定義一個Filter,咱們須要實現javax.servlet.Filter
接口,該接口的源碼以下所示:app
package javax.servlet; import java.io.IOException; public interface Filter { default void init(FilterConfig filterConfig) throws ServletException { } void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException; default void destroy() { } }
能夠看到,有兩個默認方法,所以,咱們實現該接口,只需實現其doFilter方法便可。maven
package com.zhaoyi.springboot.restweb.filter; import javax.servlet.*; import java.io.IOException; public class MyFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("this is my filter..."); filterChain.doFilter(servletRequest, servletResponse); } }
filterChain.doFilter
放行請求,咱們能夠看到Chain(鏈條)的字樣,在實際生產中其實咱們須要定義不少Filter,他們在應用中造成一個鏈條,依次過濾,因此頗有chain的味道。
接下來,咱們將該Filter註冊到容器中,並設置須要過濾的映射路徑:
// Filter @Bean public FilterRegistrationBean filterRegistrationBean(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); filterFilterRegistrationBean.setFilter(new MyFilter()); filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/index","/myFilter")); return filterFilterRegistrationBean; }
這樣,咱們訪問localhost:8085/index
、localhost:8085/myFilter
這些路徑的時候,就會在控制檯打印以下信息:
this is my filter...
而其餘的路徑則不受影響,代表過濾器生效了。
package com.zhaoyi.springboot.restweb.listener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("contextInitialized... application start ...."); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("contextDestroyed... application end ...."); } }
package com.zhaoyi.springboot.restweb.config; import com.zhaoyi.springboot.restweb.filter.MyFilter; import com.zhaoyi.springboot.restweb.listener.MyListener; import com.zhaoyi.springboot.restweb.servlet.MyServlet; import org.springframework.boot.web.server.ConfigurableWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.Arrays; @Configuration public class MyServerConfig { @Bean // servlet public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){ return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() { @Override public void customize(ConfigurableWebServerFactory factory) { factory.setPort(8085); } }; } /** * 配置自定義Servlet組件 * @return */ @Bean public ServletRegistrationBean servletRegistrationBean(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet"); return registrationBean; } // Filter @Bean public FilterRegistrationBean filterRegistrationBean(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); filterFilterRegistrationBean.setFilter(new MyFilter()); filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/index","/myFilter")); return filterFilterRegistrationBean; } // Listener @Bean public ServletListenerRegistrationBean servletListenerRegistrationBean(){ ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(new MyListener()); return servletListenerRegistrationBean; } }
在應用啓動的時候,能夠看到控制檯打印
contextInitialized... application start ....
咱們點擊左下角的Exit
按鈕,注意不是紅色方塊按鈕退出的時候,能夠看到控制檯打印
contextDestroyed... application end ....
Spring Boot幫咱們自動配置SpringMVC的時候,自動的註冊了SpringMVC的前端控制器,DispatcherServlet
,查看DispatcherServletAutoConfiguration
的源碼
@Bean( name = {"dispatcherServletRegistration"} ) @ConditionalOnBean( value = {DispatcherServlet.class}, name = {"dispatcherServlet"} ) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, this.webMvcProperties.getServlet().getPath()); // 默認攔截: / 全部請求,包括靜態資源,可是不攔截JSP請求。注意/*會攔截JSP // 能夠經過server.servletPath來修改SpringMVC前端控制器默認攔截的請求路徑 registration.setName("dispatcherServlet"); registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration; }
tomcat、Undertow、Netty、Jetty。Netty應該是後來的版本加入的支持,這裏就不在闡述了。咱們關注其餘三個便可。
SpringBoot支持:tomcat jetty undertow,其中tomcat是默認使用的.而使用tomcat 的緣由是項目引入了web啓動場景包,該場景包默認引用的就是tomcat容器,所以,假若咱們想要換成其餘的容器,要在dependencies中排除默認的tomcat場景包,加入其餘的包便可。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
進入其中,查看關聯引用能夠找到對應的tomcat場景引入包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.1.1.RELEASE</version> <scope>compile</scope> </dependency>
若是咱們還需繼續研究下去的話,會發現當前tomcat場景啓動器包所用的tomcat版本爲9.x版本,比較新:
... <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-el</artifactId> <version>9.0.13</version> <scope>compile</scope> </dependency> ...
也就是說,若是咱們將tomcat-starter排除,而後在pom文件中引入其餘的servlet容器場景包便可。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> ...
解下來直接運行項目,咱們會發現,除了啓動容器變成了jetty,其餘的一切按正常配置運行:
Jetty started on port(s) 8085 (http/1.1) with context path '/'
undertow和jetty如出一轍的方式,直接吧jetty改成undertow就好了。繼續啓動:
Undertow started on port(s) 8085 (http) with context path ''
接下來咱們分析spring boot的內在工做原理,在此以前,別忘了換回tomcat做爲內嵌容器。
要換回tomcat容器,只需將排除代碼塊
<exclusions>
以及其餘內嵌容器場景包引入代碼刪除便可。
在這裏仍是推薦學習一下maven相關知識,推薦書籍 Maven實戰。
@Configuration @ConditionalOnWebApplication @EnableConfigurationProperties({ServerProperties.class}) public class EmbeddedWebServerFactoryCustomizerAutoConfiguration { public EmbeddedWebServerFactoryCustomizerAutoConfiguration() { } @Configuration @ConditionalOnClass({HttpServer.class}) public static class NettyWebServerFactoryCustomizerConfiguration { public NettyWebServerFactoryCustomizerConfiguration() { } @Bean public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new NettyWebServerFactoryCustomizer(environment, serverProperties); } } @Configuration @ConditionalOnClass({Undertow.class, SslClientAuthMode.class}) public static class UndertowWebServerFactoryCustomizerConfiguration { public UndertowWebServerFactoryCustomizerConfiguration() { } @Bean public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new UndertowWebServerFactoryCustomizer(environment, serverProperties); } } @Configuration @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class}) public static class JettyWebServerFactoryCustomizerConfiguration { public JettyWebServerFactoryCustomizerConfiguration() { } @Bean public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new JettyWebServerFactoryCustomizer(environment, serverProperties); } } @Configuration @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class}) public static class TomcatWebServerFactoryCustomizerConfiguration { public TomcatWebServerFactoryCustomizerConfiguration() { } @Bean public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new TomcatWebServerFactoryCustomizer(environment, serverProperties); } } }
從其中就能夠看出,該組件註冊各個嵌入式Servlet容器的時候,會根據當前對應的某個class是否位於類路徑上,纔會實例化一個Bean,也就是說,咱們導入不一樣的包,則會致使這裏根據咱們導入的包生成對應的xxxxWebServerFactoryCustomizer
組件。這些組件在一樣的路徑下定義了具體的信息
咱們以嵌入式的tomcat容器工廠TomcatWebServerFactoryCustomizer
爲例進行分析,當咱們引入了tomcat場景啓動包後,springboot就會爲咱們註冊該組件。咱們查看其源碼:
public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered { ... }
該工廠類配置了tomcat的基本環境。其中:
public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { this.environment = environment; this.serverProperties = serverProperties; }
傳入了咱們提供的環境信息以及服務器配置信息。
咱們以前講過能夠經過WebServerFactoryCustomizer
這個定製器幫咱們修改容器的配置。如今咱們能夠看到也能夠經過修改ServerProperties
.想要定製servlet容器,給容器中添加一個WebServerFactoryCustomizer
類型的組件就能夠了。
步驟:
WebServerFactoryCustomizer
。WebServerFactoryCustomizerBeanPostProcessor
就會工做。(這裏版本不同,有點難以理解。)WebServerFactory
類型的Factory,例如咱們以前配置的ConfigurableWebServerFactory
,調用定製器的定製方法。何時建立嵌入式的servlet容器工廠? 何時獲取嵌入式的Servlet容器並啓動tomcat?
如下過程可經過斷點慢慢查看。
一句話,ioc容器啓動時,會建立嵌入式的servlet容器(tomcat).