Spring中@ModelAttribute以及超级坑爹的@SessionAttributes注解

重要的结论要先说出来,千万不要用@SessionAttributes来保存http session的属性!!!

在Spring MVC中,@ModelAttribute注解作用在方法或者方法参数上,表示将被注解的方法的返回值或者是被注解的参数作为Model的属性加入到Model中,然后Spring框架自会将这个Model传递给ViewResolver。Model的生命周期只有一个http请求的处理过程,请求处理完后,Model就销毁了。

@SessionAttributes(attrName)注解的意思就是将Model中的attrName属性作为session的属性保存下来。

但关键是它并不仅仅是将这个属性放到HttpSession中这么简单!它的做法大概可以理解为将Model中的被注解的attrName属性保存在一个SessionAttributesHandler中,在每个RequestMapping的方法执行后,这个SessionAttributesHandler都会将它自己管理的“属性”从Model中写入到真正的HttpSession;同样,在每个RequestMapping的方法执行前,SessionAttributesHandler会将HttpSession中的被@SessionAttributes注解的属性写入到新的Model中。注意!它的生命周期很别扭哦!

所以如果在方法中用HttpSession.removeAttribute()来删除被@SessionAttributes注解的属性,你会发现根本没有效果,因为方法执行结束后,它又被SessionAttributesHandler从Model中写回HttpSession了。可以用一个SessionStatus.setComplete()方法来让SessionAttributesHandler在方法结束后不接手工作,从而达到不写入HttpSession的目的,但这方法太鸡巴扯了我感觉。

所以,最重要的是,千万不要使用@SessionAttributes注解来管理session的属性!!!

下面贴一个简单的例子,自己试一下就知道了

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/**
* 被@SessionAttributes注解的类,会有一个SessionAttributesHandler加入它的HandlerExecutionChain中
* 在每个RequestMapping的方法执行前,SessionAttributesHandler都会将testSession从HttpSession写入Model
* 在每个RequestMapping的方法执行后,SessionAttributesHandler都会将testSession从Model写入HttpSession
*/
@Controller
@RequestMapping("/test")
@SessionAttributes("testSession")
public class TestController {
// 每次调用Controller的其他方法前,会先调用这个被@ModelAttribute注解的方法
// 并将返回值作为model的属性保存在model中
@ModelAttribute("time")
public String testModelAttribute() {
System.out.println("被@ModelAttribute注解的方法会在该Controller的其他方法执行前先执行一次,"
+ "然后将返回值放入model");
return new Date().toString();
}
@RequestMapping("/page1")
public String page1(Model model, HttpSession session) {
// 现在,model中已经有了time属性
// 另外,由于在类上注解了@SessionAttributes("testSession"),所以方法被执行前,SessionAttributesHandler会去HttpSession中查找testSession属性写入model
model.addAttribute("testSession", "what the fuck!");
model.addAttribute("test1", "11111");
session.setAttribute("realSession", "我才是真的http session属性");
System.out.println("test1:");
System.out.println(model.toString());
printSession(session);
return "test";
// 由于在类上注解了@SessionAttributes("testSession"),所以方法结束后,SessionAttributesHandler会将model中的testSession属性写入model
}
@RequestMapping("/page2")
public String page2(Model model, HttpSession session) {
model.addAttribute("test2", "22222");
System.out.println("test2:");
System.out.println(model.toString());
printSession(session);
session.removeAttribute("testSession");
// 由于有@SessionAttributes("testSession")在,这个话根本就无法删除testSession属性
System.out.println("remove httpsession attr:");
printSession(session); // 现在打印出来的httpsession中确实没有testSession属性,但方法结束后又被SessionAttributesHandler写回去了
return "test";
}
@RequestMapping("/page3")
public String page3(Model model, HttpSession session, SessionStatus sessionStatus) {
model.addAttribute("test3", "33333");
System.out.println("test3:");
System.out.println(model.toString());
printSession(session);
session.removeAttribute("testSession");
sessionStatus.setComplete(); // 告诉SessionAttributesHandler你可以暂时下班了,本方法结束后不用处理@SessionAttributes了
System.out.println("remove httpsession attr:");
printSession(session);
return "test";
}
public void printSession(HttpSession session) {
StringBuffer sb = new StringBuffer("httpsession: [");
Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
sb.append(name + "=" + session.getAttribute(name) + ", ");
}
sb.append("]");
System.out.println(sb.toString());
}
}
/** * 被@SessionAttributes注解的类,会有一个SessionAttributesHandler加入它的HandlerExecutionChain中 * 在每个RequestMapping的方法执行前,SessionAttributesHandler都会将testSession从HttpSession写入Model * 在每个RequestMapping的方法执行后,SessionAttributesHandler都会将testSession从Model写入HttpSession */ @Controller @RequestMapping("/test") @SessionAttributes("testSession") public class TestController { // 每次调用Controller的其他方法前,会先调用这个被@ModelAttribute注解的方法 // 并将返回值作为model的属性保存在model中 @ModelAttribute("time") public String testModelAttribute() { System.out.println("被@ModelAttribute注解的方法会在该Controller的其他方法执行前先执行一次," + "然后将返回值放入model"); return new Date().toString(); } @RequestMapping("/page1") public String page1(Model model, HttpSession session) { // 现在,model中已经有了time属性 // 另外,由于在类上注解了@SessionAttributes("testSession"),所以方法被执行前,SessionAttributesHandler会去HttpSession中查找testSession属性写入model model.addAttribute("testSession", "what the fuck!"); model.addAttribute("test1", "11111"); session.setAttribute("realSession", "我才是真的http session属性"); System.out.println("test1:"); System.out.println(model.toString()); printSession(session); return "test"; // 由于在类上注解了@SessionAttributes("testSession"),所以方法结束后,SessionAttributesHandler会将model中的testSession属性写入model } @RequestMapping("/page2") public String page2(Model model, HttpSession session) { model.addAttribute("test2", "22222"); System.out.println("test2:"); System.out.println(model.toString()); printSession(session); session.removeAttribute("testSession"); // 由于有@SessionAttributes("testSession")在,这个话根本就无法删除testSession属性 System.out.println("remove httpsession attr:"); printSession(session); // 现在打印出来的httpsession中确实没有testSession属性,但方法结束后又被SessionAttributesHandler写回去了 return "test"; } @RequestMapping("/page3") public String page3(Model model, HttpSession session, SessionStatus sessionStatus) { model.addAttribute("test3", "33333"); System.out.println("test3:"); System.out.println(model.toString()); printSession(session); session.removeAttribute("testSession"); sessionStatus.setComplete(); // 告诉SessionAttributesHandler你可以暂时下班了,本方法结束后不用处理@SessionAttributes了 System.out.println("remove httpsession attr:"); printSession(session); return "test"; } public void printSession(HttpSession session) { StringBuffer sb = new StringBuffer("httpsession: ["); Enumeration<String> names = session.getAttributeNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); sb.append(name + "=" + session.getAttribute(name) + ", "); } sb.append("]"); System.out.println(sb.toString()); } }
/**
 * 被@SessionAttributes注解的类,会有一个SessionAttributesHandler加入它的HandlerExecutionChain中
 * 在每个RequestMapping的方法执行前,SessionAttributesHandler都会将testSession从HttpSession写入Model
 * 在每个RequestMapping的方法执行后,SessionAttributesHandler都会将testSession从Model写入HttpSession
 */
@Controller
@RequestMapping("/test")
@SessionAttributes("testSession")
public class TestController {
    
    // 每次调用Controller的其他方法前,会先调用这个被@ModelAttribute注解的方法
    // 并将返回值作为model的属性保存在model中
    @ModelAttribute("time")
    public String testModelAttribute() {
        System.out.println("被@ModelAttribute注解的方法会在该Controller的其他方法执行前先执行一次,"
                + "然后将返回值放入model");
        return new Date().toString();
    }
    
    @RequestMapping("/page1")
    public String page1(Model model, HttpSession session) {
        // 现在,model中已经有了time属性
        // 另外,由于在类上注解了@SessionAttributes("testSession"),所以方法被执行前,SessionAttributesHandler会去HttpSession中查找testSession属性写入model
        model.addAttribute("testSession", "what the fuck!");
        model.addAttribute("test1", "11111");
        session.setAttribute("realSession", "我才是真的http session属性");
        
        System.out.println("test1:");
        System.out.println(model.toString());
        printSession(session);
        
        return "test";
        // 由于在类上注解了@SessionAttributes("testSession"),所以方法结束后,SessionAttributesHandler会将model中的testSession属性写入model
    }
    
    @RequestMapping("/page2")
    public String page2(Model model, HttpSession session) {
        model.addAttribute("test2", "22222");
        System.out.println("test2:");
        System.out.println(model.toString());
        
        printSession(session);
        session.removeAttribute("testSession"); 
        // 由于有@SessionAttributes("testSession")在,这个话根本就无法删除testSession属性
        System.out.println("remove httpsession attr:");
        printSession(session);  // 现在打印出来的httpsession中确实没有testSession属性,但方法结束后又被SessionAttributesHandler写回去了       
        
        return "test";
    }
    
    @RequestMapping("/page3")
    public String page3(Model model, HttpSession session, SessionStatus sessionStatus) {
        model.addAttribute("test3", "33333");
        System.out.println("test3:");
        System.out.println(model.toString());
        printSession(session);
        session.removeAttribute("testSession");
        sessionStatus.setComplete();    // 告诉SessionAttributesHandler你可以暂时下班了,本方法结束后不用处理@SessionAttributes了
        System.out.println("remove httpsession attr:");
        printSession(session);        
        return "test";
    }
    
    public void printSession(HttpSession session) {
        StringBuffer sb = new StringBuffer("httpsession: [");
        Enumeration<String> names = session.getAttributeNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            sb.append(name + "=" + session.getAttribute(name) + ", ");
        }
        sb.append("]");
        System.out.println(sb.toString());
    }
    
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<html>
<head></head>
<body>
time:${time!} <br />
realSession: ${realSession!} <br />
testSession: ${testSession!} <br />
test1: ${test1!} <br />
test2:${test2!} <br />
test3:${test3!}
</body>
</html>
<html> <head></head> <body> time:${time!} <br /> realSession: ${realSession!} <br /> testSession: ${testSession!} <br /> test1: ${test1!} <br /> test2:${test2!} <br /> test3:${test3!} </body> </html>
<html>
<head></head>
<body>
	time:${time!} <br /> 
	realSession: ${realSession!} <br /> 
	testSession: ${testSession!} <br /> 
	test1: ${test1!} <br />
	test2:${test2!} <br /> 
	test3:${test3!}
</body>
</html>

重要的事情说第三遍!前往不要用@SessionAttributes注解!要自己手动来操作HttpSession!

1 thought on “Spring中@ModelAttribute以及超级坑爹的@SessionAttributes注解”

  1. 我一个同事在项目里就大笔一挥用了这个@SessionAttributes,我这几天接手他的代码琢磨来琢磨去头发都要琢磨没了*滑稽
    看了你这篇顿时明白多了

Leave a Comment

Your email address will not be published. Required fields are marked *