从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

    • SpringBoot教程-尚硅谷

      • SpringBoot课程介绍
      • Spring和SpringBoot
      • HelloWorld
      • 了解自动配置原理
      • 底层注解-@Configuration详解
      • 底层注解-@Import导入组件
      • 底层注解-@Conditional条件装配
      • 原生配置文件引入-@ImportResource
      • 底层注解-配置绑定@ConfigurationProperties
      • 自动配置原理
      • 自动配置流程
      • Lombok简化开发
      • DevTools
      • Spring-Initailizr
      • 配置文件-Yaml用法
      • Web开发简介
      • web开发-静态资源规则于定制化
      • 静态资源配置原理
      • Rest映射及源码解析
      • 请求映射原理
      • 常用参数注解使用
      • MatrixVariable:矩阵变量
      • 各种类型参数解析原理
      • Servlet-API参数解析原理
      • Model、Map参数解析原理
      • 自定义对象参数绑定原理
      • 自定义Converter原理
      • 数据响应原理
      • 内容协商原理
      • 基于请求参数的内容原理
      • 自定义MessageConverter原理
      • Thymeleaf初体验
      • web实验-后台管理系统
      • web实验-抽取公共页面
      • web实验-遍历数据
      • 源码分析-视图解析器与视图
      • 拦截器-登录检查与静态资源放行
      • 拦截器的执行时机和原理
      • 单文件和多文件上传的使用
      • 文件上传原理
      • 错误处理机制
      • 错误处理-底层组件源码分析
      • 异常处理流程
      • 几种异常处理原理
      • Web原生对象注入
      • 嵌入式Servlet容器
        • 原理
        • 切换其他服务器
        • 定制服务器
      • 定制化原理
      • 数据库场景的自动配置分析和整合测试
      • 自定义方式整合Druid
      • 通过starter整合Druid
      • 整合Mybatis
      • 使用注解整合Mybatis
      • 整合MybatisPlus操作数据库
      • MybatisPlus-列表分页展示
      • 整合Redis
      • 单元测试-Junit5
      • 单元测试-断言机制
      • 单元测试-前置条件
      • 单元测试-嵌套测试
      • 单元测试-参数化测试
      • 指标监控-基本概念
      • 指标监控-配置EndPoint
      • 指标监控-可视化
      • 原理解析-Profile功能
      • 配置文件深入
      • 自定义Starter
      • SpringApplication初始化过程
      • SpringBoot完整启动过程
      • SpringBoot
  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • Java
  • SpringBoot
  • SpringBoot教程-尚硅谷
2023-08-22
目录

嵌入式Servlet容器

# 460.嵌入式Servlet容器

之前我们说过,SpringBoot内置了Tomcat,只需启动应用即可监听端口,本文就来讲讲其原理   ‍

ps:实用性不高,了解即可

‍

# 原理

在官网文档,是这样说的:

The ServletWebServerApplicationContext

Under the hood, Spring Boot uses a different type of ApplicationContext for embedded servlet container support. The ServletWebServerApplicationContext​ is a special type of WebApplicationContext that bootstraps itself by searching for a single ServletWebServerFactory​ bean. Usually a TomcatServletWebServerFactory​, JettyServletWebServerFactory​, or UndertowServletWebServerFactory​ has been auto-configured.

‍

大意:如果SpringBoot应用启动的时候,发现当前应用是Web应用,并且导入了Tomcat,那么就会一个Web版本的IoC容器,名字是ServletWebServerApplicationContext​;

该IoC容器在启动的时候会搜索ServletWebServerFactory​,直译就是Servlet的web容器工厂,这个工厂生产的就是Web容器,例如TomcatServletWebServerFactory​,JettyServletWebServerFactory​ 或 UndertowServletWebServerFactory​,对应生成的Web容器就是Tomcat​,Jetty​和Undertow​

注意,Undertow​不支持JSP

‍

这些工厂是SpringBoot底层默认有的,在配置类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 {
1
2
3
4
5
6
7
8
9
10

‍

可以看到Import了ServletWebServerFactoryConfiguration​,而该类又注入了对应的容器工厂:

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

        @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	static class EmbeddedTomcat {  //.....


	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
	static class EmbeddedJetty {  //.....

        @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	static class EmbeddedUndertow {  //.....
//..... 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

如果我们导入了Tomcat,那么根据@ConditionalOnClass(Tomcat.class)​,就会注入Tomcat的工厂类;

由于web-starter默认会导入tomcat包,因此容器中就有 TomcatServletWebServerFactory​,然后创建出Tomcat服务器并启动。

‍

‍

在IoC容器中,启动的时候会顺便启动服务器:

@Override
protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}


private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		ServletWebServerFactory factory = getWebServerFactory();
		this.webServer = factory.getWebServer(getSelfInitializer());
		getBeanFactory().registerSingleton("webServerGracefulShutdown",
				new WebServerGracefulShutdownLifecycle(this.webServer));
		getBeanFactory().registerSingleton("webServerStartStop",
				new WebServerStartStopLifecycle(this, this.webServer));
	}
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

‍

我们可以在createWebServer​方法上打一个断点,然后启动。首先会获取工厂:

ServletWebServerFactory factory = getWebServerFactory();
1

‍

‍

其源码如下:也就是在IoC容器中找,找不到则抛异常;如果数量大于1也抛异常

protected ServletWebServerFactory getWebServerFactory() {
	// Use bean names so that we don't consider the hierarchy
	String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);

	if (beanNames.length == 0) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean.");
	}

	if (beanNames.length > 1) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
	}

	return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

‍

‍

拿到工厂后,下一步就是创建服务器了:

ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
1
2

‍

我们步入getWebServer​方法:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
    	Registry.disableRegistry();
    }
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
	tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

其内部就会初始化服务器的信息,连接器,Host和Engine等等(了解即可)。之前我们使用Tomcat,都是双击startup.bat启动;而所谓内嵌服务器,就是在代码里完成这一步

‍

‍

# 切换其他服务器

如果想切换为其他服务器,例如Undertow​,怎么做呢?

首先,Tomcat是有web场景导入的:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
1
2
3
4

‍

我们点进该依赖的详情,就有Tomcat:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>
1
2
3
4
5
6

‍

因此,我们得先排除掉Tomcat的依赖,再引入相关的依赖即可:

​​

‍

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

‍

再次启动,就能看到是Undertow​了:

​​

‍

当然,建议还是用Tomcat,因为Tomcat综合起来看还是不错的

‍

# 定制服务器

如果我们想要修改服务器相关配置,也是很简单的,首先可以看文档:

​​

可以看到要定制化,就是改配置文件,很多都是以server开头的。

‍

从源码方面,很多配置都是取值ServerProperties​类:

@EnableConfigurationProperties(ServerProperties.class)
public class ServletWebServerFactoryAutoConfiguration {
1
2

‍

该类都是以server​作为前缀的:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
	private Integer port;
	private InetAddress address;

    private final Servlet servlet = new Servlet();

        private final Tomcat tomcat = new Tomcat();

	private final Jetty jetty = new Jetty();

	private final Netty netty = new Netty();
        //......
1
2
3
4
5
6
7
8
9
10
11
12
13

‍

可以看到,还可以针对不同的服务器做配置,然后Servlet做共有的配置。

  • 例如使用Tomcat​,端口是9999,想要的日志路径是./logs2​
  • 使用undertow​的话,端口是9999,日志路径是./logs​
server:
  port: 9999
  tomcat:
    accesslog:
      directory: ./logs2
  undertow:
    accesslog:
      dir: ./logs
1
2
3
4
5
6
7
8

‍

‍

还可以自定义一个Web服务器工厂,在我们的自动配置类中,还注入了一个Customizer​,定制化器:

public class ServletWebServerFactoryAutoConfiguration {

	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		return new ServletWebServerFactoryCustomizer(serverProperties);
	}
1
2
3
4
5
6

‍

‍

‍

该定制化器,就是在web服务器工厂准备好后,再修改一些属性,比如customize​方法,就会拿到配置文件里的东西,然后绑定到工厂上:

public class ServletWebServerFactoryCustomizer
	implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {


    @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);
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

‍

这里扩展下,SpringBoot中经常有xxxxCustomizer​定制化器 这种模式,只需自定义一个定制化器,就能修改某个东西的默认行为

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/8/23 10:10:57
Web原生对象注入
定制化原理

← Web原生对象注入 定制化原理→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式