<mvc:annotation-driven message-codes-resolver ="bean ref" validator="" conversion-service=""> <mvc:return-value-handlers> <bean></bean> </mvc:return-value-handlers> <mvc:argument-resolvers> </mvc:argument-resolvers> <mvc:message-converters> </mvc:message-converters>[/color] </mvc:annotation-driven>
说明:
return-value-handlers --- 请求方法的返回值(出参)处理 ----- HandlerMethodReturnValueHandler
argument-resolvers --- 请求方法的入参处理 ----- HandlerMethodArgumentResolver 或 WebArgumentResolver
message-converters --- 消息转换处理 ----- HttpMessageConverter
1、return-value-handlers 允许注册实现了HandlerMethodReturnValueHandler接口的bean,来对handler method的特定的返回类型做处理 HandlerMethodReturnValueHandler接口中定义了两个方法 supportsReturnType 方法用来确定此实现类是否支持对应返回类型。 handleReturnValue 则用来处理具体的返回类型 例如以下的handlerMethod @RequestMapping("/testReturnHandlers") public User testHandlerReturnMethod(){ User u = new User(); u.setUserName("test"); return u; } 所返回的类型为一个pojo,正常情况下spring mvc无法解析,将转由DefaultRequestToViewNameTranslator 解析出一个缺省的view name,转到 testReturnHandlers.jsp 我们增加以下配置 <mvc:annotation-driven validator="validator"> <mvc:return-value-handlers> <bean class="net.zhepu.web.handlers.returnHandler.UserHandlers"></bean> </mvc:return-value-handlers> </mvc:annotation-driven> 实现类 public class UserHandlers implements HandlerMethodReturnValueHandler { Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public boolean supportsReturnType(MethodParameter returnType) { Class<?> type = returnType.getParameterType(); if(User.class.equals(type)) { return true; } return false; } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { logger.info("handler for return type users "); mavContainer.setViewName("helloworld"); } } 此时再访问 http://localhost:8080/springmvc/testReturnHandlers ,将交由 UserHandlers来处理返回类型为User的返回值 2、argument-resolvers 允许注册实现了WebArgumentResolver接口的bean,来对handlerMethod中的用户自定义的参数或annotation进行解析 <mvc:annotation-driven validator="validator"> <mvc:argument-resolvers> <bean class="net.zhepu.web.handlers.argumentHandler.MyCustomerWebArgumentHandler" /> </mvc:argument-resolvers> </mvc:annotation-driven> java代码如下 public class MyCustomerWebArgumentHandler implements WebArgumentResolver { @Override public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception { if (methodParameter.getParameterType().equals(MyArgument.class)) { MyArgument argu = new MyArgument(); argu.setArgumentName("winzip"); argu.setArgumentValue("123456"); return argu; } return UNRESOLVED; } } 这里我们定义了一个 customer webArgumentHandler,当handler method中参数类型为 MyArgument时生成对参数的类型绑定操作 注意新注册的webArgumentHandler的优先级最低,即如果系统缺省注册的ArgumentHandler已经可以解析对应的参数类型时,就不会再调用到新注册的customer ArgumentHandler了 message-converters 允许注册实现了HttpMessageConverter接口的bean,来对requestbody 或responsebody中的数据进行解析 例如 假设我们使用text/plain格式发送一串字符串来表示User对象,各个属性值使用”|”来分隔。 例如 winzip|123456|13818888888,期望转为user对象,各属性内容为user.username = winzip,user.password=123456;user.mobileNO = 13818888888 以下代码中supports表示此httpmessageConverter实现类针对User类进行解析。 构造函数中调用super(new MediaType("text", "plain"));以表示支持 text/plain格式的输入 public class MyCustomerMessageConverter extends AbstractHttpMessageConverter<Object> { @Override protected boolean supports(Class<?> clazz) { if (clazz.equals(User.class)) { return true; } return false; } public MyCustomerMessageConverter() { super(new MediaType("text", "plain")); } @Override protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { Charset charset; MediaType contentType = inputMessage.getHeaders().getContentType(); if (contentType != null && contentType.getCharSet() != null) { charset = contentType.getCharSet(); } else { charset = Charset.forName("UTF-8"); } String input = FileCopyUtils.copyToString(new InputStreamReader( inputMessage.getBody(), charset)); logger.info(input); String[] s = input.split("\\|"); User u = new User(); u.setUserName(s[0]); u.setPassword(s[1]); u.setMobileNO(s[2]); return u; } @Override protected void writeInternal(Object t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { } } 修改servlet context xml配置文件,增加message-converters的相应配置如下 <mvc:message-converters> <bean class="net.zhepu.web.handlers.messageConverterHandler.MyCustomerMessageConverter"></bean> </mvc:message-converters> 3、message-codes-resolver 先看看spring mvc中对于messageCodeResolver的用法 spring mvc中使用DefaultMessageCodesResolver作为缺省的MessageCodesResolver的实现类, 其作用是对valid errors中的errorcode进行解析。其解析方式如下 当解析error global object注册的errorcode时,errorcode的查找顺序为 1:errorcode.validationobjectname 2:errorcode 例如以下声明中 public String helloWorld2(@ModelAttribute("user") User u,BindingResult result) 当使用 result.reject("testFlag");来注册一个globat error object时,spring mvc将在messageSource中先查找 testFlag.user这个errorcode,当找不到时再查找testFlag这个errorcode。 当解析fields error时,将按以下顺序生成error code 1.: code + "." + object name + "." + field 2.: code + "." + field 3.: code + "." + field type 4.: code 还是以上面的代码为例,当使用 result.rejectValue("userName", "testFlag");来注册一个针对user.UserName属性的错误描述时,errors对象中将生成以下的error code list, 1.: testFlag.user.userName 2.: testFlag.userName 3.: testFlag.java.lang.String 4.: testFlag 而mvc:annotation-driven新增的属性message-codes-resolver则提供了注册自定义的MessageCodesResolver的手段。 例如上面想要在所有的error code前增加前缀validation.的话,可以这么来做 <mvc:annotation-driven validator="validator" message-codes-resolver="messageCodeResolver"> </mvc:annotation-driven> 新增messageCodeResolver bean定义如下 <bean id="messageCodeResolver" class="org.springframework.validation.DefaultMessageCodesResolver"> <property name="prefix" value="validation."></property> </bean> 此时,所有的errorcode都会生成缺省前缀 validation. 例如前面的 result.reject("testFlag"); 生成的error code list就变为了 validation.testFlag.user 和 validation.testFlag了 @RequestMapping 新增参数Consumes 和Produces @RequestMapping的参数中有一个header的参数,来指定handler method能接受的http request 请求的header内容 而consumes和produces则更进一步,直接指定所能接受或产生的request请求的content type @RequestMapping(value="/testMsgConverter",consumes="text/plain",produces="application/json") 表示handlermethod接受的请求的header中的 Content-Type为text/plain; Accept为application/json URI Template 新增功能 @PathVariable 声明的参数可自动加入到model中 例如 @RequestMapping("/develop/apps/edit/{slug}") public String editForm(@PathVariable String slug, Model model) { model.addAttribute("slug", slug); // ... } 现在可以写为 @RequestMapping("/develop/apps/edit/{slug}") public String editForm(@PathVariable String slug, Model model) { // model contains "slug" variable } handler method中的redirect string可支持url template了 @RequestMapping( value="/groups/{group}/events/{year}/{month}/{slug}/rooms", method=RequestMethod.POST) public String createRoom( @PathVariable String group, @PathVariable Integer year, @PathVariable Integer month, @PathVariable String slug) { // ... return "redirect:/groups/" + group + "/events/" + year + "/" + month + "/" + slug; } 现在可写为 @RequestMapping( value="/groups/{group}/events/{year}/{month}/{slug}/rooms", method=RequestMethod.POST) public String createRoom( @PathVariable String group, @PathVariable Integer year, @PathVariable Integer month, @PathVariable String slug) { // ... return "redirect:/groups/{group}/events/{year}/{month}/{slug}"; } url template中可支持databinding 了 @RequestMapping("/people/{firstName}/{lastName}/SSN") public String find(Person person, @PathVariable String firstName, @PathVariable String lastName) { person.setFirstName(firstName); person.setLastName(lastName); // ... } 现在可以写成 @RequestMapping("/people/{firstName}/{lastName}/SSN") public String search(Person person) { // person.getFirstName() and person.getLastName() are populated // ... } Validation For @RequestBody @RequestBody现在直接支持@valid标注了,如果validation失败,将抛出RequestBodyNotValidException 具体处理逻辑可见 spring 中的RequestResponseBodyMethodProcessor中的以下代码 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType()); if (shouldValidate(parameter, arg)) { String argName = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, argName); binder.validate(); Errors errors = binder.getBindingResult(); if (errors.hasErrors()) { throw new RequestBodyNotValidException(errors); } } return arg; }
<!-- 配置路径扩展名映射的媒体类型 --> <bean name="pathExtensionContentNegotiationStrategy" class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy"> <constructor-arg> <props> <!-- if romePresent --> <prop key="atom">application/atom+xml</prop> <prop key="rss">application/rss+xml</prop> <!-- endif --> <!-- if jackson2Present || jacksonPresent --> <prop key="json">application/json</prop> <!-- endif --> <!-- if jaxb2Present --> <prop key="xml">application/xml</prop> <!-- endif --> </props> </constructor-arg> </bean> <!-- 配置映射媒体类型的策略 --> <bean name="mvcContentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManager"> <constructor-arg> <list> <ref bean="pathExtensionContentNegotiationStrategy" /> </list> </constructor-arg> </bean> <!-- 配置方法级别的@RequestMapping处理器 --> <bean name="requestMappingHandlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="order" value="0" /> <property name="contentNegotiationManager" ref="mvcContentNegotiationManager" /> </bean> <!-- 配置数据转换服务,默认使用格式化数据转换服务,可以对日期和数字进行格式化 --> <bean name="conversionService" class="org.springframework.format.support.DefaultFormattingConversionService"> <constructor-arg index="0"> <null></null> </constructor-arg> <constructor-arg index="1"> <value>true</value> </constructor-arg> </bean> <bean name="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"></bean> <!-- 配置数据绑定,通过转换服务实现绑定,如果包含jsr303实现还将进行校验 --> <bean name="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService" /> <!-- if jsr303Present --> <property name="validator" ref="validator" /> <!-- endif --> </bean> <bean name="byteArrayHttpMessageConverter" class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></bean> <bean name="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="writeAcceptCharset" value="false" /> </bean> <bean name="resourceHttpMessageConverter" class="org.springframework.http.converter.ResourceHttpMessageConverter"></bean> <bean name="sourceHttpMessageConverter" class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></bean> <bean name="allEncompassingFormHttpMessageConverter" class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"></bean> <bean name="atomFeedHttpMessageConverter" class="org.springframework.http.converter.feed.AtomFeedHttpMessageConverter"></bean> <bean name="rssChannelHttpMessageConverter" class="org.springframework.http.converter.feed.RssChannelHttpMessageConverter"></bean> <bean name="jaxb2RootElementHttpMessageConverter" class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"></bean> <bean name="mappingJackson2HttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean> <bean name="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean> <!-- 配置@RequestBody,@ResponseBody注解可用的转换器 --> <util:list id="messageConverters" list-class="org.springframework.beans.factory.support.ManagedList.ManagedList"> <ref bean="byteArrayHttpMessageConverter" /> <ref bean="stringHttpMessageConverter" /> <ref bean="resourceHttpMessageConverter" /> <ref bean="sourceHttpMessageConverter" /> <ref bean="allEncompassingFormHttpMessageConverter" /> <!-- if romePresent --> <ref bean="atomFeedHttpMessageConverter" /> <ref bean="rssChannelHttpMessageConverter" /> <!-- endif --> <!-- if jaxb2Present --> <ref bean="jaxb2RootElementHttpMessageConverter" /> <!-- endif --> <!-- if jackson2Present --> <ref bean="mappingJackson2HttpMessageConverter" /> <!-- endif --> <!-- if jacksonPresent --> <ref bean="mappingJacksonHttpMessageConverter" /> <!-- endif --> </util:list> <!-- 将任意类型的Controller适配为Handler --> <bean name="requestMappingHandlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="contentNegotiationManager" ref="mvcContentNegotiationManager" /> <property name="webBindingInitializer" ref="webBindingInitializer" /> <property name="messageConverters" ref="messageConverters" /> </bean> <!-- 这个拦截器暴露转换器服务让spring:bind和spring:eval标签可用 --> <bean name="csInterceptor" class="org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor"> <constructor-arg index="0"> <ref bean="conversionService" /> </constructor-arg> </bean> <!-- 现在所有拦截器都必须设定响应的路径映射 --> <bean name="mappedCsInterceptor" class="org.springframework.web.servlet.handler.MappedInterceptor"> <constructor-arg index="0"> <null></null> </constructor-arg> <constructor-arg index="1"> <ref bean="csInterceptor" /> </constructor-arg> </bean> <!-- 使用@ExceptionHandler注解的方法来处理Exception,优先级为0(最高) --> <bean name="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"> <property name="contentNegotiationManager" ref="mvcContentNegotiationManager" /> <property name="messageConverters" ref="messageConverters" /> <property name="order" value="0" /> </bean> <!-- 如果抛出的Exception类带有@ResponseStatus注解,响应返回该注解的Http状态码,优先级为1 --> <bean name="responseStatusExceptionResolver" class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver"> <property name="order" value="1" /> </bean> <!-- SpringMvc内部异常处理 --> <bean name="defaultExceptionResolver" class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"> <property name="order" value="2" /> </bean>
其实相信在大多数实际应用环境中使用mvc:annotation-driven是少数,因为一般都满足不了需求,但想快速搭配环境还是比较适合的.当使用java config,记得有文章介绍不推荐配置RequestMappingHandlerMapping和RequestMappingHandlerAdapter
@JsonSerialize(using = DateJsonSerializer.class)
@JsonDeserialize(using = DateJsonDeserializer.class)
private Date createTime;
spring mvc默认是支持yyyy-MM-dd格式的字符串转换为java的java.util.Date.包括spring mvc框架本身和spring mvc支持的jackson.
对于其它格式的日期的字符串与Java的Date对象相互转化,一样可分为两种情况:
A:一种普通请求,前台的日期字符串与后台的Java Date对象转化,此情况,应使用spring mvc本身的内置日期处理.
B:另一种将参数写进请求体里面,使用application/json这样的mediaType发请求,对于此情况,应使用Jackson的序列化和反序列化来处理.
一.第1种情况(不要忘了加joda-time包哦):
1.先使用@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")在Controller的方法参数或VO的属性使用.
2.如果不使用mvc:annotation-driven,那么使用数据绑定来处理@DateTimeFormat这样的注解.配置例子如下:
@JsonSerialize(using = DateJsonSerializer.class)
@JsonDeserialize(using = DateJsonDeserializer.class)
场景:方便使用spring mvc生成json,并且兼容ie,chrome....
设计:手工指定RequestMappingHandlerMapping和RequestMappingHandlerAdapter,并给RequestMappingHandlerAdapter的messageConverters的注入属性值.不使用驱动注解(<mvc:annotation-driven />)自动配置的原因是:自动配置我没找到方法来修改response的Content-Type, 而自动配置默认的content-type是application/json;charset=UTF-8.这个contentType在谷歌浏览器很正常解析,而到了IE解析为弹出下载了,IE10,IE11一样不给面子!!!如果将contentType改为:text/html;charset=UTF-8.那IE和google浏览器都能正常解析了.
不用再去写<mvc:annotation-driven />,至于<mvc:annotation-driven />在背后做了什么,可看参考手册.注意spring版本哦,这两类从3.1才开始有的.
基于xml配置:
基于Java-config方式会更加简单一点,先让你的mvc配置继承WebMvcConfigurerAdapter,再重写configureMessageConverters方法,加入jackson包,在controller使用@ResponseBody注解,OK!
如果要全局支持jsonp(支持jsonp的做法:可以在controller的方法返回String类型,接收一下callback,然后callback调用一下json结果就可以),可以再加一个StringHttpMessageConverter,不仅能解决中文乱码,还能把json里面的换行\r\n去掉.