1.注解参数详解
handler method 参数绑定常用的注解,我们根据他们处理的Request的不同内容部分分为四类:(主要讲解常用类型)
A、处理requet uri 部分(这里指uri template中variable,不含queryString部分)的注解: @PathVariable;
B、处理request header部分的注解: @RequestHeader, @CookieValue;
C、处理request body部分的注解:@RequestParam, @RequestBody;
D、处理attribute类型是注解: @SessionAttributes, @ModelAttribute;
1、@PathVariable :
当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
上面代码把URI template 中变量 ownerId的值和petId的值,绑定到方法的参数上。若方法参数名称和需要绑定的uri template中变量名称不一致,需要在@PathVariable(“name”)指定uri template中的名称
2、@RequestHeader、@CookieValue
@RequestHeader 注解,可以把Request请求header部分的值绑定到方法的参数上
这是一个Request 的header部分:
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader(“Accept-Encoding”) String encoding,
@RequestHeader(“Keep-Alive”) long keepAlive) {
//…
}
上面的代码,把request header部分的 Accept-Encoding的值,绑定到参数encoding上了, Keep-Alive header的值绑定到参数keepAlive上。
@CookieValue 可以把Request header中关于cookie的值绑定到方法的参数上。
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue(“JSESSIONID”) String cookie) {
//…
}
即把JSESSIONID的值绑定到参数cookie上。
3、@RequestParam, @RequestBody
@RequestParam
A) 常用来处理简单类型的绑定,通过Request.getParameter() 获取的String可直接转换为简单类型的情况( String–> 简单类型的转换操作由ConversionService配置的转换器来完成);因为使用request.getParameter()方式获取参数,所以可以处理get 方式中queryString的值,也可以处理post方式中 body data的值;
B)用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容,提交方式GET、POST;
C) 该注解有两个属性: value、required; value用来指定要传入值的id名称,required用来指示参数是否必须绑定;
自定义的控制层的类EditPetForm
@Controller
@RequestMapping("/pets")
@SessionAttributes(“pet”)
public class EditPetForm {
// ...
@RequestMapping(method = RequestMethod.GET)
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
@RequestBody
该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等;
它是通过使用HandlerAdapter 配置的HttpMessageConverters来解析post data body,然后绑定到相应的bean上的。
因为配置有FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded的内容,处理完的结果放在一个MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter api;
——————————————————————*******
@RequestMapping(value = “/something”, method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
4、@SessionAttributes, @ModelAttribute
@SessionAttributes:
该注解用来绑定HttpSession中的attribute对象的值,便于在方法中的参数里使用。
该注解有value、types两个属性,可以通过名字和类型指定要使用的attribute 对象;
@Controller
@RequestMapping("/editPet.do")
@SessionAttributes(“pet”)
public class EditPetForm {
// …
}
*****------------------
@ModelAttribute
该注解有两个用法,一个是用于方法上,一个是用于参数上;
用于方法上时: 通常用来在处理@RequestMapping之前,为请求绑定需要从后台查询的model;
用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数bean上;要绑定的值来源于:
A) @SessionAttributes 启用的attribute 对象上;
B) @ModelAttribute 用于方法上时指定的model对象;
C) 上述两种情况都没有时,new一个需要绑定的bean对象,然后把request中按名称对应的方式把值绑定到bean中。
// Add one attribute
// The return value of the method is added to the model under the name “account”
// You can customize the name via @ModelAttribute(“myAccount”)
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountManager.findAccount(number);
}
这种方式实际的效果就是在调用@RequestMapping的方法之前,为request对象的model里put(“account”, Account);
用在参数上的@ModelAttribute示例代码:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) {
}
首先查询 @SessionAttributes有无绑定的Pet对象,若没有则查询@ModelAttribute方法层面上是否绑定了Pet对象,若没有则将URI template中的值按对应的名称绑定到Pet对象的各属性上。
@ResponseBody
作用:
该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
使用时机:
返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;
MappingJacksonHttpMessageConverter 调用了 objectMapper.writeValue(OutputStream stream, Object)方法,使用@ResponseBody注解返回的对象就传入Object参数内。若返回的对象为已经格式化好的json串时,不使用@RequestBody注解,而应该这样处理:
1、response.setContentType(“application/json; charset=UTF-8”);
2、response.getWriter().print(jsonStr);
直接输出到body区,然后的视图为void。
二、数据化格式
SpringMVC数据格式化主要包括两块:数值格式化与日期格式化。
首先需要明白的是,Spring对属性对象的输入/输出进行格式化,从其本质上讲依然属于“类型转换”的范畴
1、FormattingConversionService
Spring 在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类。
该实现类扩展了GenericConversionService,因此它既具有类型转换的功能又具有格式化的功能。
FormattingConversionService拥有一个FormattingConversionServiceFactoryBean工厂类,后者用于在Spring上下文中构造前者
ormattingConversionService内部注册了两个工厂类:
① NumberFormatAnnotationFormatterFactory:支持对数字类型的属性使用@NumberFormat注解;
② JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用@DateTimeFormat注解。
【这下就明白了,为什么可以使用@NumberFormat和@DateTimeFormat注解进行数据格式化了】。
装配了FormattingConversionServiceFactoryBean后,就可以在SpringMVC入参绑定及模型数据输出时使用注解驱动了
1、@DateTimeFormat注解:
该注解可对java.util.Date , java.util.Calendar , java.long.Long 时间类型进行标注。
① pattern属性:
类型为字符串。指定解析/格式化字段数据的模式,如“yyyy-MM-dd HH:mm:ss”。
注意:如果是12小时制,使用 hh:mm:ss ; 如果是24小时制,使用HH:mm:ss。
② iso属性:
类型为DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种:
ISO.NONE;
ISO.DATE(yyyy-MM-dd);
ISO.TIME(hh:mm:ss.SSSZ);
ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)。
③ style属性
字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式。
S:短日期/时间格式;
M:中日期/时间格式;
L:长日期/时间格式;
F:完整日期/时间格式;
-:忽略日期或时间格式。
2、@NumberFormat注解
该注解可对类似数字类型的属性进行标注,它有两个互斥的属性:
① style
类型为NumberFormat.Style。用于指定样式类型,包括三种:
Style.NUMBER(正常数字类型);
Style.CURRENCY(货币类型);
Style.PERCENT(百分数类型)
② pattern
类型为String,自定义样式,如pattern=”#,###”。
具体实现例子:
package com.offcn.entity;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import java.util.Date;
public class Product {
private String pname;
private String price;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date pdate;
@NumberFormat(pattern = "#,###,###.#")
private Float salary;
public Date getPdate() {
return pdate;
}
public void setPdate(Date pdate) {
this.pdate = pdate;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public Product(String pname, String price) {
this.pname = pname;
this.price = price;
}
public Product() {
super();
}
}
springmvc-servlet.xml文件配置如下:
<!--当你的请求地址是index.html 就会到UserController这个类-->
<!-- <bean name="/index.pp" class="com.offcn.controller.UserController"></bean>-->
<!--注解配置扫描controller包-->
<context:component-scan base-package="com.offcn.controller"></context:component-scan>
<!--开起注解驱动-->
<mvc:annotation-driven/>
<!-- 注解驱动 -->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"/>
<!-- 配置Conveter转换器 转换工厂 (日期、去掉前后空格) -->
<bean id="conversionServiceFactoryBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 配置 多个转换器-->
<property name="converters">
<list>
<!--自定义的时间转换类DateConverter-->
<bean class="com.offcn.utils.DateConverter"/>
</list>
</property>
</bean>
<!--完成视图的对应-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀-->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!--配置后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
**相关jsp文件来取值:** <%-- Created by IntelliJ IDEA. User: ibm Date: 2018/10/16 Time: 16:15 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <%--这里需要导入相关标签库--%>
三、返回值类型
一、SpringMVC 使用 ModelAndView 来处理返回值问题。
在 springmvc 框架中,ModelAndView 表示同时持有 Model 和 View 。Model 和 View 是完全不同的。
这个类使一个控制器返回单独的一个值同时包含 model 和 view 成为一种可能。
同时也代表着一个处理器返回一个 model 和 view ,会被 DispatcherServlet 解析。
View 对象如果是通过一个字符串形式的 view name 获取到的,则能被 ViewResolver 对象解析。
或者也可以直接指定 View 对象。model 是一个 Map 类型,可以使用多个对象的键值对。
2.这里要搞明白一点,为什么说是通过 ModelAndView 来处理返回值问题,事实上,返回的类型可以是很多种,可以是 ModelAndView 类型,
也可以是String类型,还可以是View类型,还有很多。但是他们最终会被解析为 ModelAndView 类型的对象。
1、ModelAndView
2、Model
3、ModelMap
4、Map
5、View
6、String
7、Void
ModelAndView:
@RequestMapping(method=RequestMethod.GET)
public ModelAndView index(){
ModelAndView modelAndView = new ModelAndView("/user/index");
modelAndView.addObject(“xxx”, “xxx”);
return modelAndView;}
通过ModelAndView构造方法可以指定返回的页面名称,也可以通过setViewName()方法跳转到指定的页面 ,
使用addObject()设置需要返回的值,addObject()有几个不同参数的方法,可以默认和指定返回对象的名字。
调用addObject()方法将值设置到一个名为ModelMap的类属性,ModelMap是LinkedHashMap的子类,
具体请看类。
@RequestMapping(method=RequestMethod.GET)
public ModelAndView index(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("xxx", "xxx");
modelAndView.setViewName("/user/index");
return modelAndView;
}
Model
一个模型对象,主要包含spring封装好的model和modelMap,以及Java.util.Map,当没有视图返回的时候视图名称将由requestToViewNameTranslator决定;Model 是一个接口, 其实现类为ExtendedModelMap,继承了ModelMap类。
Map
@RequestMapping(method=RequestMethod.GET)
public Map<String, String> index(){
Map<String, String> map = new HashMap<String, String>();
map.put(“1”, “1”);
//map.put相当于request.setAttribute方法
return map;
}
View
这个时候如果在渲染页面的过程中模型的话,就会给处理器方法定义一个模型参数,然后在方法体里面往模型中添加值。可以返回pdf excel等,暂时没详细了解。
String
对于String的返回类型,笔者是配合Model来使用的;
@RequestMapping(method = RequestMethod.GET)
public String index(Model model) {
String retVal = "user/index";
List<User> users = userService.getUsers();
model.addAttribute("users", users);
return retVal;}
或者通过配合@ResponseBody来将内容或者对象作为HTTP响应正文返回(适合做即时校验);
@RequestMapping(value = “/valid”, method = RequestMethod.GET)
public @ResponseBody
String valid(@RequestParam(value = "userId", required = false) Integer userId,
@RequestParam(value = "logName") String strLogName) {
return String.valueOf(!userService.isLogNameExist(strLogName, userId));
}
String 指定返回的视图页面名称,结合设置的返回地址路径加上页面名称后缀即可访问到。
注意:如果方法声明了注解@ResponseBody ,则会直接将返回值输出到页面,而不是跳转到jsp文件
Java代码
@RequestMapping(value = “/something”, method = RequestMethod.GET)
@ResponseBody
publicString helloWorld() {
return"Hello World";
}
注意:如果返回的String代表逻辑视图名的话,那么Model的返回方式如下:
public String testController(Model model){
model.addAttribute(attrName,attrValue);//相当于ModelAndView的addObject方法
return “逻辑视图名”;
}
代表redirect重定向:
redirect的特点和servlet一样,使用redirect进行重定向那么地址栏中的URL会发生变化,同时不会携带上一次的request
public String testController(Model model){
return “redirect:path”;//path代表重定向的地址
}
代表forward转发:
通过forward进行转发,地址栏中的URL不会发生改变,同时会将上一次的request携带到写一次请求中去
public String testController(Model model){
return “forward:path”;//path代表转发的地址
}
Void
当返回类型为Void的时候,则响应的视图页面为对应着的访问地址
@Controller
@RequestMapping(value="/type")
public class TypeController extends AbstractBaseController{
@RequestMapping(method=RequestMethod.GET)
public void index(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("xxx", "xxx");
}
}
返回的结果页面还是:/type
这个时候我们一般是将返回结果写在了HttpServletResponse 中了,如果没写的话,spring就会利用RequestToViewNameTranslator 来返回一个对应的视图名称。如果这个时候需要模型的话,处理方法和返回字符串的情况是相同的。
小结:
1.使用 String 作为请求处理方法的返回值类型是比较通用的方法,这样返回的逻辑视图名不会和请求 URL 绑定,具有很大的灵活性,而模型数据又可以通过 ModelMap 控制。
2.使用void,map,Model 时,返回对应的逻辑视图名称真实url为:prefix前缀+视图名称 +suffix后缀组成。
3.使用String,ModelAndView返回视图名称可以不受请求的url绑定,ModelAndView可以设置返回的视图名称。
Model model,HttpServletRequest request, ModelMap map声明变量
request.getSession().setAttribute(“test”, “haiwei2Session”);
request.setAttribute(“test”, “haiwei1request”);
map.addAttribute(“test”, “haiweiModelMap”);
model.addAttribute(“test”, “haiweiModel”);
我通过${test}这个方式取值,优先取Model和ModelMap的,Model和ModelMap是同一个东西,谁最后赋值的就取谁的,然后是request,最后是从session中获取