一、配置嵌入式Servlet容器
SpringBoot默认使用Tomcat作为嵌入式servlet容器
产生的问题:
如果是外置的话可以直接找到tomcat中的server.xml进行修改。
那么如何定制和修改SpringBoot中的Servlet容器的相关配置?
1、修改和server有关的配置(ServerProperties):
server.port = 8080
server.context.path=/crud
server.tomcat.url-encoding=UTF-8
//通用的Servlet容器设置
server.xxx
//Tomcat相关的设置
server.tomcat.xxx
2、手动编写一个嵌入式的Servlet容器的定制器
编写一个EmbeddedServletContainerCustomizer:嵌入式的servlet容器的定制器,来修改servlet容器的配置(Spring Boot2.0以上版本EmbeddedServletContainerCustomizer被WebServerFactoryCustomizer替代)
//配置嵌入式的servlet容器
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
//customize方法就是根据相关配置初始化Servlet容器
@Override
public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
//优先级高于配置文件
configurableWebServerFactory.setPort(8083);
}
};
}
补充一下前面提到的如何修改SpringBoot的默认配置?
模式:
- Springboot在自动配置组件的时候,会先看容器中是否有用户自己配置的(@Bean、@Component),如果有则优先使用用户配置的,如果没有再使用自动配置,有些组件如果允许配置多个(ViewResource)将会把用户配置的和默认配置的组合起来。
- 在SpringBoot中会有非常多的XXXConfigure帮助我们进行扩展配置
- 在SpringBoot中会有非常多的XXXCustomize帮助我们进行定制配置
二、在SpringBoot中注册三大组件(Servlet、Filter、Listener)
springboot是以jar包的方式启动嵌入式的tomcat,,而不是创建一个标准的web应用的目录结构。如果是一个web应用的目录结构,webapp/WEB-INF/web.xml三大组件将注册在web.xml中。然而springboot没有提供web.xml文件,怎么注册呢?
注册三大组件用以下方式:
1、注册Servlet
在容器中注册自定义的Servlet:
@Configuration
public class MyServerConfiguration{
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return servletRegistrationBean;
}
}
自定义的Servlet:
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.write("Hello,MyServlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
2、注册过滤器Filter
在容器中注册自定义的过滤器:
@Configuration
public class MyServerConfiguration {
/*注册过滤器Filter组件*/
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet","/hello"));
return filterRegistrationBean;
}
}
自定义的过滤器:
public class MyFilter implements Filter {
/* *
* @description //TODO 过滤器的初始化方法
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/*
* @description //TODO 过滤器的执行方法
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFiler is processing……");
chain.doFilter(servletRequest,servletResponse);
}
/* 过滤器的销毁方法*/
@Override
public void destroy() {
}
}
3、注册监听器(Listener)
在容器中注册监听器:
@Bean
public ServletListenerRegistrationBean myServletListener(){
ServletListenerRegistrationBean<MyServletListener> servletRegistrationBean = new ServletListenerRegistrationBean<MyServletListener>(new MyServletListener());
return servletRegistrationBean;
}
自定义的监听器:
public class MyServletListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("web应用启动……");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("web容器销毁……");
}
}
可以注册的监听器:
static {
Set<Class<?>> types = new HashSet();
types.add(ServletContextAttributeListener.class);
types.add(ServletRequestListener.class);
types.add(ServletRequestAttributeListener.class);
types.add(HttpSessionAttributeListener.class);
types.add(HttpSessionListener.class);
types.add(ServletContextListener.class);
SUPPORTED_TYPES = Collections.unmodifiableSet(types);
}
SpringBoot会帮助我们自动注册Spring MVC的前端控制器 (DispatcherServlet)
默认拦截(/):除jsp请求外,拦截所有的请求,包括静态资源;(/*):会拦截jsp请求。
可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径。
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
三、嵌入式Servlet容器自动配置原理
springboot2.x。
回顾修改servlet容器的相关配置:从下面的代码片,可以看到我们要从容器中获取一个组件ConfigurableWebServerFactory ,然后利用configurableWebServerFactory进行属性的设置,设置完属性,把WebServerFactoryCustomizer注入到容器中。
//配置嵌入式的servlet容器
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
//customize方法就是根据相关配置初始化Servlet容器
@Override
public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
//优先级高于配置文件
configurableWebServerFactory.setPort(8083);
}
};
}
1.猜想自动配置时,要先在容器中注册WebServerFactory,看一下WebServerFactory继承结构:
看ServletWebServerFactoryConfiguration:这是一个配置类,Spring容器启动时,添加到容器中,并且如果导入了tomcat,jetty或者Undertow依赖,就会把对应的XXXServletWebServerFactory 导入到容器中。
class ServletWebServerFactoryConfiguration {
ServletWebServerFactoryConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
//如果有这三个类就生效,即如果依赖了tomcat,这个就生效,下面的也是如此
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedTomcat {
EmbeddedTomcat() {
}
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll((Collection)connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers().addAll((Collection)contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers().addAll((Collection)protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedUndertow {
EmbeddedUndertow() {
}
@Bean
UndertowServletWebServerFactory undertowServletWebServerFactory(ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers, ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.getDeploymentInfoCustomizers().addAll((Collection)deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
factory.getBuilderCustomizers().addAll((Collection)builderCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
@Bean
UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new UndertowServletWebServerFactoryCustomizer(serverProperties);
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedJetty {
EmbeddedJetty() {
}
@Bean
JettyServletWebServerFactory JettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.getServerCustomizers().addAll((Collection)serverCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
}
2.继续猜想容器中有了WebServerFactory,那么WebServerFactoryCustomizer是在哪里被注入的呢
WebServerFactoryCustomizer的继承结构:
看EmbeddedWebServerFactoryCustomizerAutoConfiguration:我们导入了那个servlet容器依赖,哪个xxxWebServerFactoryCustomizer 就别注入到容器中。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
//当容器中存在Tomcat相关类就生效,下面几个也是如此,也就是说我们导入哪个依赖,哪个就生效
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
public static class JettyWebServerFactoryCustomizerConfiguration {
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
public static class UndertowWebServerFactoryCustomizerConfiguration {
@Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
3.容器中WebServerFactory有了,WebServerFactoryCustomizer也有了,那么是什么时候初始化Servlet容器的呢(就是调用WebServerFactoryCustomizer的customize方法)
先看类ServletWebServerFactoryAutoConfiguration :
@Configuration(proxyBeanMethods = false)
@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 {
//...
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
//...
//这个类的作用是往容器中添加一些组件
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
//往容器中添加了WebServerFactoryCustomizerBeanPostProcessor组件(web服务工厂定制器的后置处理器)。
//这个类实现了BeanPostProcessor,属于bean的后置处理器。作用是在bean初始化前后加一些自己的逻辑处理
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
再看bean的后置处理器WebServerFactoryCustomizerBeanPostProcessor:简单点说就是在bean(xxxServletWebServerFactory )的初始化之前,获取所有的定制器来先定制servlet相关配置。
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private List<WebServerFactoryCustomizer<?>> customizers;
...
//bean初始化前调用
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//判断这个bean的类型是WebServerFactory
//TomcatServletWebServerFactory继承了WebServerFactory,所以它初始化时,会往下执行
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
//bean初始化后调用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe
//获取Web服务工厂定制器(WebServerFactoryCustomizer)
.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
//调用customizer的customize方法,定制嵌入式容器的servlet容器相关的规则,优先级高于配置文件
.invoke((customizer) -> customizer.customize(webServerFactory));
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
//返回WebServerFactoryCustomizer类型的Customizer(定制器)
//上面自动配置类注册的Web服务工厂定制器(xxxWebServerFactoryCustomizer)就是继承了WebServerFactoryCustomizer,
//所以这里将那些Customizer(定制器)返回
return (Collection) this.beanFactory
.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
}
四、嵌入式Servlet自动启动原理
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat?
获取嵌入式的Servlet容器工厂:
- Springboot应用启动运行run方法
- 、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
- refresh(context);刷新刚才创建好的ioc容器;
- onRefresh(); web的ioc容器重写了onRefresh方法
- webioc容器会创建嵌入式的Servlet容器; createEmbeddedServletContainer();
- 获取嵌入式的Servlet容器工EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置; - 使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());
- 嵌入式的Servlet容器创建对象并启动Servlet容器;先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;
IOC容器启动创建嵌入式的Servlet容器
五、如何使用其他Servlet容器
在SpringBoot中,默认使用Tomcat服务器,但是也可以切换成·Undertow(不支持JSP)和Jetty(适合开发长连接)。
如何切换其他容器?
【1】先排除SpringBoot的默认容器Tomcat
<!--导入web模块-->
<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>
【2】引入目标容器的依赖
① 切换成undertow容器:
<!--切换其他容器
1、先排除默认的tomcat容器
2、引入要切换的容器的坐标
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
② 切换成jetty容器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>版本号</version>
</dependency>