摘要:見名之意,它是處理器,也就是解析這個注解的核心。管理通過標(biāo)注了的特定會話屬性,存儲最終是委托了來實現(xiàn)。只會清楚注解放進(jìn)去的,并不清除放進(jìn)去的它的唯一實現(xiàn)類實現(xiàn)也簡單。在更新時,模型屬性與會話同步,如果缺少,還將添加屬性。
每篇一句
不是你當(dāng)上了火影大家就認(rèn)可你,而是大家都認(rèn)可你才能當(dāng)上火影前言
該注解顧名思義,作用是將Model中的屬性同步到session會話當(dāng)中,方便在下一次請求中使用(比如重定向場景~)。
雖然說Session的概念在當(dāng)下前后端完全分離的場景中已經(jīng)變得越來越弱化了,但是若為web開發(fā)者來說,我仍舊強烈不建議各位扔掉這個知識點,so我自然就建議大家能夠熟練使用@SessionAttribute來簡化平時的開發(fā),本文帶你入坑~
這個注解只能標(biāo)注在類上,用于在多個請求之間傳遞參數(shù),類似于Session的Attribute。
但不完全一樣:一般來說@SessionAttribute設(shè)置的參數(shù)只用于暫時的傳遞,而不是長期的保存,長期保存的數(shù)據(jù)還是要放到Session中。(比如重定向之間暫時傳值,用這個注解就很方便)
==官方解釋==:當(dāng)用@SessionAttribute標(biāo)注的Controller向其模型Model添加屬性時,將根據(jù)該注解指定的名稱/類型檢查這些屬性,若匹配上了就順帶也會放進(jìn)Session里。匹配上的將一直放在Sesson中,直到你調(diào)用了SessionStatus.setComplete()方法就消失了~~~
// @since 2.5 它只能標(biāo)注在類上 @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 {}; }
注意理解這句話:用戶可以調(diào)用SessionStatus.setComplete來清除,這個方法只是清除SessionAttribute里的參數(shù),而不會應(yīng)用于Session中的參數(shù)。也就是說使用API自己放進(jìn)Session內(nèi)和使用@SessionAttribute注解放進(jìn)去還是有些許差異的~
Demo Show下面用一個比較簡單的例子演示一下@SessionAttribute它的作用:
@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手動放一個進(jìn)去 httpSession.setAttribute("hero", "fsx"); //跳轉(zhuǎn)之前將數(shù)據(jù)保存到Model中,因為注解@SessionAttribute中有,所以book和description應(yīng)該都會保存到SessionAttributes里(注意:不是session里) return "redirect:get"; } // 關(guān)于@ModelAttribute 下文會講 @RequestMapping("/get") public String get(@ModelAttribute("book") String book, ModelMap model, HttpSession httpSession, SessionStatus sessionStatus) { //可以從model中獲得book、description和price的參數(shù) System.out.println(model.get("book") + ";" + model.get("description") + ";" + model.get("price")); // 從sesson中也能拿到值 System.out.println(httpSession.getAttribute("book")); System.out.println("API方式手動放進(jìn)去的:" + httpSession.getAttribute("hero")); // 使用@ModelAttribute也能拿到值 System.out.println(book); // 手動清除SessionAttributes sessionStatus.setComplete(); return "redirect:complete"; } @RequestMapping("/complete") @ResponseBody public String complete(ModelMap modelMap, HttpSession httpSession) { //已經(jīng)被清除,無法獲取book的值 System.out.println(modelMap.get("book")); System.out.println("API方式手動放進(jìn)去的:" + httpSession.getAttribute("hero")); return "sessionAttribute"; } }
我們只需要訪問入口請求/index就可以直接看到控制臺輸出如下:
天龍八部;我喬峰是個契丹人;1000.0 天龍八部 API方式手動放進(jìn)去的:fsx 天龍八部 null API方式手動放進(jìn)去的:fsx
瀏覽器如下圖:
初識的小伙伴可以認(rèn)真的觀察本例,它佐證了我上面說的理論知識。
@SessionAttribute注解設(shè)置的參數(shù)有3類方式去使用它:
在視圖view中(比如jsp頁面等)通過request.getAttribute()或session.getAttribute獲取
在后面請求返回的視圖view中通過session.getAttribute或者從model中獲取(這個也比較常用)
自動將參數(shù)設(shè)置到后面請求所對應(yīng)處理器的Model類型參數(shù)或者有@ModelAttribute注釋的參數(shù)里面(結(jié)合@ModelAttribute一起使用應(yīng)該是我們重點關(guān)注的)
通過示例知道了它的基本使用,下面從原理層面去分析它的執(zhí)行過程,實現(xiàn)真正的掌握它。
SessionAttributesHandler見名之意,它是@SessionAttributes處理器,也就是解析這個注解的核心。管理通過@SessionAttributes標(biāo)注了的特定會話屬性,存儲最終是委托了SessionAttributeStore來實現(xiàn)。
// @since 3.1 public class SessionAttributesHandler { private final SetattributeNames = new HashSet<>(); private final Set > attributeTypes = new HashSet<>(); // 注意這個重要性:它是注解方式放入session和API方式放入session的關(guān)鍵(它只會記錄注解方式放進(jìn)去的session屬性~~) private final Set knownAttributeNames = Collections.newSetFromMap(new ConcurrentHashMap<>(4)); // sessonAttr存儲器:它最終存儲到的是WebRequest的session域里面去(對httpSession是進(jìn)行了包裝的) // 因為有WebRequest的處理,所以達(dá)到我們上面看到的效果。complete只會清楚注解放進(jìn)去的,并不清除API放進(jìn)去的~~~ // 它的唯一實現(xiàn)類DefaultSessionAttributeStore實現(xiàn)也簡單。(特點:能夠制定特殊的前綴,這個有時候還是有用的) // 前綴attributeNamePrefix在構(gòu)造器里傳入進(jìn)來 默認(rèn)是“” private final SessionAttributeStore sessionAttributeStore; // 唯一的構(gòu)造器 handlerType:控制器類型 SessionAttributeStore 是由調(diào)用者上層傳進(jìn)來的 public SessionAttributesHandler(Class> handlerType, SessionAttributeStore sessionAttributeStore) { Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null"); this.sessionAttributeStore = sessionAttributeStore; // 父類上、接口上、注解上的注解標(biāo)注了這個注解都算 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 這個注解標(biāo)上了也沒啥用 public boolean hasSessionAttributes() { return (!this.attributeNames.isEmpty() || !this.attributeTypes.isEmpty()); } // 看看指定的attributeName或者type是否在包含里面 // 請注意:name和type都是或者的關(guān)系,只要有一個符合條件就成 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屬性們存儲起來 進(jìn)到WebRequest 里 public void storeAttributes(WebRequest request, Map attributes) { attributes.forEach((name, value) -> { if (value != null && isHandlerSessionAttribute(name, value.getClass())) { this.sessionAttributeStore.storeAttribute(request, name, value); } }); } // 檢索所有的屬性們 用的是knownAttributeNames哦~~~~ // 也就是說手動API放進(jìn)Session的 此處不會被檢索出來的 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; } // 同樣的 只會清除knownAttributeNames public void cleanupAttributes(WebRequest request) { for (String attributeName : this.knownAttributeNames) { this.sessionAttributeStore.cleanupAttribute(request, attributeName); } } // 對底層sessionAttributeStore的一個傳遞調(diào)用~~~~~ // 畢竟可以拼比一下sessionAttributeStore的實現(xiàn)~~~~ @Nullable Object retrieveAttribute(WebRequest request, String attributeName) { return this.sessionAttributeStore.retrieveAttribute(request, attributeName); } }
這個類是對SessionAttribute這些屬性的核心處理能力:包括了所謂的增刪改查。因為要進(jìn)一步理解到它的原理,所以要說到它的處理入口,那就要來到ModelFactory了~
ModelFactorySpring MVC對@SessionAttribute的處理操作入口,是在ModelFactory.initModel()方法里會對@SessionAttribute的注解進(jìn)行解析、處理,然后方法完成之后也會對它進(jìn)行屬性同步。
ModelFactory是用來維護(hù)Model的,具體包含兩個功能:
處理器執(zhí)行前,初始化Model
處理器執(zhí)行后,將Model中相應(yīng)的參數(shù)同步更新到SessionAttributes中(不是全量,而是符合條件的那些)
// @since 3.1 public final class ModelFactory { // ModelMethod它是一個私有內(nèi)部類,持有InvocableHandlerMethod的引用 和方法的dependencies依賴們 private final ListmodelMethods = new ArrayList<>(); private final WebDataBinderFactory dataBinderFactory; private final SessionAttributesHandler sessionAttributesHandler; public ModelFactory(@Nullable List handlerMethods, WebDataBinderFactory binderFactory, SessionAttributesHandler attributeHandler) { // 把InvocableHandlerMethod轉(zhuǎn)為內(nèi)部類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里所有的屬性們(首次進(jìn)來肯定木有,但同一個session第二次進(jìn)來就有了) Map sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); // 和當(dāng)前請求中 已經(jīng)有的model合并屬性信息 // 注意:sessionAttributes中只有當(dāng)前model不存在的屬性,它才會放進(jìn)去 container.mergeAttributes(sessionAttributes); // 此方法重要:調(diào)用模型屬性方法來填充模型 這里ModelAttribute會生效 // 關(guān)于@ModelAttribute的內(nèi)容 我放到了這里:https://blog.csdn.net/f641385712/article/details/98260361 // 總之:完成這步之后 Model就有值了~~~~ invokeModelAttributeMethods(request, container); // 最后,最后,最后還做了這么一步操作~~~ // findSessionAttributeArguments的作用:把@ModelAttribute的入?yún)⒁擦腥隨essionAttributes(非常重要) 詳細(xì)見下文 // 這里一定要掌握:因為使用中的坑坑經(jīng)常是因為沒有理解到這塊邏輯 for (String name : findSessionAttributeArguments(handlerMethod)) { // 若ModelAndViewContainer不包含此name的屬性 才會進(jìn)來繼續(xù)處理 這一點也要注意 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內(nèi)放置一份~ container.addAttribute(name, value); } } } // 把@ModelAttribute標(biāo)注的入?yún)⒁擦腥隨essionAttributes 放進(jìn)sesson里(非常重要) // 這個動作是很多開發(fā)者都忽略了的 private List findSessionAttributeArguments(HandlerMethod handlerMethod) { List result = new ArrayList<>(); // 遍歷所有的方法參數(shù) for (MethodParameter parameter : handlerMethod.getMethodParameters()) { // 只有參數(shù)里標(biāo)注了@ModelAttribute的才會進(jìn)入繼續(xù)解析~~~ if (parameter.hasParameterAnnotation(ModelAttribute.class)) { // 關(guān)于getNameForParameter拿到modelKey的方法,這個策略是需要知曉的 String name = getNameForParameter(parameter); Class> paramType = parameter.getParameterType(); // 判斷isHandlerSessionAttribute為true的 才會把此name合法的添加進(jìn)來 // (也就是符合@SessionAttribute標(biāo)注的key或者type的) if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) { result.add(name); } } } return result; } // 靜態(tài)方法:決定了parameter的名字 它是public的,因為ModelAttributeMethodProcessor里也有使用 // 請注意:這里不是MethodParameter.getParameterName()獲取到的形參名字,而是有自己的一套規(guī)則的 // @ModelAttribute指定了value值就以它為準(zhǔn),否則就是類名的首字母小寫(當(dāng)然不同類型不一樣,下面有給范例) 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)); } // 關(guān)于方法這塊的處理邏輯,和上差不多,主要是返回類型和實際類型的區(qū)分 // 比如List 它對應(yīng)的名是: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的模型數(shù)據(jù),提升到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就是默認(rèn)defaultModel if (!container.isRequestHandled() && container.getModel() == defaultModel) { updateBindingResult(request, defaultModel); } } // 將bindingResult屬性添加到需要該屬性的模型中。 // isBindingCandidate:給定屬性在Model模型中是否需要bindingResult。 private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception { List 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()); } } } } // 看看這個靜態(tài)內(nèi)部類ModelMethod private static class ModelMethod { // 持有可調(diào)用的InvocableHandlerMethod 這個方法 private final InvocableHandlerMethod handlerMethod; // 這字段是搜集該方法標(biāo)注了@ModelAttribute注解的入?yún)? private final Set dependencies = new HashSet<>(); public ModelMethod(InvocableHandlerMethod handlerMethod) { this.handlerMethod = handlerMethod; // 把方法入?yún)⒅兴袠?biāo)注了@ModelAttribute了的Name都搜集進(jìn)來 for (MethodParameter parameter : handlerMethod.getMethodParameters()) { if (parameter.hasParameterAnnotation(ModelAttribute.class)) { this.dependencies.add(getNameForParameter(parameter)); } } } ... } }
ModelFactory協(xié)助在控制器方法調(diào)用之前初始化Model模型,并在調(diào)用之后對其進(jìn)行更新。
初始化時,通過調(diào)用方法上標(biāo)注有@ModelAttribute的方法,使用臨時存儲在會話中的屬性填充模型。
在更新時,模型屬性與會話同步,如果缺少,還將添加BindingResult屬性。
關(guān)于默認(rèn)名稱規(guī)則的核心在Conventions.getVariableNameForParameter(parameter)這個方法里,我在上文給了一個范例,介紹常見的各個類型的輸出值,大家記憶一下便可。參考:從原理層面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【一起學(xué)Spring MVC】
將一個參數(shù)設(shè)置到@SessionAttribute中需要同時滿足兩個條件:
在@SessionAttribute注解中設(shè)置了參數(shù)的名字或者類型
在處理器(Controller)中將參數(shù)設(shè)置到了Model中(這樣方法結(jié)束后會自動的同步到SessionAttr里)
總結(jié)這篇文章介紹了@SessionAttribute的核心處理原理,以及也給了一個Demo來介紹它的基本使用,不出意外閱讀下來你對它應(yīng)該是有很好的收獲的,希望能幫助到你簡化開發(fā)~
相關(guān)閱讀從原理層面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【一起學(xué)Spring MVC】
知識交流==The last:如果覺得本文對你有幫助,不妨點個贊唄。當(dāng)然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==
**若對技術(shù)內(nèi)容感興趣可以加入wx群交流:Java高工、架構(gòu)師3群。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會手動邀請入群**
若文章格式混亂或者圖片裂開,請點擊`:原文鏈接-原文鏈接-原文鏈接
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/75808.html
摘要:同時另外一個目的是希望完全屏蔽掉源生,增加它的擴(kuò)展性。本文我以為例進(jìn)行講解,因為也是后推出的注解不管從使用和原理上都是一模一樣的。作用從中取對應(yīng)的屬性值。 每篇一句 改我們就改得:取其精華,去其糟粕。否則木有意義 前言 如果說知道@SessionAttributes這個注解的人已經(jīng)很少了,那么不需要統(tǒng)計我就可以確定的說:知道@RequestAttribute注解的更是少之又少。我覺得主...
摘要:和一起使用參照博文從原理層面掌握的使用一起學(xué)。至于具體原因,可以移步這里輔助理解從原理層面掌握的使用核心原理篇一起學(xué)再看下面的變種例子重要訪問。 每篇一句 每個人都應(yīng)該想清楚這個問題:你是祖師爺賞飯吃的,還是靠老天爺賞飯吃的 前言 上篇文章 描繪了@ModelAttribute的核心原理,這篇聚焦在場景使用上,演示@ModelAttribute在不同場景下的使用,以及注意事項(當(dāng)然有些...
摘要:雖然它不是必須,但是它是個很好的輔助官方解釋首先看看官方的對它怎么說它將方法參數(shù)方法返回值綁定到的里面。解析注解標(biāo)注的方法參數(shù),并處理標(biāo)注的方法返回值。 每篇一句 我們應(yīng)該做一個:胸中有藍(lán)圖,腳底有計劃的人 前言 Spring MVC提供的基于注釋的編程模型,極大的簡化了web應(yīng)用的開發(fā),我們都是受益者。比如我們在@RestController標(biāo)注的Controller控制器組件上用@...
摘要:并且,并且如果或者不為空不為且不為,將中斷處理直接返回不再渲染頁面對返回值的處理對返回值的處理是使用完成的對異步處理結(jié)果的處理使用示例文首說了,作為一個非公開,如果你要直接使用起來,還是稍微要費點勁的。 每篇一句 想當(dāng)火影的人沒有近道可尋,當(dāng)上火影的人同樣無路可退 前言 HandlerMethod它作為Spring MVC的非公開API,可能絕大多數(shù)小伙伴都對它比較陌生,但我相信你對它...
摘要:并且,并且如果或者不為空不為且不為,將中斷處理直接返回不再渲染頁面對返回值的處理對返回值的處理是使用完成的對異步處理結(jié)果的處理使用示例文首說了,作為一個非公開,如果你要直接使用起來,還是稍微要費點勁的。 每篇一句 想當(dāng)火影的人沒有近道可尋,當(dāng)上火影的人同樣無路可退 前言 HandlerMethod它作為Spring MVC的非公開API,可能絕大多數(shù)小伙伴都對它比較陌生,但我相信你對它...
閱讀 3515·2021-11-15 11:38
閱讀 834·2021-11-08 13:27
閱讀 2245·2021-07-29 14:50
閱讀 2977·2019-08-29 13:06
閱讀 844·2019-08-29 11:22
閱讀 2416·2019-08-29 11:04
閱讀 3508·2019-08-28 18:23
閱讀 895·2019-08-26 13:46