springMVC源码分析--@SessionAttribute用法及原理解析SessionAttributesHandler和SessionAttributeStore
@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的更多相关文章
- SpringMVC源码分析6:SpringMVC的视图解析原理
title: SpringMVC源码分析6:SpringMVC的视图解析原理 date: 2018-06-07 11:03:19 tags: - SpringMVC categories: - 后端 ...
- springMVC源码分析--异常处理机制HandlerExceptionResolver执行原理(二)
上一篇博客springMVC源码分析--异常处理机制HandlerExceptionResolver简单示例(一)中我们简单地实现了一个异常处理实例,接下来我们要介绍一下HandlerExceptio ...
- [心得体会]SpringMVC源码分析
1. SpringMVC (1) springmvc 是什么? 前端控制器, 主要控制前端请求分配请求任务到service层获取数据后反馈到springmvc的view层进行包装返回给tomcat, ...
- 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解
从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...
- springMVC源码分析--AbstractControllerUrlHandlerMapping(六)
上一篇博客springMVC源码分析--AbstractDetectingUrlHandlerMapping(五)中我们介绍了AbstractDetectingUrlHandlerMapping,其定 ...
- springMVC源码分析--HandlerMapping(一)
HandlerMapping的工作就是为每个请求找到合适的请求找到一个处理器handler,其实现机制简单来说就是维持了一个url到Controller关系的Map结构,其提供的实际功能也是根据req ...
- springMVC源码分析--动态样式ThemeResolver(二)
在上一篇博客springMVC源码分析--动态样式ThemeResolver(一)中我们介绍了多样式ThemeResolver的使用方法,接下来我们对源码进行简单的分析一下. ThemeResolve ...
- springMVC源码分析--页面跳转RedirectView(三)
之前两篇博客springMVC源码分析--视图View(一)和springMVC源码分析--视图AbstractView和InternalResourceView(二)中我们已经简单的介绍了View相 ...
- 框架-springmvc源码分析(一)
框架-springmvc源码分析(一) 参考: http://www.cnblogs.com/heavenyes/p/3905844.html#a1 https://www.cnblogs.com/B ...
随机推荐
- 定点分析: MySQL InnoDB是如何保证系统异常断电情况下的数据可靠性?
MySQL支持事务,所以保证数据可靠的前提是对数据的修改事务已经成功提交 这个问题可以解释为'MySQL InnoDB是如何保证事务C(一致性)D(持久性)性的?' 可能出现的两种情况: (一致性)数 ...
- CodeForces 916E Jamie and Tree(树链剖分+LCA)
To your surprise, Jamie is the final boss! Ehehehe. Jamie has given you a tree with n vertices, numb ...
- [LeetCode] Prime Number of Set Bits in Binary Representation 二进制表示中的非零位个数为质数
Given two integers L and R, find the count of numbers in the range [L, R] (inclusive) having a prime ...
- [JSOI2008]球形空间产生器
Description 有一个球形空间产生器能够在n维空间中产生一个坚硬的球体.现在,你被困在了这个n维球体中,你只知道球 面上n+1个点的坐标,你需要以最快的速度确定这个n维球体的球心坐标,以便于摧 ...
- ●洛谷P2934 [USACO09JAN]安全出行Safe Travel
题链: https://www.luogu.org/problemnew/show/P2934 题解: 最短路(树),可并堆(左偏堆),并查集. 个人感觉很好的一个题. 由于题目已经明确说明:从1点到 ...
- 【BZOJ3631】【JLOI2014】松鼠的新家
原题传送门 题意:给你一棵树,然后有一个遍历顺序,你需要补全这个遍历顺序,然后输出这个遍历顺序中每个点的出现次数. 解题思路:本来想找树剖的题,结果发现了一题可以直接写lca的.... 做法1:非常简 ...
- 【NOIP2016】换教室
题目描述 对于刚上大学的牛牛来说, 他面临的第一个问题是如何根据实际情况中情合适的课程. 在可以选择的课程中,有2n节课程安排在n个时间段上.在第 i ( 1≤ i≤n)个时同段上, 两节内容相同的课 ...
- [bzoj4824][Cqoi2017]老C的键盘
来自FallDream的博客,未经允许,请勿转载,谢谢. 老 C 是个程序员. 作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序在某种 ...
- oracle安装过程和创建本地数据库
环境: win7 64位 工具: PLSQL Developer 百度云下载:链接:https://pan.baidu.com/s/14L3VCG8YwHzpdhEN7ama0w 密码:jlre or ...
- C++的入口函数
我们最开始学习c++时,就知道要写一个main()函数,并且知道这是整个函数的入口,但是c++不只有main()函数这一个入口. 一.对于不同的程序函数入口是不同的. main()是WINDOWS的控 ...