抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了如何在Spring Boot中使用Spring MVC进行Web开发。

环境

Windows 10 企业版 LTSC 21H2
Java 1.8
Maven 3.6.3
Spring 5.3.31
Spring Boot 2.7.18

1 概述

Spring Boot提供了Spring MVC的自动配置,简化了Web开发的配置和管理。在Spring Boot中使用Spring MVC不能使用@EnableWebMvc注解。

在Spring MVC默认配置的基础上,Spring Boot还提供了以下功能:

  • 包括ContentNegotiatingViewResolver视图解析器和BeanNameViewResolver视图解析器。
  • 支持静态资源处理,包括WebJars技术。
  • 自动注册转换器和格式化器。
  • 支持HttpMessageConverters消息转换器。
  • 自动注册MessageCodesResolver消息解析器。
  • 支持静态文件index.html作为首页。
  • 自动使用ConfigurableWebBindingInitializer初始化Web绑定器。

支持扩展:

  • 如果需要保留Spring Boot的自动配置,增加自定义配置,可以使用@Configuration注解标识实现了WebMvcConfigurer接口的配置类。
  • 如果需要保留Spring Boot的自动配置,支持自定义RequestMappingHandlerMapping请求处理器映射器,可以使用WebMvcRegistrations接口,也可以使用WebMvcConfigurer接口实现进更细致的定制。
  • 如果需要保留Spring Boot的自动配置,支持自定义RequestMappingHandlerAdapter请求处理器适配器,可以使用WebMvcRegistrations接口,也可以使用WebMvcConfigurer接口实现进更细致的定制。
  • 如果需要保留Spring Boot的自动配置,支持自定义ExceptionHandlerExceptionResolver异常解析器,可以使用WebMvcRegistrations接口,也可以使用WebMvcConfigurer接口实现进更细致的定制。
  • 如果不需要保留Spring Boot的自动配置,可以使用@EnableWebMvc注解开启自定义配置,使用DelegatingWebMvcConfiguration进行自定义配置。

2 静态资源

2.1 标准资源

2.1.1 默认配置

默认从以下位置加载静态资源:

  • classpath:/static/
  • classpath:/public/
  • classpath:/resources/
  • classpath:/META-INF/resources/

静态资源默认访问路径是/**,对应默认静态资源处理路径是/**

2.1.2 设置存放路径

支持在配置文件中设置静态资源存放路径:

properties
1
spring.web.resources.static-locations=classpath:/txt/

支持设置多个静态资源存放路径:

properties
1
2
spring.web.resources.static-locations[0]=classpath:/txt/
spring.web.resources.static-locations[1]=classpath:/home/

存在多个同名静态资源时,默认会使用第一个找到的静态资源。

2.1.3 设置访问路径

支持在配置文件中设置静态资源路径:

properties
1
spring.mvc.static-path-pattern=/static/**

2.1.4 设置缓存策略

支持在配置文件中设置静态资源缓存策略:

properties
1
spring.web.resources.cache.period=3600

2.2 WebJars

2.2.1 说明

支持使用WebJars技术加载静态资源,WebJars技术是一种将静态资源打包成Jar包的技术,使用时通过依赖导入Jar包加载静态资源。

官网:https://webjars.org/

2.2.2 使用

可以通过/webjars/**访问Jar包里的静态资源。

导入JQuery依赖:

pom.xml
1
2
3
4
5
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>

使用/webjars/jquery/3.6.0/jquery.min.js访问JQuery资源。

3 欢迎页面

默认从以下位置加载index.html欢迎页面:

  • classpath:/static/
  • classpath:/public/
  • classpath:/resources/
  • classpath:/META-INF/resources/

设置静态资源访问路径会导致默认欢迎页面路径失效,需要手动配置欢迎页面的访问路径。

4 网站图标

默认从以下位置加载favicon.ico网站图标:

  • classpath:/static/
  • classpath:/public/
  • classpath:/resources/
  • classpath:/META-INF/resources/

5 REST

5.1 接口调用

如果不是表单请求,使用REST风格的请求方法可以直接调用接口,无需特殊配置:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class DemoController {
@GetMapping("/demo")
public String demoGet() {
return "调用GET请求";
}
@PostMapping("/demo")
public String demoPost() {
return "调用POST请求";
}
@PutMapping("/demo")
public String demoPut() {
return "调用PUT请求";
}
@DeleteMapping("/demo")
public String demoDelete() {
return "调用DELETE请求";
}
}

5.2 表单提交

默认情况下,如果表单提交PUT请求和DELETE请求,会使用GET请求处理:

html
1
2
3
4
5
6
7
8
9
10
11
12
<form action="/demo" method="get">
<input type="submit" value="调用GET请求">
</form>
<form action="/demo" method="post">
<input type="submit" value="调用POST请求">
</form>
<form action="/demo" method="put">
<input type="submit" value="调用PUT请求">
</form>
<form action="/demo" method="delete">
<input type="submit" value="调用DELETE请求">
</form>

默认提供了HiddenHttpMethodFilter过滤器,需要在配置文件中手动开启:

properties
1
spring.mvc.hiddenmethod.filter.enabled=true

同时需要将表单作为POST请求提交,在表单中使用_method作为名称添加隐藏域,将真正的请求方法作为值提交:

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form action="/demo" method="get">
<input type="submit" value="调用GET请求">
</form>
<form action="/demo" method="post">
<input type="submit" value="调用POST请求">
</form>
<form action="/demo" method="post">
<input type="hidden" name="_method" value="put">
<input type="submit" value="调用PUT请求">
</form>
<form action="/demo" method="post">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="调用DELETE请求">
</form>

5.3 定制名称

如果需要自定义隐藏域的名称,可以在配置类中注入HiddenHttpMethodFilter过滤器对象:

java
1
2
3
4
5
6
7
8
9
@Configuration(proxyBeanMethods = false)
public class DemoConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("rest");
return hiddenHttpMethodFilter;
}
}

6 内容协商

在配置文件中开启基于请求参数的内容协商,默认使用format作为参数名:

properties
1
spring.mvc.contentnegotiation.favor-parameter=true

支持通过配置文件指定参数名:

properties
1
spring.mvc.contentnegotiation.parameter-name=content

7 文件上传

配置文件:

properties
1
2
3
4
5
6
7
8
9
10
# 开启文件上传,默认开启
spring.servlet.multipart.enabled=true
# 设置最大上传文件大小
spring.servlet.multipart.max-request-size=100MB
# 设置单个文件最大大小
spring.servlet.multipart.max-file-size=10MB
# 设置内存阈值,超过写入临时文件
spring.servlet.multipart.file-size-threshold=10MB
# 设置临时文件目录,默认为系统临时目录
spring.servlet.multipart.location=tmp/

文件保存在C:\Users\当前用户名\AppData\Local\Temp目录下的tomcat目录中。

8 原生组件

8.1 注册Servlet组件

8.1.1 使用@ServletComponentScan注解

在启动类上使用@ServletComponentScan注解扫描Tomcat组件:

java
1
2
3
4
5
6
7
@SpringBootApplication
@ServletComponentScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

自定义Servlet组件:

java
1
2
3
4
5
6
7
8
9
10
@WebServlet("/demo")
public class DemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
System.out.println("执行Servlet");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("成功");
}
}

8.1.2 使用RegistrationBean注入

自定义Servlet组件:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 注册Servlet
@Bean
public ServletRegistrationBean<HttpServlet> demoServletBean() {
ServletRegistrationBean<HttpServlet> bean = new ServletRegistrationBean<>();
bean.setServlet(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
System.out.println("执行Servlet");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("成功");
}
});
bean.addUrlMappings("/demo");
return bean;
}

8.2 注册Filter组件

8.2.1 使用@ServletComponentScan注解

这种方式支持自定义拦截规则,不支持自定义Filter的顺序。

在启动类上使用@ServletComponentScan注解扫描Tomcat组件:

java
1
2
3
4
5
6
7
@SpringBootApplication
@ServletComponentScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

自定义Filter组件:

java
1
2
3
4
5
6
7
8
9
@WebFilter("/*")
public class DemoFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("经过Filter");
chain.doFilter(request, response);
}
}

8.2.2 使用RegistrationBean注入

这种方式比较复杂,支持自定义拦截规则,支持自定义Filter的顺序。

自定义Filter组件:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 注册Filter
@Bean
public FilterRegistrationBean<Filter> demoFilterBean() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new Filter() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("经过Filter");
chain.doFilter(request, response);
}
});
bean.addUrlPatterns("/*");
bean.setOrder(1);
return bean;
}

8.2.3 使用@Component注解

这种方式比较简单,支持自定义Filter的顺序,不支持设置拦截规则,默认拦截所有请求。

自定义Filter组件:

java
1
2
3
4
5
6
7
8
9
10
@Component
@Order(1)
public class DemoFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("经过Filter");
chain.doFilter(request, response);
}
}

8.3 注册Listener组件

8.3.1 使用@ServletComponentScan注解

在启动类上使用@ServletComponentScan注解扫描Tomcat组件:

java
1
2
3
4
5
6
7
@SpringBootApplication
@ServletComponentScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

自定义Listener组件:

java
1
2
3
4
5
6
7
8
9
10
11
@WebListener
public class DemoListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("项目启动执行Listener");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("项目关闭执行Listener");
}
}

8.3.2 使用RegistrationBean注入

自定义Listener组件:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 注册Listener
@Bean
public ServletListenerRegistrationBean<ServletContextListener> demoListenerBean() {
ServletListenerRegistrationBean<ServletContextListener> bean = new ServletListenerRegistrationBean<>();
bean.setListener(new ServletContextListener() {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("项目启动执行Listener");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("项目关闭执行Listener");
}
});
return bean;
}

8.3.3 使用@Component注解

自定义Listener组件:

java
1
2
3
4
5
6
7
8
9
10
11
@Component
public class DemoListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("项目启动执行Listener");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("项目关闭执行Listener");
}
}

9 流程说明

9.1 启动类

使用@SpringBootApplication注解可以标记启动类,启动类会按需导入需要的XxxAutoConfiguration配置类。

9.2 配置类

9.2.1 启动条件

在MVC环境中会启用WebMvcAutoConfiguration配置类,判断条件:

java
1
2
3
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

9.2.2 注册过滤器

在WebMvcAutoConfiguration配置类中会注册两个过滤器:

java
1
2
3
4
5
6
7
8
9
10
// 注册HiddenHttpMethodFilter过滤器,用于处理REST请求
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter();
// 注册FormContentFilter过滤器,用于处理POST请求中的表单数据
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter();

9.2.3 内置适配器

9.2.3.1 绑定配置

创建WebMvcAutoConfigurationAdapter内部类:

java
1
2
3
4
5
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}

使用@EnableConfigurationProperties注解绑定配置:

  • WebMvcProperties:绑定spring.mvc前缀的配置项。
  • WebProperties:绑定spring.web前缀的配置项。
9.2.3.2 处理资源

使用addResourceHandlers()方法定义资源处理路径:

java
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
// 默认资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 支持禁用默认资源处理
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
// 处理WebJars资源
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
// 处理静态资源
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
// 处理资源,只能处理路径
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
}
// 处理资源,支持复杂配置
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
customizer.accept(registration);
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
customizeResourceHandlerRegistration(registration);
}
9.2.3.3 设置缓存

支持在addResourceHandlers()方法中设置缓存策略:

java
1
2
3
4
5
6
// 设置缓存过期时间,单位为秒,默认不设置,不添加Cache-Control头,由浏览器控制缓存
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
// 设置缓存控制策略,默认不设置,不添加Cache-Control头,由浏览器控制缓存
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
// 设置是否使用最后修改时间,默认启用,用于判断资源是否过期
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());

9.2.4 内置配置类

java
1
2
3
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {}

在配置类中设置了欢迎页面的处理逻辑。

评论