摘要:本文学习了如何在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 设置存放路径
支持在配置文件中设置静态资源存放路径:
properties1
| spring.web.resources.static-locations=classpath:/txt/
|
支持设置多个静态资源存放路径:
properties1 2
| spring.web.resources.static-locations[0]=classpath:/txt/ spring.web.resources.static-locations[1]=classpath:/home/
|
存在多个同名静态资源时,默认会使用第一个找到的静态资源。
2.1.3 设置访问路径
支持在配置文件中设置静态资源路径:
properties1
| spring.mvc.static-path-pattern=/static/**
|
2.1.4 设置缓存策略
支持在配置文件中设置静态资源缓存策略:
properties1
| 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.xml1 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风格的请求方法可以直接调用接口,无需特殊配置:
java1 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请求处理:
html1 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过滤器,需要在配置文件中手动开启:
properties1
| spring.mvc.hiddenmethod.filter.enabled=true
|
同时需要将表单作为POST请求提交,在表单中使用_method作为名称添加隐藏域,将真正的请求方法作为值提交:
html1 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过滤器对象:
java1 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作为参数名:
properties1
| spring.mvc.contentnegotiation.favor-parameter=true
|
支持通过配置文件指定参数名:
properties1
| spring.mvc.contentnegotiation.parameter-name=content
|
7 文件上传
配置文件:
properties1 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组件:
java1 2 3 4 5 6 7
| @SpringBootApplication @ServletComponentScan public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
|
自定义Servlet组件:
java1 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组件:
java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @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组件:
java1 2 3 4 5 6 7
| @SpringBootApplication @ServletComponentScan public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
|
自定义Filter组件:
java1 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组件:
java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @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组件:
java1 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组件:
java1 2 3 4 5 6 7
| @SpringBootApplication @ServletComponentScan public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
|
自定义Listener组件:
java1 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组件:
java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @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组件:
java1 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配置类,判断条件:
java1 2 3
| @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
|
9.2.2 注册过滤器
在WebMvcAutoConfiguration配置类中会注册两个过滤器:
java1 2 3 4 5 6 7 8 9 10
| @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled") public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter();
@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内部类:
java1 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()方法定义资源处理路径:
java1 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; } 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()方法中设置缓存策略:
java1 2 3 4 5 6
| registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
|
9.2.4 内置配置类
java1 2 3
| @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WebProperties.class) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {}
|
在配置类中设置了欢迎页面的处理逻辑。
条