SpringBoot从入门到精通—嵌入式Servlet容器的配置、切换

声明:以下的所有方法、原理、源码全部是建立在Spring Boot 2.1.7版本。

1、 修改SpringBoot对嵌入式Server容器的默认配置

Spring Boot默认使用Tomcat作为嵌入式的Servlet容器。实际应用中我们需要对他进行专门的定制。定制的方式不外乎两种:在配置文件中配置或在配置类中注册组件的方式配置

1.1 直接在application.properties/application.xml文件中配置和server有关的属性

配置方式是server.属性名=值,下面是ServerProperties类中定义的绝大多数可以配置的属性

server配置
server.address 指定server绑定的地址
server.compression.enabled 是否开启压缩,默认为false.
server.compression.excluded-user-agents 指定不压缩的user-agent,多个以逗号分隔,默认值为:text/html,text/xml,text/plain,text/css
server.compression.mime-types 指定要压缩的MIME type,多个以逗号分隔.
server.compression.min-response-size 执行压缩的阈值,默认为2048
server.context-parameters.[param name] 设置servlet context 参数
server.context-path 设定应用的context-path.
server.display-name 设定应用的展示名称,默认: application
server.jsp-servlet.class-name 设定编译JSP用的servlet,默认: org.apache.jasper
servlet.JspServlet)
server.jsp-servlet.init-parameters.[param name] 设置JSP servlet 初始化参数.
server.jsp-servlet.registered 设定JSP servlet是否注册到内嵌的servlet容器,默认true
server.port 设定http监听端口
server.servlet-path 设定dispatcher servlet的监听路径,默认为: /

cookie、session配置
server.session.cookie.comment 指定session cookie的comment
server.session.cookie.domain 指定session cookie的domain
server.session.cookie.http-only 是否开启HttpOnly.
server.session.cookie.max-age 设定session cookie的最大age.
server.session.cookie.name 设定Session cookie 的名称.
server.session.cookie.path 设定session cookie的路径.
server.session.cookie.secure 设定session cookie的“Secure” flag.
server.session.persistent 重启时是否持久化session,默认false
server.session.timeout session的超时时间
server.session.tracking-modes 设定Session的追踪模式(cookie, url, ssl).

ssl配置
server.ssl.ciphers 是否支持SSL ciphers.
server.ssl.client-auth 设定client authentication是wanted 还是 needed.
server.ssl.enabled 是否开启ssl,默认: true
server.ssl.key-alias 设定key store中key的别名.
server.ssl.key-password 访问key store中key的密码.
server.ssl.key-store 设定持有SSL certificate的key store的路径,通常是一个.jks文件.
server.ssl.key-store-password 设定访问key store的密码.
server.ssl.key-store-provider  设定key store的提供者.
server.ssl.key-store-type  设定key store的类型.
server.ssl.protocol  使用的SSL协议,默认: TLS
server.ssl.trust-store  持有SSL certificates的Trust store.
server.ssl.trust-store-password  访问trust store的密码.
server.ssl.trust-store-provider  设定trust store的提供者.
server.ssl.trust-store-type   指定trust store的类型.

tomcat配置
server.tomcat.access-log-enabled  是否开启access log ,默认: false)
server.tomcat.access-log-pattern  设定access logs的格式,默认: common
server.tomcat.accesslog.directory  设定log的目录,默认: logs
server.tomcat.accesslog.enabled  是否开启access log,默认: false
server.tomcat.accesslog.pattern  设定access logs的格式,默认: common
server.tomcat.accesslog.prefix  设定Log 文件的前缀,默认: access_log
server.tomcat.accesslog.suffix  设定Log 文件的后缀,默认: .log
server.tomcat.background-processor-delay  后台线程方法的Delay大小: 30
server.tomcat.basedir  设定Tomcat的base 目录,如果没有指定则使用临时目录.
server.tomcat.internal-proxies  设定信任的正则表达式,默认:“10\.\d{1,3}\.\d{1,3}\.\d{1,3}| 192\.168\.\d{1,3}\.\d{1,3}| 169\.254\.\d{1,3}\.\d{1,3}| 127\.\d{1,3}\.\d{1,3}\.\d{1,3}| 172\.1[6-9]{1}\.\d{1,3}\.\d{1,3}| 172\.2[0-9]{1}\.\d{1,3}\.\d{1,3}|172\.3[0-1]{1}\.\d{1,3}\.\d{1,3}”
server.tomcat.max-http-header-size  设定http header的最小值,默认: 0
server.tomcat.max-threads  设定tomcat的最大工作线程数,默认为: 0
server.tomcat.port-header  设定http header使用的,用来覆盖原来port的value.
server.tomcat.protocol-header  设定Header包含的协议,通常是 X-Forwarded-Proto,如果remoteIpHeader有值,则将设置为RemoteIpValve.
server.tomcat.protocol-header-https-value  设定使用SSL的header的值,默认https.
server.tomcat.remote-ip-header  设定remote IP的header,如果remoteIpHeader有值,则设置为RemoteIpValve
server.tomcat.uri-encoding  设定URI的解码字符集.

undertow配置
server.undertow.access-log-dir  设定Undertow access log 的目录,默认: logs
server.undertow.access-log-enabled  是否开启access log,默认: false
server.undertow.access-log-pattern  设定access logs的格式,默认: common
server.undertow.accesslog.dir  设定access log 的目录.
server.undertow.buffer-size  设定buffer的大小.
server.undertow.buffers-per-region  设定每个region的buffer数
server.undertow.direct-buffers  设定堆外内存
server.undertow.io-threads  设定I/O线程数.
server.undertow.worker-threads  设定工作线程数


配置示例:

server:
  port: 80    #端口
  servlet:
    context-path: /crud   #项目的context-path
  tomcat:
    uri-encoding: UTF-8     #tomcat URI的字符编码
    basedir: localhost/crud   #tomcat基路径
  compression:    #是否开启压缩文件
    enabled: true   #true是开启
    mime-types: text/html,text/css,text/javascript    #压缩的文件类型
1.2 向IoC容器中添加servlet容器工厂定制器 WebServerFactoryCustomizer
@Configuration
public class ServerConfig {

    @Bean   //注册到IOC容器中
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> webServerFactoryCustomizer(){
        return factory -> {
            //在这里设置server有关对属性
            factory.setPort(8085);
            factory.setContextPath("/crud");
            factory.setUriEncoding(Charset.forName("utf-8"));
            Compression compression = new Compression();
            compression.setEnabled(true);
            String []mimes={"text/html","text/css"};
            compression.setMimeTypes(mimes);
            factory.setCompression(compression);
        };
    }
}

运行结果:

如果使用的是Spring Boot 1.x版本可以参考下面的方法来使用配置

public class ServerConfig extends WebMvcConfigurerAdapter {
	public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
		return new EmbeddedServletContainerCustomizer() {
			@Override
			public void customize(ConfigurableEmbeddedServletContainer container) {
				//配置server属性
				container.setPort(8083);
				...
			}
		};
	}
}
1.3 向IoC容器中添加可配置的servlet容器工厂 ConfigurableServletWebServerFactory
@Configuration
public class ServerConfig {

    @Bean
    public ConfigurableServletWebServerFactory configurableServletWebServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        //设置属性
        factory.setPort(8089);
        factory.setContextPath("/springboot");
        factory.setUriEncoding(Charset.forName("utf-8"));
        Compression compression = new Compression();
        compression.setEnabled(true);
        String[] mimes = {"text/html", "text/css"};
        compression.setMimeTypes(mimes);
        factory.setCompression(compression);
        return factory;
    }
}

运行自然是没有什么问题,在地址栏输入http://localhost:8089/springboot/也来到了目标页面:

1.4 在Spring Boot中注册Servlet三大组件【Servlet、Filter、Listener

以前注册这些组件都是在web.xml中配置,由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。

注册三大组件用以下方式:

①、借助ServletRegistrationBean注册自定义Servlet组件;
自定义【MyServlet】:

public class MyServlet extends HttpServlet {
 
   //重写service方法
    @Override
    protected void service(HttpServletRequest req,HttpServletResponse resp){
        resp.getWriter().write("Hello Spring Boot!");
    }
}

定义【ServletRegistrationBean】并放入容器中:

@Bean
public ServletRegistrationBean myServlet(){
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
    return registrationBean;
}

②、借助FilterRegistrationBean注册自定义Filter组件;(自定义的MyFilter忽略)

@Bean
public FilterRegistrationBean myFilter(){
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new MyFilter());
    registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
    return registrationBean;
}

③、借助ServletListenerRegistrationBean注册自定义Listener组件;(自定义的MyListener忽略)

@Bean
public ServletListenerRegistrationBean myListener(){
    ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
    return registrationBean;
}

2、 嵌入式Servlet容器切换

2.1 三大容器比较
容器 优点 缺点 默认
tomcat 功能齐全 庞大,荣泽 true
jetty 轻量 功能不全 false
undertow 异步,高效 不支持jsp false
2.2 容器切换
  • Tomcat

Spring Boot引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器。

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • `Jetty
<!-- 引入web模块 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <!--排除tomcat-->
   <exclusions>
      <exclusion>
         <artifactId>spring-boot-starter-tomcat</artifactId>
         <groupId>org.springframework.boot</groupId>
      </exclusion>
   </exclusions>
</dependency>

<!--引入jetty容器-->
<dependency>
   <artifactId>spring-boot-starter-jetty</artifactId>
   <groupId>org.springframework.boot</groupId>
</dependency>
  • undertow
<!-- 引入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>

<!--引入其他的Servlet容器-->
<dependency>
   <artifactId>spring-boot-starter-undertow</artifactId>
   <groupId>org.springframework.boot</groupId>
</dependency>

3、 嵌入式Servlet容器自动配置原理

主要看三个类:
* ServletWebServerFactoryConfiguration
* ServletWebServerFactoryAutoConfiguration
* ServletWebServerFactoryCustomizer

3.1 ServletWebServerFactoryConfiguration

有三个:EmbeddedUndertow ,EmbeddedJetty,EmbeddedTomcat @Conditionalxxx 标注的类;条件满足才向容器中添加组件。

@Configuration
class ServletWebServerFactoryConfiguration {

    //默认配置的就是Tomcat
	@Configuration
	//容器中存在servlet.class(存在servlet依赖)  tomcat.class(tomcat依赖)  才会执行向容器中添加组件
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	//判断容器中没有用户自定义的ServletWeServerbFactory
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

        //向IOC容器中添加tomcat
		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
			return new TomcatServletWebServerFactory();
		}

	}

	/**
	 * 如果配置了Jetty就会自动配置jetty
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyServletWebServerFactory JettyServletWebServerFactory() {
			return new JettyServletWebServerFactory();
		}

	}

	/**
	 * 如果配置了Undertow就会自动配置Undertow
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowServletWebServerFactory undertowServletWebServerFactory() {
			return new UndertowServletWebServerFactory();
		}
	}
}
}
  • ServletWebServerFactory:嵌入式Servlet工厂。作用:创建嵌入式Servlet容器
@FunctionalInterface
public interface ServletWebServerFactory {

	//只有一个接口方法getWebServer
	WebServer getWebServer(ServletContextInitializer... initializers);

}

在判断@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})是否导入依赖,满足条件后return new TomcatServletWebServerFactory(); 添加对应的Servlet容器工厂;通过工厂的唯一方法getWebServer 获取对应的Servlet容器TomcatServer.

  • TomcatServletWebServerFactory
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {


  public WebServer getWebServer(ServletContextInitializer... initializers) {
       1. 创建Tomcat对象       
       Tomcat tomcat = new Tomcat();      
           
       File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        
      2.完成tomct 配置的基本操作
        Connector connector = new Connector(this.protocol);
        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);
        
       3.将tomcat 传入方法:getTomcatWebServer()
        return this.getTomcatWebServer(tomcat);
    }

}
  • TomcatWebServer
public class TomcatWebServer implements WebServer {
   //对应的构造函数:在TomcatWebServer 中
    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

     //initialize方法执行初始化
    private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
						// Remove service connectors so that protocol binding doesn't
						//服务启动的时候执行
						removeServiceConnectors();
					}
				});

				//启动tomcat
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}

				// Unlike Jetty, all Tomcat threads are daemon threads. We create a
				// blocking non-daemon to stop immediate shutdown
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				destroySilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}
}
3.2 ServletWebServerFactoryAutoConfiguration

修改定制Servlet 容器的方法:
   1、配置文件中添加配置。
   2、ServerProperties 绑定/修改定制组件 WebServerFactoryCustomizer

他们的本质是一样的:在ServletWebServerFactoryAutoConfiguration配置类中@EnableConfigurationProperties({ServerProperties.class})。
导入了BeanPostProcessorsRegistrar,在这个类的方法中添加了组件WebServerFactoryCustomizerBeanPostProcessor(定制器后置处理器)。也就是说一旦容器中添加任何组件都会启动定制后置处理器,进行Servlet的赋值。
后置处理器起作用的过程:

@Configuration
@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 {



}
3.3 ServletWebServerFactoryCustomizer

在这个配置类中使用customize(ConfigurableServletWebServerFactory factory)这个方法 完成了Tomcat的各项配置的修改和定制

public class ServletWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

    //具体对Tomcat的配置细节
	@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()::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);
	}
}
总结配置修改定制原理:以tomcat为例总结配置修改定制原理:以tomcat为例

1、Spring Boot根据根据导入依赖的情况,给容器中添加相应的ServletWebServerFactory(比如tomcat就会添加TomcatServletWebServerFactory)

2、如果使用的是通过application.propertoes 修改配置,那么server 相关的配置修改是与ServerProperties 类绑定的,所以相关的修改会直接通过Serverproperties 的方法实现【相关的配置类:ServletWebServerFactoryCustomizer

3、如果使用的是修改定制器 WebServerFactoryCustomizer的方法来配置server,那么定制器会创建ConfigurableWebServerFactory对象,这样一来就会触发WebServerFactoryCustomizerBeanPostProcessor 后置处理器,判断是否为WebServerFactory 类型;满足条件后,就会获取容器中的所有定制器(customizer.cutomize(bean)),为Servlet容器修改和定制配置【相关的配置类ServletWebServerFactoryAutoConfiguration,导入了定制处理器】

留言区

还能输入500个字符