SpringMVC @SessionAttributes 使用详解以及源码分析

SpringMVC @SessionAttributes 使用详解以及源码分析

 

@sessionattributes

复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
    String[] value() default {};
    Class[] types() default {};
}
复制代码

 @sessionattributes注解应用到Controller上面,可以将Model中的属性同步到session当中。

先看一个最基本的方法

复制代码
@Controller
@RequestMapping("/Demo.do")
@SessionAttributes(value={"attr1","attr2"})
public class Demo {
    
    @RequestMapping(params="method=index")
    public ModelAndView index() {
        ModelAndView mav = new ModelAndView("index.jsp");
        mav.addObject("attr1", "attr1Value");
        mav.addObject("attr2", "attr2Value");
        return mav;
    }
    
    @RequestMapping(params="method=index2")
    public ModelAndView index2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2) {
        ModelAndView mav = new ModelAndView("success.jsp");
        return mav;
    }
}
复制代码

index方法返回一个ModelAndView 其中包括视图index.jsp 和 两个键值放入model当中,在没有加入@sessionattributes注解的时候,放入model当中的键值是request级别的。

现在因为在Controller上面标记了@SessionAttributes(value={"attr1","attr2"}) 那么model中的attr1,attr2会同步到session中,这样当你访问index 然后在去访问index2的时候也会获取这俩个属性的值。

当需要清除session当中的值得时候,我们只需要在controller的方法中传入一个SessionStatus的类型对象 通过调用setComplete方法就可以清除了。

 

@RequestMapping(params="method=index3")
  public ModelAndView index4(SessionStatus status) {
  ModelAndView mav = new ModelAndView("success.jsp");
  status.setComplete();
  return mav;
}

 

 

 

 

下面就直接分析代码来看Spring是如可封装的。

首先我们需要看2个类 DefaultSessionAttributeStore和SessionAttributesHandler

DefaultSessionAttributeStore这个类是用来往WebRequest存取数据的工具类,WebRequest是Spring包装的HttpServletRequest,大家理解为普通的HttpServletRequest就行了。

复制代码
public class DefaultSessionAttributeStore implements SessionAttributeStore {

    private String attributeNamePrefix = "";


    public void setAttributeNamePrefix(String attributeNamePrefix) {
        this.attributeNamePrefix = (attributeNamePrefix != null ? attributeNamePrefix : "");
    }


    public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {
        Assert.notNull(request, "WebRequest must not be null");
        Assert.notNull(attributeName, "Attribute name must not be null");
        Assert.notNull(attributeValue, "Attribute value must not be null");
        String storeAttributeName = getAttributeNameInSession(request, attributeName);
        request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);
    }

    public Object retrieveAttribute(WebRequest request, String attributeName) {
        Assert.notNull(request, "WebRequest must not be null");
        Assert.notNull(attributeName, "Attribute name must not be null");
        String storeAttributeName = getAttributeNameInSession(request, attributeName);
        return request.getAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
    }

    public void cleanupAttribute(WebRequest request, String attributeName) {
        Assert.notNull(request, "WebRequest must not be null");
        Assert.notNull(attributeName, "Attribute name must not be null");
        String storeAttributeName = getAttributeNameInSession(request, attributeName);
        request.removeAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
    }


    protected String getAttributeNameInSession(WebRequest request, String attributeName) {
        return this.attributeNamePrefix + attributeName;
    }

}
复制代码

 


Spring会为每一个Controller初始化一个SessionAttributesHandler实例,用来记录@SessionAttributes(value={"attr1","attr2"})里面的属性信息,当需要同步model的值时,会先判断是否在SessionAttributes当中定义。

复制代码
public class SessionAttributesHandler {

    private final Set<String> attributeNames = new HashSet<String>();

    private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();

    private final Set<String> knownAttributeNames = Collections.synchronizedSet(new HashSet<String>(4));

    private final SessionAttributeStore sessionAttributeStore;

    public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
        Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
        this.sessionAttributeStore = sessionAttributeStore;

        SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
        if (annotation != null) {
            this.attributeNames.addAll(Arrays.asList(annotation.value()));
            this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
        }

        this.knownAttributeNames.addAll(this.attributeNames);
    }


    public boolean hasSessionAttributes() {
        return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
    }


    public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
        Assert.notNull(attributeName, "Attribute name must not be null");
        if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
            this.knownAttributeNames.add(attributeName);
            return true;
        }
        else {
            return false;
        }
    }


    public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
        for (String name : attributes.keySet()) {
            Object value = attributes.get(name);
            Class<?> attrType = (value != null) ? value.getClass() : null;

            if (isHandlerSessionAttribute(name, attrType)) {
                this.sessionAttributeStore.storeAttribute(request, name, value);
            }
        }
    }

    public Map<String, Object> retrieveAttributes(WebRequest request) {
        Map<String, Object> attributes = new HashMap<String, Object>();
        for (String name : this.knownAttributeNames) {
            Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
            if (value != null) {
                attributes.put(name, value);
            }
        }
        return attributes;
    }


    public void cleanupAttributes(WebRequest request) {
        for (String attributeName : this.knownAttributeNames) {
            this.sessionAttributeStore.cleanupAttribute(request, attributeName);
        }
    }


    Object retrieveAttribute(WebRequest request, String attributeName) {
        return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
    }

}
复制代码

 

 

当我们访问controller中的一个方法时,会调用RequestMappingHandlerAdapter类当中的invokeHandleMethod的方法。

 View Code

 

我们看这行代码

modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);

跟进去看源码

 View Code

 

其中这两句是关键的地方,把session的值取出来全部放入到ModelAndViewContainer当中去,这样我就可以再controller当中的方法中获取了,当然直接从session中拿也是可以的。

Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
mavContainer.mergeAttributes(attributesInSession);

 

在controller中获取sessionAttributes的只有两种方式。

一、public ModelAndView index2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2) 

二、public ModelAndView index2(ModelMap map)

这俩种方式的区别是,如果使用@ModelAttribute属性获取值,并且@SessionAttributes注解当中还设置了该属性,当属性为null时会跑出异常,因为这几行代码。

复制代码
for (String name : findSessionAttributeArguments(handlerMethod)) {
            if (!mavContainer.containsAttribute(name)) {
                Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
                if (value == null) {
                    throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
                }
                mavContainer.addAttribute(name, value);
            }
}
复制代码

 

 

以上分析的都是取值的过程,那么Spring是如何将Model中的数据同步到session当中的呢。

 

我们看上面invokeHandleMethod方法中最后一句话 return getModelAndView(mavContainer, modelFactory, webRequest); 我们跟进去看源码。

 View Code

 

有一行代码是modelFactory.updateModel(webRequest, mavContainer); 我们在跟进去看源码。

我们看到首先判断一下ModelAndViewContainer中的SessionStatus是否是完成状态,如果是TRUE就清除session中的值,反之将ModelAndViewContainer中的model值设置进去。

 View Code

 

 

看到这里可能还有一个疑问,就是我们的数据时设置到ModelAndView里面的,那么这些数据时如何跑到ModelAndViewContainer当中去的呢,其实是在方法执行完成之后Spring帮我们做的,

具体细节查看 ModelAndViewMethodReturnValueHandler方法中的最后一行代码mavContainer.addAllAttributes(mav.getModel());

 View Code

posted on 2018-04-26 17:02  David_Deng  阅读(1313)  评论(0编辑  收藏  举报

导航