當(dāng)前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
Spring Session源码解析
生活随笔
收集整理的這篇文章主要介紹了
Spring Session源码解析
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
AbstractHttpSessionApplicationInitializer,很明顯它是一個初始化的類,它是一個抽象類,可以理解為一個公用的基類,然后看一下onStartup這個方法,最主要的方法看這里,insertSessionRepositoryFilter,把servlet的一個上下文傳進來了,/*** Registers the springSessionRepositoryFilter.* @param servletContext the {@link ServletContext}*/private void insertSessionRepositoryFilter(ServletContext servletContext) {String filterName = DEFAULT_FILTER_NAME;DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(filterName);String contextAttribute = getWebApplicationContextAttribute();if (contextAttribute != null) {springSessionRepositoryFilter.setContextAttribute(contextAttribute);}registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);}DEFAULT_FILTER_NAME是什么public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter";DEFAULT_FILTER_NAME正是springSessionRepositoryFilter,那看到這個名字呢,還記不記得我們web.xml里面聲明的,<!-- spring session框架和自己實現(xiàn)的redis二選一 --><filter><filter-name>springSessionRepositoryFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSessionRepositoryFilter</filter-name><url-pattern>*.do</url-pattern></filter-mapping>正是這個name,進入insertSessionRepositoryFilter這個方法,這里面動態(tài)的加載了一個DelegatingFilterProxy,這里只有一個參數(shù),而這個參數(shù)呢,通過這個名字我們就能夠看出來,這個filter最終會把請求,代理給具體的一個filter,那通過入?yún)⒌某A磕?就可以看出來,它是委派給springSessionRepositoryFilter這個filter,而這個filter是由Spring容器管理的,那說到了這里我們就要看一下DelegatingFilterProxy這個類,首先這個類的包在哪個包下呢,這個包是引用Spring4.0.3,我們可以看一下他的繼承關(guān)系
首先它繼承了GenericFilterBean,然后GenericFilterBean又實現(xiàn)了以上這些接口,同時他也實現(xiàn)了Filter接口,所以我們可以在filter里面配置他,他的父類是一個抽象類,我們看一下他的初始化方法,/*** Standard way of initializing this filter.* Map config parameters onto bean properties of this filter, and* invoke subclass initialization.* @param filterConfig the configuration for this filter* @throws ServletException if bean properties are invalid (or required* properties are missing), or if subclass initialization fails.* @see #initFilterBean*/@Overridepublic final void init(FilterConfig filterConfig) throws ServletException {Assert.notNull(filterConfig, "FilterConfig must not be null");if (logger.isDebugEnabled()) {logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");}this.filterConfig = filterConfig;// Set bean properties from init parameters.PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());Environment env = this.environment;if (env == null) {env = new StandardServletEnvironment();}bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {String msg = "Failed to set bean properties on filter '" +filterConfig.getFilterName() + "': " + ex.getMessage();logger.error(msg, ex);throw new NestedServletException(msg, ex);}}// Let subclasses do whatever initialization they like.initFilterBean();if (logger.isDebugEnabled()) {logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");}}這里面有一個initFilterBean,也就是filterBean是在這里初始化的,我們可以看到這個方法是空實現(xiàn)/*** Subclasses may override this to perform custom initialization.* All bean properties of this filter will have been set before this* method is invoked.* <p>Note: This method will be called from standard filter initialization* as well as filter bean initialization in a Spring application context.* Filter name and ServletContext will be available in both cases.* <p>This default implementation is empty.* @throws ServletException if subclass initialization fails* @see #getFilterName()* @see #getServletContext()*/protected void initFilterBean() throws ServletException {}這個時候按CTRL+T進入到他的實現(xiàn)里面@Overrideprotected void initFilterBean() throws ServletException {synchronized (this.delegateMonitor) {if (this.delegate == null) {// If no target bean name specified, use filter name.if (this.targetBeanName == null) {this.targetBeanName = getFilterName();}// Fetch Spring root application context and initialize the delegate early,// if possible. If the root application context will be started after this// filter proxy, we'll have to resort to lazy initialization.WebApplicationContext wac = findWebApplicationContext();if (wac != null) {this.delegate = initDelegate(wac);}}}}這個時候就跳入到DelegatingFilterProxy這個類里面這個類上邊有一個同步塊,首先是為了防止Spring容器在啟動的時候,保證一下他們的執(zhí)行順序,然后最后獲取一下WebApplicationContext,然后再把委托初始化一下,然后我們再看一下他的doFilter方法,@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {// Lazily initialize the delegate if necessary.Filter delegateToUse = this.delegate;if (delegateToUse == null) {synchronized (this.delegateMonitor) {delegateToUse = this.delegate;if (delegateToUse == null) {WebApplicationContext wac = findWebApplicationContext();if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: " +"no ContextLoaderListener or DispatcherServlet registered?");}delegateToUse = initDelegate(wac);}this.delegate = delegateToUse;}}// Let the delegate perform the actual doFilter operation.invokeDelegate(delegateToUse, request, response, filterChain);}所以每個請求都會走到doFilter方法,我們主要看這個方法,invokeDelegate,/*** Actually invoke the delegate Filter with the given request and response.* @param delegate the delegate Filter* @param request the current HTTP request* @param response the current HTTP response* @param filterChain the current FilterChain* @throws ServletException if thrown by the Filter* @throws IOException if thrown by the Filter*/protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {delegate.doFilter(request, response, filterChain);}也就是在執(zhí)行doFilter里邊,他又調(diào)用了一個委托的一個方法,我們進來看一下,從他的執(zhí)行實現(xiàn)里邊呢,可以看出來,他又調(diào)用了委托的doFilter方法,我們看看他所屬的包,第一個所屬Spring session包,第二個呢,所屬spring web 4.0.3 release,那我們肯定是看spring session包的,那這個類的名字叫OncePerRequestFilter,也就是我每一次只filter一次,那所以這里會做一個判斷,它是如何判斷的呢,在這里是否是被filter的attribute,然后從里面拿了一個attribute,如果拿到的值不等于空,那就對他做一個判斷,/*** This {@code doFilter} implementation stores a request attribute for* "already filtered", proceeding without filtering again if the attribute is already* there.* @param request the request* @param response the response* @param filterChain the filter chain* @throws ServletException if request is not HTTP request* @throws IOException in case of I/O operation exception*/public final void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain) throws ServletException, IOException {if (!(request instanceof HttpServletRequest)|| !(response instanceof HttpServletResponse)) {throw new ServletException("OncePerRequestFilter just supports HTTP requests");}HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;boolean hasAlreadyFilteredAttribute = request.getAttribute(this.alreadyFilteredAttributeName) != null;if (hasAlreadyFilteredAttribute) {// Proceed without invoking this filter...filterChain.doFilter(request, response);}else {// Do invoke this filter...request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);try {doFilterInternal(httpRequest, httpResponse, filterChain);}finally {// Remove the "already filtered" request attribute for this request.request.removeAttribute(this.alreadyFilteredAttributeName);}}}而this.alreadyFilteredAttributeName呢,這個name就是使用這個類名,連上這個常量,而這個常量呢,private String alreadyFilteredAttributeName = getClass().getName().concat(ALREADY_FILTERED_SUFFIX);就是已經(jīng)被過濾的,通過doFilter方法可以得知,首先它通過request里面的attribute,判斷是否已經(jīng)過濾,每次請求只做一次過濾邏輯,如果這個請求是第一次進入這個filter,并且執(zhí)行現(xiàn)在的doFilter方法,那就會調(diào)用這個方法,而這個方法呢
doFilterInternal方法,之前我們說過Spring Session框架呢,會對現(xiàn)有的request和response,進行一個包裝,@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response, this.servletContext);SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);HttpServletRequest strategyRequest = this.httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse);HttpServletResponse strategyResponse = this.httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse);try {filterChain.doFilter(strategyRequest, strategyResponse);}finally {wrappedRequest.commitSession();}}SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper這兩個類,包裝好的request和response,我們看一下這個類,看一下SessionRepositoryRequestWrapper的類圖
首先它繼承了HttpServletRequestWrapper,而這個是servlet原生提供的一個類,他繼承他之后呢,他就擁有了可以包裝request的一個功能,通過重寫方法的一個方式,那response也是同理,這里面覆蓋了很多方法,首先拿getSession來說@Overridepublic HttpSessionWrapper getSession() {return getSession(true);}他首先執(zhí)行這個方法,這個方法就把getSession方法進行了一個重寫,@Overridepublic HttpSessionWrapper getSession(boolean create) {HttpSessionWrapper currentSession = getCurrentSession();if (currentSession != null) {return currentSession;}String requestedSessionId = getRequestedSessionId();if (requestedSessionId != null&& getAttribute(INVALID_SESSION_ID_ATTR) == null) {S session = getSession(requestedSessionId);if (session != null) {this.requestedSessionIdValid = true;currentSession = new HttpSessionWrapper(session, getServletContext());currentSession.setNew(false);setCurrentSession(currentSession);return currentSession;}else {// This is an invalid session id. No need to ask again if// request.getSession is invoked for the duration of this requestif (SESSION_LOGGER.isDebugEnabled()) {SESSION_LOGGER.debug("No session found by id: Caching result for getSession(false) for this HttpServletRequest.");}setAttribute(INVALID_SESSION_ID_ATTR, "true");}}if (!create) {return null;}if (SESSION_LOGGER.isDebugEnabled()) {SESSION_LOGGER.debug("A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "+ SESSION_LOGGER_NAME,new RuntimeException("For debugging purposes only (not an error)"));}S session = SessionRepositoryFilter.this.sessionRepository.createSession();session.setLastAccessedTime(System.currentTimeMillis());currentSession = new HttpSessionWrapper(session, getServletContext());setCurrentSession(currentSession);return currentSession;}有興趣的小伙伴可以看一下,setLastAccessedTime,這個值會存到redis當(dāng)中,session又用HttpSessionWrapper進行了一個包裝,SessionWrapper和requestWrapper,responseWrapper是同理的,然后把當(dāng)前的session進行一個set,看一下set是如何做的private void setCurrentSession(HttpSessionWrapper currentSession) {if (currentSession == null) {removeAttribute(this.CURRENT_SESSION_ATTR);}else {setAttribute(this.CURRENT_SESSION_ATTR, currentSession);}}如果為空的話就remove,如果不為空就set上,把currentSession都放到這里,而KEY是一個常量,private final String CURRENT_SESSION_ATTR = HttpServletRequestWrapper.class.getName();reqeuestWrapper的一個類名,那我們還要看一個關(guān)鍵的類,HttpSessionStrategy,它是一個接口,那我們看一下這個接口有哪些方法,首先是獲取sessionId/*** Obtains the requested session id from the provided* {@link javax.servlet.http.HttpServletRequest}. For example, the session id might* come from a cookie or a request header.** @param request the {@link javax.servlet.http.HttpServletRequest} to obtain the* session id from. Cannot be null.* @return the {@link javax.servlet.http.HttpServletRequest} to obtain the session id* from.*/String getRequestedSessionId(HttpServletRequest request);從request當(dāng)中獲取sessionId,因為參數(shù)是一個request,例如我們的sessionId就存到了cookie當(dāng)中,那還有一種方式就是把sessionId放到請求的header當(dāng)中,那咱們用的是cookie,還有一個onNewSession/*** This method is invoked when a new session is created and should inform a client* what the new session id is. For example, it might create a new cookie with the* session id in it or set an HTTP response header with the value of the new session* id.** Some implementations may wish to associate additional information to the* {@link Session} at this time. For example, they may wish to add the IP Address,* browser headers, the username, etc to the* {@link org.springframework.session.Session}.** @param session the {@link org.springframework.session.Session} that is being sent* to the client. Cannot be null.* @param request the {@link javax.servlet.http.HttpServletRequest} that create the* new {@link org.springframework.session.Session} Cannot be null.* @param response the {@link javax.servlet.http.HttpServletResponse} that is* associated with the {@link javax.servlet.http.HttpServletRequest} that created the* new {@link org.springframework.session.Session} Cannot be null.*/void onNewSession(Session session, HttpServletRequest request,HttpServletResponse response);這里面?zhèn)髁巳齻€參數(shù),request,response,這個方法是用到什么時候呢,是當(dāng)新session被創(chuàng)建,并創(chuàng)建新的sessionId時,這個方法才會被調(diào)用,簡單理解為new session的時候,才會被調(diào)用,/*** This method is invoked when a session is invalidated and should inform a client* that the session id is no longer valid. For example, it might remove a cookie with* the session id in it or set an HTTP response header with an empty value indicating* to the client to no longer submit that session id.** @param request the {@link javax.servlet.http.HttpServletRequest} that invalidated* the {@link org.springframework.session.Session} Cannot be null.* @param response the {@link javax.servlet.http.HttpServletResponse} that is* associated with the {@link javax.servlet.http.HttpServletRequest} that invalidated* the {@link org.springframework.session.Session} Cannot be null.*/void onInvalidateSession(HttpServletRequest request, HttpServletResponse response);當(dāng)這個session失效的時候,才會被調(diào)用,我們看一下他的實現(xiàn)都有哪些
剛剛說的放到requestHeader里面呢,就是這種實現(xiàn),而我們是使用的cookie這種實現(xiàn)方式,這個就是cookie session的一個策略,CookieHttpSessionStrategy,這里面的核心方法我們可以看一個,比如getRequestedSessionIdpublic String getRequestedSessionId(HttpServletRequest request) {Map<String, String> sessionIds = getSessionIds(request);String sessionAlias = getCurrentSessionAlias(request);return sessionIds.get(sessionAlias);}那這個方法首先調(diào)用了getSessionIds,從request當(dāng)中獲取,然后拼接成一個mappublic Map<String, String> getSessionIds(HttpServletRequest request) {List<String> cookieValues = this.cookieSerializer.readCookieValues(request);String sessionCookieValue = cookieValues.isEmpty() ? "": cookieValues.iterator().next();Map<String, String> result = new LinkedHashMap<String, String>();StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " ");if (tokens.countTokens() == 1) {result.put(DEFAULT_ALIAS, tokens.nextToken());return result;}while (tokens.hasMoreTokens()) {String alias = tokens.nextToken();if (!tokens.hasMoreTokens()) {break;}String id = tokens.nextToken();result.put(alias, id);}return result;}這個方法從request中獲取cookieValues,并把alias別名和id,維護成一個map,也就是result,然后對他進行一個返回,這個時候就調(diào)用這個方法getCurrentSessionAlias,獲取當(dāng)前session的別名,通過request拿到session的一個別名,然后通過剛剛得到一個map,組裝成一個map,還有session的一個別名,來get一個string,而String就是sessionId的一個value,有興趣的可以仔細(xì)閱讀一下里面的源碼,然后我們再回來SessionRepository接口,這個接口有幾個方法呢,有4個,分別是創(chuàng)建session,刪除,和save session,那save session就是持久化了,我們的實現(xiàn)方案是使用redis
RedisOperationsSessionRepository這個類,他有哪些常量呢,記得我們Spring session存的前綴,那就在這里/*** The prefix of the key for used for session attributes. The suffix is the name of* the session attribute. For example, if the session contained an attribute named* attributeName, then there would be an entry in the hash named* sessionAttr:attributeName that mapped to its value.*/static final String SESSION_ATTR_PREFIX = "sessionAttr:";還有這么一個keyPrefix/*** The prefix for every key used by Spring Session in Redis.*/private String keyPrefix = DEFAULT_SPRING_SESSION_REDIS_PREFIX;key的prefix,我們看一下這個常量是什么,/*** The default prefix for each key and channel in Redis used by Spring Session.*/static final String DEFAULT_SPRING_SESSION_REDIS_PREFIX = "spring:session:";所以就和我們之前看到的都對上了,那從名字就可以看出來,這里面會有大量的對redis的一個操作,下面還有獲取session的一個實現(xiàn),public RedisSession getSession(String id) {return getSession(id, false);}而這個實現(xiàn)又調(diào)用了這個方法/*** Gets the session.* @param id the session id* @param allowExpired if true, will also include expired sessions that have not been* deleted. If false, will ensure expired sessions are not returned.* @return the Redis session*/private RedisSession getSession(String id, boolean allowExpired) {Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();if (entries.isEmpty()) {return null;}MapSession loaded = loadSession(id, entries);if (!allowExpired && loaded.isExpired()) {return null;}RedisSession result = new RedisSession(loaded);result.originalLastAccessTime = loaded.getLastAccessedTime();return result;}還有l(wèi)oadSessionprivate MapSession loadSession(String id, Map<Object, Object> entries) {MapSession loaded = new MapSession(id);for (Map.Entry<Object, Object> entry : entries.entrySet()) {String key = (String) entry.getKey();if (CREATION_TIME_ATTR.equals(key)) {loaded.setCreationTime((Long) entry.getValue());}else if (MAX_INACTIVE_ATTR.equals(key)) {loaded.setMaxInactiveIntervalInSeconds((Integer) entry.getValue());}else if (LAST_ACCESSED_ATTR.equals(key)) {loaded.setLastAccessedTime((Long) entry.getValue());}else if (key.startsWith(SESSION_ATTR_PREFIX)) {loaded.setAttribute(key.substring(SESSION_ATTR_PREFIX.length()),entry.getValue());}}return loaded;}還有DefaultCookieSerializer這么一個類,之前我們在注入cookie的時候有講,所以這個類也是非常重要的,那通過Spring session的源碼解析呢,我們把剛剛關(guān)鍵的幾個類,都介紹了一遍
?
總結(jié)
以上是生活随笔為你收集整理的Spring Session源码解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Session实战4
- 下一篇: Spring Schedule定时关单快