咱们已经知道,使用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).