在默认情况下,当ModelMap中的属性作用域是request级别时,也就是说,当本次请求结束后,ModelMap中的属性将销毁。如果希望在多个请求中共享ModelMap中的属性,必须将其属性转存到session中,这样ModelMap的属性才会被跨请求访问;
spring允许我们有选择地指定ModelMap中的哪些属性需要转存到session中,以便下一个请求属对应的ModelMap的属性列表中还能访问到这些属性。
注意:这里所说的将ModelMap中的属性转存Seesion中,不单单指ModelMap,其实控制器向jsp传值绑定数据的五种方式(ModelMap、Model、Session、Map、Request)都可以将其属性转存到Session中,下面就直接以ModelMap为例进行说明。
SpringMVC为我们提供这样一个注解来实现上面的场景:
@SessionAttributes
:将ModelMap的属性值共享到session中。
1、@SessionAttributes注解:
【重点】:通过@SessionAttributes注解将ModelMap中的属性存入到Session中以后:
- 可以在本次请求对应的jsp页面中使用
request.getAttribute("");
和session.getAttribute("");
获取到该属性;- 还可以通过下次其它请求对应的jsp页面中使用
session.getAttribute("");
和ModelMap#get("");
获得到该属性;- 也可以通过@ModelAattribute注解标识的参数中获取。
补充:request.getAttribute("")的scope为request,也就是在当前请求,从控制器到jsp携带的ModelMap对象中获取,而session.getAattribute("")的scope为session,是从Session中去获取属性值。
注意:@SessionAttributes注解只能使用在类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttributes设置的参数只用于暂时的传递(存入sessionAttributeStore),而不是长期的保存,长期保存的数据还是要放到Session中。
有两种方式将ModelMap中的属性值共享到session中:
- 使用注解的value属性:可以通过属性名指定需要放到会话中的属性;
- 使用注解的types属性:还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。
方式一:通过注解的value属性:
@Controller
@SessionAttributes("user") //将ModelMap中key为user的属性共享到session中
public class DemoController {
@RequestMapping("/hello")
public String hello(ModelMap model) {
//向ModelMap中添加key为user和user1的属性
model.addAttribute("user", new User(520, "U love me"));
model.addAttribute("user1", new User("I love U"));
return "result";
}
}
@Controller
public class Demo2Controller {
@RequestMapping(value = "/demo")
public String dmeo(){
return "result";
}
}
- 在DemoController的hello方法中,将key为user和user1的User对象存入ModelMap中,在hello请求对应的jsp页面可以获得到相应key为user和user1的User对象;同时通过注解@SessionAttributes将key为user的User对象存入到Session中。
- 在Demo2Controller的demo方法中,我们并没有向jsp页面传入任何的值,正是因为在DemoController中通过注解已将key为user的属性存入到session中,所以在demo请求对应的jsp页面中还是可以获得到key为user对应的User对象。
- 上面的例子中,我们通过属性名的方式,将对应的属性存入到session中。
方式二:通过注解的types属性:
@SessionAttributes(types = {User.class})
@Controller
public class DemoController{
@RequestMapping("/hello")
public String hello(Map<String, Object> map){
map.put("user1", new User(520, "U love me"));
return "hello";
}
}
@Controller
public class Demo2Controller {
@RequestMapping(value = "/demo")
public String dmeo(){
return "result";
}
}
- 类似的例子,这次是通过@SessionAttributes注解的types属性指定了User类型,所以它会将User类型的所有属性值存入到Session中,这样我们再demo请求所对应的jsp页面可以正常的获取到key为user1对应的属性值。
补充:这里我们仅将一个ModelMap的属性放入Session中,其实@SessionAttributes注解允许通过字符串数组的方式指定多个属性,例:
@SessionAttributes(types = {User.class, String.class}, value={"user1", "user2"}
。
@SessionAttributes使用后可以调用
SessionStatus.setComplete
来清除,这个方法只是清除SessionAttribute里的参数,而不会应用Session中的参数。
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status){
if (errors.hasErrors) {
// ...
status.setComplete(); //调用SessionStatus.setComplete来清除
// ...
}
}
}
2、@SessionAttribute注解:
Springmvc中有一个和@SessionAttributes很像的注解:@SessionAttribute注解标注在方法参数上,它的作用是获取session中可能预先存入的属性值。(我自己测试该注解并没有什么作用,想在当前控制器内获取其它控制器通过@SessionAttributes预先存入Session中的值,主要还是需要在当前控制器的类上添加@SessionAttributes注解的方式获取,看下面测试)
测试1:
在DemoController中通过demo请求将key为user值为User user的属性通过@SessionAttributes(“user”)存入到session中,在demo请求对应的demo.jsp页面中可以通过
${user}
获得key为user对应的属性值,也可以通过${sessionScope.user}
获取到该属性值;
@Controller
@SessionAttributes("user")
public class DemoController {
@RequestMapping("demo")
public String demo(Map<String, Object> map){
User user = new User(520, "U love me");
map.put("user", user);
return "result";
}
}
此时,我在Demo2Controller中通过demo2请求的方法中,将User user参数标注为@SessionAttribute(“user”),此时在方法中并没有获取到Session中的key为user的属性值,在demo2请求对应的jsp页面中能
${sessionScope.user}
属性值,不能通过${user}
获取到对应的属性值,控制台打印:UserId: 0、UserName: null;
结论:说明只在方法参数上添加@SessionAttribute注解,在当前方法中并不能从session获取到key为user的属性值,也没有将其属性值存入到当前控制器的ModelMap中,@SessionAttribute注解没生效。
测试2:
DemoController不变,在Demo2Controller类上添加@SessionAttributes(“user”)注解,将参数中的@SessionAttribute(“user”)注解去掉,然后通过demo2方法对应的jsp页面中,成功通过
${sessionScope.user}
和${user}
获取到了对应的属性值,控制台打印:UserId: 520,UserName: U love me。
@Controller
@SessionAttributes("user")
public class DemoController {
@RequestMapping("demo")
public String demo(Map<String, Object> map){
User user = new User(520, "U love me");
map.put("user", user);
return "result";
}
}
@Controller
@SessionAttributes("user")
public class Demo2Controller {
@RequestMapping("demo2")
public String demo2(User user){
System.out.println("UserId: " + user.getId());
System.out.println("UserName: " + user.getDesc());
return "result";
}
}
结论:在方法参数上添加@SessionAttribute注解并没有什么实际意义,要想在当前控制器中获取Session中的属性值,必须在当前控制器类上添加@SessionAttributes注解才能生效。
测试3:
DemoController不变,将Demo2Controller类上将
@SessionAttributes("user1")
注解的value值改为user1,在demo2请求对应的jsp页面中能${sessionScope.user}
属性值,不能通过${user}
获取到对应的属性值,控制台打印:UserId: 0、UserName: null;
@Controller
@SessionAttributes("user1")
public class Demo2Controller {
@RequestMapping("demo2")
public String demo2(User user){
System.out.println("UserId: " + user.getId());
System.out.println("UserName: " + user.getDesc());
return "result";
}
}
结论:要想在当前控制器获取到预先存入到Session的属性值,@SessionAttributes注解的value值必须和预先存入的值相对应。
测试4:
Demo2Controller和测试3保持不变,将DemoController类上的@SessionAttribute(“user1”)注解上的value值改为user1,将map中put的key也改为user1,保证将key为user1的值存入到Session中,结果在demo2对应的jsp页面中,成功通过
${sessionScope.user}
和${user}
获取到了对应的属性值,控制台打印:UserId: 0、UserName: null;
@Controller
@SessionAttributes("user1")
public class DemoController {
@RequestMapping("demo")
public String demo(Map<String, Object> map){
User user = new User(520, "U love me");
map.put("user1", user);
return "result";
}
}
@Controller
@SessionAttributes("user1")
public class Demo2Controller {
@RequestMapping("demo2")
public String demo2(User user){
System.out.println("UserId: " + user.getId());
System.out.println("UserName: " + user.getDesc());
return "result";
}
}
结论:在demo2请求对应的jsp页面可以通过
${sessionScope.user}
和${user}
获取到了对应的属性值,说明@SessionAttributes注解已经成功接收到key为user1的属性值,并绑定到demo2请求的ModelMap中传入jsp页面,但是控制台并打印UserId: 0、UserName: null,说明demo2方法并没有接收到该属性值。
测试5:
在测试4的基础上,在Demo2Controller的参数添加@SessionAttribute(“user1”)注解,结果和测试3相同,并没有任何改变;
@Controller
@SessionAttributes("user1")
public class DemoController {
@RequestMapping("demo")
public String demo(Map<String, Object> map){
User user = new User(520, "U love me");
map.put("user1", user);
return "result";
}
}
@Controller
@SessionAttributes("user1")
public class Demo2Controller {
@RequestMapping("demo2")
public String demo2(@SessionAttribute("user1") User user){
System.out.println("UserId: " + user.getId());
System.out.println("UserName: " + user.getDesc());
return "result";
}
}
结论:@SessionAttribute真的一点作用都没有!!!
测试6:
将DemoController的@SessionAttributes(“user1”)注解value值改为value1,map.put(“user1”,user)的key也改为user1,将Demo2Controller类上的@SessionAttributes注解改为通过types属性接收,结果405报错,提示无法从session中获取key为user的属性值;
@Controller
@SessionAttributes("user1")
public class DemoController {
@RequestMapping("demo")
public String demo(Map<String, Object> map){
User user = new User(520, "U love me");
map.put("user1", user);
return "result";
}
}
@Controller
@SessionAttributes(types = User.class)
public class Demo2Controller {
@RequestMapping("demo2")
public String demo2(User user){
System.out.println("UserId: " + user.getId());
System.out.println("UserName: " + user.getDesc());
return "result";
}
}
结论:通过@SessionAttributes注解的types属性接收时,会根据类型的首字母小写对应的key去Session中获取对应的属性。
测试7:
修改DemoController类上@SessionAttributes(“user”)改回user,map.put(“user”,user),结果成功通过
${sessionScope.user}
和${user}
获取到了对应的属性值,控制台打印:UserId: 520,UserName: U love me;
@Controller
@SessionAttributes("user")
public class DemoController {
@RequestMapping("demo")
public String demo(Map<String, Object> map){
User user = new User(520, "U love me");
map.put("user", user);
return "result";
}
}
上面接收的类型为User类型,如果换为String类型,并且同时在Session中存入两个相同的类型的值,再根据types接收会怎么样呢,看下面测试8;
测试8:
DemoController存入两个String类型的key:abc和bcd的属性值到Session中,在DemoController2中通过@SessionAttributes()的types获取属性值,结果在demo2对应的jsp页面中可以通过
${abc}
、${bcd}
、${sessionScope.abc}
、${sessionScope.bcd}
获取到对应的属性值,控制台打印:abc: null,bcd: null;
@Controller
@SessionAttributes("abc,bdc")
public class DemoController {
@RequestMapping("demo")
public String demo(Map<String, Object> map){
map.put("abc", "abcStr");
map.put("bcd", "bcdStr");
return "result";
}
}
@Controller
@SessionAttributes(types = String.class)
public class Demo2Controller {
@RequestMapping("demo2")
public String demo2(String abc, String bcd) {
System.out.println("abc: " + abc);
System.out.println("bcd: " + bcd);
return "result";
}
}
结论:在Session中存在多个同类型属性,而@SessionAttributes根据types匹配时,在请求对应的jsp页面中可以获取到对应的属性值,而在当前方法中则获取不到,此时必须通过value去获取。
总结:
@SessionAttributes注解可以将当前Controller向jsp绑定的数据转存到Session中,如果在其它Controller中想获取到预先存入Session中的值,也要在该Controller的类上添加@SessionAttributes注解,并且保证存入和取出的value值相匹配,如果根据types从Session中获取值时,必须保证同类型只有一个属性值保存到Session中否则必须通过value去获取。
补充:在控制器的方法中,如果想从Session获取到自定义的key值对应的属性,可以使用@ModelAttribute进行获取。
3、控制器方法参数初始化顺序:【重点】
假设控制器中方法带有User user参数:(底层初始化顺序)
- 如果请求参数中带有User user,那么不论是否标有其它注解,都会使用请求中的User user值(即使有其它注解其值也会被请求中的参数值覆盖);
- 如果发现请求中没有User user参数,那么会检查该类上是否标有@SessionAttributes注解(scope为session),如果有会尝试去Session中获取;
- 如果发现类上没有@SessionAttributes属性,则会检查参数上是否有@ModelAttribute属性(scope为request),如果有则尝试从当前请求的model中获取;
- 如果都没有获取到值,则会初始化一个空的对象并存入到当前请求的ModelMap中。