Bootstrap

SpringMVC源码分析-HandlerAdapter(5)-SessionAttributesHandler组件分析

SessionAttributesHandler 组件分析

1,SessionAttributesHandler 介绍

用于处理@SessionAttributes注释的参数
具体存储工由又SessionAttributeStore完成
SessionAttributeStore并不是保存数据的容器,而是保存数据的一个工具类
保存数据的容器默认使用Session,
也可以使用其他容器,重写SessionAttributeStore设置到RequestMappngHandlerAdapter即可
就会在SessionAttributeStore中保存到其他容器

SessionAttributesHandler 的属性

// 用于存储@SessionAttributes注解中的参数名value值
private final Set attributeNames = new HashSet();

// 用于存储@SessionAttributes注解中的参数类型types值
private final Set> attributeTypes = new HashSet>();

// 用于存储所有已知可以被当前处理器处理的属性名
// 1,构造方法里会将所有attributeNames设置到knownAttributeNames
// 2,当调用isHandlerSessionAttribute方法检查,
//   并且是当前Handler所管理的SessionAttributes
private final Set knownAttributeNames =
    Collections.newSetFromMap(new ConcurrentHashMap(4));

// 具体执行Attribute的存储工作
private final SessionAttributeStore sessionAttributeStore;

1,attributeNames
  用于存储@SessionAttributes注解中的参数名value值
  
2,attributeTypes
  用于存储@SessionAttributes注解中的参数类型types值
  
3,knownAttributeNames
  用于存储所有已知可以被当前处理器处理的属性名
  保存了使用value配置的名称和通过types配置的已经保存过的属性名
  
  knownAttributeNames来自两个地方:
  1,构造方法里会将所有attributeNames设置到knownAttributeNames
  2,当调用isHandlerSessionAttribute方法检查,
  并且是当前Handler所管理的SessionAttributes时,也会添加到knownAttributeNames中
  保存属性的storeAttributes方法会在每个属性保存前调用isHandlerSessionAttribute
  判断是否支持要保存的属性,所以,所有保存过得属性名称都会保存在knownAttributeNames中
  
4,sessionAttributeStore
  具体执行Attribute的存储工作

SessionAttributesHandler 源码:

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

  SessionAttributes annotation =
      AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
  if (annotation != null) {
    this.attributeNames.addAll(Arrays.asList(annotation.names()));
    this.attributeTypes.addAll(Arrays.asList(annotation.types()));
  }

  for (String attributeName : this.attributeNames) {
    this.knownAttributeNames.add(attributeName);
  }
}

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;
  }
}

SessionAttributesHandler 提供了 Attribute 操作:

// 保存属性
public void storeAttributes(WebRequest request, Map 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 retrieveAttributes(WebRequest request) {
  Map attributes = new HashMap();
  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);
}

取出全部属性retrieveAttributes和清除属性cleanupAttributes都遍历了knownAttributeNames
knownAttributeNames中只保存了当前Handler注释里所有使用过的属性名
所以这两个方法的操作值针对当前处理器类的@SessionAttributes注释里的配置起作用

按名称取属性的方法可以在整个SessionAttributes中查找,没有knownAttributeNames的限制

需要注意:
  如果在不同的Handler中使用SessionAttributes保存的属性使用了相同名称,它们会相互影响

2,SessionAttributeStore

SessionAttributeHandler 中对每个参数的保存、取回、删除工作都是由 SessionAttributeStore 完成的;

SessionAttributeStore:是一个接口,里面的三个方法对应以上的三个功能;

SessionAttributeStore源码:

public interface SessionAttributeStore {

  void storeAttribute(WebRequest request, String attributeName, Object attributeValue);

  Object retrieveAttribute(WebRequest request, String attributeName);

  void cleanupAttribute(WebRequest request, String attributeName);

}

SessionAttributeStore 有一个默认实现类:DefaultSessionAttributeStore;

RequestMappingHandlerAdapter 初始化 sessionAttributeStore 就使用了该默认实现类;

private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

DefaultSessionAttributeStore

DefaultSessionAttributeStore:将参数保存到 Session 中;

DefaultSessionAttributeStore源码:

public class DefaultSessionAttributeStore implements SessionAttributeStore {
    private String attributeNamePrefix = "";

    public DefaultSessionAttributeStore() {
    }

    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 = this.getAttributeNameInSession(request, attributeName);
        request.setAttribute(storeAttributeName, attributeValue, 1);
    }

    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 = this.getAttributeNameInSession(request, attributeName);
        return request.getAttribute(storeAttributeName, 1);
    }

    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 = this.getAttributeNameInSession(request, attributeName);
        request.removeAttribute(storeAttributeName, 1);
    }

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

需要注意:
这里虽Session的操作使用的是request的setAttribute和getAttribute以及removeAttribute
虽然是request调用的,但并不是设置到了request上面,通过最后一个参数制定了设置范围

这里使用的request实际是ServletWebRequest,
这三个方法是在其父类ServletRequestAttributes中定义的
最后一个参数指定了操作的范围

3,ServletRequestAttributes#setAttribute

@Override
public void setAttribute(String name, Object value, int scope) {
  // request
  if (scope == SCOPE_REQUEST) {
    if (!isRequestActive()) {
      throw new IllegalStateException(
          "Cannot set request attribute - request is not active anymore!");
    }
    this.request.setAttribute(name, value);
  }
  // session
  else {
    HttpSession session = getSession(true);
    this.sessionAttributesToUpdate.remove(name);
    session.setAttribute(name, value);
  }
}

如果第三个参数传入的是SCOPE_REQUEST会存储到request,否则存到Session

sessionAttributesToUpdate属性:
  用于保存从Session中获取过的值,request使用完后再同步给Session
  因为Session中的值可能已经使用别的方式修改过
代码中使用了SCOPE_SESSION,会对Session操作,而不是对request操作

4,总结

SessionAttributesHandler与@SessionAttributes注释相对应,用于对SessionAttributes操作
SessionAttributesHandler包含判断参数是否可被处理及批量操作多个参数等功能
具体对单个参数的操由SessionAttributeStore完成,默认实现为DefaultSessionAttributeStore
DefaultSessionAttributeStore使用ServletWebRequest将参数设置到Session中
SessionAttributesHandler是在ModelFactory中使用的

下一篇,ModelFactory...