@SessionAttribute作用于处理器类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttribute设置的参数只用于暂时的传递,而不是长期的保存,长期保存的数据还是要放到Session中。

通过@SessionAttribute注解设置的参数有3类用法:

(1)在视图中通过request.getAttribute或session.getAttribute获取

(2)在后面请求返回的视图中通过session.getAttribute或者从model中获取

(3)自动将参数设置到后面请求所对应处理器的Model类型参数或者有@ModelAttribute注释的参数里面。

将一个参数设置到SessionAttribute中需要满足两个条件:

(1)在@SessionAttribute注解中设置了参数的名字或者类型

(2)在处理器中将参数设置到了model中。

@SessionAttribute用户后可以调用SessionStatus.setComplete来清除,这个方法只是清除SessionAttribute里的参数,而不会应用Session中的参数。

示例如下:注解@SessionAttribute中设置book、description和types={Double},这样值会被放到@SessionAttribute中,但Redirect跳转时就可以重新获得这些数据了,接下来操作sessionStatus.setComplete(),则会清除掉所有的数据,这样再次跳转时就无法获取数据了。

@Controller
@RequestMapping("/book")
@SessionAttributes(value ={"book","description"},types={Double.class})
public class RedirectController {

	@RequestMapping("/index")
	public String index(Model model){
		model.addAttribute("book", "金刚经");
		model.addAttribute("description","不擦擦擦擦擦擦擦车");
		model.addAttribute("price", new Double("1000.00"));
		//跳转之前将数据保存到book、description和price中,因为注解@SessionAttribute中有这几个参数
		return "redirect:get.action";
	}

	@RequestMapping("/get")
	public String get(@ModelAttribute ("book") String book,ModelMap model,
			SessionStatus sessionStatus){
		//可以获得book、description和price的参数
		System.out.println(model.get("book")+";"+model.get("description")+";"+model.get("price"));
		sessionStatus.setComplete();
		return "redirect:complete.action";
	}

	@RequestMapping("/complete")
	public String complete(ModelMap modelMap){
		//已经被清除,无法获取book的值
		System.out.println(modelMap.get("book"));
		modelMap.addAttribute("book", "妹纸");
		return "sessionAttribute";
	}

}

接下来我们分析一下@SessionAttribute的实现机制

第一步:我们首先要获取注解@SessionAttribute的值的情况,在RequestMappingHandlerAdapter中的getModelFactory中处理。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		//这里面对注解的@SessionAttribute的处理器做处理
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		........
		//会对@SessionAttribute操作的值进行处理
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		........

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}

在getModelFactory中会创建@SessionAttribute的处理器SessionAttributeHandler

	private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
		//创建SessionAttribute处理器
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);

		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}

getSessionAttributesHandler的操作就是获取或者初始化SessionAttributesHandler

//已经获取过的SessionAttribute放到Map中,如果没有则需要初始化
	private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
		Class<?> handlerType = handlerMethod.getBeanType();
		SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
		if (sessionAttrHandler == null) {
			synchronized (this.sessionAttributesHandlerCache) {
				sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
				if (sessionAttrHandler == null) {
					//初始化sessionAttrHandler,并放到map中
					sessionAttrHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
					this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler);
				}
			}
		}
		return sessionAttrHandler;
	}

SessionAttributesHandler的构造函数中的操作如下,其实就是解析被@SessionAttribute注解的处理器,这样就完成了@SessionAttribute注解中设置的key的解析。

public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
		Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
		this.sessionAttributeStore = sessionAttributeStore;
		//解析被@SessionAttribute注解的处理器
		SessionAttributes annotation = AnnotationUtils.findAnnotation(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);
		}
	}

接下来我们看看springMVC对@SessionAttribute的处理操作,在ModelFactory.initModel会对@SessionAttribute的注解进行处理操作。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		//这里面对注解的@SessionAttribute的处理器做处理
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		........
		//会对@SessionAttribute操作的值进行处理
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		........

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}

initModel其实做了两步操作,一是:获取上一次请求保存在SessionAttributeHandler中值,给这次请求的值用,二是将这次请求的处理结果可能会对上次的@SessionAttribute中的值进行改变的值进行保存给下一次请求使用。

public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
			throws Exception {
		//获取所有的@SessionAttribute注解设置的key中值
		Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
		//将获取的值传递给下一个请求使用
		mavContainer.mergeAttributes(sessionAttributes);

		invokeModelAttributeMethods(request, mavContainer);

		//请求访问完之后将修改的值重新放到@SessionAttributeStore设置的key中
		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);
			}
		}
	}

sessionAttributesHandler.retrieveAttributes的操作是将request中值,按照注解@SessionAttribute中key取值处理,然后保存到attribute中,作为这次请求的传递值使用。

public Map<String, Object> retrieveAttributes(WebRequest request) {
		Map<String, Object> attributes = new HashMap<String, Object>();
		//获取注解@SessionAttribute中设置的key
		for (String name : this.knownAttributeNames) {
			//如果设置的key有值则把它保存到attribute中,给跳转之后的请求使用
			Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
			if (value != null) {
				attributes.put(name, value);
			}
		}
		return attributes;
	}

这样就完成了将值保存在SessionAttributeHandler中,这样下一次请求过来时依然可以从SessionAttributeHandler中获取上次的结果,完成了类似Session的实现机制,但明显感觉和Session不一样,所有的请求其值是保存在一个同一个SessionAttributeHandler中。

SessionAttributeHandler其实维持了一个Map结构来存取数据,功能主要有解析@SessionAttribute注解,get和set相关值,源码如下:

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.newSetFromMap(new ConcurrentHashMap<String, Boolean>(4));

	private final SessionAttributeStore sessionAttributeStore;

	//构造函数,解析@SessionAttribute注解,将其设置额key等信息保存
	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.names()));
			this.attributeTypes.addAll(Arrays.asList(annotation.types()));
		}

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

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

}

springMVC源码分析--@SessionAttribute用法及原理解析SessionAttributesHandler和SessionAttributeStore的更多相关文章

  1. SpringMVC源码分析6:SpringMVC的视图解析原理

    title: SpringMVC源码分析6:SpringMVC的视图解析原理 date: 2018-06-07 11:03:19 tags: - SpringMVC categories: - 后端 ...

  2. springMVC源码分析--异常处理机制HandlerExceptionResolver执行原理(二)

    上一篇博客springMVC源码分析--异常处理机制HandlerExceptionResolver简单示例(一)中我们简单地实现了一个异常处理实例,接下来我们要介绍一下HandlerExceptio ...

  3. [心得体会]SpringMVC源码分析

    1. SpringMVC (1) springmvc 是什么? 前端控制器, 主要控制前端请求分配请求任务到service层获取数据后反馈到springmvc的view层进行包装返回给tomcat, ...

  4. 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

    从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...

  5. springMVC源码分析--AbstractControllerUrlHandlerMapping(六)

    上一篇博客springMVC源码分析--AbstractDetectingUrlHandlerMapping(五)中我们介绍了AbstractDetectingUrlHandlerMapping,其定 ...

  6. springMVC源码分析--HandlerMapping(一)

    HandlerMapping的工作就是为每个请求找到合适的请求找到一个处理器handler,其实现机制简单来说就是维持了一个url到Controller关系的Map结构,其提供的实际功能也是根据req ...

  7. springMVC源码分析--动态样式ThemeResolver(二)

    在上一篇博客springMVC源码分析--动态样式ThemeResolver(一)中我们介绍了多样式ThemeResolver的使用方法,接下来我们对源码进行简单的分析一下. ThemeResolve ...

  8. springMVC源码分析--页面跳转RedirectView(三)

    之前两篇博客springMVC源码分析--视图View(一)和springMVC源码分析--视图AbstractView和InternalResourceView(二)中我们已经简单的介绍了View相 ...

  9. 框架-springmvc源码分析(一)

    框架-springmvc源码分析(一) 参考: http://www.cnblogs.com/heavenyes/p/3905844.html#a1 https://www.cnblogs.com/B ...

随机推荐

  1. hdu 6095 Rikka with Competition---思维题贪心

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6095 题目大意: 任意两个人相比,相差大于K,分低的淘汰,否则两人都有可能赢,剩下的继续比,问有最多 ...

  2. ansible批量加用户

    ansible批量加用户 1.生成密码 pip install passlib python -c "from passlib.hash import sha512_crypt; print ...

  3. php过滤表单提交的html等危险代码

    表单提交如果安全做得不好就很容易因为这个表单提交导致网站被攻击了,下面我来分享两个常用的php过滤表单提交的危险代码的实例,各位有需要的朋友可参考. PHP过滤提交表单的html代码里可能有被利用引入 ...

  4. STL之map排序

    描述 STL的map中存储了字符串以及对应出现的次数,请分别根据字符串顺序从小到大排序和出现次数从小到大排序. 部分代码已经给出,请补充完整,提交时请勿包含已经给出的代码. int main() { ...

  5. MySQL/MariaDB触发器

    本文目录:1.创建触发器2.insert触发器3.delete触发器4.update触发器5.通过on duplicate key update分析触发器触发原理6.replace to算法验证7.查 ...

  6. 使用IntelliJ IDEA的小技巧快乐编程(1)

    前言 我很喜欢和别人讨论一些问题,有时候,在公司里,讨论这样的问题需要演示代码.常常会碰到的一种情况是(根据我的记忆这半年多来至少超过了10次),别人会打断你的演示,抛出一个问题:等等,你刚才的操作是 ...

  7. 手写简单的jq雪花飘落

    闲来无事,准备写个雪花飘落的效果,没有写太牛逼的特效,极大的简化了代码量,这样容易读取代码,用起来也很简单,对于那些小白简直是福利啊,简单易读易学.先直接上代码吧,然后再一一讲解,直接复制粘贴就可以拿 ...

  8. 教你从手机中提取system镜像制作线刷救砖包的简单方法

    其实在制作刷机包的过程中,有时候没有官方或者第三方提供的救砖包(线刷),那怎么办?常规的方法有两种:(此处为常规方法,回读的方式暂不说明)     1.卡刷包转线刷包     2.dd命令导出分区镜像 ...

  9. [SDOI 2015]约数个数和

    Description  设d(x)为x的约数个数,给定N.M,求 $\sum^N_{i=1}\sum^M_{j=1}d(ij)$ Input 输入文件包含多组测试数据. 第一行,一个整数T,表示测试 ...

  10. C++Primer学习——类型转换

    无符号之间的运算 当一个是无符号类型,另外一个是带符号类型: 如果无符号不小于带符号,那么带符号转换成无符号. 如果无符号小于带符号,当无符号类型的所有值都能存到带符号中时,则无符号转换成带符号,否则 ...