Spring MVC 学习笔记 十五 what's new in spring mvc 3.1

1:mvc annotation-driven 新增标签
以下为spring mvc 3.1中annotation-driven所支持的全部配置。

Xml代码 复制代码  收藏代码
  1. <mvc:annotation-driven  message-codes-resolver ="bean ref" validator="" conversion-service="">  
  2.       
  3.      <mvc:return-value-handlers>  
  4.         <bean></bean>  
  5.     </mvc:return-value-handlers>  
  6.        
  7.     <mvc:argument-resolvers>  
  8.     </mvc:argument-resolvers>  
  9.        
  10.     <mvc:message-converters>  
  11.     </mvc:message-converters>[/color]   
  12. </mvc:annotation-driven>  
<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>



其中3.1新增部分如下   
return-value-handlers
允许注册实现了HandlerMethodReturnValueHandler接口的bean,来对handler method的特定的返回类型做处理。
HandlerMethodReturnValueHandler接口中定义了两个方法
supportsReturnType 方法用来确定此实现类是否支持对应返回类型。
handleReturnValue 则用来处理具体的返回类型。

例如以下的handlerMethod

Java代码 复制代码  收藏代码
  1. @RequestMapping("/testReturnHandlers")   
  2. public User testHandlerReturnMethod(){   
  3.     User u  = new User();   
  4.     u.setUserName("test");   
  5.     return u;   
  6. }  
	@RequestMapping("/testReturnHandlers")

	public User testHandlerReturnMethod(){

		User u  = new User();

		u.setUserName("test");

		return u;

	}


所返回的类型为一个pojo,正常情况下spring mvc无法解析,将转由DefaultRequestToViewNameTranslator 解析出一个缺省的view name,转到 testReturnHandlers.jsp,
我们增加以下配置

Xml代码 复制代码  收藏代码
  1. <mvc:annotation-driven validator="validator">  
  2. color=red]        <mvc:return-value-handlers>    
  3.            <bean  class="net.zhepu.web.handlers.returnHandler.UserHandlers"></bean>    
  4.     </mvc:return-value-handlers>[/color]   
  5. </mvc:annotation-driven>  
	<mvc:annotation-driven validator="validator">

[color=red]        <mvc:return-value-handlers> 

            <bean  class="net.zhepu.web.handlers.returnHandler.UserHandlers"></bean> 

		</mvc:return-value-handlers>[/color]

	</mvc:annotation-driven>


及如下实现类

Java代码 复制代码  收藏代码
  1. public class UserHandlers implements HandlerMethodReturnValueHandler {   
  2.     Logger logger = LoggerFactory.getLogger(this.getClass());   
  3.     @Override  
  4.     public boolean supportsReturnType(MethodParameter returnType) {   
  5.         Class<?> type = returnType.getParameterType();   
  6.         if(User.class.equals(type))   
  7.         {   
  8.             return true;   
  9.         }   
  10.         return false;   
  11.     }   
  12.   
  13.     @Override  
  14.     public void handleReturnValue(Object returnValue,   
  15.             MethodParameter returnType, ModelAndViewContainer mavContainer,   
  16.             NativeWebRequest webRequest) throws Exception {   
  17.         logger.info("handler  for return type users ");   
  18.         mavContainer.setViewName("helloworld");   
  19.     }   
  20.   
  21. }  
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的返回值。

argument-resolvers
允许注册实现了WebArgumentResolver接口的bean,来对handlerMethod中的用户自定义的参数或annotation进行解析
例如

Xml代码 复制代码  收藏代码
  1. <mvc:annotation-driven validator="validator">  
  2.     <mvc:argument-resolvers>  
  3.         <bean  
  4.             class="net.zhepu.web.handlers.argumentHandler.MyCustomerWebArgumentHandler" />  
  5.     </mvc:argument-resolvers>  
  6.   
  7. </mvc:annotation-driven>  
	<mvc:annotation-driven validator="validator">

		<mvc:argument-resolvers>

			<bean

				class="net.zhepu.web.handlers.argumentHandler.MyCustomerWebArgumentHandler" />

		</mvc:argument-resolvers>



	</mvc:annotation-driven>


对应java代码如下

Java代码 复制代码  收藏代码
  1. public class MyCustomerWebArgumentHandler implements WebArgumentResolver {   
  2.   
  3.     @Override  
  4.     public Object resolveArgument(MethodParameter methodParameter,   
  5.             NativeWebRequest webRequest) throws Exception {   
  6.         if (methodParameter.getParameterType().equals(MyArgument.class)) {   
  7.             MyArgument argu = new MyArgument();   
  8.             argu.setArgumentName("winzip");   
  9.             argu.setArgumentValue("123456");   
  10.             return argu;   
  11.         }   
  12.         return UNRESOLVED;   
  13.     }   
  14.   
  15. }  
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格式的输入。

Java代码 复制代码  收藏代码
  1. public class MyCustomerMessageConverter extends  
  2.         AbstractHttpMessageConverter<Object> {   
  3.     @Override  
  4.     protected boolean supports(Class<?> clazz) {   
  5.         if (clazz.equals(User.class)) {   
  6.             return true;   
  7.         }   
  8.         return false;   
  9.     }   
  10.   
  11.     public MyCustomerMessageConverter() {   
  12.         super(new MediaType("text""plain"));   
  13.     }   
  14.   
  15.     @Override  
  16.     protected Object readInternal(Class<? extends Object> clazz,   
  17.             HttpInputMessage inputMessage) throws IOException,   
  18.             HttpMessageNotReadableException {   
  19.         Charset charset;   
  20.         MediaType contentType = inputMessage.getHeaders().getContentType();   
  21.         if (contentType != null && contentType.getCharSet() != null) {   
  22.             charset = contentType.getCharSet();   
  23.         } else {   
  24.             charset = Charset.forName("UTF-8");   
  25.         }   
  26.         String input = FileCopyUtils.copyToString(new InputStreamReader(   
  27.                 inputMessage.getBody(), charset));   
  28.         logger.info(input);   
  29.         String[] s = input.split("\\|");   
  30.         User u = new User();   
  31.         u.setUserName(s[0]);   
  32.         u.setPassword(s[1]);   
  33.         u.setMobileNO(s[2]);   
  34.         return u;   
  35.     }   
  36.   
  37.     @Override  
  38.     protected void writeInternal(Object t, HttpOutputMessage outputMessage)   
  39.             throws IOException, HttpMessageNotWritableException {   
  40.   
  41.     }  
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的相应配置如下。

Xml代码 复制代码  收藏代码
  1. <mvc:message-converters>  
  2.     <bean class="net.zhepu.web.handlers.messageConverterHandler.MyCustomerMessageConverter"></bean>  
  3. </mvc:message-converters>  
        <mvc:message-converters>

            <bean class="net.zhepu.web.handlers.messageConverterHandler.MyCustomerMessageConverter"></bean>

        </mvc:message-converters>




message-codes-resolver
先看看spring mvc中对于messageCodeResolver的用法。
spring mvc中使用DefaultMessageCodesResolver作为缺省的MessageCodesResolver的实现类,其作用是对valid errors中的errorcode进行解析。其解析方式如下
当解析error global object注册的errorcode时,errorcode的查找顺序为
1:errorcode.validationobjectname
2:errorcode
例如
以下声明中

Java代码 复制代码  收藏代码
  1. public String helloWorld2(@ModelAttribute("user") User u,   
  2.             BindingResult result)  
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.的话,可以这么来做

Xml代码 复制代码  收藏代码
  1. <mvc:annotation-driven validator="validator" message-codes-resolver="messageCodeResolver">  
  2. </mvc:annotation-driven>  
<mvc:annotation-driven validator="validator" message-codes-resolver="messageCodeResolver">

</mvc:annotation-driven>


新增messageCodeResolver bean定义如下

Java代码 复制代码  收藏代码
  1. <bean id="messageCodeResolver" class="org.springframework.validation.DefaultMessageCodesResolver">   
  2.     <property name="prefix" value="validation."></property>   
  3. </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了。




2: @RequestMapping 新增参数Consumes 和Produces
前面介绍过@RequestMapping的参数中有一个header的参数,来指定handler method能接受的http request 请求的header内容。
而consumes和produces则更进一步,直接指定所能接受或产生的request请求的content type。
例如

Java代码 复制代码  收藏代码
  1. @RequestMapping(value="/testMsgConverter",consumes="text/plain",produces="application/json")  
@RequestMapping(value="/testMsgConverter",consumes="text/plain",produces="application/json")


表示handlermethod接受的请求的header中的 Content-Type为text/plain;
Accept为application/json


3: URI Template 新增功能
这部分的例子直接照抄Spring 3.1 M2: Spring MVC Enhancements中的示例

1: @PathVariable 声明的参数可自动加入到model中。
    例如

Java代码 复制代码  收藏代码
  1. @RequestMapping("/develop/apps/edit/{slug}")   
  2. public String editForm(@PathVariable String slug, Model model) {   
  3.     model.addAttribute("slug", slug);   
  4.     // ...   
  5. }  
@RequestMapping("/develop/apps/edit/{slug}")

public String editForm(@PathVariable String slug, Model model) {

	model.addAttribute("slug", slug);

    // ...

}


现在可以写为

Java代码 复制代码  收藏代码
  1. @RequestMapping("/develop/apps/edit/{slug}")   
  2. public String editForm(@PathVariable String slug, Model model) {   
  3.     // model contains "slug" variable   
  4. }  
@RequestMapping("/develop/apps/edit/{slug}")

public String editForm(@PathVariable String slug, Model model) {

    // model contains "slug" variable

}



2:handler method中的redirect string可支持url template了
例如

Java代码 复制代码  收藏代码
  1. @RequestMapping(   
  2.     value="/groups/{group}/events/{year}/{month}/{slug}/rooms",   
  3.     method=RequestMethod.POST)   
  4. public String createRoom(   
  5.     @PathVariable String group, @PathVariable Integer year,   
  6.     @PathVariable Integer month, @PathVariable String slug) {   
  7.     // ...   
  8.     return "redirect:/groups/" + group + "/events/" + year + "/" + month + "/" + slug;   
  9. }  
@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;

}


现在可写为

Java代码 复制代码  收藏代码
  1. @RequestMapping(   
  2.     value="/groups/{group}/events/{year}/{month}/{slug}/rooms",   
  3.     method=RequestMethod.POST)   
  4. public String createRoom(   
  5.     @PathVariable String group, @PathVariable Integer year,   
  6.     @PathVariable Integer month, @PathVariable String slug) {   
  7.     // ...   
  8.     return "redirect:/groups/{group}/events/{year}/{month}/{slug}";   
  9. }  
@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}";

}


3:url template中可支持databinding 了
例如

Java代码 复制代码  收藏代码
  1. @RequestMapping("/people/{firstName}/{lastName}/SSN")   
  2. public String find(Person person,   
  3.                    @PathVariable String firstName,   
  4.                    @PathVariable String lastName) {   
  5.     person.setFirstName(firstName);   
  6.     person.setLastName(lastName);   
  7.     // ...   
  8. }  
@RequestMapping("/people/{firstName}/{lastName}/SSN")

public String find(Person person,

                   @PathVariable String firstName,

                   @PathVariable String lastName) {

    person.setFirstName(firstName);

    person.setLastName(lastName);

    // ...

}



现在可以写成

Java代码 复制代码  收藏代码
  1. @RequestMapping("/people/{firstName}/{lastName}/SSN")   
  2. public String search(Person person) {   
  3.     // person.getFirstName() and person.getLastName() are populated   
  4.     // ...   
  5. }  
@RequestMapping("/people/{firstName}/{lastName}/SSN")

public String search(Person person) {

    // person.getFirstName() and person.getLastName() are populated

    // ...

}



4: Validation For @RequestBody

@RequestBody现在直接支持@valid标注了,如果validation失败,将抛出
RequestBodyNotValidException。
具体处理逻辑可见 spring 中的RequestResponseBodyMethodProcessor中的以下代码。

Java代码 复制代码  收藏代码
  1.        
  2. public Object resolveArgument(MethodParameter parameter,   
  3.                                   ModelAndViewContainer mavContainer,   
  4.                                   NativeWebRequest webRequest,   
  5.                                   WebDataBinderFactory binderFactory) throws Exception {   
  6.         Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());   
  7.         if (shouldValidate(parameter, arg)) {   
  8.             String argName = Conventions.getVariableNameForParameter(parameter);   
  9.             WebDataBinder binder = binderFactory.createBinder(webRequest, arg, argName);   
  10.             binder.validate();   
  11.             Errors errors = binder.getBindingResult();   
  12.             if (errors.hasErrors()) {   
  13.                 throw new RequestBodyNotValidException(errors);   
  14.             }   
  15.         }   
  16.         return arg;   
  17.     }  
	

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;

	}



5:annotation-driven缺省注册类的改变

Spring 3.0.x中使用了annotation-driven后,缺省使用DefaultAnnotationHandlerMapping 来注册handler method和request的mapping关系。

AnnotationMethodHandlerAdapter来在实际调用handlermethod前对其参数进行处理。

并在dispatcherServlet中,当用户未注册自定义的ExceptionResolver时,注册AnnotationMethodHandlerExceptionResolver来对使用@ExceptionHandler标注的异常处理函数进行解析处理(这也导致当用户注册了自定义的exeptionResolver时将可能导致无法处理@ExceptionHandler)。

在spring mvc 3.1中,对应变更为
DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping
AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter
AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver

以上都在使用了annotation-driven后自动注册。
  而且对应分别提供了AbstractHandlerMethodMapping , AbstractHandlerMethodAdapter和 AbstractHandlerMethodExceptionResolver以便于让用户更方便的实现自定义的实现类。

你可能感兴趣的:(spring mvc)