0%

SpringBoot内置的Servlet容器

文章字数:784,阅读全文大约需要3分钟

SpringBoot内置了Servlet容器,所以可以直接运行。而传统的javaWeb程序需要嵌入到Tomcat之类的Servlet容器中才能运行。方便之余却带了问题,我们不能像传统的javaWeb程序那样操作web.xml。所以Spring提供了自定义容器内容的途径。

自动配置

自动配置类EmbeddedServletContainerAutoConfiguration

首先看springBoot如何自动配置的。
spring-boot-autoconfigure-xxx.jarweb模块中可以找到

EmbeddedServletContainerAutoConfiguration

1
2
3
4
5
6
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
...

这个类中主要作用是配置三个容器工厂的bean
都是EmbeddedServletContainerFactory接口的

  1. TomcatEmbeddedServletContainerFactory
  2. JettyEmbeddedServletContainerFactory
  3. UndertowEmbeddedServletContainerFactory

通过注解设置有servlet依赖和类对应的servlet容器依赖时,没有其它EmbeddedServletContainerFactory接口时创建。(重点1)

容器工厂接口EmbeddedServletContainerFactory

EmbeddedServletContainerFactory接口内部只有获取嵌入式servlet容器的方法

1
2
3
4
5
public interface EmbeddedServletContainerFactory {

EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}

容器接口EmbeddedServletContainer

EmbeddedServletContainerFactory获取的容器就是EmbeddedServletContainer返回的类型。

也对应三个实现类

  1. TomcatEmbeddedServletContainer
  2. JettyEmbeddedServletContainer
  3. UndertowEmbeddedServletContainer

Tomcat容器工厂TomcatEmbeddedServletContainerFactory类为例:

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
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
//创建一个Tomcat
Tomcat tomcat = new Tomcat();

//配置Tomcat的基本环节
File baseDir = (this.baseDirectory != null this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 绑定端口的连接
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
// 是否设置Tomcat自动部署
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);

//包装tomcat对象,返回一个嵌入式Tomcat容器,内部会启动该tomcat容器
return getTomcatEmbeddedServletContainer(tomcat);
}

最后调用的方法,主要就是创建Tomcat容器,启动容器。

1
2
3
4
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}

自定义容器参数

EmbeddedServletContainerCustomizer定制器

自定义属性可以通过ServerPropertiesEmbeddedServletContainerCustomizer定制器实现,ServerPropertiesEmbeddedServletContainerCustomizer的子类,所以其实都是EmbeddedServletContainerCustomizer在起作用。

BeanPostProcessorsRegistrar给容器导入组件的类

在最顶级的自动配置类EmbeddedServletContainerAutoConfiguration上有个注解@Import(BeanPostProcessorsRegistrar.class)
导入了BeanPostProcessorsRegistrar这个类成Bean

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
35
36
37
38
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

private ConfigurableListableBeanFactory beanFactory;

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}

// 重点方法,导入了配置器
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
//注册了一个EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器的Bean
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}

private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
String name, Class beanClass) {
if (ObjectUtils.isEmpty(
this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
  1. 后置处理器:在bean初始化前(创建完成,还未属性赋值),会执行初始化工作。

  2. registerBeanDefinitions这个方法导入了EmbeddedServletContainerCustomizerBeanPostProcessorBean

  3. 这个Bean会从ICO容器中拿到所有EmbeddedServletContainerCustomizer类,也就是上面说的定制器。可以引入这个定制器来操作容器参数内容

通过容器工厂自定义参数

EmbeddedServletContainerFactory容器工厂

也就是上面的重点1,spring会根据依赖和有没有其他的工厂来判断是否要注入。我们可以自定义工厂,并加入Bean

例:

  1. 禁用post和get之外的方法
    配置类中
    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
    @Bean  
    public EmbeddedServletContainerFactory servletContainer() {
    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {// 1
    protected void postProcessContext(Context context) {
    SecurityConstraint securityConstraint = new SecurityConstraint();
    securityConstraint.setUserConstraint("CONFIDENTIAL");
    SecurityCollection collection = new SecurityCollection();
    // 对于所有的请求
    collection.addPattern("/*");
    // 都验证一下http方法(servlet自带的权限验证,因为没有定义用户,所有直接拦截)
    collection.addMethod("HEAD");
    collection.addMethod("PUT");
    collection.addMethod("DELETE");
    collection.addMethod("OPTIONS");
    collection.addMethod("TRACE");
    collection.addMethod("COPY");
    collection.addMethod("SEARCH");
    collection.addMethod("PROPFIND");
    securityConstraint.addCollection(collection);
    context.addConstraint(securityConstraint);
    }
    };
    //如果需要禁用TRACE请求,需添加以下代码:
    tomcat.addConnectorCustomizers(connector -> {
    connector.setAllowTrace(true);
    });
    return tomcat;
    }

同等于传统的web.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<security-constraint>  
<web-resource-collection>
<url-pattern>/*</url-pattern>
<http-method>HEAD</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
<http-method>COPY</http-method>
<http-method>SEARCH</http-method>
<http-method>PROPFIND</http-method>
</web-resource-collection>
<auth-constraint>
</auth-constraint>
</security-constraint>
  1. 转发端口(http转到https)
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
35
36
37
38
39
40
41
42
@Bean
public EmbeddedServletContainerFactory servletContainer() {
// 创建容器工厂
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {
// 重写方法
@Override
protected void postProcessContext(Context context) {
// 新建一个安全设置
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
// 对与所有的请求
collection.addPattern("/*");
// 拦截验证这些方法的权限(即禁用)
collection.addMethod("HEAD");
collection.addMethod("PUT");
collection.addMethod("DELETE");
collection.addMethod("OPTIONS");
collection.addMethod("TRACE");
collection.addMethod("COPY");
collection.addMethod("SEARCH");
collection.addMethod("PROPFIND");
collection.addMethod("BOGUS");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
// 添加连接(除了默认的https连接,这里又监听了http的)
tomcat.addAdditionalTomcatConnectors(httpConnector());
return tomcat;
}

@Bean
public Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(httpPort);
connector.setSecure(false);
// 转发到https端口
connector.setRedirectPort(httpsPort);
return connector;
}