SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理相关的知识,希望对你有一定的参考价值。

使用SpringMVC开发时,可以使用@SessionAttributes注解缓存信息.这样业务开发时,就不需要一次次手动操作session保存,读数据.

1 @Controller
2 @RequestMapping("telephones")
3 @SessionAttributes(value={"name","degree"},types={Double.class})
4 public class AttributeController {
5     // ...
6 }

 

SpringMVC实际处理时这部分时,主要涉及3个概念:

  @SessionAttributes注解定义

  注解信息初始化与容器SessionAttributesHanlder

  session操作类SessionAttributeStore,虽然叫做store其实叫utils更贴切

 

SessionAttributesHanlder在初始化时扫描类里的方法,找出@SessionAttributes注解,并解析,然后直接保存到attributeNames和attributeTypes中,再更新knownAttributeNames.

保存的话,也可以在后期storeAttributes和isHandlerSessionAttribute进行.

在读取,清除时,都是以knownAttributeNames为索引,然后委托SessionAttributeStore处理.

 SessionAttributeStore具体的session操作是委托WebRequest处理的,他主要是封装了一个属性前缀.

 

具体分析目录:

  1. 各类定义科普:@SessionAttributes,SessionAttributesHandler,SessionAttributeStore

  2. session属性的保存

  3. session属性的读取

  4. session属性的清除

 

1. 各类定义科普

  1.1 先看@SessionAttributes注解的定义吧,这边就两种配置方式,一种是value定义属性的name,一种是types定义属性的类型,如Date

 1 package org.springframework.web.bind.annotation;
 2 
 3 @Target({ElementType.TYPE})
 4 @Retention(RetentionPolicy.RUNTIME)
 5 @Inherited
 6 @Documented
 7 public @interface SessionAttributes {
 8     
 9     String[] value() default {};
10     Class[] types() default {};
11 }

  1.2 SessionAttributesHandler

  其实有点容器的味道,这个可以从attributeNames和attributeTypes属性可以看出.

  同时也维护着对应属性的保存,读取与清除,这个看看构造发方法,retrieveAttributes,cleanupAttributes api就很清楚

  当然具体对session的操作,是通过SessionAttributeStore处理的.

  其中knownAttributeNames使用了ConcurrentHashMap,说明这边是线程安全的.

 1 package org.springframework.web.method.annotation;
 2 
 3 public class SessionAttributesHandler {
 4     // 属性名称,对应注解的value
 5     private final Set<String> attributeNames = new HashSet<String>();
 6     // 属性的数据类型,对应注解的types
 7     private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();
 8     // 缓存配置的attribute,包括根据类型扫描得到的属性,这样清除的时候,可以直接以这个为索引操作
 9     // using a ConcurrentHashMap as a Set
10     private final Map<String, Boolean> knownAttributeNames = new ConcurrentHashMap<String, Boolean>(4);
11     // 具体操作session的utils,个人感觉名字起的有点古怪
12     private final SessionAttributeStore sessionAttributeStore;
13 
14 
15     /**
16      * 实例化的时候,直接解析注解
17      */
18     public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
19         Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
20         this.sessionAttributeStore = sessionAttributeStore;
21 
22         SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
23         if (annotation != null) {
24             this.attributeNames.addAll(Arrays.asList(annotation.value()));
25             this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
26         }
27 
28         for (String attributeName : this.attributeNames) {
29             this.knownAttributeNames.put(attributeName, Boolean.TRUE);
30         }
31     }
32 
33     public boolean hasSessionAttributes() {
34         return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
35     }
36 
37     /**
38      * 判断是否支持的同时直接缓存attributeName
39      */
40     public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
41         if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
42             this.knownAttributeNames.put(attributeName, Boolean.TRUE);
43             return true;
44         }
45         else {
46             return false;
47         }
48     }
49     // 保存
50     public void storeAttributes(WebRequest request, Map<String, ?> attributes) {}
51     // 读取
52     public Map<String, Object> retrieveAttributes(WebRequest request) {}
53     Object retrieveAttribute(WebRequest request, String attributeName) {}
54     // 清除
55     public void cleanupAttributes(WebRequest request) {}
56 
57 }

  1.3 SessionAttributeStore用于session中属性的操作

  这边定义了一个接口,并提供默认实现.

    接口很简单,直接定义了保存,读取,清除的接口

    默认实现DefaultSessionAttributeStore,在实现接口的基础上,添加了一个前缀的概念用于区分,并委托WebRequest处理

 

SessionAttributeStore接口定义

1 package org.springframework.web.bind.support;
2 public interface SessionAttributeStore {
3     void storeAttribute(WebRequest request, String attributeName, Object attributeValue);
4     Object retrieveAttribute(WebRequest request, String attributeName);
5     void cleanupAttribute(WebRequest request, String attributeName);
6 
7 }

DefaultSessionAttributeStore其实主要是一个attributeNamePrefix的定义,并封装属性名称getAttributeNameInSession,其他的都是直接委托

 1 package org.springframework.web.bind.support;
 2 public class DefaultSessionAttributeStore implements SessionAttributeStore {
 3     // 属性名称前缀
 4     private String attributeNamePrefix = "";
 5     // 封装属性名称
 6     protected String getAttributeNameInSession(WebRequest request, String attributeName) {
 7         return this.attributeNamePrefix + attributeName;
 8     }
 9     // ...
10 }

 

 

2. session属性的保存

保存可以分为两类操作,一个是实际保存属性,一个是标记是否已经处理.

实际保存的属性是:Set<String> attributeNames和Set<Class<?>> attributeTypes,在保存时,可以使用构造方法和storeAttributes.

标记是否已经处理是Map<String, Boolean> knownAttributeNames,保存时使用构造方法或者isHandlerSessionAttribute.storeAttributes在保存是也会调用isHandlerSessionAttribute.

knownAttributeNames在读取,清除时,都是作为索引使用的,特别是使用types进行注解时,没有这个做索引还真不方便.

 1 package org.springframework.web.method.annotation;
 2 
 3 public class SessionAttributesHandler {
 4     /**
 5      * 实例化的时候,直接解析注解
 6      */
 7     public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
 8         Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
 9         this.sessionAttributeStore = sessionAttributeStore;
10 
11         SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
12         if (annotation != null) {
13             this.attributeNames.addAll(Arrays.asList(annotation.value()));
14             this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
15         }
16 
17         for (String attributeName : this.attributeNames) {
18             this.knownAttributeNames.put(attributeName, Boolean.TRUE);
19         }
20     }
21 
22     public boolean hasSessionAttributes() {
23         return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
24     }
25 
26     /**
27      * 判断是否支持的同时直接缓存attributeName
28      */
29     public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
30         if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
31             this.knownAttributeNames.put(attributeName, Boolean.TRUE);
32             return true;
33         }
34         else {
35             return false;
36         }
37     }
38     /**
39      * Store a subset of the given attributes in the session. Attributes not
40      * declared as session attributes via {@code @SessionAttributes} are ignored.
41      * @param request the current request
42      * @param attributes candidate attributes for session storage
43      */
44     public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
45         for (String name : attributes.keySet()) {
46             Object value = attributes.get(name);
47             Class<?> attrType = (value != null) ? value.getClass() : null;
48 
49             if (isHandlerSessionAttribute(name, attrType)) {
50                 this.sessionAttributeStore.storeAttribute(request, name, value);
51             }
52         }
53     }
54 
55 }

 

1 package org.springframework.web.bind.support;
2 public class DefaultSessionAttributeStore implements SessionAttributeStore {
3     public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {
4         String storeAttributeName = getAttributeNameInSession(request, attributeName);
5         request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);
6     }
7     // ...
8 }

 

3. session属性的读取

可以根据WebRequest读取对应的属性.

具体的处理逻辑:

  迭代knownAttributeNames

  委托sessionAttributeStore处理

  而sessionAttributeStore是通过WebRequest.SCOPE_SESSION,委托WebRequest处理的

  同时过滤出null的属性

 

这么描述逻辑感觉老是委托委托有那么点烦,但人Spring的确每层都封装了一个概念.

 1 //     SessionAttributesHandler
 2     public Map<String, Object> retrieveAttributes(WebRequest request) {
 3         Map<String, Object> attributes = new HashMap<String, Object>();
 4         for (String name : this.knownAttributeNames.keySet()) {
 5             Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
 6             if (value != null) {
 7                 attributes.put(name, value);
 8             }
 9         }
10         return attributes;
11     }

 

1 // DefaultSessionAttributeStore
2     public Object retrieveAttribute(WebRequest request, String attributeName) {
3         String storeAttributeName = getAttributeNameInSession(request, attributeName);
4         return request.getAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
5     }

 

 

4. session属性的清除

只有SessionStatus.setComplete后才会清除属性.

清楚属性的逻辑根据保存差不多,就不解释,直接看代码吧

1 // SessionAttributesHandler
2     public void cleanupAttributes(WebRequest request) {
3         for (String attributeName : this.knownAttributeNames.keySet()) {
4             this.sessionAttributeStore.cleanupAttribute(request, attributeName);
5         }
6     }

 

1 // DefaultSessionAttributeStore
2     public void cleanupAttribute(WebRequest request, String attributeName) {
3         String storeAttributeName = getAttributeNameInSession(request, attributeName);
4         request.removeAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
5     }

 

以上是关于SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理的主要内容,如果未能解决你的问题,请参考以下文章

SpringMVC源码分析6:SpringMVC的视图解析原理

源码深度解析SpringMvc请求运行机制

SpringMVC源码解析- HandlerAdapter - ModelFactory

SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理

SpringMVC源码解析- HandlerAdapter初始化

SpringMVC源码解析 - HandlerAdater - ModelAndViewContainer上下文容器