@sessionattributes

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
String[] value() default {};
Class[] types() default {};
}

@sessionattributes注解应用到Controller上面,可以将Model中的属性同步到session当中。

先看一个最基本的方法

@Controller
@RequestMapping("/Demo.do")
@SessionAttributes(value={"attr1","attr2"})
public class Demo { @RequestMapping(params="method=index")
public ModelAndView index() {
ModelAndView mav = new ModelAndView("index.jsp");
mav.addObject("attr1", "attr1Value");
mav.addObject("attr2", "attr2Value");
return mav;
} @RequestMapping(params="method=index2")
public ModelAndView index2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2) {
ModelAndView mav = new ModelAndView("success.jsp");
return mav;
}
}

index方法返回一个ModelAndView 其中包括视图index.jsp 和 两个键值放入model当中,在没有加入@sessionattributes注解的时候,放入model当中的键值是request级别的。

现在因为在Controller上面标记了@SessionAttributes(value={"attr1","attr2"}) 那么model中的attr1,attr2会同步到session中,这样当你访问index 然后在去访问index2的时候也会获取这俩个属性的值。

当需要清除session当中的值得时候,我们只需要在controller的方法中传入一个SessionStatus的类型对象 通过调用setComplete方法就可以清除了。

@RequestMapping(params="method=index3")
  public ModelAndView index4(SessionStatus status) {
  ModelAndView mav = new ModelAndView("success.jsp");
  status.setComplete();
  return mav;
}

下面就直接分析代码来看Spring是如可封装的。

首先我们需要看2个类 DefaultSessionAttributeStore和SessionAttributesHandler

DefaultSessionAttributeStore这个类是用来往WebRequest存取数据的工具类,WebRequest是Spring包装的HttpServletRequest,大家理解为普通的HttpServletRequest就行了。

public class DefaultSessionAttributeStore implements SessionAttributeStore {

    private String attributeNamePrefix = "";

    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 = getAttributeNameInSession(request, attributeName);
request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);
} 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 = getAttributeNameInSession(request, attributeName);
return request.getAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
} 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 = getAttributeNameInSession(request, attributeName);
request.removeAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
} protected String getAttributeNameInSession(WebRequest request, String attributeName) {
return this.attributeNamePrefix + attributeName;
} }

Spring会为每一个Controller初始化一个SessionAttributesHandler实例,用来记录@SessionAttributes(value={"attr1","attr2"})里面的属性信息,当需要同步model的值时,会先判断是否在SessionAttributes当中定义。

public class SessionAttributesHandler {

    private final Set<String> attributeNames = new HashSet<String>();

    private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();

    private final Set<String> knownAttributeNames = Collections.synchronizedSet(new HashSet<String>(4));

    private final SessionAttributeStore sessionAttributeStore;

    public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
this.sessionAttributeStore = sessionAttributeStore; SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
if (annotation != null) {
this.attributeNames.addAll(Arrays.asList(annotation.value()));
this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
} this.knownAttributeNames.addAll(this.attributeNames);
} public boolean hasSessionAttributes() {
return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
} 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;
}
} 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);
} }

当我们访问controller中的一个方法时,会调用RequestMappingHandlerAdapter类当中的invokeHandleMethod的方法。

    private ModelAndView invokeHandleMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = AsyncWebUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout); final WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest); if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult(); if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
} requestMappingMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) {
return null;
} return getModelAndView(mavContainer, modelFactory, webRequest);
}

我们看这行代码

modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);

跟进去看源码

public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
throws Exception { Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
mavContainer.mergeAttributes(attributesInSession); invokeModelAttributeMethods(request, mavContainer); for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!mavContainer.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}
}
}

其中这两句是关键的地方,把session的值取出来全部放入到ModelAndViewContainer当中去,这样我就可以再controller当中的方法中获取了,当然直接从session中拿也是可以的。

Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
mavContainer.mergeAttributes(attributesInSession);

在controller中获取sessionAttributes的只有两种方式。

一、public ModelAndView index2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2)

二、public ModelAndView index2(ModelMap map)

这俩种方式的区别是,如果使用@ModelAttribute属性获取值,并且@SessionAttributes注解当中还设置了该属性,当属性为null时会跑出异常,因为这几行代码。

for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!mavContainer.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}
}

以上分析的都是取值的过程,那么Spring是如何将Model中的数据同步到session当中的呢。

我们看上面invokeHandleMethod方法中最后一句话 return getModelAndView(mavContainer, modelFactory, webRequest); 我们跟进去看源码。

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}

有一行代码是modelFactory.updateModel(webRequest, mavContainer); 我们在跟进去看源码。

我们看到首先判断一下ModelAndViewContainer中的SessionStatus是否是完成状态,如果是TRUE就清除session中的值,反之将ModelAndViewContainer中的model值设置进去。

    public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {

        if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
else {
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
} if (!mavContainer.isRequestHandled()) {
updateBindingResult(request, mavContainer.getModel());
}
}

看到这里可能还有一个疑问,就是我们的数据时设置到ModelAndView里面的,那么这些数据时如何跑到ModelAndViewContainer当中去的呢,其实是在方法执行完成之后Spring帮我们做的,

具体细节查看 ModelAndViewMethodReturnValueHandler方法中的最后一行代码mavContainer.addAllAttributes(mav.getModel());

    public void handleReturnValue(
Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception { if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
} ModelAndView mav = (ModelAndView) returnValue;
if (mav.isReference()) {
String viewName = mav.getViewName();
mavContainer.setViewName(viewName);
if (viewName != null && viewName.startsWith("redirect:")) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
View view = mav.getView();
mavContainer.setView(view);
if (view instanceof SmartView) {
if (((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
}
}
mavContainer.addAllAttributes(mav.getModel());
}

SpringMVC @SessionAttributes 使用详解以及源码分析的更多相关文章

  1. SpringMVC异常处理机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...

  2. [转]SpringMVC拦截器详解[附带源码分析]

      目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...

  3. SpringMVC拦截器详解[附带源码分析]

    目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:h ...

  4. SpringMVC视图机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门bl ...

  5. SpringMVC拦截器详解[附带源码分析](转)

    本文转自http://www.cnblogs.com/fangjian0423/p/springMVC-interceptor.html 感谢作者 目录 前言 重要接口及类介绍 源码分析 拦截器的配置 ...

  6. Android应用AsyncTask处理机制详解及源码分析

    1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...

  7. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

  8. Spring Boot启动命令参数详解及源码分析

    使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...

  9. 【转载】Android应用AsyncTask处理机制详解及源码分析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个 ...

随机推荐

  1. FreeSWITCH快速录音

    一.背景 测试人员反映FreeSWITCH录音不及时,需要大约5秒的时间才能捕获到RTP流. 二.原因及解决 查了下资料,FreeSWITCH默认的录音参数配置是开启缓冲的, 即RTP流大小到达655 ...

  2. django-TDD

    1.什么是TDD: 测试驱动开发(Test-Driven Development) 它的总体思想是在写“实现”之前先写针对实现的“测试”,由于编写测试的时候 你要思考很多的可能性能,更多的思考也就意味 ...

  3. xcode自动打ipa包脚本 资料

    http://webfrogs.me/2012/09/19/buildipa/ http://blog.csdn.net/baxiaxx/article/details/8267295 http:// ...

  4. 使用组件构建Android应用程序

    原文链接:http://android.eoe.cn/topic/android_sdk 应用程序组件 Android's application framework lets you create ...

  5. Atitit it行业图像处理行业软件行业感到到迷茫的三大原因和解决方案

    Atitit it行业图像处理行业软件行业感到到迷茫的三大原因和解决方案 1. 迷茫的原因最大原因是未知1 1.1. 我在哪里??自己的定位,1 1.2. 正确方向是什么??1 1.3. 虽然找到方向 ...

  6. CentOS7静态IP设置

    [root@localhost network-scripts]# pwd /etc/sysconfig/network-scripts [root@localhost network-scripts ...

  7. 【Unity】2.8 相机(Camera)

    分类:Unity.C#.VS2015 创建日期:2016-03-31 一.简介 Unity的相机用来向玩家呈现游戏世界.你在场景中始终至少有一个相机,但也可以有多个.多个相机可以带给您双人分屏效果或创 ...

  8. 最新Windows下c++读写锁SRWLock介绍

    https://blog.csdn.net/MoreWindows/article/details/7650574 https://blog.csdn.net/chenzhjlf/article/de ...

  9. Oracle 数据库连接池

    jdbc:oracle:thin:@(DESCRIPTION=(failover=on)(enable=broken)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOS ...

  10. 不可恢复的生成错误mergemod.dll 2.0.2600.0

    在进行Visual Studio 2008 进行Winform打包时,提示 不可恢复的生成错误,很是郁闷,1.在“开始 - 运行” 中输入以下内容分三次来重新注册下Mergemod.dll. regs ...