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