SpringBoot接管SpringMvc
Spring Web MVC framework(通常简称为“Spring MVC”)是一个丰富的“model 视图控制器”web framework。 Spring MVC 允许您创建特殊的@Controller
或@RestController
beans 来处理传入的 HTTP 请求。控制器中的方法使用@RequestMapping
annotations 映射到 HTTP。
以下 code 显示了为 JSON 数据提供服务的典型@RestController
:
@RestController
@RequestMapping(value="/users")
public class MyRestController {
@RequestMapping(value="/{user}", method=RequestMethod.GET)
public User getUser(@PathVariable Long user) {
// ...
}
@RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
List<Customer> getUserCustomers(@PathVariable Long user) {
// ...
}
@RequestMapping(value="/{user}", method=RequestMethod.DELETE)
public User deleteUser(@PathVariable Long user) {
// ...
}
}
Spring MVC 是核心 Spring Framework 的一部分,详细信息可在reference 文档中找到。还有几个 guides 覆盖了spring.io/guides的 Spring MVC。
Spring MVC Auto-configuration
Spring Boot 为 Spring MVC 提供 auto-configuration,适用于大多数 applications。
auto-configuration 在 Spring 的默认值之上添加以下 features:
包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
beans。支持提供静态资源,包括对 WebJars 的支持(稍后在本文档中 ))。
自动注册
Converter
,GenericConverter
和Formatter
beans。支持HttpMessageConverters(稍后在本文档中)。
自动注册MessageCodesResolver(稍后在本文档中).
静态
index.html
支持。自定义
Favicon
支持(稍后在本文档中)。自动使用
ConfigurableWebBindingInitializer
bean(稍后在本文档中)。
如果你想保留 Spring Boot MVC features 并且想要添加额外的MVC configuration(拦截器,格式化程序,视图控制器和其他 features),你可以添加自己的@Configuration
class 类型为WebMvcConfigurer
但而不是 @EnableWebMvc
。如果您希望提供RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
或ExceptionHandlerExceptionResolver
的自定义实例,则可以声明WebMvcRegistrationsAdapter
实例以提供此类组件。
如果要完全控制 Spring MVC,可以添加自己的@Configuration
注释@EnableWebMvc
。
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了
我们需要在配置类中添加@EnableWebMvc即可;
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/topcheer").setViewName("success");
}
}
原理:
为什么@EnableWebMvc自动配置就失效了;
1)@EnableWebMvc的核心
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
2)、
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
3)、
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
//容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
5)、导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;
HttpMessageConverters
Spring MVC 使用HttpMessageConverter
接口转换 HTTP 请求和响应。明智的默认设置包含在开箱即用中。例如,objects 可以自动转换为 JSON(通过使用 Jackson library)或 XML(如果可用,则使用 Jackson XML 扩展,或者如果 Jackson XML 扩展不可用,则使用 JAXB)。默认情况下,strings 在UTF-8
中编码。
如果需要添加或自定义转换器,可以使用 Spring Boot 的HttpMessageConverters
class,如下面的清单所示:
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;
@Configuration
public class MyConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = ...
HttpMessageConverter<?> another = ...
return new HttpMessageConverters(additional, another);
}
}
context 中存在的任何HttpMessageConverter
bean 都会添加到转换器列表中。您也可以以相同的方式覆盖默认转换器。
自定义 JSON 序列化程序和反序列化程序
如果使用 Jackson 序列化和反序列化 JSON 数据,则可能需要编写自己的JsonSerializer
和JsonDeserializer
classes。自定义序列化程序通常是通过模块在 Jackson 注册,但 Spring Boot 提供了另一种@JsonComponent
注释,可以更容易地直接注册 Spring Beans。
您可以直接在JsonSerializer
或JsonDeserializer
__mplement 上使用@JsonComponent
annotation。您也可以在包含 serializers/deserializers 作为内部 classes 的 classes 上使用它,如下面的示例所示:
import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.jackson.*;
@JsonComponent
public class Example {
public static class Serializer extends JsonSerializer<SomeObject> {
// ...
}
public static class Deserializer extends JsonDeserializer<SomeObject> {
// ...
}
}
ApplicationContext
中的所有@JsonComponent
beans 都会自动在 Jackson 中注册。因为@JsonComponent
是 meta-annotated 和@Component
,所以适用通常的 component-scanning 规则。
也可以用FastJson进行序列化和反序列化
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
/*
先把JackSon的消息转换器删除.
备注: (1)源码分析可知,返回json的过程为:
Controller调用结束后返回一个数据对象,for循环遍历conventers,找到支持application/json的HttpMessageConverter,然后将返回的数据序列化成json。
具体参考org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor的writeWithMessageConverters方法
(2)由于是list结构,我们添加的fastjson在最后。因此必须要将jackson的转换器删除,不然会先匹配上jackson,导致没使用fastjson
*/
for (int i = converters.size() - 1; i >= 0; i--) {
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
converters.remove(i);
}
}
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
//自定义fastjson配置
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(
SerializerFeature.WriteMapNullValue, // 是否输出值为null的字段,默认为false,我们将它打开
SerializerFeature.WriteNullListAsEmpty, // 将Collection类型字段的字段空值输出为[]
SerializerFeature.WriteNullStringAsEmpty, // 将字符串类型字段的空值输出为空字符串
SerializerFeature.WriteNullNumberAsZero, // 将数值类型字段的空值输出为0
SerializerFeature.WriteDateUseDateFormat,
SerializerFeature.DisableCircularReferenceDetect // 禁用循环引用
);
fastJsonHttpMessageConverter.setFastJsonConfig(config);
// 添加支持的MediaTypes;不添加时默认为*/*,也就是默认支持全部
// 但是MappingJackson2HttpMessageConverter里面支持的MediaTypes为application/json
List<MediaType> fastMediaTypes = new ArrayList<>();
MediaType mediaType = MediaType.parseMediaType("text/html;charset=UTF-8");
fastMediaTypes.add(mediaType);
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
// fastJsonHttpMessageConverter.setDefaultCharset(Charset.forName("UTF-8"));
converters.add(fastJsonHttpMessageConverter);
}
Spring Boot 还提供了JsonObjectSerializer和JsonObjectDeserializer base classes,它们在序列化 objects 时为标准 Jackson 版本提供了有用的替代方法。有关详细信息,请参阅 Javadoc 中的JsonObjectSerializer和JsonObjectDeserializer。
MessageCodesResolver
Spring MVC 有一个生成错误代码的策略,用于从 binding 错误中呈现错误消息:MessageCodesResolver
。如果设置spring.mvc.message-codes-resolver.format
property PREFIX_ERROR_CODE
或POSTFIX_ERROR_CODE
,Spring Boot 会为您创建一个(请参阅DefaultMessageCodesResolver.Format中的枚举)。
静态内容
默认情况下,Spring Boot 从 classpath 中的/static
(或/public
或/resources
或/META-INF/resources
)目录或ServletContext
的根目录中提供静态内容。它使用来自 Spring MVC 的ResourceHttpRequestHandler
,以便您可以通过添加自己的WebMvcConfigurer
并覆盖addResourceHandlers
方法来修改该行为。
在 stand-alone web application 中,容器中的默认 servlet 也被启用并充当回退,如果 Spring 决定不处理它,则从ServletContext
的根目录提供内容。大多数 time,这都不会发生(除非你修改默认的 MVC configuration),因为 Spring 总是可以通过DispatcherServlet
来处理请求。
默认情况下,资源映射到/**
,但您可以使用spring.mvc.static-path-pattern
property 对其进行调整。例如,将所有资源重新定位到/resources/**
可以实现如下:
spring.mvc.static-path-pattern=/resources/**
您还可以使用spring.resources.static-locations
property 自定义静态资源位置(将默认值替换为目录位置列表)。根 Servlet context 路径"/"
也会自动添加为位置。
除了前面提到的“标准”静态资源位置之外,还为Webjars 内容做了一个特例。如果 jar files 包含在 Webjars 格式中,则中包含路径的所有资源都将从 jar files 提供。
Spring Boot 还支持 Spring MVC 提供的高级资源处理 features,允许使用 cache-busting 静态资源等用例或使用 version 不可知 URL 进行 Webjars。
要为 Webjars 使用 version 不可知 URL,请添加webjars-locator-core
依赖项。然后声明你的 Webjar。使用 jQuery 作为 example,添加"/webjars/jquery/jquery.min.js"
会导致"/webjars/jquery/x.y.z/jquery.min.js"
。其中x.y.z
是 Webjar version。
要使用缓存清除,以下 configuration 会为所有静态资源配置缓存清除解决方案,从而在 URL 中有效添加内容哈希,例如``:
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
当使用例如 JavaScript 模块加载器动态加载资源时,不能重命名 files。这就是为什么其他策略也得到支持并可以合并的原因。 “固定”策略在 URL 中添加静态 version string 而不更改文件 name,如下面的示例所示:
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/lib/
spring.resources.chain.strategy.fixed.version=v12
使用此 configuration,位于"/js/lib/"
下的 JavaScript 模块使用固定版本控制策略("/v12/js/lib/mymodule.js"
),而其他资源仍使用内容 1(``)。
有关更多支持的选项,请参阅资源属性。
欢迎页面
Spring Boot 支持静态和模板化欢迎页面。它首先在配置的静态内容位置中查找index.html
文件。如果找不到,则查找index
模板。如果找到任何一个,它将自动用作 application 的欢迎页面。
Custom Favicon
Spring Boot 在配置的静态内容位置和 classpath 的根(在该 order 中)中查找favicon.ico
。如果存在这样的文件,它将自动用作 application 的 favicon。
路径匹配和内容谈判
Spring MVC 可以通过查看请求路径并将其与 application 中定义的映射相匹配来将传入的 HTTP 请求映射到处理程序(对于 example,注解在 Controller 方法上)。
Spring Boot 选择默认禁用后缀 pattern 匹配,这意味着像"GET /projects/spring-boot.json"
这样的请求将不会与@GetMapping("/projects/spring-boot")
映射匹配。这被认为是Spring MVC applications 的最佳实践。这个 feature 在过去主要用于 HTTP clients,它没有发送适当的“Accept”请求 headers;我们需要确保将正确的 Content Type 发送到 client。如今,Content Negotiation 更加可靠。
还有其他方法可以处理不一致发送正确的“接受”请求 headers 的 HTTP 客户端。我们可以使用查询参数来确保像"GET /projects/spring-boot?format=json"
这样的请求将映射到@GetMapping("/projects/spring-boot")
,而不是使用后缀匹配:
spring.mvc.contentnegotiation.favor-parameter=true
# We can change the parameter name, which is "format" by default:
# spring.mvc.contentnegotiation.parameter-name=myparam
# We can also register additional file extensions/media types with:
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
如果您了解警告并仍希望您的 application 使用后缀 pattern 匹配,则需要以下 configuration:
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-suffix-pattern=true
或者,不是打开所有后缀模式,而是仅支持已注册的后缀模式更安全:
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-registered-suffix-pattern=true
# You can also register additional file extensions/media types with:
# spring.mvc.contentnegotiation.media-types.adoc=text/asciidoc
ConfigurableWebBindingInitializer
Spring MVC 使用WebBindingInitializer
为特定请求初始化WebDataBinder
。如果您创建自己的ConfigurableWebBindingInitializer
@Bean
,Spring Boot 会自动配置 Spring MVC 以使用它。
模板引擎
与 REST web services 一样,您也可以使用 Spring MVC 来提供动态 HTML 内容。 Spring MVC 支持各种模板技术,包括 Thymeleaf,FreeMarker 和 JSP。此外,许多其他模板引擎包括他们自己的 Spring MVC 集成。
Spring Boot 包括对以下模板引擎的 auto-configuration 支持:
当您使用其中一个模板引擎和默认的 configuration 时,您的模板将从src/main/resources/templates
自动获取。
错误处理
默认情况下,Spring Boot 提供/error
映射,以合理的方式处理所有错误,并在 servlet 容器中注册为“global”错误页面。对于机器客户端,它会生成一个 JSON 响应,其中包含错误,HTTP 状态和 exception 消息的详细信息。对于浏览器客户端,有一个“whitelabel”错误视图,以 HTML 格式呈现相同的数据(要自定义它,添加一个解析为error
的View
)。要完全替换默认行为,可以实现ErrorController
并注册该类型的 bean 定义,或者添加ErrorAttributes
类型的 bean 以使用现有机制但替换内容。
您还可以定义一个使用@ControllerAdvice
注释的 class,以便为特定控制器 and/or exception 类型自定义 JSON 文档 return,如下面的示例所示:
@ControllerAdvice(basePackageClasses = AcmeController.class)
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(YourException.class)
@ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
在前面的示例中,如果由与AcmeController
在同一个包中定义的控制器抛出,则使用CustomErrorType
POJO 的 JSON 表示而不是ErrorAttributes
表示。
自定义错误页面
如果要为给定状态 code 显示自定义 HTML 错误页面,可以将文件添加到/error
文件夹。错误页面可以是静态 HTML(即,添加到任何静态资源文件夹下),也可以使用模板构建。文件的 name 应该是确切的状态 code 或系列掩码。
例如,要 map 404
到静态 HTML 文件,您的文件夹结构如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
要使用 FreeMarker 模板 map 所有5xx
错误,您的文件夹结构如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftl
+- <other templates>
对于更复杂的映射,您还可以添加实现ErrorViewResolver
接口的 beans,如下面的示例所示:
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
return ...
}
}
您还可以使用常规的 Spring MVC features,例如@ExceptionHandler 方法和@ControllerAdvice。 ErrorController
然后选择任何未处理的 exceptions。
Spring MVC 之外的映射错误页面
对于不使用 Spring MVC 的 applications,可以使用ErrorPageRegistrar
接口直接注册ErrorPages
。这种抽象直接与底层嵌入式 servlet 容器一起工作,即使你没有 Spring MVC DispatcherServlet
也能正常工作。
@Bean
public ErrorPageRegistrar errorPageRegistrar(){
return new MyErrorPageRegistrar();
}
// ...
private static class MyErrorPageRegistrar implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}
@Bean
public FilterRegistrationBean myFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new MyFilter());
...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
请注意,默认FilterRegistrationBean
不包括ERROR
调度程序类型。
CAUTION:When 部署到 servlet 容器,Spring Boot 使用其错误页面过滤器将具有错误状态的请求转发到相应的错误页面。如果尚未提交响应,则只能将请求转发到正确的错误页面。缺省情况下,WebSphere Application Server 8.0 及更高版本在成功完成 servlet 的服务方法后提交响应。您应该通过将com.ibm.ws.webcontainer.invokeFlushAfterService
设置为false
来禁用此行为。
Spring HATEOAS
如果您开发使用超媒体的 RESTful API,Spring Boot 为 Spring HATEOAS 提供 auto-configuration,适用于大多数 applications。 auto-configuration 取代了使用@EnableHypermediaSupport
的需要,并注册了许多 beans 以简化 building hypermedia-based applications,包括LinkDiscoverers
(用于 client 侧支持)和ObjectMapper
配置为正确地将响应编组到所需的表示中。通过设置各种spring.jackson.*
properties 或(如果存在)Jackson2ObjectMapperBuilder
bean 来自定义ObjectMapper
。
您可以使用@EnableHypermediaSupport
控制 Spring HATEOAS 的 configuration。请注意,这样做会禁用前面描述的ObjectMapper
自定义。
CORS 支持
Cross-origin 资源共享(CORS)是由大多数浏览器实现的W3C 规范,它允许您以灵活的方式指定哪种 cross-domain 请求被授权,而不是使用一些安全性较低且功能较弱的方法,如 IFRAME 或 JSONP。
截至 version 4.2,Spring MVC 支持 CORS。在 Spring Boot application 中使用控制器方法 CORS configuration和@CrossOrigin annotations 不需要任何特定的 configuration。可以通过使用自定义的addCorsMappings(CorsRegistry)
方法注册WebMvcConfigurer
bean 来定义Global CORS configuration,如下面的示例所示:
@Configuration
public class MyConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}