上回說到, spring組件的注解Scope大約有singleton、prototype、request、session、global session 這么幾種常用的場景。這里需要特別說明一下,根據源代碼顯示 Scope注解分為ConfigurableBeanFactory和WebApplicationContext兩個大類,ConfigurableBeanFactory包含(singleton、prototype)兩種Scope,WebApplicationContext下面有(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,request,session,application,servletContext,contextParameters,contextAttributes)這么幾種Scope。今天的示例重點是對request,session兩個場景進行一次測試。
那在談到具體的示例前,我先分享下對這兩種場景的使用心得,以便與各位看官進行思想上的神交! 我們都知道B/S站點運行起來后,是一個多線程的運行環境。每個客戶端登錄都會產生一個session會話,它的生命周期 從登錄系統到 session過期,期間session上存儲的信息都是有效可用的,我習慣於叫它會話級的緩存,像用戶登錄的身份信息我們一般都會綁定到這個session上。這里我們要講的@Scope("session"),就是spring提供出來的一個會話級bean方案,在這種模式下,用spring的DI功能來獲取組件,可以做到在會話的生命周期中這個組件只有一個實例。接下來再說請求(request),http協議的處理模型,從客戶端發起request請求,到服務端的處理,最后response給客戶端,我們稱為一次完整的請求。在這樣的一次請求過程中,我們的服務站可能要串行調用funcA->funcB->funcC·... 這樣的一串函數,假如我們的系統需要做細致的權限校驗(用戶權限,數據權限),更可怕的是funcA,funcB,funcC是3個人分別實現的,而且都要做權限校驗。那么極有可能會出現3個人各連接了一次數據庫,讀取了同一批權限數據。這里想象一下,假如一個數據讀取要花2秒,那么3個方法就要花費6秒的處理時間。但實際上這些數據只用在這個請求過程中讀取一次,緩存在request上下文環境中,我習慣稱之為線程級緩存。關於線程級緩存java有ThreadLocal方案,像Hibernate的事務就是用這種方案維持一次執行過程中數據庫連接的唯一。當然,今天要講的@Scope("request")也可以做到這種線程級別的緩存。下面我們看看具體的測試示例
實現測試步驟說明:
1、創建一個@Scope("session")標注的Bean組件。關於proxyMode這個參數,是為了解決依存的會話或者請求上下文環境還沒有時,自動裝載組件報錯,這里交給JDK代理,可以保證環境准備就緒時再執行組件裝載。
@Component //@Scope(value=WebApplicationContext.SCOPE_SESSION) @Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES) public class SessionBean implements ISessionBean { private UUID uuid; public SessionBean(){ uuid = UUID.randomUUID(); } public void printId(){ System.out.println("SessionBean:"+uuid); } }
2、創建一個@Scope("request")標注的Bean組件
@Component @Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode= ScopedProxyMode.INTERFACES) public class RequestBean implements IRequestBean { private UUID uuid; public RequestBean() { uuid = UUID.randomUUID(); } public void printId() { System.out.println("RequestBean:" + uuid); } }
3、添加一個多例的組件自動組裝服務類,方便獲取session、request組件。
@Service @Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class BeanInstance { @Autowired private IRequestBean requestBean; @Autowired private ISessionBean sessionBean; public IRequestBean getRequestBean() { return requestBean; } public ISessionBean getSessionBean() { return sessionBean; } }
4、添加測試代碼
@Controller @RequestMapping("/user") // 添加session信息的注解,可以實現 session信息與map的映射 賦值 @SessionAttributes("user") public class UserController { @Autowired private BeanInstance beanInstance1; @Autowired private BeanInstance beanInstance2; @RequestMapping(value = "/login", method = RequestMethod.GET) public String login(String name, Model model, HttpServletRequest request, HttpSession session) { model.addAttribute("user", name); System.out.println("SessionBean-1"); beanInstance1.getSessionBean().printId(); System.out.println("SessionBean-2"); beanInstance2.getSessionBean().printId(); System.out.println("RequestBean-1"); beanInstance1.getRequestBean().printId(); System.out.println("RequestBean-2"); beanInstance2.getRequestBean().printId(); return "user/check"; } /** * 檢查自動裝載的信息 * @param model * @param request * @param session * @return */ @RequestMapping(value = "/check", method = RequestMethod.GET) public String check(Model model, HttpServletRequest request, HttpSession session) { System.out.println("SessionBean-1"); beanInstance1.getSessionBean().printId(); System.out.println("SessionBean-2"); beanInstance2.getSessionBean().printId(); System.out.println("RequestBean-1"); beanInstance1.getRequestBean().printId(); System.out.println("RequestBean-2"); beanInstance2.getRequestBean().printId(); return "user/check"; } }
5、先發起一次登錄請求,再發起一次登錄后的檢查請求。從打印的測試結果可以看到兩次請求 拿到的session組件,其對應的ID都是相同的,所以在同一個會話中,session組件是唯一的。而request組件,在同一個request請求過程中,調用兩次都得到同一個組件ID,而在第二次請求中request組件的ID改變了。因此正如上面所說,@Scope("request")模式在同一請求過程中,spring返回的組件也是唯一的,我們可以用這個方案來做線程級別的數據緩存。
參考資料
https://www.cnblogs.com/waytofall/p/3460533.html
https://www.cnblogs.com/lonecloud/p/5937513.html