14.数据绑定
1)功能处理方法支持的"参数类型"
注意下面这些参数都可以在功能处理方法中直接声明并且没有指定顺序,spring会自动注入的
第一种
ServletRequest/HttpServletRequest 和 ServletResponse/HttpServletResponse
SpringWebMVC框架会自动帮助我们把相应的Servlet请求/响应作为参数传递过来。
第二种
InputStream/OutputStream 和 Reader/Writer
分别对应的是request.getInputStream();
response.getOutputStream();
request.getReader();
response.getWriter()。
InputStream/OutputStream 和 Reader/Writer两组不能同时使用,只能使用其中的一组。
注意:
//代码如下,访问的时候会报500错误,因为使用输出流,就不能再发回视图了,因为使用输出流那么就是使用了response,使用了response就是要自己处理返回给浏览器的内容,那么也就不能再让Controller返回视图了
@RequestMapping("/test")
public String test(InputStream in,OutputStream out){
System.out.println(in);
System.out.println(out);
return "test";
}
第三种
WebRequest/NativeWebRequest
WebRequest是SpringMVC提供的统一请求访问接口,不仅仅可以访问请求相关数据(如参数区数据、请求头数据,但访问不到Cookie区数据),还可以访问会话和上下文中的数据;NativeWebRequest继承了WebRequest,并提供访问本地ServletAPI的方法。
例如:
public String webRequest(WebRequest webRequest, NativeWebRequest nativeWebRequest) {
System.out.println(webRequest.getParameter("test"));
webRequest.setAttribute("name", "tom",WebRequest.SCOPE_REQUEST);
System.out.println(webRequest.getAttribute("name", WebRequest.SCOPE_REQUEST));
HttpServletRequest request =
nativeWebRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response =
nativeWebRequest.getNativeResponse(HttpServletResponse.class);
return "success";
}
webRequest.getParameter() 访问请求参数区的数据webRequest.getHeader() 访问请求头数据
webRequest.setAttribute/getAttribute:到指定的作用范围内取/放属性数据,Servlet定义的三个作用范围分别使用如下常量代表:
SCOPE_REQUEST :代表请求作用范围;
SCOPE_SESSION :代表会话作用范围;
SCOPE_GLOBAL_SESSION :代表全局会话作用范围,即ServletContext上下文作用范围。
得到本地的Servlet API。
nativeWebRequest.getNativeRequest
nativeWebRequest.getNativeResponse
第四种
HttpSession
public String session(HttpSession session) {
System.out.println(session);
return "success";
}
注意:session访问不是线程安全的,如果需要线程安全,需要自己手动的设置AnnotationMethodHandlerAdapter或RequestMappingHandlerAdapter的synchronizeOnSession属性为true,即可线程安全的访问session。
在spring2.5中是AnnotationMethodHandlerAdapter
在spring3中是RequestMappingHandlerAdapter.
第五种
命令/表单对象(也可以是是自定义对象 例如User)
SpringMVC能够自动将请求参数绑定到功能处理方法的命令/表单对象上。
这里说的命令/表单对象并不需要实现任何接口,仅是一个拥有若干属性的POJO类对象
例如:
@RequestMapping(value = "/create"
public String toCreateUser(HttpServletRequest request,User user) {
return null;
}
如果提交的表单(包含username和password文本域),将自动将请求参数绑定到命令对象user中去。
第六种
Model、Map、ModelMap
SpringMVC提供Model、Map或ModelMap让我们能去封装/处理模型数据。
例如:
@RequestMapping(value="/model")
public String test(Model m1, Map<String,Object> m2, ModelMap m3) {
m1.addAttribute("a", "a");
m2.put("b", "b");
m3.put("c", "c");
System.out.println(m1 == m2);
System.out.println(m2 == m3);
System.out.println(m1.getClass());
System.out.println(m2.getClass());
System.out.println(m3.getClass());
return "success";
}
虽然此处注入的是三个不同的类型(Model model, Map model2, ModelMap model3),但三者是同一个对象
第七种
HttpEntity<T>和ResponseEntity<T>
例如:HttpEntity的使用
@RequestMapping(value="test")
public String test(HttpEntity<String> httpEntity){
//获得请求中的所有的header部分
HttpHeaders headers = httpEntity.getHeaders();
//获得请求中的所有的body部分
String body = httpEntity.getBody();
Set<Entry<String,List<String>>> set = headers.entrySet();
StringBuffer s = new StringBuffer();
for(Entry<String,List<String>> entry:set){
String key = entry.getKey();
s.append(key+": ");
List<String> list = entry.getValue();
for(String value:list){
s.append(value+" ");
}
System.out.println(s.toString());
s.setLength(0);
}
System.out.println("----------------");
System.out.println("body = "+body);
}
例如:ResponseEntity的使用,可以自定义响应的各个部分
@RequestMapping("/test")
public ResponseEntity<String> test(){
//创建响应头对象
HttpHeaders headers = new HttpHeaders();
//创建MediaType对象
MediaType mt = new MediaType("text","html",Charset.forName("UTF-8"));
//设置ContentType
headers.setContentType(mt);
//准备好相应体
String content = new String("hello world");
//根据响应内容/响应头信息/响应状态码创建响应对象
ResponseEntity<String> re = new ResponseEntity<String>(content,headers,HttpStatus.OK);
//返回ResponseEntity对象
return re;
}
第八种
SessionStatus
SessionStatus中的setComplete()方法可以用来清除使用@SessionAttributes注解放到session中的数据,下面介绍注解的例子的例子中会说明这个问题
@RequestMapping("/test")
public String test(SessionStatus status)
第九种
RedirectAttributes
问题描述:
当前一个表单提交(post)给Controller之后,Controller处理完带着数据(msg="添加成功")进行服务器内部跳转到一个成功页面,页面里使用EL表达式拿出msg中的值并显示出来,这个时候用户按F5刷新,那么表单数据会再被提交一次。
如果上述过程中不是使用服务器内部跳转而是使用客户端重定向,那么用户在成功页面按F5刷新就不能重新提交表单了,但是由于使用了重定向,那么之前我们放在request范围的数据就拿不到了。
RedirectAttributes可以帮我们解决这个问题,既要使用客户端重定向,又要把一些有用的数据保留到下一次重定向的请求之中
例如:
@RequestMapping(value="/user/add",method=RequestMethod.POST)
public String addUser(User user,RedirectAttributes redirectAttributes){
System.out.println("user = "+user);
redirectAttributes.addFlashAttribute("msg", "添加用户成功");
//这里不能使用servlet重定向的方式
//response.sendRedirect()
//要使用springMVC提供的重定向方式
return "redirect:/user/add/success";
}
@RequestMapping("/user/add/success")
public String index(){
return "success";
}
客户端重定向之后,用户可以在添加成功页面看到提示信息,但是用户按F5刷新之后,提示信息就没有了,变量msg只是被保存到了一下次重定向的请求之中。页面中使用${requestScope.msg }即可获得数据,当然也可以${msg}
第十种
BindingResult
BindingResult对象里面可以保存SpringMVC数据校验中的错误信息,要结合数据校验功能来使用
其他
除了以上几种类型参数之外,还支持一些其他的类型,如:Errors、Locale、Principal、UriComponentsBuilder等
---------功能处理方法中的注解
2)@RequestParam绑定单个请求参数值
用于将请求参数区数据映射到功能处理方法的参数上
public String test(@RequestParam String username)
请求中包含username 参数(如/test?username=tom),则自动传入。
注:这时候也可以简写为
public String test(String username)
如果url为/test?name=tom,即俩边参数名不一致,那么就需要手动指定
public String test(@RequestParam("name") String username)
@RequestParam注解中主要有哪些参数
value:参数名字,即入参的请求参数名字
required:是否必须,默认是true
defaultValue:默认值,表示如果请求中对应参数时则采用此默认值,默认值可以是spring中的SpEL 表达式,如
"#{systemProperties['java.vm.version']}"。
注意:systemProperties是spring自动方法ioc容器中的一个Properties对象,里面方法中很多系统变量,要取值只需#{systemProperties['key']}即可
注:后面知识点中有对spEL的更多介绍和使用
3)@PathVariable绑定URI模板变量值
用于将请求URL中的模板变量映射到功能处理方法的参数上。
@RequestMapping(value="/users/{userId}/topics/{topicId}")
public String test(@PathVariable int userId,
@PathVariable int topicId){
return "index";
}
如请求的 URL为"/users/123/topics/456",则自动将URL 中模板变量{userId}和{topicId}绑定到通过@PathVariable注解的同名参数上,即入参后userId=123、topicId=456。
如果参数不同名,则需要自己手动指定
@RequestMapping(value="/users/{uid}/topics/{tid}")
public String test(@PathVariable("uid") int userId,
@PathVariable("tid") int topicId){
return "index";
}
注意:这种方法还可以自动的把数据放到模型中,在视图渲染的时候使用,例如:
//放到模型中的数据的key为name
@RequestMapping(value="/user/{name}")
public String test(@PathVariable String name)
或者:
//放到模型中的数据的key为name
@RequestMapping(value="/user/{name}")
public String test(@PathVariable("name") String username)
//运行报错
@RequestMapping(value="/user/{name}")
public String test(@PathVariable String username)
4)@CookieValue绑定Cookie数据值
用于将请求的Cookie数据映射到功能处理方法的参数上。
public String test(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId)
如上配置将自动将JSESSIONID 值入参到sessionId 参数上,defaultValue 表示Cookie中没有JSESSIONID时默认为空。
传入参数类型也可以是javax.servlet.http.Cookie类型。
public String test(@CookieValue(value="JSESSIONID", defaultValue="") Cookie cookie)
5)@RequestHeader 绑定请求头数据
用于将请求的头信息区数据映射到功能处理方法的参数上。
@RequestMapping(value="/header")
public String test(@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value="Accept") String[] accepts)
如上配置将自动将请求头"User-Agent"的值,入参到userAgent参数上,并将"Accept"请求头值入参到accepts参数上。
6)@ModelAttribute 绑定请求参数到命令/表单对象
该注解具有如下三个作用:
1.绑定请求参数到命令对象,同时将对象存放到模型中(可以指定存放的名字)
例如在用户登录时,我们需要捕获用户登录的请求参数(用户名、密码)并封装为用户对象,此时我们可以使用@ModelAttribute绑定多个请求参数到我们的命令对象。
public String test(@ModelAttribute("my_user") User u)
和上面接收到的"第五种"情况一样,只是此处多了一个注解@ModelAttribute("my_user"),它的作用是将该绑定的命令对象以"my_user"为名称添加到模型对象中供视图页面展示使用。我们此时可以在视图页面使用${my_user.username}来获取绑定的命令对象的属性。
如果不写@ModelAttribute("my_user")这个注解,那么默认添加到模型中的名字是该类型的类名首字母小写,例如这里便是user,页面中取值就需要这样${user.username}
例如:
@RequestMapping("/test5_1")
public String test5_1(User u){
//这种情况 会把参数对象u自动的绑定到模型中
//绑定的key值为user 类型的首字母小写
System.out.println("u ="+u);
return "index";
}
@RequestMapping("/test5_2")
public String test5_2(User u,Model model){
//这种情况 也会把参数对象u自动的绑定到模型中
//绑定的key值为user 类型的首字母小写
//同时我们又手动的把对象u绑定到了模型中,key值为u
System.out.println("u ="+u);
model.addAttribute("u", u);
return "index";
}
@RequestMapping("/test5_3")
public String test5_3(@ModelAttribute("u") User u){
//这种情况 使用注解把参数对象u绑定到模型中
//绑定的key值也是自动指定的为u
System.out.println("u ="+u);
return "index";
}
2.在功能处理方法执行前,向模型中添加数据
@ModelAttribute("cityList")
public List<String> cityList(String username) {
return Arrays.asList("北京", "山东");
}
如果当前模型中没有名字为cityList的数据时,如上代码会在执行处理器中任意功能处理方法之前执行,并将返回值自动添加到模型对象中,在功能处理方法中调用Model对象中的方法containsAttribute("cityList")将会返回true。
注意:模型中数据的作用范围是request级别的
注意:此方法中依然是可以接收本次请求的参数的,默认和客户端所传参数名字保持一致,也可以使用@RequestParam指定参数名
注意:如何有俩个同名的命令对象,如下
@ModelAttribute("user")
public User getUser(String username) {
User user = new User();
user.setUsername("briup");
return user;
}
@RequestMapping(value="/model")
public String test1(@ModelAttribute("user") User user, Model model){
//输出结果为briup
System.out.println(user.getUsername());
//返回值是true
System.out.println(user == model.asMap().get("user"));
return "index";
}
说明springMVC对于模型中重名的对象,不会重复创建,默认模型中已经有了这个名字的对象,那么就直接拿出来使用
例如:
@ModelAttribute("user")
public User getUser(){
// System.out.println("in getUser");
User user = new User(1L,"zhangsan",20);
return user;
}
@RequestMapping("/test5_4")
public String test5_4(User u){
System.out.println("u ="+u);
return "index";
}
上面第一个方法getUser及其注解的含义:
当客户端访问/test5_4的时候,SpringMVC会先检查一下模型中有没有一个key为user的数据,如果有就算了,如果没有就会调用getUser方法把返回值作为数据存放到模型中,并且key值为user
上面第二个方法test5_4及其注解的含义:
首先test5_4(User u)这样写,相当于:
test5_4(@ModelAttribute User u)
这样又相当于
test5_4(@ModelAttribute("user") User u)
它的意思是先从模型中查找有没有一个key为user的对象,如果有就拿出来使用,如果没有那么就调用User类中的无参构造器创建一个新对象来使用,并且在最后还把这个对象存放到模型中,key值为user
3.把功能处理方法的返回值添加到模型数据中
@RequestMapping(value="/index")
public @ModelAttribute("u") User test3(){
User user = new User();
user.setUsername("tom");
user.setPassword("123");
return user;
}
注意:这时候SpringMVC会根据RequestToViewNameTranslator进行逻辑视图名的翻译,这个例子中也就会把"index"作为逻辑视图名进行解析
注意:对于集合类型(Collection接口的实现者们,包括数组),生成的模型对象属性名为"简单类名(首字母小写)"+"List",如List<String>生成的模型对象属性名"stringList",List<User>生成的模型对象属性名为"userList"。
例如:
public @ModelAttribute List<String> test()
public @ModelAttribute List<User> test()
7)@SessionAttributes绑定命令对象到session
@SessionAttributes(String[] value,Class[] type)
@SessionAttributes(value={},types={})
@SessionAttributes(value={"user"})写在处理器类上面
表示将模型数据中的名字为"user" 的对象存储到会话中,此处value指定将模型数据中的哪些数据(名字进行匹配)存储到会话中,此外还有一个types属性表示模型数据中的哪些类型的对象存储到会话范围内,如果同时指定value和types属性则那些名字和类型都匹配的对象才能存储到会话范围内。
注意,模型数据的作用范围是request级别的,所以一次请求过后,之前模型中的数据就没有了,@SessionAttributes注解可以将当前模型中指定的数据存放到session中,并且还可以从session中把指定数据取出来返回模型中。
1.如果模型里有名字为user的数据,并且使用了@SessionAttributes("user"),那么这个模型中的数据user会被放到session中
2.如果要从模型中拿名字为user的数据,模型中没有,这个时候就拿不到了,但是这个时候使用了@SessionAttributes("user"),那么它会帮我们把数据从session取出来放到模型中
所以处理器类的上面有没有加@SessionAttributes("user")注解,会影响到我们使用下面方式是否能拿到值
@RequestMapping("/session")
public String session(User u) {
System.out.println(u);
return "index";
}
默认只是从模型中拿名字叫user的值,如果加了@SessionAttributes("user")这个注解,还可以拿到session中的user对象
也可以指定用哪个名字拿值,例如:
@RequestMapping("/session")
public String session(@ModelAttribute("my_user") User u) {
System.out.println(u);
return "index";
}
也可以使用SessionStatus对象的方法把@SessionAttributes指定的数据从session中清除掉
@RequestMapping("/session")
public String session(User u,SessionStatus status) {
if(true){
//从session中清除注解中指定的数据
status.setComplete();
}
System.out.println(u);
return "index";
}
8)@Value绑定SpEL表示式
用于将一个SpEL表达式结果映射到到功能处理方法的参数上。
public String test(@Value("#{systemProperties['java.vm.version']}") String jvmVersion){
System.out.println(jvmVersion);
return "index";
}
SpEL表达式的使用,例如:
取名字为stu的bean的name字段的值,这里指的是property
public String test(@Value("#{stu.name}") String username)
对其他bean中某个方法的引用
public String test(@Value("#{stu.sayHello()}") String username)
public String test(@Value("#{stu.sayHello('tom')}") String username)
表达式(?.)可以确保在sayHello()返回不为空的情况下调用toUpperCase()方法,如果返回空那么不继续调用后面的方法
public String test(@Value("#{stu.sayHello()?.toUpperCase()}") String username)
如果要调用的某个类是外部类,而不是spring中定义的bean,使用表达式T()
public String test(@Value("#{T(java.lang.Math).random()}") String username)
例如:
@RequestMapping("/test7_1")
public String test7_1(@Value("hello") String msg){
//注意这里不是使用msg来接收客户端参数(也接收不到)
//而是使用@Value注解来给msg赋值
//同时这里面可以使用SpEL
System.out.println("msg = "+msg);
return "index";
}
@RequestMapping("/test7_2")
public String test7_2(@Value("#{util.sayHello()}") String msg){
System.out.println("msg = "+msg);
return "index";
}
9)@InitBinder注解
可以解决类型转换的问题
例如一个表单提交数据给Controller,表单中有日期数据
@Controller
public class InitBinderController{
@RequestMapping(value="/register",method=RequestMethod.GET)
public String registerPage(){
return "register";
}
@RequestMapping(value="/register",method=RequestMethod.POST)
public String register(User user){
System.out.println("user = "+user);
return "index";
}
}
表单提交的时候有一个字符串形式的日期数据"1999-10-23",SpringMVC默认不支持这个格式的转换,所以需要手动配置日期类型的转换,否则会报错。
在这个Controller中加入写一个转换的方法,加上@InitBinder即可
@Controller
public class InitBinderController{
@InitBinder
public void test(WebDataBinder binder){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
//true表示允许为空
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
@RequestMapping(value="/register",method=RequestMethod.GET)
public String registerPage(){
return "register";
}
@RequestMapping(value="/register",method=RequestMethod.POST)
public String register(User user){
System.out.println("user = "+user);
return "index";
}
}
在Spring3中引入了一个Converter接口,它支持从一个任意类型转为另一个任意类型。
例如:
自己编写的转换器代码:
public class StringToDateConverter implements Converter<String, Date>{
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date convert(String str) {
Date date = null;
try {
if(str!=null&&!"".equals(str.trim())){
date = dateFormat.parse(str);
}
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
spring的配置文件:例如spring的一个工厂类,产生一个转换服务,同时把我们自己的转换器注入进去,可以有多个
<bean name="formatService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.briup.web.converter.StringToDateConverter"></bean>
</set>
</property>
</bean>
//在mvc标签中指定这个转换服务器
<mvc:annotation-driven conversion-service="formatService"/>
还有另外一种解决日期的方式,就是利用spring提供的一个专门针对日期转换的注解:@DateTimeFormat(pattern="yyyy-MM-dd")
例如:
public class User {
private String username;
private String password;
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date dob;
get/set
}
注意这个时候就不需要我们再写自定义的转换器了,但是如果是其他类型的转换,我们还得需要自己编写自定义的类型转换器
10).@Scope注解
Controller默认情况下和servlet一样也是单例,但是spring提供了一个@Scope注解可以让Controller对象变为非单例,只需在Controller类上面加入@Scope("prototype")即可
例如:
@Controller
@RequestMapping("/hello")
@Scope("prototype")
public class HelloWorldController{
....
....
}
11)@RequestBody注解
可以接收客户端ajax请求中的json数据并且转换为对象,但是只能接收post请求中的数据,因为post请求的数据在请求体中(request-body).
需要引入操作json的相关jar包:
jackson-core-2.8.5.jar
jackson-annotations-2.8.5.jar
jackson-databind-2.8.5.jar
或者
jackson-mapper-asl-1.9.13.jar
jackson-core-asl-1.9.13.jar
注意:在javascript中,json对象和字符串之间的转换:
JSON.stringify(obj)将JSON转为字符串。
JSON.parse(string) 将字符串转为JSON格式;
例如:
处理器中代码:
@RequestMapping(value="/json/update",consumes="application/json",method=RequestMethod.POST)
public void update(@RequestBody User user,Writer out)throws Exception{
System.out.println("user = "+user);
out.write("helloworld");
}
页面js中代码:
注意:http中的Content-Type,在jquery中是contentType
$("#btn").on("click",function(){
var json = {username:"tom",password:"123",dob:"1999-10-27"};
$.ajax({
type:"post",
url:"json/update",
contentType:"application/json",
data:JSON.stringify(json),
dataType:"text",
success:function(data){
console.log("data = "+data);
}
});
});
客户端使用ajax发送json数据给Controller,Controller里面接收json数据并且转换为对象
1.ajax请求发送的时候要指定为post方式
2.ajax请求发送的时候要指定contentType:"application/json"
3.ajax请求发送的时候要把json对象转换为字符串再发送
4.Controller中要使用@RequestBody指定接收数据的参数
5.项目中要引入json相关的jar包
6.如果此ajax请求还希望Controller返回的数据也是json格式的,那么需要在发送ajax请求的时候指定dataType:"json",
7.Controller中的方法要返回json格式的数据给客户端,可以使用@ResponseBody标签 或者 在方法中自己使用response对象获得io流给客户端写回去
注意:
ajax发送请求的时候,请求头中的Content-Type默认值是: application/x-www-form-urlencoded,表示当前请求中如果有数据的话,将会是key=value&key=value的形式
12)@ResponseBody注解
该注解用于将处理器中功能处理方法返回的对象,经过转换为指定格式后,写入到Response对象的body数据区(响应正文).一般返回的数据不是html标签的页面,而是其他某种格式的数据时使用,例如给ajax请求返回json数据.
例如:在@RequestBody的例子中进行修改
处理器中代码:添加了@ResponseBody,修改了方法的返回值
@RequestMapping(value="/update",consumes="application/json",method=RequestMethod.POST)
@ResponseBody
public User update(@RequestBody User user)throws Exception{
System.out.println("user = "+user);
user.setUsername("张三");
user.setPassword("123");
user.setDob(new Date());
return user;
}
页面js中代码: 注意这里的dataType属性的值
$("#btn").on("click",function(){
var json = {username:"tom",password:"123",dob:"1999-10-27"};
$.ajax({
type:"post",
url:"json/update",
contentType:"application/json",
data:JSON.stringify(json),
dataType:"json",
success:function(data){
console.log("data = "+data);
console.log(data.username);
console.log(data.password);
console.log(data.dob);
}
});
});
这里还是会有日期的问题,就是把user对象放入响应正文返回给客户端后,被转换为了json对象,从firebug中可以看出,这个返回的json对象为{"username":"张三","password":"123","dob":1478621620119},它默认把dob这个日期对象转为了一个时间戳
如果我们想按照自己的日期格式进行转换,那么需要这样处理:
自定义一个json的日期类型格式化类:
public class DateJsonSerializer extends JsonSerializer<Date> {
@Override
public void serialize(Date value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
String formattedDate = formatter.format(value);
jgen.writeString(formattedDate);
}
}
在User类中的getDob方法上添加注解
@JsonSerialize(using=DateJsonSerializer.class)
public Date getDob() {
return dob;
}
@ResponseBody注解可以处理以下常见类型的返回值,如果可以的话还会把返回值转换为json格式字符串
1.单值(基本数据类型和字符串)
这时候ajax中要设置dataType: "text"
2.bean对象(例如User对象、Student对象等,对象中需要有property)
3.数组
4.List/Set集合
5.Map集合