源码基于SpringMVC 5.2.7版本
请求进来的时候创建SessionAttributesHandler并将之前的session属性填充到model数据中,请求返回时将model中数据根据注解@SessionAttribute配置保存到SessionAttributesHandler中供下次请求使用。如果请求处理类标记SessionStatus为已完成,则在请求返回时将SessionAttributesHandler清理掉
@SessionAttributes
@SessionAttributes注解修饰在类上,一般是修饰在处理http请求的类上,为其定义会话级attributes。@SessionAttributes定义的会话级attributes是跟请求Handler的model attributes关联的。处理请求时将@SessionAttributes指定的属性从Session或者其它会话级存储介质中同步到model attributes中;返回响应时将@SessionAttributes指定的model attributes保存到Session或者其它会话级存储介质。
public @interface SessionAttributes {
@AliasFor("names")
String[] value() default {};
@AliasFor("value")
String[] names() default {};
Class<?>[] types() default {};
}
- names数组:String类型数组,指定属性的名字
- types数组:Class<?>类型数组,指定属性的类型
满足names数组或者types数组的属性都会存储在session或其它会话级存储介质。如果请求Handler明确说明已经完成会话,与该Handler的@SessionAttributes指定的会话级属性将被从Session中清除。所以如果开发者需要永久的会话属性可以直接使用Session,而不是用@SessionAttributes来管理永久的会话属性。
SessionAttributesHandler
SessionAttributesHandler是处理@SessionAttributes的处理器
主要成员变量
- attributeNames:属性的名字,对应注解@SessionAttributes的names数组
- attributeTypes:属性的类型,对应注解@SessionAttributes的types数组
- knownAttributeNames:String数组,当前SessionAttributesHandler已经识别的属性名,其一所有attributeNames中元素属于已识别的属性,其二匹配过attributeTypes的属性名
- sessionAttributeStore:会话级attributes的存储介质。默认实现类DefaultSessionAttributeStore,DefaultSessionAttributeStore以Session作为存储介质
主要成员方法
storeAttributes
存储属性到会话级存储介质
public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
attributes.forEach((name, value) -> {
if (value != null && isHandlerSessionAttribute(name, value.getClass())) {
this.sessionAttributeStore.storeAttribute(request, name, value);
}
});
}
retrieveAttributes
从存储介质中提取所有已识别的属性
public Map<String, Object> retrieveAttributes(WebRequest request) {
Map<String, Object> attributes = new HashMap<>();
for (String name : this.knownAttributeNames) {
Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
if (value != null) {
attributes.put(name, value);
}
}
return attributes;
}
cleanupAttributes
从存储介质中清除已识别的属性,当请求的Handler明确说明完成会话,该方法将被调用。
public void cleanupAttributes(WebRequest request) {
for (String attributeName : this.knownAttributeNames) {
this.sessionAttributeStore.cleanupAttribute(request, attributeName);
}
}
SpringMVC对@SessionAttributes的支持
@SessionAttributes处理流程
先看一下测试demo,提供3个接口,
"/focuse/hello"里面给model添加一个属性("sessionAttr1")。因为@SessionAttributes机制,这个属性将成为session级属性。
"/focuse/hello2"从model中获取属性"sessionAttr1"。
为了简化demo,没有给demo程序配置视图解析器,SpringMVC默认使用InternalResourceView,请求转发到"/focuse/hello3"。
运行的结果将是:我们先请求"/focuse/hello",再请求"/focuse/hello2",在"/focuse/hello2"中能取到属性"sessionAttr1"。
@Controller
@RequestMapping("/focuse")
@SessionAttributes(names = {"sessionAttr1"})
public class DemoController {
@RequestMapping("hello")
public String hello(Model model) {
model.addAttribute("sessionAttr1", "sessionAttr1Val");
return "/focuse/hello3";
}
@RequestMapping("hello2")
public String hello2(Model model) {
String sessionAttr1 = (String) model.getAttribute("sessionAttr1");
return "/focuse/hello3";
}
@ResponseBody
@RequestMapping("hello3")
public String hello3(Model model) {
return "hello world!";
}
}
首先,开发者在请求处理类上添加注解@SessionAttributes
请求处理类一般是@Controller,如demo中DemoController,笔者添加了注解@SessionAttributes,并指定names数组包含元素"sessionAttr1"
其次,RequestMappingHandlerAdapter调用请求处理方法之前初始化该请求处理类的SessionAttributesHandler
这里初始化SessionAttributesHandler主要时根据请求处理类上的注解@SessionAttributes。源码在RequestMappingHandlerAdapter#getModelFactory
public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");
this.sessionAttributeStore = sessionAttributeStore;
//笔者注:根据注解@SessionAttributes设置SessionAttributesHandler属性
SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
if (ann != null) {
Collections.addAll(this.attributeNames, ann.names());
Collections.addAll(this.attributeTypes, ann.types());
}
this.knownAttributeNames.addAll(this.attributeNames);
}
再次,RequestMappingHandlerAdapter初始化model时将SessionAttributesHandler中存储的属性添加进去
这里添加2部署数据到model中,
- 一种是SessionAttributesHandler存储的属性,
- 另一种是请求处理方法的注解@ModelAttribute注解的参数且参数类型与@SessionAttributes指定的类型匹配。这种除了添加到model中,同时也会将其标记为session属性并且在接下来的步骤中添加到SessionAttributesHandler中
public final class ModelFactory {
... ...
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
throws Exception {
//笔者注:从请求处理类对应的sessionAttributesHandler取出属性添加到model中
Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
container.mergeAttributes(sessionAttributes);
invokeModelAttributeMethods(request, container);
//笔者注:找到请求处理方法中@ModelAttribute注解的参数,如果参数类型匹配@SessionAttributes设置的类型,也添加到model属性中
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!container.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
}
container.addAttribute(name, value);
}
}
}
... ...
}
然后,请求处理完之后获取ModelAndView时根据当前请求处理类的SessionStatus状态去清除SessionAttributes或者去保存SessionAttributes
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
... ...
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//笔者注:获取ModelAndView对象时更新Model
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
... ...
}
... ...
}
public final class ModelFactory {
... ...
//笔者注:更新model
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
ModelMap defaultModel = container.getDefaultModel();
//笔者注:根据当前请求类的SessionStatus状态清除或者保存SessionAttributes
if (container.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
else {
this.sessionAttributesHandler.storeAttributes(request, defaultModel);
}
if (!container.isRequestHandled() && container.getModel() == defaultModel) {
updateBindingResult(request, defaultModel);
}
}
... ...
}
简单总结一下就是:请求进来的时候创建SessionAttributesHandler并将之前的session属性填充到model数据中,请求返回时将model中数据根据注解@SessionAttribute配置保存到SessionAttributesHandler中供下次请求使用。如果请求处理类标记SessionStatus为已完成,则在请求返回时将SessionAttributesHandler清理掉。
如何设置SessionStatus
前面说了,如果要清除掉@SessionAttributes保持的session属性,可以将请求处理类的SessionStatus设置为完成,那么如何设置?
需要在请求处理方法上注入SessionStatus类型的参数,然后设置SessionStatus#setComplete。那么请求在返回之前就会清除相应的属性。参考下面的demo
@RequestMapping("hello4")
public String hello4(Model model, SessionStatus status) {
status.setComplete();
return "/focuse/hello3";
}
目录 目录