SpringBoot接管SpringMvc

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:

  • 包含ContentNegotiatingViewResolverBeanNameViewResolver beans。

  • 支持提供静态资源,包括对 WebJars 的支持(稍后在本文档中 ))。

  • 自动注册ConverterGenericConverterFormatter beans。

  • 支持HttpMessageConverters(稍后在本文档中)。

  • 自动注册MessageCodesResolver(稍后在本文档中).

  • 静态index.html支持。

  • 自定义Favicon支持(稍后在本文档中)。

  • 自动使用ConfigurableWebBindingInitializer bean(稍后在本文档中)。

如果你想保留 Spring Boot MVC features 并且想要添加额外的MVC configuration(拦截器,格式化程序,视图控制器和其他 features),你可以添加自己的@Configuration class 类型为WebMvcConfigurer而不是 @EnableWebMvc。如果您希望提供RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver的自定义实例,则可以声明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 数据,则可能需要编写自己的JsonSerializerJsonDeserializer classes。自定义序列化程序通常是通过模块在 Jackson 注册,但 Spring Boot 提供了另一种@JsonComponent 注释,可以更容易地直接注册 Spring Beans。

您可以直接在JsonSerializerJsonDeserializer __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 还提供了JsonObjectSerializerJsonObjectDeserializer base classes,它们在序列化 objects 时为标准 Jackson 版本提供了有用的替代方法。有关详细信息,请参阅 Javadoc 中的JsonObjectSerializerJsonObjectDeserializer

MessageCodesResolver

Spring MVC 有一个生成错误代码的策略,用于从 binding 错误中呈现错误消息:MessageCodesResolver。如果设置spring.mvc.message-codes-resolver.format property PREFIX_ERROR_CODEPOSTFIX_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 提供。

如果 application 打包为 jar,请不要使用src/main/webapp目录。虽然这个目录是一个 common 标准,但它只能用 war 打包,如果生成一个 jar,它会被大多数 build 工具默默忽略。

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。

如果使用 JBoss,则需要声明webjars-locator-jboss-vfs依赖项而不是webjars-locator-core。否则,所有 Webjars 都将解析为404

要使用缓存清除,以下 configuration 会为所有静态资源配置缓存清除解决方案,从而在 URL 中有效添加内容哈希,例如``:

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

由于ResourceUrlEncodingFilter对于 Thymeleaf 和 FreeMarker 来说是 auto-configured,因此在运行时会在模板中重写资源链接。您应该在使用 JSP 时手动声明此过滤器。目前不支持其他模板引擎,但可以使用自定义模板 macros/helpers 并使用ResourceUrlProvider

当使用例如 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(``)。

有关更多支持的选项,请参阅资源属性

这个 feature 已在专用的博客文章和 Spring Framework 的reference 文档中进行了详细描述。

欢迎页面

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 支持:

如果可能,应该避免使用 JSP。使用嵌入式 servlet 容器时有几个已知限制

当您使用其中一个模板引擎和默认的 configuration 时,您的模板将从src/main/resources/templates自动获取。

根据您运行 application 的方式,IntelliJ IDEA 以不同方式命令 classpath。 从主方法中运行 IDE 中的 application 导致与使用 Maven 或 Gradle 或从其打包的 jar 运行 application 时不同的 ordering。这可能导致 Spring Boot 无法在 classpath 上找到模板。如果遇到此问题,可以重新排序 IDE 中的 classpath 以首先放置模块的 classes 和 resources。或者,您可以配置模板前缀以搜索 classpath 上的每个templates目录,如下所示:classpath*:/templates/

错误处理

默认情况下,Spring Boot 提供/error映射,以合理的方式处理所有错误,并在 servlet 容器中注册为“global”错误页面。对于机器客户端,它会生成一个 JSON 响应,其中包含错误,HTTP 状态和 exception 消息的详细信息。对于浏览器客户端,有一个“whitelabel”错误视图,以 HTML 格式呈现相同的数据(要自定义它,添加一个解析为errorView)。要完全替换默认行为,可以实现ErrorController并注册该类型的 bean 定义,或者添加ErrorAttributes类型的 bean 以使用现有机制但替换内容。

BasicErrorController可以用作自定义ErrorController的 base class。如果要为新的 content type 添加处理程序(默认情况下是专门处理text/html并为其他所有内容提供后备),这将特别有用。为此,请扩展BasicErrorController,添加具有produces属性的@RequestMapping的公共方法,并创建新类型的 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 方法@ControllerAdviceErrorController然后选择任何未处理的 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"));
    }
​
}

 

如果你注册一个ErrorPage的路径由Filter处理(如 common 与bb 框架,如 Jersey 和 Wicket),那么Filter必须显式注册为ERROR调度程序,如下所示例:

@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/**");
            }
        };
    }
}

 

posted @ 2019-11-02 17:41  天宇轩-王  阅读(661)  评论(0编辑  收藏  举报