SpringBoot的嵌入式servlet容器
1、SpringBoot默认的servlet容器
SpringBoot默认的servlet容器是tomcat,如下图所示:
2、嵌入式servlet配置修改
2.1、通过全局配置文件修改
1、可以通过 server.xxx 来进行 web 服务配置,没有带服务器名称的则是通用配置,如下所示:
2、通过带了具体的服务器名称则是单独对该服务器进行设置,比如 server.tomcat.xxx 就是专门针对 tomcat 的配置。
server.port=8081
server.servlet.context-path=/tomcat
2.2、通过WebServerFactoryCustomizer接口的Bean修改
我们创建一个类实现于WebServerFactoryCustomizer接口,在里面的配置会优先于全局配置的配置,且会互补,代码如下所示:
package cool.ale.component;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(8090);
}
}
3、注册servlet三大组件
首先我们需要知道servlet的三大组件都是什么
servlet的三大组件是:servlet、listener、filter
我们有两种方式进行注册
3.1、servlet3.0规范提供的注解方式注册
我们需要先创建一个类,导入这几个注解其中之一即可。
@WebServlet
@WebListener
@WebFilter
然后在启动类中加入可以扫描以上注解的注解@ServletComponentScan,如下所示:
package cool.ale;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class SpringbootServletApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootServletApplication.class, args);
}
}
然后再完善一下刚才创建的类,如下所示,启动SpringBoot后,即可访问。
package cool.ale.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "HelloServlet",urlPatterns = "/HelloServlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("hello servlet!");
}
}
3.2、SpringBoot提供的servlet三大组件注册
可以将 Servlet、Filter、Listener注册为相应的Spring Bean
可以使用以下三个Bean注册:ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean,将这三个类其中一个注册到配置文件中并返回即可。
先创建一个servlet类,如下所示:
package cool.ale.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class BeanServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("hello beanServlet!");
}
}
然后创建一个配置类,返回上面ServletRegistrationBean类,并将我们刚才创建的servlet类配置进去,如下所示:
package cool.ale.config;
import cool.ale.servlet.BeanServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebMvcConfiguration {
@Bean
public ServletRegistrationBean myServlet(){
// 声明一个 servlet 注册器 Bean
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
// 设置相应的 servlet
servletRegistrationBean.setServlet(new BeanServlet());
// 设置名称
servletRegistrationBean.setName("BeanServlet");
// 添加映射规则
servletRegistrationBean.addUrlMappings("/BeanServlet");
return servletRegistrationBean;
}
}
直接启动访问即可。
4、切换其它嵌入式servlet容器
SpringBoot包含对嵌入式Tomcat、Jetty和Undertow服务器的支持
tomcat(默认的嵌入式servlet容器)
Jetty(一般用于长链接,比如socket等)
Undertow(一般用于无阻塞式、响应式等)
一般我们需要切换成其它两个servlet容器时,需要操作以下两个步骤
1、排除掉tomcat服务器,在下面图中选中
然后代码中会对应如下所示:
<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、在pom文件中加上jetty的依赖或者undertow的依赖,如下所示:
<!-- jetty的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!-- undertow的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
启动即可。
5、嵌入式servlet容器自动配置原理
我们在进行源码跟踪之前,先要提出几个问题,以方便我们寻求我们去跟踪源码的目的。
1、为什么可以根据配置的依赖自动使用对应的servlet容器。
2、怎么根据配置文件中server.xxx 以及 继承WebServerFactoryCustomizer接口时可以去设置servlet容器的一些参数。
3、怎么让所有的WebServerFactoryCustomizer Bean一一配合使用?
4、嵌入式的tomcat容器是如何启动的。
下来我们就根据这几个问题一起来进入servlet自动配置类来看看原理。
ServletWebServerFactoryAutoConfiguration。
先分析一下该类的注解
@Configuration(
proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
// 只要依赖了任意一个servlet容器,都会依赖该类
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
// 启用了 server.xxx 的配置绑定到了当前类上,具体可分析ServerProperties.class
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
}
5.1、为什么可以根据配置的依赖自动使用对应的servlet容器?
由于 ServletWebServerFactoryAutoConfiguration 类导入了三个类,如下所示:
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
}
在上面导入的这几个类中,我们可以随意的点击一个进去,就比如,我点击EmbeddedTomcat.class进到实现,这块注解写着当有Tomcat.class类存在于IOC容器中,tomcat的配置将生效。
@Configuration(
proxyBeanMethods = false
)
// 依赖于Servlet.class, Tomcat.class, UpgradeProtocol.class三个类
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedTomcat {
EmbeddedTomcat() {
}
}
5.2、怎么根据配置文件中server.xxx设置servlet容器的一些参数?
在ServletWebServerFactoryAutoConfiguration类中我们看到了如下方法,ServletWebServerFactoryCustomizer 类也是实现了WebServerFactoryCustomizer类,用于servlet容器的自动化配置,代码如下所示:
@Bean
// 通过WebServerFactoryCustomizer接口的Bean修改servlet配置
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
return new ServletWebServerFactoryCustomizer(serverProperties, (List)webListenerRegistrars.orderedStream().collect(Collectors.toList()));
}
当我们点击 ServletWebServerFactoryCustomizer 类看到它里面的customize方法,是使用了jdk8的新特性的方法,从配置文件里面拿出相应的配置参数来修改默认值:
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
ServerProperties var10001 = this.serverProperties;
var10001.getClass();
map.from(var10001::getPort).to(factory::setPort);
var10001 = this.serverProperties;
var10001.getClass();
map.from(var10001::getAddress).to(factory::setAddress);
Servlet var5 = this.serverProperties.getServlet();
var5.getClass();
map.from(var5::getContextPath).to(factory::setContextPath);
var5 = this.serverProperties.getServlet();
var5.getClass();
map.from(var5::getApplicationDisplayName).to(factory::setDisplayName);
var5 = this.serverProperties.getServlet();
var5.getClass();
map.from(var5::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
var5 = this.serverProperties.getServlet();
var5.getClass();
map.from(var5::getSession).to(factory::setSession);
var10001 = this.serverProperties;
var10001.getClass();
map.from(var10001::getSsl).to(factory::setSsl);
var5 = this.serverProperties.getServlet();
var5.getClass();
map.from(var5::getJsp).to(factory::setJsp);
var10001 = this.serverProperties;
var10001.getClass();
map.from(var10001::getCompression).to(factory::setCompression);
var10001 = this.serverProperties;
var10001.getClass();
map.from(var10001::getHttp2).to(factory::setHttp2);
var10001 = this.serverProperties;
var10001.getClass();
map.from(var10001::getServerHeader).to(factory::setServerHeader);
var5 = this.serverProperties.getServlet();
var5.getClass();
map.from(var5::getContextParameters).to(factory::setInitParameters);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
Iterator var3 = this.webListenerRegistrars.iterator();
while(var3.hasNext()) {
WebListenerRegistrar registrar = (WebListenerRegistrar)var3.next();
registrar.register(factory);
}
}
当然,类似于下面这个方法,和上面的结构一样,是用来配置tomcat的参数,代码如下,也可以点击进去看相应的 customize 方法
@Bean
@ConditionalOnClass(
name = {"org.apache.catalina.startup.Tomcat"}
)
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
5.3、怎么让所有的WebServerFactoryCustomizer Bean一一配合使用?
我们都知道,当我们创建了一个类实现了 WebServerFactoryCustomizer 接口之后,在这个类里面就可以去设置相应的servlet容器的参数配置,但是这些参数和全局配置是互补的,所以下面需要研究一下为什么是互补的,其实重点就在BeanPostProcessorsRegistrar类上,这个类就是我们的自动配置文件导入进来的第一个类,代码如下所示:
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
}
当我们点击进去之后,我们发现 BeanPostProcessorsRegistrar.class 这个类实现了 ImportBeanDefinitionRegistrar 接口,这个接口会提供一个方法registerBeanDefinitions,并提供Bean注册器使我们去注册Bean,代码如下:
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (this.beanFactory != null) {
this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class, WebServerFactoryCustomizerBeanPostProcessor::new);
this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
}
}
依照上面代码我们发现,注册了 WebServerFactoryCustomizerBeanPostProcessor.class 类,WebServerFactoryCustomizerBeanPostProcessor.class 类又实现了BeanPostProcessor,这个类在Bean初始化的时候提供了两个方法postProcessBeforeInitialization、postProcessAfterInitialization,我们看一下下面两个方法,就可以看出互补的地方:
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 每一个实现了 WebServerFactoryCustomizer 接口的类都会走到这里,所以是互补的
if (bean instanceof WebServerFactory) {
// 当是 WebServerFactory 的子类的时候,调用 postProcessBeforeInitialization 方法
this.postProcessBeforeInitialization((WebServerFactory)bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
进到 postProcessBeforeInitialization 方法去看一下,回调相应的customize方法,代码如下:
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
// 调用 getCustomizers() 方法,获取到所有实现 WebServerFactoryCustomizer 的Bean
((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).
// 这里的invoke() 方法就会拿到刚才获取到的所有实现 WebServerFactoryCustomizer 的Bean的数组去循环调用其对应的customize方法
invoke((customizer) -> {
customizer.customize(webServerFactory);
});
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
5.4、嵌入式的tomcat容器是如何启动的?
在我们程序.run的时候,会去加载Spring Ioc,加载Ioc的时候就会去调用下面的方法去启动tomcat。
// TomcatServletWebServerFactory
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}