In principle level to master the use of [@SessionAttribute together to learn Spring MVC]

Each one

When you are not on the Naruto everyone recognized you, but we all can recognize you when on Naruto

Foreword

The annotation name implies, is the role of Modelthe sync attribute to sessionthe session among the convenient use of the next request (such as redirection Scene ~).
Although Sessionthe concept of complete separation before and after the end of the current scene has become increasingly weakened, but if it is web developer, I still strongly recommend that you throw away this knowledge, so naturally, I suggest that you be able to skilled use @SessionAttributeto simplify the usual development, herein - into the pit with you

@SessionAttribute

The annotation on annotation only class, for among the plurality of requests passed parameters, like Sessionthe Attribute.
But not exactly the same : general @SessionAttributeparameters used only for temporary transfer, rather than long-term preservation, long-term preservation of data still have to put Sessionin. (Such as traditional values between the temporary redirection, very convenient to use this annotation)

== == official explanation: When a @SessionAttributemarked Controllerwhen adding properties to their model Model, will examine these properties based on the annotations specified name / type, if the match will be passing on to put the Session . The match has been placed on Sessonuntil you invoke a SessionStatus.setComplete()method disappeared ~~~

// @since 2.5   它只能标注在类上
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {

    // 只有名称匹配上了的  Model上的属性会向session里放置一份~~~
    @AliasFor("names")
    String[] value() default {};
    @AliasFor("value")
    String[] names() default {};

    // 也可以拿类型来约束
    Class<?>[] types() default {};
}

Note understand this sentence: users can call SessionStatus.setCompleteto clear, this method is only clear SessionAttributein the argument, but does not apply to Sessionthe parameters . That put yourself using the API and use within Session @SessionAttribute~ Note liberation or go in some differences of

Demo Show

Here with a relatively simple example illustrates what @SessionAttributeit does:

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

    @RequestMapping("/index")
    public String index(Model model, HttpSession httpSession) {
        model.addAttribute("book", "天龙八部");
        model.addAttribute("description", "我乔峰是个契丹人");
        model.addAttribute("price", new Double("1000.00"));

        // 通过Sesson API手动放一个进去
        httpSession.setAttribute("hero", "fsx");

        //跳转之前将数据保存到Model中,因为注解@SessionAttribute中有,所以book和description应该都会保存到SessionAttributes里(注意:不是session里)
        return "redirect:get";
    }

    // 关于@ModelAttribute 下文会讲
    @RequestMapping("/get")
    public String get(@ModelAttribute("book") String book, ModelMap model, HttpSession httpSession, SessionStatus sessionStatus) {
        //可以从model中获得book、description和price的参数
        System.out.println(model.get("book") + ";" + model.get("description") + ";" + model.get("price"));

        // 从sesson中也能拿到值
        System.out.println(httpSession.getAttribute("book"));
        System.out.println("API方式手动放进去的:" + httpSession.getAttribute("hero"));
        // 使用@ModelAttribute也能拿到值
        System.out.println(book);

        // 手动清除SessionAttributes
        sessionStatus.setComplete();
        return "redirect:complete";
    }

    @RequestMapping("/complete")
    @ResponseBody
    public String complete(ModelMap modelMap, HttpSession httpSession) {
        //已经被清除,无法获取book的值
        System.out.println(modelMap.get("book"));
        System.out.println("API方式手动放进去的:" + httpSession.getAttribute("hero"));
        return "sessionAttribute";
    }

}

We only need to request access to the entrance /indexyou can directly see the console output is as follows:

天龙八部;我乔峰是个契丹人;1000.0
天龙八部
API方式手动放进去的:fsx
天龙八部
null
API方式手动放进去的:fsx

Browser below:
Here Insert Picture Description
acquaintance junior partner may be careful observation in this case, it is evidence of the theoretical knowledge I said above.

@SessionAttributeNotes parameter set has three categories ways to use it:

  1. In view of the view (such jsp pages, etc.) by request.getAttribute()or session.getAttributeacquired
  2. In the view request returned back view session.getAttributeor from the model acquired in (this is also the more commonly used)
  3. Automatically setting the parameter to the requesting processor corresponding to the rear Modeltype or have parameters @ModelAttributeParameter Comment inside (in conjunction with @ModelAttributeuse with our focus should be)

By way of example we know its basic use, from the following principles to analyze the level of its execution, a real master it.

SessionAttributesHandler

See the meaning of the name, it is the @SessionAttributesprocessor, which is the analytical core of this annotation. Management by @SessionAttributestagging a specific session attributes are stored eventually commissioned SessionAttributeStoreto achieve.

// @since 3.1
public class SessionAttributesHandler {

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

    // 注意这个重要性:它是注解方式放入session和API方式放入session的关键(它只会记录注解方式放进去的session属性~~)
    private final Set<String> knownAttributeNames = Collections.newSetFromMap(new ConcurrentHashMap<>(4));
    // sessonAttr存储器:它最终存储到的是WebRequest的session域里面去(对httpSession是进行了包装的)
    // 因为有WebRequest的处理,所以达到我们上面看到的效果。complete只会清楚注解放进去的,并不清除API放进去的~~~
    // 它的唯一实现类DefaultSessionAttributeStore实现也简单。(特点:能够制定特殊的前缀,这个有时候还是有用的)
    // 前缀attributeNamePrefix在构造器里传入进来  默认是“”
    private final SessionAttributeStore sessionAttributeStore;

    // 唯一的构造器 handlerType:控制器类型  SessionAttributeStore 是由调用者上层传进来的
    public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
        Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");
        this.sessionAttributeStore = sessionAttributeStore;

        // 父类上、接口上、注解上的注解标注了这个注解都算
        SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
        if (ann != null) {
            Collections.addAll(this.attributeNames, ann.names());
            Collections.addAll(this.attributeTypes, ann.types());
        }
        this.knownAttributeNames.addAll(this.attributeNames);
    }

    // 既没有指定Name 也没有指定type  这个注解标上了也没啥用
    public boolean hasSessionAttributes() {
        return (!this.attributeNames.isEmpty() || !this.attributeTypes.isEmpty());
    }

    // 看看指定的attributeName或者type是否在包含里面
    // 请注意:name和type都是或者的关系,只要有一个符合条件就成
    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;
        }
    }

    // 把attributes属性们存储起来  进到WebRequest 里
    public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
        attributes.forEach((name, value) -> {
            if (value != null && isHandlerSessionAttribute(name, value.getClass())) {
                this.sessionAttributeStore.storeAttribute(request, name, value);
            }
        });
    }

    // 检索所有的属性们  用的是knownAttributeNames哦~~~~
    // 也就是说手动API放进Session的 此处不会被检索出来的
    public Map<String, Object> retrieveAttributes(WebRequest request) {
        Map<String, Object> attributes = new HashMap<>();
        for (String name : this.knownAttributeNames) {
            Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
            if (value != null) {
                attributes.put(name, value);
            }
        }
        return attributes;
    }

    // 同样的 只会清除knownAttributeNames
    public void cleanupAttributes(WebRequest request) {
        for (String attributeName : this.knownAttributeNames) {
            this.sessionAttributeStore.cleanupAttribute(request, attributeName);
        }
    }


    // 对底层sessionAttributeStore的一个传递调用~~~~~
    // 毕竟可以拼比一下sessionAttributeStore的实现~~~~
    @Nullable
    Object retrieveAttribute(WebRequest request, String attributeName) {
        return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
    }
}

This class is SessionAttributethe core processing capabilities of these attributes include: the so-called CRUD. Because you want to further understand how it works, so it comes to handling the entrance, they would have to come ModelFactoryup ~

model Factory

Spring MVCFor @SessionAttributethe processing operation of the entrance, in ModelFactory.initModel()the method will in @SessionAttributeanalyzing comments, processing, and after the completion of the method it will sync attribute.

ModelFactoryIs used to maintain the Model , specifically includes two functions:

  • Before the processor executes initializationModel
  • After the processor to perform the Modelcorresponding parameter in the synchronization update SessionAttributes(not the full amount, but meet those conditions)
// @since 3.1
public final class ModelFactory {
    // ModelMethod它是一个私有内部类,持有InvocableHandlerMethod的引用  和方法的dependencies依赖们
    private final List<ModelMethod> modelMethods = new ArrayList<>();
    private final WebDataBinderFactory dataBinderFactory;
    private final SessionAttributesHandler sessionAttributesHandler;

    public ModelFactory(@Nullable List<InvocableHandlerMethod> handlerMethods, WebDataBinderFactory binderFactory, SessionAttributesHandler attributeHandler) {
    
        // 把InvocableHandlerMethod转为内部类ModelMethod
        if (handlerMethods != null) {
            for (InvocableHandlerMethod handlerMethod : handlerMethods) {
                this.modelMethods.add(new ModelMethod(handlerMethod));
            }
        }
        this.dataBinderFactory = binderFactory;
        this.sessionAttributesHandler = attributeHandler;
    }


    // 该方法完成Model的初始化
    public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {
        // 先拿到sessionAttr里所有的属性们(首次进来肯定木有,但同一个session第二次进来就有了)
        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
        // 和当前请求中 已经有的model合并属性信息
        // 注意:sessionAttributes中只有当前model不存在的属性,它才会放进去
        container.mergeAttributes(sessionAttributes);
        // 此方法重要:调用模型属性方法来填充模型  这里ModelAttribute会生效
        // 关于@ModelAttribute的内容  我放到了这里:https://blog.csdn.net/f641385712/article/details/98260361
        // 总之:完成这步之后 Model就有值了~~~~
        invokeModelAttributeMethods(request, container);

        // 最后,最后,最后还做了这么一步操作~~~
        // findSessionAttributeArguments的作用:把@ModelAttribute的入参也列入SessionAttributes(非常重要) 详细见下文
        // 这里一定要掌握:因为使用中的坑坑经常是因为没有理解到这块逻辑
        for (String name : findSessionAttributeArguments(handlerMethod)) {
        
            // 若ModelAndViewContainer不包含此name的属性   才会进来继续处理  这一点也要注意
            if (!container.containsAttribute(name)) {

                // 去请求域里检索为name的属性,若请求域里没有(也就是sessionAttr里没有),此处会抛出异常的~~~~
                Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
                if (value == null) {
                    throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
                }
                // 把从sessionAttr里检索到的属性也向容器Model内放置一份~
                container.addAttribute(name, value);
            }
        }
    }


    // 把@ModelAttribute标注的入参也列入SessionAttributes 放进sesson里(非常重要)
    // 这个动作是很多开发者都忽略了的
    private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
        List<String> result = new ArrayList<>();
        // 遍历所有的方法参数
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
            // 只有参数里标注了@ModelAttribute的才会进入继续解析~~~
            if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
                // 关于getNameForParameter拿到modelKey的方法,这个策略是需要知晓的
                String name = getNameForParameter(parameter);
                Class<?> paramType = parameter.getParameterType();

                // 判断isHandlerSessionAttribute为true的  才会把此name合法的添加进来
                // (也就是符合@SessionAttribute标注的key或者type的)
                if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) {
                    result.add(name);
                }
            }
        }
        return result;
    }

    // 静态方法:决定了parameter的名字  它是public的,因为ModelAttributeMethodProcessor里也有使用
    // 请注意:这里不是MethodParameter.getParameterName()获取到的形参名字,而是有自己的一套规则的

    // @ModelAttribute指定了value值就以它为准,否则就是类名的首字母小写(当然不同类型不一样,下面有给范例)
    public static String getNameForParameter(MethodParameter parameter) {
        ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
        String name = (ann != null ? ann.value() : null);
        return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter));
    }

    // 关于方法这块的处理逻辑,和上差不多,主要是返回类型和实际类型的区分
    // 比如List<String>它对应的名是:stringList。即使你的返回类型是Object~~~
    public static String getNameForReturnValue(@Nullable Object returnValue, MethodParameter returnType) {
        ModelAttribute ann = returnType.getMethodAnnotation(ModelAttribute.class);
        if (ann != null && StringUtils.hasText(ann.value())) {
            return ann.value();
        } else {
            Method method = returnType.getMethod();
            Assert.state(method != null, "No handler method");
            Class<?> containingClass = returnType.getContainingClass();
            Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
            return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
        }
    }

    // 将列为@SessionAttributes的模型数据,提升到sessionAttr里
    public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
        ModelMap defaultModel = container.getDefaultModel();
        if (container.getSessionStatus().isComplete()){
            this.sessionAttributesHandler.cleanupAttributes(request);
        } else { // 存储到sessionAttr里
            this.sessionAttributesHandler.storeAttributes(request, defaultModel);
        }

        // 若该request还没有被处理  并且 Model就是默认defaultModel
        if (!container.isRequestHandled() && container.getModel() == defaultModel) {
            updateBindingResult(request, defaultModel);
        }
    }

    // 将bindingResult属性添加到需要该属性的模型中。
    // isBindingCandidate:给定属性在Model模型中是否需要bindingResult。
    private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
        List<String> keyNames = new ArrayList<>(model.keySet());
        for (String name : keyNames) {
            Object value = model.get(name);
            if (value != null && isBindingCandidate(name, value)) {
                String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
                if (!model.containsAttribute(bindingResultKey)) {
                    WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name);
                    model.put(bindingResultKey, dataBinder.getBindingResult());
                }
            }
        }
    }

    // 看看这个静态内部类ModelMethod
    private static class ModelMethod {
        // 持有可调用的InvocableHandlerMethod 这个方法
        private final InvocableHandlerMethod handlerMethod;
        // 这字段是搜集该方法标注了@ModelAttribute注解的入参们
        private final Set<String> dependencies = new HashSet<>();

        public ModelMethod(InvocableHandlerMethod handlerMethod) {
            this.handlerMethod = handlerMethod;
            // 把方法入参中所有标注了@ModelAttribute了的Name都搜集进来
            for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
                if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
                    this.dependencies.add(getNameForParameter(parameter));
                }
            }
        }
        ...
    }
}

ModelFactoryHelp initialize method is called before the controller Modelmodel, and after a call to update it .

  • Initialization by calling the method marked with @ModelAttributea method using temporary storage property filling model in the session.
  • When updating the model attribute synchronization session, if missing, will also add BindingResultattributes.

About the default name of core rule in Conventions.getVariableNameForParameter(parameter)this method, I gave an example above, describes common output value of each type, you can remember it. Reference: master the use of HandlerMethod, InvocableHandlerMethod, ServletInvocableHandlerMethod from the principle of level [together] to learn Spring MVC

A parameter to @SessionAttributethe need to satisfy two conditions:

  1. In @SessionAttributesetting the parameter name or type of annotation
  2. A processor ( Controller) in the parameter set to Modelthe (automatically synchronized to the end of such a process in SessionAttr)

to sum up

This article describes @SessionAttributethe core principles of treatment, and also gave a Demoto introduce its basic use, not surprisingly down to read it you should have a good harvest, hoping to help you simplify the development ~

Related Reading

In principle level to master the use HandlerMethod, InvocableHandlerMethod, ServletInvocableHandlerMethod the school with Spring MVC []

Knowledge Exchange

== The last: If you think this article helpful to you, may wish to point a praise chant. Of course, your circle of friends to share so that more small partners also are seeing 作者本人许可的~==

If interested in technology content can join the group wx exchange: Java高工、架构师3群.
If the group fails two-dimensional code, please add wx number: fsx641385712(or two-dimensional code is scanned beneath wx). And Note: "java入群"the word, will be invited into the group manually

If the article 格式混乱or图片裂开 click here ': description link - text link - text link

Guess you like

Origin www.cnblogs.com/fangshixiang/p/11299545.html