Part VI. WEB-22. Web MVC框架-22.16 配置Spring MVC

时间:2021-01-23 20:30:11

第22.2.1节“WebApplicationContext中的特殊Bean类型”和第22.2.2节“默认DispatcherServlet配置”介绍了Spring MVC的特殊bean以及DispatcherServlet使用的默认实现。在本节中,您将了解到配置Spring MVC的另外两种方法。即MVC Java配置和MVC XML命名空间。

MVC Java配置和MVC命名空间提供类似的默认配置,可以覆盖DispatcherServlet默认值。目标是使大多数应用程序不必创建相同的配置,并提供更高级别的构造,用于配置作为简单起始点的Spring MVC,并且几乎不需要基础配置的先前知识。

您可以根据自己的喜好选择MVC Java配置或MVC命名空间。另外,您将在下面进一步看到,使用MVC Java配置,可以更容易地查看底层配置,以及直接对创建的Spring MVC bean进行细粒度的定制。但是让我们从一开始就开始。

22.16.1启用MVC Java Config或MVC XML命名空间

要启用MVC Java配置,将@EnableWebMvc注释添加到您的一个@Configuration类中

@Configuration
@EnableWebMvc
public class WebConfig {

}

要在XML中实现相同的操作,请使用DispatcherServlet上下文中的mvc:annotation-driven元素(如果您没有定义DispatcherServlet上下文,则在根上下文中):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd"
>


<mvc:annotation-driven/>

</beans>

以上注册了一个RequestMappingHandlerMapping,一个RequestMappingHandlerAdapter和一个ExceptionHandlerExceptionResolver(以及其他),以支持使用注释(如@RequestMapping@ExceptionHandler等)的注释控制器方法处理请求。

它还支持以下功能:

  1. 除了用于数据绑定的JavaBeans PropertyEditor之外,还可以通过ConversionService实例进行Spring 3风格类型转换。

  2. 支持格式化通过ConversionService使用@NumberFormat注释的Number字段。

  3. 支持使用@DateTimeFormat注释格式化日期,日历,长和和达时间字段。

  4. 如果类路径中存在JSR-303提供程序,则支持使用@Valid验证@Controller输入。

  5. HttpMessageConverter支持@RequestBody方法参数和@ResponseBody方法从@RequestMapping@ExceptionHandler方法返回值。

这是由mvc设计的HttpMessageConverters的完整列表:annotation-driven

  • ByteArrayHttpMessageConverter转换字节数组。

  • StringHttpMessageConverter转换字符串。

  • ResourceHttpMessageConverter转换为/从org.springframework.core.io.Resource为所有媒体类型。

  • SourceHttpMessageConverter转换为/从javax.xml.transform.Source转换。

  • FormHttpMessageConverter将表单数据转换为MultiValueMap <String,String>

  • Jaxb2RootElementHttpMessageConverter将Java对象转换为/从XML中添加 - 如果存在JAXB2,并且类别路径中不存在Jackson 2 XML扩展名。

  • MappingJackson2HttpMessageConverter转换为/从JSON添加,如果Jackson 2存在于类路径中。

  • MappingJackson2XmlHttpMessageConverter转换为/从XML中添加,如果Jackson 2 XML扩展存在于类路径中。

  • AtomFeedHttpMessageConverter转换Atom feed - 如果罗马存在于类路径中,则会被添加。

  • RssChannelHttpMessageConverter转换RSS源 - 如果罗马存在于类路径中则添加。

有关如何自定义这些默认转换器的更多信息,请参见第22.16.12节“消息转换器”。

Jackson JSON和XML转换器使用由Jackson2ObjectMapperBuilder创建的ObjectMapper实例创建,以提供更好的默认配置。 
此构建器使用以下命令自定义Jackson的默认属性:

  1. DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES被禁用。
  2. MapperFeature.DEFAULT_VIEW_INCLUSION被禁用。 
    如果在类路径中检测到它们,它还会自动注册以下众所周知的模块:
  3. jackson-datatype-jdk7:支持Java 7类型,如java.nio.file.Path。
  4. jackson-datatype-joda:支持Joda-Time类型。
  5. jackson-datatype-jsr310:支持Java 8 Date&Time API类型。
  6. jackson-datatype-jdk8:支持其他Java 8类型,如可选。

22.16.2定制提供的配置

要自定义Java中的默认配置,您只需实现WebMvcConfigurer接口,或者更有可能扩展WebMvcConfigurerAdapter类并覆盖所需的方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

// Override configuration methods...

}

要自定义<mvc:annotation-driven />的默认配置,请检查它支持哪些属性和子元素。 您可以查看Spring MVC XML模式或使用IDE的代码完成功能来发现哪些属性和子元素可用。

22.16.3转换和格式化

默认情况下,安装了Number和Date类型的格式化程序,包括对@NumberFormat@DateTimeFormat注释的支持。 如果Joda Time存在于类路径中,也会安装Joda Time格式化库的完整支持。 要注册自定义格式化程序和转换器,请覆盖addFormatters方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addFormatters(FormatterRegistry registry) {
// Add formatters and/or converters
}

}

在MVC命名空间中,当添加<mvc:annotation-driven>时,默认值也适用。 要注册自定义格式化程序和转换器,只需提供一个ConversionService

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd"
>


<mvc:annotation-driven conversion-service="conversionService"/>

<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">

<property name="converters">
<set>
<bean class="org.example.MyConverter"/>
</set>
</property>
<property name="formatters">
<set>
<bean class="org.example.MyFormatter"/>
<bean class="org.example.MyAnnotationFormatterFactory"/>
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.example.MyFormatterRegistrar"/>
</set>
</property>
</bean>

</beans>

有关何时使用FormatterRegistrars的更多信息,请参见第9.6.4节“FormatterRegistrar SPI”和FormattingConversionServiceFactoryBean。

22.16.4 Validation

Spring提供了一个Validator接口,可用于在应用程序的所有层中进行验证。 在Spring MVC中,您可以将其配置为用作全局Validator实例,以在遇到@Valid@Validated控制器方法参数时使用,和/或通过@InitBinder方法作为控制器中的本地验证器。 全局和本地验证器实例可以组合以提供复合验证。

Spring还通过LocalValidatorFactoryBean支持JSR-303 / JSR-349 Bean验证,它将Springorg.springframework.validation.Validator接口适配为Bean验证javax.validation.Validator合同。 这个类可以插入Spring MVC作为下面描述的全局验证器。

默认情况下,在类路径中检测到一个Bean验证提供程序(如Hibernate Validator)时,@EnableWebMvc<mvc:annotation-driven>会在Spring MVC中通过LocalValidatorFactoryBean自动注册Bean验证支持。

或者,您可以配置自己的全局Validator实例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public Validator getValidator(); {
// return "global" validator
}

}

和XML:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd"
>


<mvc:annotation-driven validator="globalValidator"/>

</beans>

要将全局和本地验证结合起来,只需添加一个或多个本地验证器:

@Controller
public class MyController {

@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}

}

使用这个最小配置,任何时候遇到@Valid@Validated方法参数,它将被配置的验证器验证。 任何验证违规将自动暴露为BindingResult中的错误,作为方法参数可访问,并且可在Spring MVC HTML视图中呈现。

22.16.5拦截器

您可以将HandlerInterceptorsWebRequestInterceptors配置为适用于所有传入请求或限制到特定URL路径模式。

在Java中注册拦截器的示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}

}

在XML中使用<mvc:interceptors>元素:

<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="org.example.SecurityInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

22.16.6 Content Negotiation

您可以配置Spring MVC如何根据请求确定所请求的介质类型。 可用的选项是检查文件扩展名的URL路径,检查“接受”标题,特定查询参数,或者在没有请求时返回默认内容类型。 默认情况下,首先检查请求URI中的路径扩展名,并检查“Accept”头。

默认情况下,MVC Java配置和MVC命名空间注册jsonxmlrssatom,如果相应的依赖关系在类路径上。 额外的路径扩展到媒体类型的映射也可以被明确地注册,并且还具有将其作为安全扩展白名单作为RFD攻击检测的作用的效果(更详细地参见“后缀模式匹配和RFD”一节)。

以下是通过MVC Java配置定制内容协商选项的示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON);
}
}

在MVC命名空间中,<mvc:annotation-driven>元素具有content-negotiation-manager属性,该属性需要ContentNegotiationManager,而ContentNegotiationManager又可以通过ContentNegotiationManagerFactoryBean创建:

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>

如果不使用MVC Java配置或MVC命名空间,则需要创建一个ContentNegotiationManager的实例,并使用它来配置RequestMappingHandlerMapping以进行请求映射,以及RequestMappingHandlerAdapterExceptionHandlerExceptionResolver进行内容协商。

请注意,ContentNegotiatingViewResolver现在也可以配置ContentNegotiationManager,因此您可以在Spring MVC中使用一个共享实例。

在更高级的情况下,配置多个ContentNegotiationManager实例可能有用,而这些实例又可能包含自定义的ContentNegotiationStrategy实现。 例如,您可以使用ContentNegotiationManager配置ExceptionHandlerExceptionResolver,它始终将请求的介质类型解析为“application / json”。 或者,如果没有请求内容类型,您可能希望插入具有某种逻辑选择默认内容类型(例如,XML或JSON)的自定义策略。

22.16.7查看控制器

这是一个快捷方式,用于定义一个ParameterizableViewController,当调用时立即转发到视图。 在静态情况下,在视图生成响应之前没有Java控制器逻辑执行时使用它。

向Java中的“home”视图转发“/”请求的示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}

}

而XML中使用的相同则使用<mvc:view-controller>元素:

<mvc:view-controller path="/" view-name="home"/>

22.16.8查看解决方案

MVC配置简化了视图解析器的注册。

以下是Java配置示例,使用FreeMarker HTML模板配置内容协商视图分辨率,并将Jackson作为默认的JSON渲染视图:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}

}

和XML一样:

<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:jsp/>
</mvc:view-resolvers>

不过请注意,FreeMarker,Velocity,Tiles,Groovy Markup和脚本模板也需要配置基础视图技术。

MVC命名空间提供专用元素。 例如与FreeMarker:

<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
<mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>

在Java配置中,只需添加相应的“Configurer”bean:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.freeMarker().cache(false);
}

@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/");
return configurer;
}

22.16.9资源服务

此选项允许遵循特定URL模式的静态资源请求由ResourceHttpRequestHandler从资源位置列表中的任何一个提供。 这提供了一种方便的方法来从除Web应用程序根以外的位置(包括类路径上的位置)提供静态资源。 缓存期属性可用于设置远期未来的过期标头(1年是优化工具的推荐,如Page Speed和YSlow),以便客户端更有效地利用它们。 处理程序也正确地评估Last-Modified头(如果存在),以便适当地返回304状态代码,避免客户端已经缓存的资源的不必要的开销。 例如,要从Web应用程序根目录中的public-resource目录中提供具有/ resources / **的URL模式的资源请求,您将使用:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
}

}

和XML一样:

<mvc:resources mapping="/resources/**" location="/public-resources/"/>

为了在未来1年的时间内为这些资源提供服务,以确保最大限度地利用浏览器缓存和减少浏览器发出的HTTP请求:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/").setCachePeriod(31556926);
}

}
<mvc:resources mapping="/resources/**" location="/public-resources/" cache-period="31556926"/>

有关详细信息,请参阅静态资源的HTTP缓存支持。

映射属性必须是可以由SimpleUrlHandlerMapping使用的Ant模式,location属性必须指定一个或多个有效的资源目录位置。 可以使用逗号分隔的值列表来指定多个资源位置。 指定的位置将按照指定的顺序检查是否存在任何给定请求的资源。 例如,要启用来自Web应用程序根目录和/etc/public-web-resources /的已知路径的资源,可以在类路径中的任何jar中使用:

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/", "classpath:/META-INF/public-web-resources/");
}

}
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/public-web-resources/"/>

当部署新版本的应用程序时可能会更改的资源,建议您将版本字符串合并到用于请求资源的映射模式中,以便您可以强制客户端请求新部署的应用程序资源版本。版本化URL的支持内置于框架中,可以通过在资源处理程序中配置资源链来实现。该链由一个ResourceResolver实例后跟一个或多个ResourceTransformer实例组成。他们一起可以提供任意解决和资源转型。

内置的VersionResourceResolver可以配置不同的策略。例如,FixedVersionStrategy可以使用属性,日期或其他作为版本。 ContentVersionStrategy使用从资源内容(称为“指纹识别”URL)计算出的MD5哈希值。请注意,在服务资源时,VersionResourceResolver会自动将解析的版本字符串用作HTTP ETag头值。

ContentVersionStrategy是一个很好的默认选择,除非不能使用(例如使用JavaScript模块加载器)。您可以根据不同的模式配置不同的版本策略,如下所示。请记住,计算基于内容的版本是昂贵的,因此在生产中应该启用资源链缓存。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public-resources/")
.resourceChain(true).addResolver(
new VersionResourceResolver().addContentVersionStrategy("/**"));
}

}
<mvc:resources mapping="/resources/**" location="/public-resources/">
<mvc:resource-chain>
<mvc:resource-cache/>
<mvc:resolvers>
<mvc:version-resolver>
<mvc:content-version-strategy patterns="/**"/>
</mvc:version-resolver>
</mvc:resolvers>
</mvc:resource-chain>
</mvc:resources>

当部署新版本的应用程序时可能会更改的资源,建议您将版本字符串合并到用于请求资源的映射模式中,以便您可以强制客户端请求新部署的应用程序资源版本。版本化URL的支持内置于框架中,可以通过在资源处理程序中配置资源链来实现。该链由一个ResourceResolver实例后跟一个或多个ResourceTransformer实例组成。他们一起可以提供任意解决和资源转型。

内置的VersionResourceResolver可以配置不同的策略。例如,FixedVersionStrategy可以使用属性,日期或其他作为版本。 ContentVersionStrategy使用从资源内容(称为“指纹识别”URL)计算出的MD5哈希值。请注意,在服务资源时,VersionResourceResolver会自动将解析的版本字符串用作HTTP ETag头值。

ContentVersionStrategy是一个很好的默认选择,除非不能使用(例如使用JavaScript模块加载器)。您可以根据不同的模式配置不同的版本策略,如下所示。请记住,计算基于内容的版本是昂贵的,因此在生产中应该启用资源链缓存。

22.16.10回退在“默认”Servlet服务资源

这允许将DispatcherServlet映射到“/”(从而覆盖容器的默认Servlet的映射),同时仍允许静态资源请求由容器的默认Servlet处理。 它配置一个DefaultServletHttpRequestHandler,其URL映射为“/ **”,相对于其他URL映射为最低优先级。

该处理程序将所有请求转发到默认Servlet。 因此,重要的是它按照所有其他URL HandlerMappings的顺序保持最后。 如果您使用<mvc:annotation-driven>或者如果您正在设置自己的Customler HandlerMapping实例,请确保将其order属性设置为低于DefaultServletHttpRequestHandler(Integer.MAX_VALUE)的值。

要使用默认设置启用该功能,请使用:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

}
<mvc:default-servlet-handler/>

覆盖“/”Servlet映射的注意事项是,默认Servlet的RequestDispatcher必须通过名称而不是路径检索。 DefaultServletHttpRequestHandler将尝试在启动时自动检测容器的默认Servlet,使用大多数主要Servlet容器(包括Tomcat,Jetty,GlassFish,JBoss,Resin,WebLogic和WebSphere)的已知名称列表。 如果默认的Servlet已经使用不同的名称自定义配置,或者如果使用了默认Servlet名称未知的其他Servlet容器,则必须显式提供缺省Servlet的名称,如以下示例所示:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable("myCustomDefaultServlet");
}

}
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>

22.16.11路径匹配

这允许自定义与URL映射和路径匹配相关的各种设置。 有关各个选项的详细信息,请查看PathMatchConfigurer API。

以下是Java配置中的一个示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper());
}

@Bean
public UrlPathHelper urlPathHelper() {
//...
}

@Bean
public PathMatcher antPathMatcher() {
//...
}

}

和XML一样,使用<mvc:path-matching>元素:

<mvc:annotation-driven>
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher"/>

</mvc:annotation-driven>

<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

22.16.12消息转换器

如果要替换由Spring MVC创建的默认转换器,或者如果您只想自定义extendMessageConverters()或将额外的转换器添加到默认转换器),可以通过覆盖configureMessageConverters()来实现HttpMessageConverter的定制。

以下是使用自定义ObjectMapper而不是默认值添加Jackson JSON和XML转换器的示例:

@Configuration
@EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(new ParameterNamesModule());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build()));
}

}

在这个例子中,Jackson2ObjectMapperBuilder用于为MappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverter创建一个通用配置,并启用缩进功能,自定义日期格式以及jackson-module-parameter-names的注册,增加了访问参数名称的支持(Java 8中添加的功能)。