博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC @SessionAttributes 使用详解以及源码分析
阅读量:2399 次
发布时间:2019-05-10

本文共 12593 字,大约阅读时间需要 41 分钟。

@sessionattributes

复制代码

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @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
attributeNames = new HashSet
(); private final Set
> attributeTypes = new HashSet
>(); private final Set
knownAttributeNames = Collections.synchronizedSet(new HashSet
(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.
>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
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
retrieveAttributes(WebRequest request) { Map
attributes = new HashMap
(); 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
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
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());    }

复制代码

 

原文链接:

 

转载地址:http://fkjob.baihongyu.com/

你可能感兴趣的文章
Oracle Data Guard Feature 12cR2系列(一)
查看>>
MySQL InnoDB Update和Crash Recovery流程
查看>>
Oracle RushQL勒索病毒恢复方法
查看>>
Oracle RAC Cache Fusion 系列十:Oracle RAC Enqueues And Lock Part 1
查看>>
MySQL问题两则
查看>>
MySQL执行计划explain的key_len解析
查看>>
基于Oracle的私有云架构探析(连载一)
查看>>
ASM 翻译系列第十弹:ASM Internal ASM DISK header
查看>>
ASM 翻译系列第一弹:基础知识 ASM AU,Extents,Mirroring 和 Failgroups
查看>>
MySQL排序内部原理探秘
查看>>
ASM 翻译系列第八弹:ASM Internal ASM file extent map
查看>>
利用sys schema解决一次诡异的语句hang问题
查看>>
数据库容器化|未来已来
查看>>
容器化RDS|计算存储分离架构下的 IO 优化
查看>>
MySQL 5.7复制配置不规范修改导致的坑(一)
查看>>
MySQL8.0——Resource Group(资源组)
查看>>
基于Oracle的私有云架构探析(连载二)
查看>>
MySQL分区如何迁移
查看>>
Oracle压缩黑科技(一)—基础表压缩
查看>>
容器化RDS—计算存储分离架构下的“Split-Brain”
查看>>