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

304 阅读4分钟

SessionAttributesHandler组件分析

SessionAttributesHandler介绍

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

SessionAttributesHandler的属性

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

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

// 用于存储所有已知可以被当前处理器处理的属性名
// 1,构造方法里会将所有attributeNames设置到knownAttributeNames
// 2,当调用isHandlerSessionAttribute方法检查,
//   并且是当前Handler所管理的SessionAttributes
private final Set<String> knownAttributeNames =
        Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(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<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);
}
取出全部属性retrieveAttributes和清除属性cleanupAttributes都遍历了knownAttributeNames
knownAttributeNames中只保存了当前Handler注释里所有使用过的属性名
所以这两个方法的操作值针对当前处理器类的@SessionAttributes注释里的配置起作用

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

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

SessionAttributeStore

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

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中定义的
最后一个参数指定了操作的范围

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操作

总结

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

下面来看ModelFactory...