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

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的属性!!!

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

/**
 * 被@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());
    }
    
}
<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!

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

  1. wh

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

Leave a Reply

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