27.1 The Spring Web MVC Framework

Spring Web MVC框架(通常被称为“Spring MVC”)是一个丰富的“模型-视图-控制器”的Web框架. Spring MVC允许您创建特殊@Controller或@RestController控制器实例来处理传入的HTTP请求.控制器中的方法通过@RequestMapping注解映射到HTTP.

下面的代码显示了一个典型的@RestController控制器用于响应输出JSON数据:

  1. @RestController
  2. @RequestMapping(value="/users")
  3. public class MyRestController {
  4. @RequestMapping(value="/{user}", method=RequestMethod.GET)
  5. public User getUser(@PathVariable Long user) {
  6. // ...
  7. }
  8. @RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
  9. List<Customer> getUserCustomers(@PathVariable Long user) {
  10. // ...
  11. }
  12. @RequestMapping(value="/{user}", method=RequestMethod.DELETE)
  13. public User deleteUser(@PathVariable Long user) {
  14. // ...
  15. }
  16. }

Spring MVC是核心Spring框架的一部分,在具体的信息可在参考文档中查询.在spring.io/guide中也有一些介绍Spring MVC的指南.

Spring MVC Auto-configuration

Spring Boot为Spring MVC提供了自动配置,这对大多数应用程序都很有效. 自动配置在Spring默认设置的基础上增加了以下特性:

  • 包括ContentNegotiatingViewResolverBeanNameViewResolver实例.
  • 处理静态资源支持,包括对WebJars的支持.
  • 自动注册Converter,GenericConverterFormatter实例.
  • 支持HttpMessageConverters.
  • 自动注册MessageCodesResolver.
  • 静态index.html支持.
  • 自定义Favicon支持.
  • 自动使用ConfigurableWebBindingInitializer实例.

如果你想保持Spring Boot MVC功能且又想添加额外的MVC配置(拦截器,格式器,视图控制器和其他功能),你可以添加添加你自己的WebMvcConfigurer @Configuration配置类,无需使用@EnableWebMvc注解. 如果您希望提供自定义的RequestMappingHandlerMapping,RequestMappingHandlerAdapter或者ExceptionHandlerExceptionResolver实例,你可以声明一个WebMvcRegistrationsAdapter实例来提供这样的组件.

如果你想要完全控制Spring MVC,您可以添加您自己的@Configuration配置类并标注@EnableWebMvc注解.

HttpMessageConverters

Spring MVC使用HttpMessageConverter接口来转换HTTP请求和响应.合理的默认值都包含开箱即用的.例如,对象可以自动转换成JSON(使用Jackson库)或XML(使用Jackson XML扩展,如果可用,或者通过使用JAXB如果Jackson XML扩展不可用). 默认情况下,字符串使用UTF-8编码.

如果您需要添加或自定义转换器,您可以使用Spring Boot的HttpMessageConverters类,如以下清单所示:

  1. import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
  2. import org.springframework.context.annotation.*;
  3. import org.springframework.http.converter.*;
  4. @Configuration
  5. public class MyConfiguration {
  6. @Bean
  7. public HttpMessageConverters customConverters() {
  8. HttpMessageConverter<?> additional = ...;
  9.      HttpMessageConverter<?> another = ...;
  10. return new HttpMessageConverters(additional, another);
  11. }
  12. }

上下文中出现的任何HttpMessageConverter实例都会被添加到转换器列表中.你也可以以同样的方式覆盖默认的转换器.

Custom JSON Serializers and Deserializers

如果你使用进行JSON数据的序列化和反序列化,您可能要编写自己的JsonSerializerJsonDeserializer类. 自定义序列化器通常是通过模块向Jackson注册,但Spring Boot提供了另一种@JsonComponent注解,可以让它更容易直接注册为Spring Bean实例.

您可以使用@JsonComponent注解直接标注在JsonSerializerJsonDeserializer实现上.您还可以在包含序列化器/反序列化器内部类的类上使用,如下面的例子所示:

  1. import java.io.*;
  2. import com.fasterxml.jackson.core.*;
  3. import com.fasterxml.jackson.databind.*;
  4. import org.springframework.boot.jackson.*;
  5. @JsonComponent
  6. public class Example {
  7. public static class Serializer extends JsonSerializer<SomeObject> {
  8. // ...
  9. }
  10. public static class Deserializer extends JsonDeserializer<SomeObject> {
  11. // ...
  12. }
  13. }

ApplicationContext上下文中的所有@JsonComponent实例都自动注册到Jackson中.因为@JsonComponent是基于@Component的元注解,所以常见的组件扫描规则都适用.

Spring Boot还提供JsonObjectSerializerJsonObjectDeserializer基类,作为标准Jackson序列化对象的一种有用的替换. 有关详细信息,请参阅JsonObjectSerializer和[JsonObjectDeserializer](https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/api/org/springframework/boot/jackson/JsonObjectDeserializer.html Javadoc文档.

MessageCodesResolver

Spring MVC使用MessageCodesResolver为基于绑定的错误为指定的错误消息生成错误代码的策略.如果您设置spring.mvc.messagecodes-resolver.format属性为PREFIX_ERROR_CODEPOSTFIX_ERROR_CODE,Spring Boot将为你创建一个(见DefaultMessageCodesResolver.Format中的枚举).

Static Content

默认情况下,Spring Boot处理静态资源常见路径如下:类路径下名为/static(或/public/resources/META-INF/resources)的目录或ServletContext根目录. 它使用Spring MVC的ResourceHttpRequestHandler,以便您可以通过添加自己的WebMvcConfigurer并覆盖addResourceHandlers方法来修改该行为.

在独立的web应用程序中,来自容器的缺省servlet也被启用并充当回调,如果Spring决定不处理它,则处理ServletContext根目录中的内容.大多数情况下,这不会发生(除非你修改默认的MVC配置),因为Spring总是可以通过DispatcherServlet处理请求.

默认情况下,资源映射到/上,但是您可以使用spring.mvc.static-path-pattern属性进行更改.例如,将所有资源重新定位到`/resources/`可通过如下方式实现:

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

您还可以使用spring.resources.static-locations属性自定义静态资源路径(列表目录路径替换默认值).Servlet上下文根路径“/”是自动添加的路径.

除了前面提到的“标准”静态资源路径外,还有一个特别情况由Webjars提供.在/webjars/**路径下的任何资源如果被打包成WebJars格式,那么都将以jar格式文件处理.

Tip

如果应用程序被打包为jar,请不要使用src/main/webapp目录.虽然这个目录是一种常见的标准,它只在war打包方式下工作并且如果你生成jar文件时,该部分文件将被大多数的构建工具忽略.

Spring Boot还支持Spring MVC提供的高级资源处理功能,允许使用诸如缓存破坏或者URLs中为Webjars指定版本等.

使用URLs为Webjars指定版本,你需要添加webjars-locator-core依赖.然后声明你的Webjar.以jQuery为例,添加“/webjars/jQuery/jquery.min.js”代表”/webjars/jquery/x.y.z/jquery.min.js”.其中x.y.z为Webjar版本号.

Note

如果你使用JBoss,您需要声明webjars-locator-jboss-vfs依赖而不是webjars-locator-core.否则,所有webjar都解析为404.

使用缓存破坏,以下配置将配置所有静态资源的缓存破坏解决方案,通过在URL中有效地添加一个哈希,如<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>:

  1. spring.resources.chain.strategy.content.enabled=true
  2. spring.resources.chain.strategy.content.paths=/**

Note

使用模板技术在运行时资源链接将被重写,得益于为Thymeleaf和FreeMarker自动配置的ResourceUrlEncodingFilter.你应该在使用jsp时手动声明这个过滤器. 其他模板引擎目前暂不支持自动配置,但可以使用自定义模板宏/helpers配合使用ResourceUrlProvider.

动态加载资源时,例如,JavaScript模块加载器,重命名文件不是一个好的选择.这就是为什么其他策略也得到支持并可以合并使用. “fixed”策略在URL中添加一个静态版本字符串而无需更改文件名,如以下示例所示:

  1. spring.resources.chain.strategy.content.enabled=true
  2. spring.resources.chain.strategy.content.paths=/**
  3. spring.resources.chain.strategy.fixed.enabled=true
  4. spring.resources.chain.strategy.fixed.paths=/js/lib/
  5. spring.resources.chain.strategy.fixed.version=v12

在这个配置中,“/js/lib/”下的JavaScript模块定位使用一个固定的版本控制策略(“/v12/js/lib/mymodule.js”),而其他资源仍在使用内容(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>).

参见ResourceProperties获取更多支持.

Tip

这个功能已经在博客和Spring框架的参考文档中有详细描述.

Welcome Page

Spring Boot同时支持静态和模板欢迎页面.它首先在配置的静态内容路径下查找index.html文件.如果没有找到,则查找index模板.以上两者中如果被找到,它将自动被用作应用程序的欢迎页面.

Custom Favicon

Spring Boot在配置的静态内容路径和类路径的根目录下(依次)寻找favicon.ico文件.如果存在这样的文件,它将自动用作应用程序的图标.

Path Matching and Content Negotiation

Spring MVC通过查看请求路径映射HTTP请求并匹配定义在你的应用中的具体映射(例如,@GetMapping控制器方法注解).

Spring Boot选择禁用默认后缀模式匹配,这意味着“GET /projects/spring-boot.json”将不会匹配@GetMapping(“/projects/spring-boot”)映射. 这被认为是Spring MVC应用程序的最佳实践. 这个功能在过去的HTTP客户端不发送适当的“Accept”请求头时非常有用;我们需要确保将正确的Content Type(内容类型)发送给客户端.如今,内容协商更可靠.

还有其他的方法来处理HTTP客户端不一致发送适当的“Accept”请求头.除了使用后缀匹配外,我们可以使用查询参数来确保类似”GET /projects/spring-boot?format=json“的请求将会映射到@GetMapping("/projects/spring-boot"):

  1. spring.mvc.contentnegotiation.favor-parameter=true
  2. # We can change the parameter name, which is "format" by default:
  3. # spring.mvc.contentnegotiation.parameter-name=myparam
  4. # We can also register additional file extensions/media types with:
  5. spring.mvc.contentnegotiation.media-types.markdown=text/markdown

如果你理解说明,仍然喜欢您的应用程序使用后缀模式匹配,以下配置必须配置:

  1. spring.mvc.contentnegotiation.favor-path-extension=true
  2. # You can also restrict that feature to known extensions only
  3. # spring.mvc.pathmatch.use-registered-suffix-pattern=true
  4. # We can also register additional file extensions/media types with:
  5. # spring.mvc.contentnegotiation.media-types.adoc=text/asciidoc

ConfigurableWebBindingInitializer

Spring MVC使用WebBindingInitializer来初始化特定请求的WebDataBinder.如果你创建自己的ConfigurableWebBindingInitializer @Bean实例,Spring Boot将自动配置Spring MVC使用该实例.

Template Engines

除了REST web服务之外,您还可以使用Spring MVC来服务动态HTML内容.Spring MVC支持各种模板技术,包括Thymeleaf FreeMarker和jsp. 此外,许多其他的模板引擎包括自己的Spring MVC集成方案.

Spring Boot包括支持以下模板引擎的自动配置:

当你使用一个模板引擎采用默认配置时,您的模板文件将自动从src/main/resources/templates文件夹下获取.

Tip

取决于您运行应用程序的方式,IntelliJ IDEA对类路径的排序将会不同.相比于使用Maven或Gradle或打包的jar运行程序,在IDE中运行您的应用程序的主方法来启动程序将导致不同的类路径加载顺序. 这可以导致Spring Boot找不到类路径下的模板.如果你也碰到过此问题,你可在你的IDE中把放置模块的类和资源的类路径排在首位.或者,您可以配置模板前缀来搜索每个类路径下的模板目录,如下:classpath*:/templates/.

Error Handing

默认情况下,Spring Boot提供/error错误映射,以合理的方式处理所有错误.并将注册为servlet容器的全局错误页面.为机器客户端,它产生具体错误信息,HTTP状态,异常消息的JSON响应. 对于浏览器客户端,有一个“whitelabel”的错误视图,呈现相同的HTML格式的数据(定制它,添加一个View用来解析error).完全取代默认的行为,您可以实现ErrorController并注册为bean实例或添加ErrorAttributes类型的Bean实例,使用现有的错误处理机制除了提供错误的内容.

Tip

BasicErrorController可以用作自定义错误控制器的基类.如果你想为一个新的内容类型添加一个处理程序(默认是处理text/html并为其他所有提供一个回调),这将是特别有用的. 如果打算这么做,继承BasicErrorController,添加一个带@RequestMapping注解且配置produce属性的公共方法并创建一个你自己定义的新类型的bean.

您还可以定义一个标注@ControllerAdvice注解的类, 该类可定制特定控制器返回的JSON文档和异常类型,如以下示例所示:

  1. @ControllerAdvice(basePackageClasses = AcmeController.class)
  2. public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
  3. @ExceptionHandler(YourException.class)
  4. @ResponseBody
  5. ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
  6. HttpStatus status = getStatus(request);
  7. return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
  8. }
  9. private HttpStatus getStatus(HttpServletRequest request) {
  10. Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
  11. if (statusCode == null) {
  12. return HttpStatus.INTERNAL_SERVER_ERROR;
  13. }
  14. return HttpStatus.valueOf(statusCode);
  15. }
  16. }

在前面的例子中,如果同AcmeController在同一个包中定义的一个控制器抛出YourException异常,将使用取代ErrorAttributesCustomErrorType POJO对象的JSON响应表现.

Custom Error Pages

如果你想对于给定的状态代码显示自定义HTML错误页面,您可以在/error文件夹下添加文件.错误页面可以是静态HTML(也就是说,在任何的静态资源文件夹中添加)或由模板创建.文件的名称应该是确切的状态码或系列掩码.

例如,404映射到一个静态的HTML文件,你的文件夹结构如下:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- public/
  7. +- error/
  8. | +- 404.html
  9. +- <other public assets>

使用FreeMarker模板映射所有5xx错误,您的文件夹结构如下:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- templates/
  7. +- error/
  8. | +- 5xx.ftl
  9. +- <other templates>

对于更复杂的映射,您还可以添加bean实例来实现ErrorViewResolver接口,如以下示例所示:

  1. public class MyErrorViewResolver implements ErrorViewResolver {
  2. @Override
  3. public ModelAndView resolveErrorView(HttpServletRequest request,HttpStatus status, Map<String, Object> model) {
  4. // Use the request or status to optionally return a ModelAndView
  5. return ...
  6. }
  7. }

您还可以使用常规的Spring MVC特性如@ExceptionHandler方法和@ControllerAdvice.然后ErrorController接收任何未处理的异常.

Mapping Error Pages outside of Spring MVC

对于不使用Spring MVC的应用程序,您可以使用ErrorPageRegistrar接口直接注册ErrorPages.这种抽象方式直接工作于底层嵌入式servlet容器,即使你没有一个Spring MVC DispatcherServlet.

  1. @Bean
  2. public ErrorPageRegistrar errorPageRegistrar(){
  3. return new MyErrorPageRegistrar();
  4. }
  5. // ...
  6. private static class MyErrorPageRegistrar implements ErrorPageRegistrar {
  7. @Override
  8. public void registerErrorPages(ErrorPageRegistry registry) {
  9. registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
  10. }
  11. }

Tip

如果你注册的ErrorPage路径最终由一个过滤器(常见的一些non-Spring web框架,如Jersey和Wicket)处理,那么Filter(过滤器)必须明确注册为一个ERROR处理器,如以下示例所示:

  1. @Bean
  2. public FilterRegistrationBean myFilter() {
  3. FilterRegistrationBean registration = new FilterRegistrationBean();
  4. registration.setFilter(new MyFilter());
  5. ...
  6. registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
  7. return registration;
  8. }

注意,默认的FilterRegistrationBean不包含ERROR处理器.

警告:当部署到servlet容器时,Spring Boot使用其错误页面过滤请求转发错误状态到适当的错误页面.请求只能转发到正确的错误页面如果响应尚未提交. 默认情况下,WebSphere 8.0及后续版本在成功完成servlet的服务方法后提交响应.你应该通过设置com.ibm.ws.webcontainer.invokeFlushAfterService属性为false禁用这一行为.

Spring HATEOAS

如果你利用超媒体来开发一个RESTful API,Spring Boot为Spring HATEOAS提供了自动配置功能,Spring HATEOAS能很好的与大多数应用程序配合工作. 自动配置取代了需要使用@EnableHypermediaSupport和注册大量的Bean实例来缓解构建基于超媒体的应用,包括LinkDiscoverers(客户端支持)和正确配置响应为所需的表现形式的ObjectMapper. ObjectMapper通过spring.jackson.*属性定制,或者如果存在,通过Jackson2ObjectMapperBuilder实例定制.

你可以使用@EnableHypermediaSupport来控制Spring HATEOAS的配置.请注意,这样做将禁用前面描述的ObjectMapper的定制.

CORS Support

CORS跨源资源共享(CROS)是一个被大多数浏览器实现的W3C规范, 该规范以灵活的方式允许您指定授权的跨域请求,取代不太安全及不太强大的IFRAME或JSONP方法.

Spring MVC从版本4.2开始支持CORS. 使用控制器方法CORS配置并在你的Spring Boot应用中标注@CrossOrigin注解并不需要任何特定的配置. 全局CORS配置可通过注册WebMvcConfigurer实例并定制addCorsMappings(CorsRegistry)方法的方式来实现,如图所示:

  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean
  4. public WebMvcConfigurer corsConfigurer() {
  5. return new WebMvcConfigurer() {
  6. @Override
  7. public void addCorsMappings(CorsRegistry registry) {
  8. registry.addMapping("/api/**");
  9. }
  10. };
  11. }
  12. }