• SpringMVC @SessionAttributes 使用详解以及源码分析


    @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);
        }
    View Code

    我们看这行代码

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

    其中这两句是关键的地方,把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;
        }
    View Code

    有一行代码是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());
            } 
        }
    View Code

    看到这里可能还有一个疑问,就是我们的数据时设置到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());
        }
    View Code
  • 相关阅读:
    MOSS 2007(Designer) 自定义列表表单开发笔记1(表单字段控件格式化)
    不使用第三个变量,怎么交换两个变量的值?
    AT指令介绍
    转:iis 支持wap
    WAP传输协议
    计算生肖
    下载文件 转
    彩信MO,MT的业务流程(转)
    最详细AT指令
    WML标签手册
  • 原文地址:https://www.cnblogs.com/daxin/p/SessionAttributes.html
Copyright © 2020-2023  润新知