为什么Spring boot中,没有web.xml?

1.Springboot自动配置Servlet容器

我们上文发现,spring其实也需要实现WebApplicationInitializer接口,覆写onStartup方法。但是在springboot中,我们发现并不需要我们自己实现接口。

Spring Boot 的自动配置:Spring Boot 是 Spring 生态中的一个项目,它提供了自动配置机制,可以自动配置 Servlet 容器、DispatcherServlet 等,并且不需要 web.xml 文件。以便你专注于编写应用程序的业务逻辑而不必关心底层配置。

默认情况下,Spring Boot 的 DispatcherServlet 会映射到根路径 "/*",这意味着所有的 HTTP 请求都会经过 DispatcherServlet 进行处理。如果需要修改 DispatcherServlet 的配置,可以通过 Spring Boot 提供的一些属性来实现。

可以通过在 application.propertiesapplication.yml 文件中添加相应的属性

server.servlet.context-path=/myapp

server: servlet: context-path: /myapp

在Spring Boot中,DispatcherServlet是一个核心组件,它负责处理所有的HTTP请求。Spring Boot通过自动配置的方式简化了DispatcherServlet的注册和配置过程。下面是Spring Boot自动注册DispatcherServlet的详细流程:

见名知意我们也知道它的作用是:DispatcherServlet ----Auto(自动)----Configuration(配置)

2. DispatcherServletAutoConfiguration

Spring Boot使用@EnableAutoConfiguration注解来启用自动配置,其中包括了DispatcherServletAutoConfiguration类。该类负责配置和注册DispatcherServlet

1
2
3
4
5
6
7
8
9
10
java
复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@EnableConfigurationProperties(WebMvcProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class DispatcherServletAutoConfiguration {
// 配置相关代码
}

2.1 条件注解

DispatcherServletAutoConfiguration类上,多个条件注解用于确保该配置仅在特定条件下生效:

  • @ConditionalOnClass:只有在类路径上存在ServletDispatcherServlet时才会生效。
  • @ConditionalOnWebApplication:只有在当前应用类型为Servlet Web应用时才会生效。

3. 注册 DispatcherServlet Bean

DispatcherServletAutoConfiguration中,会创建和注册一个DispatcherServlet。通常,这通过@Bean方法来实现。

1
2
3
4
5
6
7
8
9
10
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet DispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}

4. 注册 DispatcherServletRegistrationBean

除了创建DispatcherServlet Bean,Spring Boot还会注册ServletRegistrationBean,将DispatcherServlet映射到特定的URL模式(默认是“/”)。

见名知意我们也知道它的作用是:DispatcherServlet ----Registration(登记,注册)----Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class,
name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean DispatcherServletRegistration(DispatcherServlet dispatcherServlet,WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
dispatcherServlet,webMvcProperties.getServlet().getPath());

registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
//`webMvcProperties.getServlet().getPath()`在Spring Boot应用中,主要用于获取`DispatcherServlet`的映射路径配置

我们可以看到:

1
2
3
@ConditionalOnBean(value = DispatcherServlet.class, 
name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
表明存在DispatcherServlet类才注入DispatcherServletRegistrationBean

DispatcherServletRegistrationBean体系结构

我们发现它最终也是实现了ServletContextInitializer接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
@FunctionalInterface
public interface ServletContextInitializer {

/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initialization.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;

}

4.1 RegistrationBean

RegistrationBean实现了ServletContextInitializer,之后调用register方法,这个是抽象方法,由子类DynamicRegistrationBean实现:

1
2
3
4
5
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
... ...
register(description, servletContext);
}

4.2 DynamicRegistrationBean

DynamicRegistrationBean继承了RegistrationBean,之后调用addRegistration方法,这个是抽象方法,由子类ServletListenerRegistrationBean实现:

1
2
3
4
5
6
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
... ...
configure(registration);
}

4.3 ServletListenerRegistrationBean

子类ServletRegistrationBean的addRegistration方法

1
2
3
4
5
6
7
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}

ServletRegistrationBean--->addRegistration

servletContext.addServlet():Servlet3.0+的特性,将servlet添加到容器

5.ServletContextInitializerBeans

上面我们已经有了DispatcherServletRegistrationBean,只要我们注册该类,SpringBoot就会执行一系列操作配置好我们DispathcerServlet,但是什么时候才会触发ServletContextInitializeronStartup方法呢?

在 Spring Boot 中,ServletContextInitializerBeans 是一个帮助类,用于收集和管理所有实现了 ServletContextInitializer 接口的 Bean。存储在自身的集合中。

  1. 收集 ServletContextInitializer Bean: 它会从应用上下文中收集所有实现了 ServletContextInitializer 接口的 Bean。
  2. 排序和管理: 收集到的 ServletContextInitializer Bean 可以按照一定的顺序进行管理,确保它们按正确的顺序执行。

这些 Bean 可以在应用程序启动时对 ServletContext 进行编程式配置。再看一下这图:

最终也就是我们的DispatcherServletRegistrationBean

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {

private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;

@SuppressWarnings("varargs")
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
... ...
addServletContextInitializerBeans(beanFactory);

addAdaptableBeans(beanFactory);
... ...
}

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean :
//getOrderedBeansOfType--从BeanFactory中获取所有的ServletContextInitializer
getOrderedBeansOfType(beanFactory,initializerType)) {

addServletContextInitializerBean(
initializerBean.getKey(), initializerBean.getValue(), beanFactory);

}
}
}

//将获取到的ServletContextInitializer类型的Bean,添加到属性initializers中
private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
if (initializer instanceof ServletRegistrationBean<?> servletRegistrationBean) {
Servlet source = servletRegistrationBean.getServlet();
addServletContextInitializerBean(Servlet.class, beanName, servletRegistrationBean, beanFactory, source);
}
else if (initializer instanceof FilterRegistrationBean<?> filterRegistrationBean) {
Filter source = filterRegistrationBean.getFilter();
addServletContextInitializerBean(Filter.class, beanName, filterRegistrationBean, beanFactory, source);
}
else if (initializer instanceof DelegatingFilterProxyRegistrationBean registrationBean) {
String source = registrationBean.getTargetBeanName();
addServletContextInitializerBean(Filter.class, beanName, registrationBean, beanFactory, source);
}
else if (initializer instanceof ServletListenerRegistrationBean<?> registrationBean) {
EventListener source = registrationBean.getListener();
addServletContextInitializerBean(EventListener.class, beanName, registrationBean, beanFactory, source);
}
else {
addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
initializer);
}
}

private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer,
ListableBeanFactory beanFactory, Object source) {
this.initializers.add(type, initializer);
... ...
}

... ...
}

//private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
//initializers存储的是一系列ServletContextInitializer对象。

ServletContextInitializerBean获取所有实现了ServletContextInitializer接口的类。存储到initializers集合。

可以看到,实现了ServletContextInitializer的不止是Servlet类型的,还有Listener、Filter类型的。

因为他们都需要动态添加到web容器中

6.ServletWebServerApplicationContext

ServletContextInitializerBeans 已经收集和调用所有实现了 ServletContextInitializer 接口的 Bean。

ServletWebServerApplicationContext 是 Spring Boot 中用于 Servlet 环境的 ApplicationContext 实现。它是 GenericWebApplicationContext 的子类,专门用于创建和管理嵌入式 Servlet 容器(如 Tomcat、Jetty 或 Undertow)。

6.1 工作流程

以下是 ServletWebServerApplicationContext 的工作流程:

  1. 初始化嵌入式 Servlet 容器
    • onRefresh() 方法中,ServletWebServerApplicationContext 会调用 createWebServer() 方法初始化嵌入式 Servlet 容器。
    • createWebServer() 方法会实例化 WebServerFactory(如 TomcatServletWebServerFactory)来创建具体的嵌入式容器实例。
  2. 启动嵌入式 Servlet 容器
    • 在初始化完成后,调用 webServer.start() 启动嵌入式容器。
    • start() 方法会启动容器,并将上下文路径、ServletFilter 等注册到容器中。
  3. 注册 ServletFilter
    • 在嵌入式容器启动过程中,ServletWebServerApplicationContext 会扫描并注册所有实现了 ServletFilterServletContextListener 的 Bean。
    • 它还会根据配置文件或注解来注册 DispatcherServletErrorPage 等。
  4. 关闭嵌入式 Servlet 容器
    • close() 方法中,ServletWebServerApplicationContext 会调用 webServer.stop() 关闭嵌入式容器,释放相关资源。

6.2 代码实现

6.2.1 onRefresh()

1
2
3
4
5
6
7
8
9
10
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {

@Override
protected void onRefresh() {
super.onRefresh();
createWebServer();
}
}

6.2.2 createWebServer()

1
2
3
4
5
6
 //createWebServer--->
private void createWebServer() {
... ...
this.webServer = factory.getWebServer(getSelfInitializer());
... ...
}

6.2.3 getSelfInitializer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  //getSelfInitializer--->
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}

//selfInitialize--->
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);

for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}

我们看到:通过getServletContextInitializerBeans()方法返回所有实现ServletContextInitializer接口的实现类,循环调用它们的onStartup方法。

6.2.4 getServletContextInitializerBeans()

1
2
3
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}

getServletContextInitializerBeans 方法在 Spring Boot 中的作用是获取所有实现了 ServletContextInitializer 接口的 Bean,也就是ServletContextInitializerBean的集合。

7.启动流程总结

  • Spring Boot通过DispatcherServletAutoConfiguration类简化了DispatcherServlet的注册流程。自动配置类根据条件注解自动加载并配置DispatcherServlet(DispatcherServletBean)ServletRegistrationBean(DispatcherServletRegistrationBean)

  • 再通过ServletContextInitializerBeans类收集所有实现了 ServletContextInitializer 接口的 Bean。

  • Spring Boot 会在容器启动时通过ServletWebServerApplicationContextonRefresh()方法。

  • onRefresh() 方法中,ServletWebServerApplicationContext 会调用 createWebServer() 方法初始化嵌入式 Servlet 容器。

  • 最后会通过new ServletContextInitializerBeans()的方式,将所有实现了 ServletContextInitializer 接口的 Bean保存到集合里(DispatcherServletRegistrationBean)。

  • 最后遍历集合。循环调用实现类覆写的 onStartup 方法,对 ServletContext 进行配置。最后添加到web容器中。

8.ServletContextInitializer区别

这里的ServletContextInitializer不是指前面在讲SpringMVC中的ServletContextInitializer。不要混淆。

  • Springboot中的ServletContextInitializer,为Springboot自己的包,由Spring自己管理。
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.springframework.boot.web.servlet;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.WebApplicationInitializer;

@FunctionalInterface
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
  • SpringMVC中的ServletContextInitializer,为javax.servlet的包:由容器管理。
1
2
3
4
5
6
7
package javax.servlet;

import java.util.Set;

public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

9.WebMvcProperties

WebMvcProperties 是 Spring Boot 提供的一个配置属性类,用于自定义 Spring MVC 的一些常见设置。它使得开发者可以通过配置文件(如 application.propertiesapplication.yml)方便地配置 Spring MVC 的行为,而不需要编写大量的 Java 配置代码。

webMvcProperties.getServlet().getPath()在Spring Boot应用中,主要用于获取DispatcherServlet的映射路径配置。具体来说,它返回了在Spring Boot的配置文件(如application.propertiesapplication.yml)中配置的DispatcherServlet路径。这个路径定义了应用程序中请求路径的前缀,DispatcherServlet将处理匹配这个路径的请求。

9.1 主要作用

  1. 简化配置WebMvcProperties 提供了一种集中化和简化配置的方法,使得开发者可以通过简单的属性配置来控制 Spring MVC 的行为。
  2. 统一管理: 所有与 Spring MVC 相关的配置属性都集中在一个地方,便于管理和维护。
1
2
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {}

9.2主要配置选项

以下是 WebMvcProperties 提供的一些常见配置选项及其作用:

  1. spring.mvc.servlet.path: 配置 DispatcherServlet 的路径映射。例如,设置为 /api 时,所有请求都必须以 /api 开头。

    1
    spring.mvc.servlet.path=/api
  2. spring.mvc.view.prefixspring.mvc.view.suffix: 配置视图解析器的前缀和后缀。例如,将视图名解析为 /WEB-INF/views/ 目录下的 JSP 文件。

    1
    2
    spring.mvc.view.prefix=/WEB-INF/views/
    spring.mvc.view.suffix=.jsp
  3. spring.mvc.format.date, spring.mvc.format.time, spring.mvc.format.date-time: 配置日期、时间和日期时间格式。

    1
    2
    3
    spring.mvc.format.date=yyyy-MM-dd
    spring.mvc.format.time=HH:mm:ss
    spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
  4. spring.mvc.static-path-pattern: 配置静态资源的路径模式。例如,设置为 /static/** 时,静态资源会被映射到 /static 目录。

    1
    spring.mvc.static-path-pattern=/static/**
  5. spring.mvc.throw-exception-if-no-handler-found: 是否抛出异常当没有找到处理器时,默认是 false

    1
    spring.mvc.throw-exception-if-no-handler-found=true
  6. spring.mvc.async.request-timeout: 配置异步请求的超时时间。

    1
    spring.mvc.async.request-timeout=5000  # 单位为毫秒

9.3 示例

假设我们有如下配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
mvc:
servlet:
path: /api
view:
prefix: /WEB-INF/views/
suffix: .jsp
format:
date: yyyy-MM-dd
time: HH:mm:ss
date-time: yyyy-MM-dd HH:mm:ss
static-path-pattern: /static/**
throw-exception-if-no-handler-found: true
async:
request-timeout: 5000

上述配置可以通过 WebMvcProperties 类自动绑定并应用到 Spring MVC 中,简化了配置过程。

9.4 总结

WebMvcProperties 的作用是简化和集中管理 Spring MVC 的各种配置选项,使得开发者可以通过配置文件快速定制 Spring MVC 的行为,从而提高开发效率和可维护性。

10.ispatcherServlet映射路径

Spring Boot 可以不配置 spring.mvc.servlet.path。默认情况下,如果不配置这个属性,Spring Boot 会将 DispatcherServlet 映射到根路径 /,这意味着应用程序将处理所有传入的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
public static class Servlet {

/**
* Path of the dispatcher servlet.
*/
private String path = "/";

/**
* Load on startup priority of the dispatcher servlet.
*/
private int loadOnStartup = -1;
}
}

在 Servlet 配置中,loadOnStartup 参数用于指定 Servlet 的加载和初始化时机。该参数可以设置为以下值:

10.1 值的类型

  • 正整数:表示在服务器启动时加载 Servlet。值越小,优先级越高,越早加载。
  • 零或负整数:表示在服务器启动时不加载 Servlet,而是第一次收到请求时才加载。

所以Spring boot中,Servlet加载和初始化的时机默认是第一次收到请求时才加载。


博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!


为什么Spring boot中,没有web.xml?
https://nanchengjiumeng123.top/2024/02/17/framework/spring/Spring MVC/5.为什么Spring boot中,没有web.xml?/
作者
Yang Xin
发布于
2024年2月17日
许可协议