Spring MVC 3.0学习笔记
- 支持REST风格的URL
 - 添加更多注解,可完全注解驱动
 - 引入HTTP输入输出转换器(HttpMessageConverter)
 - 和数据转换、格式化、验证框架无缝集成
 - 对静态资源处理提供特殊支持 更加灵活的控制器方法签名,可完全独立于Servlet API
 
请求处理过程的细节分析:MVC模式
下图说明了Spring MVC的请求处理流程:

结合例子:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | 
						package com.kingsmartsi.bmcs.sysmgr.ctrl; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller //Indicate that UserController is a Handler @RequestMapping ( "/user" ) public class UserController {     @RequestMapping ( value = "/home" )     //Request path to serve, relative to /user     public String register()     {         return "sysmgr/user"; //Logic view name     } }  | 
					
则处理流程图为: 
Spring MVC是根据来自HTTP请求报文中的各项属性来判断由哪一个Handler来处理当前请求的:
@RequestMapping不但支持标准的URL,还支持在URL中包含Ant风格的通配符:
| 
					 1 2 3 4 5 6  | 
						*匹配0-N个字符 /user/*/create-user      匹配 /user/any/create-user **匹配0-N个目录 /user/**/create-user     匹配 /user/create-user、/user/any/create-user ?匹配单个字符 /user/create-user ??     匹配 /user/create-user00  | 
					
@RequestMapping的URL中可以包含路径变量:
| 
					 1  | 
						/user/{id}   匹配 /user/10000  | 
					
一个URL中可以包含多个路径变量。
使用此注解可以把路径变量绑定为处理请求的方法的参数。 启用-debug编译时,如果路径变量名和方法参数名一致,可以自动匹配:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						@RequestMapping("/sysmgr/user/{id}") public ModelAndView get(@PathVariable("id") String id){     ModelAndView mv = new ModelAndView();     mv.setViewName("user/user-detail");     mv.addObject("user", userService.getUserById(id));     return mv; } @RequestMapping("/sysmgr/user-role/{userId}") public ModelAndView getRole(@PathVariable String userId){     ModelAndView mv = new ModelAndView();     mv.setViewName("user/user-role");     mv.addObject("user", userService.getRoles(id));     return mv; }  | 
					
HTTP方法,在HTTP协议中称为动词(verb),除了常用的(GET和POST)之外,标准方法集合中还包含PUT、DELETE、HEAD和OPTIONS。一般浏览器只支持GET和POST方法。各方法说明如下:
| 
 序号  | 
 请求方法  | 
 说明  | 
| 
 1  | 
 GET  | 
 使用GET方法检索一个表述(representation)——也就是对资源的描述。多次执行同一GET请求,不会对系统造成影响,GET方法具有幂等性[指多个相同请求返回相同的结果]。GET请求可以充分使用客户端的缓存。  | 
| 
 2  | 
 POST  | 
 POST方法,通常表示“创建一个新资源”,但它既不安全也不具有幂等性(多次操作会产生多个新资源)。  | 
| 
 3  | 
 DELETE  | 
 DELETE,表示删除一个资源,你也可以一遍又一遍地操作它,直到得出结果:删除不存在的东西没有任何问题  | 
| 
 4  | 
 PUT  | 
 幂等性同样适用于PUT(基本的含义是“更新资源数据,如果资源不存在的话,则根据此URI创建一个新的资源”)  | 
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						//处理任何HTTP方法: @RequestMapping(value="/delete")  public String del(@RequestParam("userId") String userId){     return "user/del";             } //仅处理POST方法提交的请求: @RequestMapping(value="/delete",method=RequestMethod.POST)  public String del(@RequestParam("userId") String userId){     return "user/del";             }  | 
					
Spring还提供了模拟HTTP方法的机制:通过在web.xml中配置一个org.springframework.web.filter.HiddenHttpMethodFilter,利用POST请求的_method参数指定HTTP方法,HiddenHttpMethodFilter动态更改HTTP头信息。
请求参数限定支持如下格式。这些格式同样适用于请求头限定:
- param1 :表示请求必须包含名为param1的请求参数
 - !param1 :表示请求不能包含名为param1的请求参数
 - param1!=value1 :表示请求包含名为param1的请求参数,但其值不能为value1
 - {"param1=value1","param2"} :请求必须包含名为param1和param2的两个请求参数,且param1参数的值必须为value1
 
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						//通过请求参数限定 @RequestMapping(value="/delete", params="userId")  public String test1(@RequestParam("userId") String userId){     ...             } //通过请求头限定 @RequestMapping(value="/show",headers="content-type=text/*") public String test2(@RequestParam("userId") String userId){      ...             }  | 
					
Spring MVC总是把各种请求数据绑定到Handler的入参中,其行为主要取决于注解配置。
常用数据绑定注解包括:
| 注解 | 说明 | 
| @RequestParam | 绑定请求参数(包括URL参数) | 
| @RequestHeader | 绑定请求头 | 
| @CookieValue | 绑定Cookie值 | 
| @PathVariable | 绑定路径变量,已经在上一章介绍 | 
| 
					 1 2 3 4 5 6 7  | 
						@RequestMapping(value="/handle") public String handle(@CookieValue("JSESSIONID") String sessionId,        @RequestHeader("Accept-Language") String accpetLanguage){ } @RequestMapping(value="/handle") public String handle(@RequestBody User u){ }  | 
					
@RequestParam注解用于绑定单个GET/POST请求参数到Handler的入参,它以下三个参数:
| 参数 | 说明 | 
| value | 请求参数的名称 | 
| required | 是否必需,默认为true,表示请求中必须包含对应的参数名,如果不存在将抛出异常 | 
| defaultValue | 默认参数名,设置该参数时,自动将required设为false | 
如果Handler入参类型是:
- Map,且注解指定了请求参数的名称,那么该参数的值依据配置的转换服务,转换为Map对象
 - Map<String, String>或者MultiValueMap<String, String>,且注解没有指定请求参数的名称,那么所有请求参数被注入到入参中
 
| 
					 1  | 
						public String request(@RequestParam("username") String username) {}   | 
					
Spring MVC支持绑定请求参数到POJO。按:HTTP请求参数名 = POJO的属性名的规则,自动绑定请求数据,支持级联属性名(属性导航),自动进行基本类型数据转换。使用级联属性名时,会自动创建点号导航牵涉到的对象,甚至是集合对象:
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						@RequestMapping(value = "/save") public String save( User user ) {     //Request URL : /user/save?address.zip=1     assertEquals(1,user.getAddress().getZip()); } @RequestMapping ( value = "/save" ) public void save( User user ) {     //Request URL : /user/save?addresses[0].zip=1     assertEquals(1,user.getAddresses().get( 0 ).getZip() ); }  | 
					
注意:被绑定的Handler入参上不需要注解,Spring MVC会自动从请求参数中查找匹配入参属性的条目。
你还可以把绑定的对象添加到模型中,供视图访问:
| 
					 1  | 
						public String request(@ModelAttribute("u") UserModel user){}  | 
					
在JSP视图页面中,我们可以通过表达式 ${u.username} 访问到上面绑定的对象。
| 对象类型 | 说明 | 
| Servlet API | 可以注入各种Servlet API对象作为Handler的参数,包括HttpServletRequest、HttpServletResponse、HttpSession等 如果Handler自行使用HttpServletResponse返回响应,则Handler的返回值设置成void即可  | 
| Spring的Servlet API代理类 | Spring MVC在org.springframework.web.context.request包中定义了若干个可代理Servlet原生API类的接口,如WebRequest、NativeWebRequest,它们也允许作为Handler的参数,通过这些代理类可访问请求对象的任何信息 | 
| I/O类 | Spring MVC允许控制器的Handler使用java.io.InputStream/java.io.Reader及java.io.OutputStream/java.io.Writer作为方法的参数 Spring MVC将获取ServletRequest的InputStream/Reader或ServletResponse的OutputStream/Writer,然后按类型匹配的方式,传递给控制器的Handler入参  | 
| 其它 | 除可以注入前述类型的参数以外,还支持java.util.Locale、java.security.Principal,可以通过Servlet的HttpServletRequest 的getLocale()及getUserPrincipal()得到相应的值。如果Handler的入参类型为Locale或Principal,Spring MVC自动从请求对象中获取相应的对象并传递给Handler | 
消息转换机制是Spring MVC数据绑定机制的补充,是@RequestBody、@ResponseBody注解正常工作的基础,其工作示意图如下:

| 
					 1 2 3 4 5 6 7  | 
						public interface HttpMessageConverter<T> {   //从HTTP输入消息中读取一个指定类型的对象   T read(Class<? extends T> clazz, HttpInputMessage inputMessage);    //将指定类型的对象写入到HTTP输出消息中   void write(T t, MediaType contentType, HttpOutputMessage         outputMessage); }  | 
					
现有的实现类包括:StringHttpMessageConverter、FormHttpMessageConverter XmlAwareFormHttpMessageConverter、ResourceHttpMessageConverter、BufferedImageHttpMessageConverter、ByteArrayHttpMessageConverter SourceHttpMessageConverter、MarshallingHttpMessageConverter Jaxb2RootElementHttpMessageConverter、MappingJacksonHttpMessageConverter RssChannelHttpMessageConverter、AtomFeedHttpMessageConverter。可以支持XML、JSON等常用格式。
这两个注解可以将HttpServletRequest的getInputStream()内容绑定到参数,或者将Handler返回值写入到HttpServletResponse的getOutputStream()中。它们支持把请求体绑定为POJO,或者把POJO写入到响应体中。读写的格式由Http消息转换器决定。Http消息转换器不必须手工配置,SpringMVC会根据请求头、请求体等进行灵活的判断。这两个注解的缺点是不能访问请求头、响应头。下面是一个例子:
| 
					 1 2 3 4 5 6 7 8  | 
						@RequestMapping(value = "/handle") public String handle (@RequestBody  User user) { } @ResponseBody public byte[] handle () throws IOException {     User user = new User()     return user; }  | 
					
这两个类和@RequestBody、@ResponseBody类似,可以访问请求头、响应头,但是失去方法签名灵活性优势。下面是一个例子:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14  | 
						@RequestMapping(value = "/handle") public String handle (HttpEntity<User> httpEntity){       User user = httpEntity.getBody();                            } @RequestMapping(value = "/handle")   public ResponseEntity<User> handle(){        HttpHeaders headers = new HttpHeaders();        MediaType mt=new MediaType("text","json", "UTF-8");        headers.setContentType(mt);        ResponseEntity<User> re=null;        User user = new User("WangZhen");        re=new ResponseEntity<User>(user ,headers, HttpStatus.OK);        return re;    }  | 
					


旧版本的Spring只支持标准的PropertyEditor类型体系,存在以下缺陷:
- 只能用于字符串和Java对象的转换,不适用于任意两个Java类型之间的转换
 - 对源对象及目标对象所在的上下文信息(如注解、所在宿主类的结构等)不敏感,在类型转换时不能利用这些上下文信息实施高级转换逻辑
 
Spring 3.0在核心模型中添加了一个通用的类型转换模块, ConversionService是Spring类型转换体系的核心接口。Spring 3.0同时支持PropertyEditor和ConversionService 进行类型转换,在Bean配置、Spring MVC的Handler参数绑定中使用类型转换体系进行转换。
由于ConversionService在进行类型转换时,可以使用到Bean类的上下文信息(包括类结构,注解信息),所以可以实施更加高级的类型转换,如注解驱动的格式化等功能:
| 
					 1 2 3 4  | 
						public class User {       @DateTimeFormat(pattern="yyyy-MM-dd")       private Date birthday; }  | 
					
以上User类,通过一个@DateTimeFormat注解,为类型转换提供了一些“额外”的信息,即代表日期的“源字符器”格式是“yyyy-MM-dd”。
你可以实现org.springframework.core.convert.converter.Converter<From,To>以自定义转换器:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13  | 
						public class DateTimeConvertor implements Converter<String, Date> {     private String[] formats;     public DateTimeConvertor( String[] formats ) {         this.formats = formats;     }     public Date convert( String source ) {         return DateUtils.parseDate( source, formats );     } }  | 
					
编写类型转换器完毕后,必须在Spring上下文中注册,才能生效:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | 
						<mvc:annotation-driven conversion-service="conversionService"/> <!-- 此工厂生成的对象是org.springframework.format.support.FormattingConversionService,它同时是转换服务、格式化服务--> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">     <!-- 它持有一系列的转换器 -->     <property name="converters">         <set>             <bean class="com.kingsmartsi.ssheutils.spring.DateTimeConvertor">                 <constructor-arg>                     <array>                         <value>#{appCfg.dateFormat}</value>                         <value>#{appCfg.dateTimeFormat}</value>                     </array>                 </constructor-arg>             </bean>         </set>     </property> </bean>  | 
					
上面的FormattingConversionService除了可以持有一系列的转换器(converters),还可以持有一系列的格式化器(formatters)。格式化器用来将来自HTTP请求报文的文本转换为Java数据类型,或者反向转换: 
你可以实现org.springframework.format.Formatter<T>接口,以便把HTTP文本解析为T类型对象,以及把T类型对象格式化为HTTP文本:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | 
						public class DateTimeFormatter implements Formatter<Date> {     private String[] formats;     public DateTimeFormatter( String[] formats ) {         this.formats = formats;     }     // 此方法用于将HTTP文本解析为Java对象     @Override     public Date parse( String text, Locale locale ) throws ParseException {         return null;     }     // 此方法用于将Java对象转换为HTTP文本     @Override     public String print( Date object, Locale locale ) {         return null;     } }  | 
					
自定义的格式化器需要到Spring上下文注册:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						<mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">     <property name="formatters">         <set>             <bean class="com.kingsmartsi.ssheutils.spring.DateTimeFormatter">                 <constructor-arg>                     <array>                         <value>#{appCfg.dateFormat}</value>                         <value>#{appCfg.dateTimeFormat}</value>                     </array>                 </constructor-arg>             </bean>         </set>     </property> </bean>  | 
					
Spring默认的 <mvc:annotation-driven/> 标签默认创建的ConversionService实例就是一个FormattingConversionServiceFactoryBean,自动支持如下的格式化注解:
| 格式化注解 | 说明 | 
| @NumberFormatter | 用于数字类型对象的格式化 | 
| @CurrencyFormatter | 用于货币类型对象的格式化 | 
| @PercentFormatter | 用于百分数数字类型对象的格式化 | 
JSR-303是Java为Bean数据合法性校验所提供的标准框架,它已经包含在Java EE 6.0中。JSR-303通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。JSR-303包含的注解有:
| 注解 | 功能说明 | 
| @Null | 被注释的元素必须为 null | 
| @NotNull | 被注释的元素必须不为 null | 
| @AssertTrue | 被注释的元素必须为 true | 
| @AssertFalse | 被注释的元素必须为 false | 
| @Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | 
| @Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | 
| @DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | 
| @DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | 
| @Size(max, min) | 被注释的元素的大小必须在指定的范围内 | 
| @Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 | 
| @Past | 被注释的元素必须是一个过去的日期 | 
| @Future | 被注释的元素必须是一个将来的日期 | 
Spring 3.0拥有自己独立的数据校验框架,位于Spring的org.springframework.validation包。该框架同时支持JSR- 303标准的校验框架。Spring 的DataBinder在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中,则可直接通过注解驱动的方式进行数据校验。
需要注意的是,Spring本身没有提供JSR-303的实现,所以必须将JSR- 303的实现者(如Hibernate Validator)的jar文件放到类路径下,Spring将自动加载并装配好JSR-303的实现者。
 
			<mvc:annotation-driven/> 会默认装配好一个LocalValidatorFactoryBean,通过在Handler参数上标注@Valid注解即可让Spring MVC在完成数据绑定后执行数据校验的工作。 
在已经标注了JSR-303注解的、作为Handler参数的POJO前标注一个@Valid,Spring MVC框架在将请求数据绑定到该对象后,就会调用校验框架根据注解声明的校验规则实施校验:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  | 
						public class User {        @Pattern(regexp="w{4,30}")     private String userName;     @Length(min=2,max=100)     private String realName;     @Past      @DateTimeFormat(pattern="yyyy-MM-dd")     private Date birthday;     @DecimalMin(value="1000.00")     @DecimalMax(value="100000.00")      @NumberFormat(pattern="#,###.##")     private long salary; } @Controller public class UserController{     @RequestMapping     public String handle(         //可以在被验证对象后面紧跟着参数用于存放验证结果,这些参数必须是BindingResult或Errors类型         @Valid User user, BindingResult br, //User和其绑定结果的对象         @Valid Dept dept, Errors errors   //Dept和其校验的结果对象 ){}  | 
					

可以通过ModelAndView访问:
| 
					 1 2 3 4 5 6 7 8  | 
						@RequestMapping(method = RequestMethod.POST) public ModelAndView createUser(User user) {     userService.createUser(user);     ModelAndView mv = new ModelAndView();     mv.setViewName("user/createSuccess");     mv.addObject("user", user);     return mv; }  | 
					
也可以通过@ModelAttribute访问:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						//Handler会先执行该方法,获取一个初始的User对象,并使用请求覆盖对应属性 @ModelAttribute("user") public User getUser(){     User user = new User();     user.setUserId("1001");      return user; } @RequestMapping(value = "/handle") public String handle(@ModelAttribute("user") User user){     user.setUserName("tom");     return "/user/showUser"; }  | 
					
如果Handler方法的参数中有:org.springframework.ui.Model、java.util.Map类型,SpringMVC会自动把请求中隐含的模型对象传递给这些参数:
| 
					 1 2 3 4 5 6 7  | 
						@RequestMapping(value = "/handle") public String  handle (ModelMap modelMap){      modelMap.addAttribute("test",“val");      User user = (User)modelMap.get("user");      user.setUserName("wz");              return "/user/showUser"; }  | 
					
如果希望在多个请求之间共用某个模型属性数据,则可以在控制器类标注一个@SessionAttributes,Spring MVC会将模型中对应的属性暂存到HttpSession中:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | 
						@Controller @RequestMapping("/user") @SessionAttributes("user")//声明会话属性 public class UserController {     @RequestMapping(value = "/handle0")     public String  handle0(@ModelAttribute("user") User user){//设置会话属性         user.setUserName("John");         return "redirect:/user/handle1.html";     }     @RequestMapping(value = "/handle1")     public String  handle1(ModelMap modelMap,SessionStatus sessionStatus){         User user = (User)modelMap.get("user");//获取会话属性         if(user != null){             user.setUserName("Jetty");             sessionStatus.setComplete();//清除会话属性             }         return "/user/showUser";     }         }  | 
					

- 完成单一解析逻辑的视图解析器:InternalResourceViewResolver、FreeMarkerViewResolver、BeanNameViewResolver、XmlViewResolver等
 - 协商视图解析器:ContentNegotiatingViewResolver。该解析器是Spring 3.0新增的,它不负责具体的视图解析,而是作为一个中间人的角色根据请求所要求的MIME类型(Accept请求头),从上下文中选择一个适合的视图解析器,再将视图解析工作委托其负责
 
视图解析器Bean的parameterName可以用来判断请求所期望的MIME类型,order则声明视图解析器的优先级:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  | 
						<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"     p:order="0" p:defaultContentType="text/html" p:ignoreAcceptHeader="true"      p:favorPathExtension="false" p:favorParameter="true" p:parameterName="content">     <property name="mediaTypes">         <map>             <entry key="html" value="text/html" />       <entry key="xml" value="application/xml" />                <entry key="json" value="application/json" />         </map>     </property>     <property name="defaultViews">         <list>             <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"                 p:renderedAttributes="userList" />             <bean class="org.springframework.web.servlet.view.xml.MarshallingView"                 p:modelKey="userList" p:marshaller-ref="xmlMarshaller" />         </list>     </property> </bean> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"     p:order="100" p:viewClass="org.springframework.web.servlet.view.JstlView"     p:prefix="/WEB-INF/views/" p:suffix=".jsp" />  | 
					
在web.xml让所有请求都由Spring MVC处理,注意url-pattern是/而不是/*:
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						<servlet>     <servlet-name>springmvc</servlet-name>     <servlet-class>         org.springframework.web.servlet.DispatcherServlet</servlet-class>     <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping>     <servlet-name>springmvc</servlet-name>     <url-pattern>/</url-pattern> </servlet-mapping>  | 
					
在MVC的Spring配置文件中设置:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						<!-- 设置默认,大多数应用服务器的Servlet的名称都是“default” --> <mvc:default-servlet-handler default-servlet-name="default"/> <!-- 物理静态资源路径映射逻辑资源路径 --> <mvc:resources mapping="/resources/**" location="/,classpath:/META-INF/publicResources/"/> <mvc:resources mapping="/json/**" location="/json/" /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/icons/**" location="/icons/" /> <mvc:resources mapping="/images/**" location="/images/" /> <mvc:resources mapping="/themes/**" location="/themes/" /> <mvc:resources mapping="/sound/**" location="/sound/" /> <mvc:resources mapping="/js/**" location="/js/" /> <mvc:resources mapping="/html/**" location="/html/" />   | 
					
            
Leave a Reply