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 }