SpringBoot的嵌入式Servlet容器
嵌入式Servlet容器
SpringBoot包含对嵌入式Tomcat、Jetty、Undertow等服务器的支持。大多数开发人员使用适当的“”启动器“” 来获取完全配置的实例。默认情况下,嵌入式服务器在port上监听HTTP请求8080
servlet容器-嵌入式servlet容器配置修改
通过全局配置文件修改修改
- 可以通过server.xxx 来进行web服务配置,没有带服务器名称的则是通用配置
- 通常带了具体服务器名称则是单独对该服务器进行设置,比如server.tomcat.xxx就是专门针对tomcat的配置
添加实现了WebServerFactoryCustomizer接口的bean来进行修改
这儿的泛型添加的tomcat的ConfigurableTomcatWebServerFactory
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory> {
@Override
public void customize(ConfigurableTomcatWebServerFactory server) {
server.setPort(8088);
}
}
servlet容器-注册servlet三大组件
- servlet三大组件
- servlet
- 监听器:listenser
- 过滤器:filter
应该如何注册呢?
- servlet3.0规范提供的注解方式进行注册
- springboot提供的注册方式
servlet3.0规范提供的注解方式进行注册
@WebServlet:注册servlet的注解
@WebListener:注册监听器的注解
@WebFilter:注册过滤器的注解
- 以webservlet进行演示
- 定义一个servlet
@WebServlet(name = "HelloServlet",urlPatterns = "/HelloServlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("hello servlet");
}
}
- 在springboot启动类上加上@ServletComponentScan,让springboot可以扫描到serverlet的bean
@SpringBootApplication
@ServletComponentScan
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class,args);
}
}
springboot提供的注册方式
- 自定义一个servlet
public class BeanServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("bean servlet");
}
}
- 定义一个bean注册的配置类
可以使用ServletRegistrationBean、ServletListenerRegistrationBean、FilterRegistrationBean分别来管理servlet、监听器、过滤器
@Configuration
public class RegistryBeanConfigration {
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean<Servlet> registrationBean = new ServletRegistrationBean<>();
//设置相应的servlet
registrationBean.setServlet(new BeanServlet());
//设置名称
registrationBean.setName("beanServlet");
//添加映射规则
registrationBean.addUrlMappings("/beanServlet");
return registrationBean;
}
}
- 测试
servlet容器-切换到其他servlet容器
srpingboot默认服务器是tomcat服务器
- 排除内嵌tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除内嵌tomcat-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
- 引入Jetty依赖
<!--引入jetty依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
- 启动项目
- 测试
servlet容器-嵌入式servlet容器自动配置原理
内嵌servelet自动配置类:ServletWebServerFactoryAutoConfiguration
- 问题:
- 为什么可以根据配置依赖自动使用servlet容器?
- 怎么根据配置文件中的server.xxx以及实现了WebServerFactoryCustomizer的类去设置serverlet容器配置?
- 嵌入式servlet容器如何启动?
ServletWebServerFactoryAutoConfiguration配置类源码分析
//启用配置文件的属性类,那么所有的配置信息都会绑定到ServerProperties.class
@EnableConfigurationProperties(ServerProperties.class)
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
//只要依赖了任何一个servlet容器,它就会生效
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
//启用配置文件的属性类,那么所有的配置信息都会绑定到ServerProperties.class
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
为什么可以根据配置依赖自动使用servlet容器?
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
通过@imort导入Embeddel类
每个Embeddel类 中,都配置了相应的@ConditionalOnClass,会根据当前servlet容器启动器依赖判断classpath是否存在对应的类,如果存在就使用对应的sevlet容器,比如tomcat:
@Configuration(proxyBeanMethods = false)
//只要添加了tomcat的场景启动器 则该注解才会匹配,如果没有对应的tomcat场景启动器,该注解就不会匹配
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
怎么根据配置文件中的server.xxx以及实现了WebServerFactoryCustomizer的类去设置serverlet容器配置?
- ServletWebServerFactoryAutoConfiguration类
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
return new ServletWebServerFactoryCustomizer(serverProperties,
webListenerRegistrars.orderedStream().collect(Collectors.toList()),
cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
}
ServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer,说明它也是定制servlet容器的
- ServletWebServerFactoryCustomizer类
根据配置文件中server.xxx来进行定制servlet容器
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.serverProperties::getPort).to(factory::setPort);
map.from(this.serverProperties::getAddress).to(factory::setAddress);
map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
map.from(this.serverProperties::getSsl).to(factory::setSsl);
map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
map.from(this.serverProperties::getCompression).to(factory::setCompression);
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
registrar.register(factory);
}
if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
}
}
- 实现了WebServerFactoryCustomizer接口的类如何进行配置呢?
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
TomcatServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer,说明它也是定制servlet容器的
- TomcatServletWebServerFactoryCustomizer也重写了customize方法
@Override
public void customize(TomcatServletWebServerFactory factory) {
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
}
if (tomcatProperties.getRedirectContextRoot() != null) {
customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
}
customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
}
- 怎么让所有的WebServerFactoryCustomizer bean一一调用呢?
- BeanPostProcessorsRegistrar
- 注册WebServerFactoryCustomizerBeanPostProcessor为bean
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
//注册了WebServerFactoryCustomizerBeanPostProcessor bean
WebServerFactoryCustomizerBeanPostProcessor.class,
WebServerFactoryCustomizerBeanPostProcessor::new);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
}
- WebServerFactoryCustomizerBeanPostProcessor
实现了BeanPostProcessor,在spring初始化bean的时候会被调用
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 判断当前创建的bean是不是webserverfactofy
if (bean instanceof WebServerFactory) {
this.postProcessBeforeInitialization((WebServerFactory)bean);
}
return bean;
}
当前对应的Embeddedxxx 启用时,就会在里面配置一个WebServerFactory类型的bean,负责创建对应的容器和启动
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
spring bean初始化前就会调用postProcessBeforeInitialization方法从而执行customize方法
- 调用getCustomizers()方法
- 调用getWebServerFactoryCustomizerBeans()方法,获取所有实现了WebServerFactoryCustomizer接口的bean(获取自定义的、ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer)
- 在invoke方法中循环调用所有实现了WebServerFactoryCustomizer接口 的bean,并调用customize()方法进行一一定制
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
((LambdaSafe.Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).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;
}
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
嵌入式servlet容器如何启动?
- TomcatServletWebServerFactory
- 自动配置根据不同的依赖,启动对应一个Enbenddedxxx,然后配置一个对应的servlet工厂类,比如TomcatServletWebServerFactory
- 在springboot应用启动的时候,就会调用refresh方法,onRefresh方法,调用getWebServer,创建servlet容器并且启动
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());
Iterator var4 = this.serverLifecycleListeners.iterator();
while(var4.hasNext()) {
LifecycleListener listener = (LifecycleListener)var4.next();
tomcat.getServer().addLifecycleListener(listener);
}
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 var8 = this.additionalTomcatConnectors.iterator();
while(var8.hasNext()) {
Connector additionalConnector = (Connector)var8.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
//启动方法在getTomcatWebServer
return this.getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
}
- TomcatWebServer类
在TomcatWebServer中调用initialize方法进行启动
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
synchronized(this.monitor) {
try {
this.addInstanceIdToEngineName();
Context context = this.findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
this.removeServiceConnectors();
}
});
this.tomcat.start();
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
} catch (NamingException var5) {
}
this.startDaemonAwaitThread();
} catch (Exception var6) {
this.stopSilently();
this.destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", var6);
}
}
}
servlet容器-使用外部servlet容器
- 外部servlet容器
- 服务器 安装tomcat 配置环境变量
- 部署: war包—>运维—>tomcat webapp startup.sh 启动
- 开发:将开发绑定本地tomcat
- 内嵌servlet容器
- 部署:jar—>运维—java -jar 启动
SpringBoot使用外部servlet
- 修改pom.xml的打包方式,修改为war包形式
设置tomcat依赖设置scope为provided,让内嵌tomcat不参与打包
<!--让它不参与打包部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<type>pom</type> //用在父子类模块中,一般使用在父模块中,此时它相当于一个一个依赖管理(Maven Parent POM)文件
<optional>true</optional> //用在父模块中,此时子模块不会继承父模块的该依赖,true:不传递,false:传递
<scope>provided</scope> //让依赖不参与打包
- 设置tomcat的启动类
目的是tomcat启动的时候调用configure方法来启动springboot的启动类
/**
* 当tomcat启动时就会调用configure方法,去启动springboot的启动类
*/
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
}
- 添加本地tomcat
- 测试-使用本地tomcat启动
servlet容器-外部servlet启动SpringBoot原理
tomcat -->web.xml—filter servlet listener
tomcat不会主动去启动springboot应用,所以tomcat启动的时候肯定调用了SpringBootServletInitializer 的configure方法
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
}
启动原理
当servlet容器启动的时候,就会去META-INF/service/文件下去找javax.servlet.ServletContainerInitializer文件
这个文件肯定绑定了一个ServletContainerInitializer
org.springframework.web.SpringServletContainerInitializer
当servlet容器启动的时候就会去该文件夹中找到ServletContainerInitializer的实现类SpringServletContainerInitializer从而创建它实例(SPI机制),并且调用onStartup方法
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
Iterator var4;
if (webAppInitializerClasses != null) {
initializers = new ArrayList(webAppInitializerClasses.size());
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (((List)initializers).isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort((List)initializers);
var4 = ((List)initializers).iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
- @HandlesTypes({WebApplicationInitializer.class})
@HandlesTypes({WebApplicationInitializer.class})传入的类为ServletContainerInitializer感兴趣的类
- 容器会自动在classpath中找到WebApplicationInitializer 会传入到onStartup方法的webAppInitializerClasses 参数中,也包括了之前自定义的TomcatStartSpringBoot
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements
- 启动WebApplicationInitializer的onStartup方法
循环调用所有WebApplicationInitializer实例的onstartup方法
if (((List)initializers).isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort((List)initializers);
var4 = ((List)initializers).iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
//循环调用所有WebApplicationInitializer实例的onstartup方法
initializer.onStartup(servletContext);
}
- 调用SpringBootServletInitializer onStartup方法
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute("logging.register-shutdown-hook", false);
this.logger = LogFactory.getLog(this.getClass());
//创建对对对 ,这个方法里就会去调用configure方法
WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
if (rootApplicationContext != null) {
servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
- createRootApplicationContext
当调用configure方法的时候,就会调用我们自己的configure方法(为了更好的理解才去父类里看),因为TomcatStartSpringBoot是继承了SpringBootServletInitializer又继承了WebApplicationInitializer(我们自己定义的TomcatStartSpringBoot也是一个WebApplicationInitializer)
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
}
createRootApplicationContext
调用build方法,就会根据传入的SpringBoot的启动类来构建一个springapplication
- SpringApplicationBuilder
public SpringApplication build(String... args) {
this.configureAsChildIfNecessary(args);
this.application.addPrimarySources(this.sources);
return this.application;
}
最后再调用run方法来启动springboot
启动springboot
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext)application.run(new String[0]);
}