https://blog.csdn.net/patrickyoung6625/article/details/45694157
1.共享Session問題 HttpSession是通過Servlet容器創建和管理的,像Tomcat/Jetty都是保存在內存中的。而如果我們把web服務器搭建成分布式的集群,然后利用LVS或Nginx做負載均衡,那么來自同一用戶的Http請求將有可能被分發到兩個不同的web站點中去。那么問題就來了,如何保證不同的web站點能夠共享同一份session數據呢?
最簡單的想法就是把session數據保存到內存以外的一個統一的地方,例如Memcached/Redis等數據庫中。那么問題又來了,如何替換掉Servlet容器創建和管理HttpSession的實現呢? (1)設計一個Filter,利用HttpServletRequestWrapper,實現自己的 getSession()方法,接管創建和管理Session數據的工作。spring-session就是通過這樣的思路實現的。 (2)利用Servlet容器提供的插件功能,自定義HttpSession的創建和管理策略,并通過配置的方式替換掉默認的策略。不過這種方式有個缺點,就是需要耦合Tomcat/Jetty等Servlet容器的代碼。這方面其實早就有開源項目了,例如memcached-session-manager,以及tomcat-redis-session-manager。暫時都只支持Tomcat6/Tomcat7。
2.Spring Session介紹 Spring Session是Spring的項目之一,GitHub地址:https://github.com/spring-projects/spring-session。
Spring Session提供了一套創建和管理Servlet HttpSession的方案。Spring Session提供了集群Session(Clustered Sessions)功能,默認采用外置的Redis來存儲Session數據,以此來解決Session共享的問題。
?
?
?
下面是來自官網的特性介紹:
?
Features ?
Spring Session provides the following features:
API and implementations for managing a user's session HttpSession?- allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way Clustered Sessions?- Spring Session makes it trivial to support clustered sessions without being tied to an application container specific solution. Multiple Browser Sessions?- Spring Session supports managing multiple users' sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google). RESTful APIs?- Spring Session allows providing session ids in headers to work with RESTful APIs WebSocket?- provides the ability to keep the HttpSession alive when receiving WebSocket messages 3.集成Spring Session的正確姿勢 ?
下面是實際調試通過的例子,包含下面4個步驟:
(1)第一步,添加Maven依賴
?
根據官網Quick Start展示的依賴,在項目pom.xml中添加后各種找不到類引用。于是查看Spring Session項目的build.gradle文件,居然沒有配置依賴的項目,難道還要我自己去找它的依賴,太不專業了吧?!!!
?
[html] ?view plaincopy
<dependencies>?? ????<dependency>?? ????????<groupId>org.springframework.session</groupId>?? ????????<artifactId>spring-session</artifactId>?? ????????<version>1.0.1.RELEASE</version>?? ????</dependency>?? </dependencies>?? 終于在多番仔細研究Spring Session項目源碼之后,看到了spring-session-data-redis項目:
?
build.gradle文件里配置了Spring Session編譯依賴的3個項目:
?
[plain] ?view plaincopy
apply?from:?JAVA_GRADLE?? apply?from:?MAVEN_GRADLE?? ?? apply?plugin:?'spring-io'?? ?? description?=?"Aggregator?for?Spring?Session?and?Spring?Data?Redis"?? ?? dependencies?{?? ????compile?project(':spring-session'),?? ????????????"org.springframework.data:spring-data-redis:$springDataRedisVersion",?? ????????????"redis.clients:jedis:$jedisVersion",?? ????????????"org.apache.commons:commons-pool2:$commonsPoolVersion"?? ?? ????springIoVersions?"io.spring.platform:platform-versions:${springIoVersion}@properties"?? }?? 于是,真正的Maven依賴改成spring-session-data-redis就OK了:
?
[html] ?view plaincopy
<dependency>?? ????<groupId>org.springframework.session</groupId>?? ????<artifactId>spring-session-data-redis</artifactId>?? ????<version>1.0.1.RELEASE</version>?? </dependency>?? (2)第二步,編寫一個配置類,用來啟用RedisHttpSession功能,并向Spring容器中注冊一個RedisConnectionFactory。
[java] ?view plaincopy
import?org.springframework.context.annotation.Bean;?? import?org.springframework.data.redis.connection.RedisConnectionFactory;?? import?org.springframework.data.redis.connection.jedis.JedisConnectionFactory;?? import?org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;?? ?? @EnableRedisHttpSession(maxInactiveIntervalInSeconds?=?7200)?? public?class?RedisHttpSessionConfig?{?? ?? ????@Bean?? ????public?RedisConnectionFactory?connectionFactory()?{?? ????????JedisConnectionFactory?connectionFactory?=?new?JedisConnectionFactory();?? ????????connectionFactory.setPort(6379);?? ????????connectionFactory.setHostName("10.18.15.190");?? ????????return?connectionFactory;?? ????}?? }?? (3)第三步,將RedisHttpSessionConfig加入到WebInitializer#getRootConfigClasses()中,讓Spring容器加載RedisHttpSessionConfig類。WebInitializer是一個自定義的AbstractAnnotationConfigDispatcherServletInitializer實現類,該類會在Servlet啟動時加載(當然也可以采用別的加載方法,比如采用掃描@Configuration注解類的方式等等)。
?
[java] ?view plaincopy
public?class?WebInitializer?extends?AbstractAnnotationConfigDispatcherServletInitializer?{?? ?????? ????@Override?? ????protected?Class<?>[]?getRootConfigClasses()?{?? ????????return?new?Class[]{Config1.class,?Config2.class,?RedisHttpSessionConfig.class};?? ????}?? ?????? ???? }?? ?
(4)第四步,編寫一個一個AbstractHttpSessionApplicationInitializer實現類,用于向Servlet容器中添加springSessionRepositoryFilter。
?
[java] ?view plaincopy
import?org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;?? ?? public?class?SpringSessionInitializer?extends?AbstractHttpSessionApplicationInitializer?{?? }?? ?
4. Spring Session原理 ?
(1)前面集成spring-sesion的第二步中,編寫了一個配置類RedisHttpSessionConfig,它包含注解@EnableRedisHttpSession,并通過@Bean注解注冊了一個RedisConnectionFactory到Spring容器中。
而@EnableRedisHttpSession注解通過Import,引入了RedisHttpSessionConfiguration配置類。該配置類通過@Bean注解,向Spring容器中注冊了一個SessionRepositoryFilter(SessionRepositoryFilter的依賴關系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory)。
?
?
[java] ?view plaincopy
package?org.springframework.session.data.redis.config.annotation.web.http;?? ?? @Configuration?? @EnableScheduling?? public?class?RedisHttpSessionConfiguration?implements?ImportAware,?BeanClassLoaderAware?{?? ???? ?????? ????@Bean?? ????public?RedisTemplate<String,ExpiringSession>?sessionRedisTemplate(RedisConnectionFactory?connectionFactory)?{?? ???????? ????????return?template;?? ????}?? ?????? ????@Bean?? ????public?RedisOperationsSessionRepository?sessionRepository(RedisTemplate<String,?ExpiringSession>?sessionRedisTemplate)?{?? ???????? ????????return?sessionRepository;?? ????}?? ?????? ????@Bean?? ????public?<S?extends?ExpiringSession>?SessionRepositoryFilter<??extends?ExpiringSession>?springSessionRepositoryFilter(SessionRepository<S>?sessionRepository,?ServletContext?servletContext)?{?? ???????? ????????return?sessionRepositoryFilter;?? ????}?? ?????? ???? }?? (2)集成spring-sesion的第四步中,我們編寫了一個SpringSessionInitializer 類,它繼承自AbstractHttpSessionApplicationInitializer。該類不需要重載或實現任何方法,它的作用是在Servlet容器初始化時,從Spring容器中獲取一個默認名叫sessionRepositoryFilter的過濾器類(之前沒有注冊的話這里找不到會報錯),并添加到Servlet過濾器鏈中。
?
?
[java] ?view plaincopy
package?org.springframework.session.web.context;?? ?? @Order(100)?? public?abstract?class?AbstractHttpSessionApplicationInitializer?implements?WebApplicationInitializer?{?? ?? ????private?static?final?String?SERVLET_CONTEXT_PREFIX?=?"org.springframework.web.servlet.FrameworkServlet.CONTEXT.";?? ?? ????public?static?final?String?DEFAULT_FILTER_NAME?=?"springSessionRepositoryFilter";?? ?? ???? ?? ????public?void?onStartup(ServletContext?servletContext)?? ????????????throws?ServletException?{?? ????????beforeSessionRepositoryFilter(servletContext);?? ????????if(configurationClasses?!=?null)?{?? ????????????AnnotationConfigWebApplicationContext?rootAppContext?=?new?AnnotationConfigWebApplicationContext();?? ????????????rootAppContext.register(configurationClasses);?? ????????????servletContext.addListener(new?ContextLoaderListener(rootAppContext));?? ????????}?? ????????insertSessionRepositoryFilter(servletContext); ????????afterSessionRepositoryFilter(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);?? ????}?? ?????? ???? }?? SessionRepositoryFilter是一個優先級最高的javax.servlet.Filter,它使用了一個SessionRepositoryRequestWrapper類接管了Http Session的創建和管理工作。
注意下面給出的是簡化過的示例代碼,與spring-session項目的源代碼有所差異。
?
[java] ?view plaincopy
@Order(SessionRepositoryFilter.DEFAULT_ORDER)?? public?class?SessionRepositoryFilter?implements?Filter?{?? ?? ????????public?doFilter(ServletRequest?request,?ServletResponse?response,?FilterChain?chain)?{?? ????????????????HttpServletRequest?httpRequest?=?(HttpServletRequest)?request;?? ????????????????SessionRepositoryRequestWrapper?customRequest?=?? ????????????????????????new?SessionRepositoryRequestWrapper(httpRequest);?? ?? ????????????????chain.doFilter(customRequest,?response,?chain);?? ????????}?? ?? ???????? }?? ?
[java] ?view plaincopy
public?class?SessionRepositoryRequestWrapper?extends?HttpServletRequestWrapper?{?? ?? ????????public?SessionRepositoryRequestWrapper(HttpServletRequest?original)?{?? ????????????????super(original);?? ????????}?? ?? ????????public?HttpSession?getSession()?{?? ????????????????return?getSession(true);?? ????????}?? ?? ????????public?HttpSession?getSession(boolean?createNew)?{?? ???????????????? ????????}?? ?? ???????? }?? (3)好了,剩下的問題就是,如何在Servlet容器啟動時,加載下面兩個類。幸運的是,這兩個類由于都實現了WebApplicationInitializer接口,會被自動加載。
WebInitializer,負責加載配置類。它繼承自AbstractAnnotationConfigDispatcherServletInitializer,實現了WebApplicationInitializer接口 SpringSessionInitializer,負責添加sessionRepositoryFilter的過濾器類。它繼承自AbstractHttpSessionApplicationInitializer,實現了WebApplicationInitializer接口 ?
在Servlet3.0規范中,Servlet容器啟動時會自動掃描javax.servlet.ServletContainerInitializer的實現類,在實現類中我們可以定制需要加載的類。在spring-web項目中,有一個ServletContainerInitializer實現類SpringServletContainerInitializer,它通過注解@HandlesTypes(WebApplicationInitializer.class),讓Servlet容器在啟動該類時,會自動尋找所有的WebApplicationInitializer實現類。
?
[java] ?view plaincopy
package?org.springframework.web;?? ?? @HandlesTypes(WebApplicationInitializer.class)?? public?class?SpringServletContainerInitializer?implements?ServletContainerInitializer?{?? ?? ???? ????@Override?? ????public?void?onStartup(Set<Class<?>>?webAppInitializerClasses,?ServletContext?servletContext)?? ????????????throws?ServletException?{?? ???????? ????}?? ?? }?? ?
5. 如何在Redis中查看Session數據? ?
(1)Http Session數據在Redis中是以Hash結構存儲的。
(2)可以看到,還有一個key="spring:session:expirations:1431577740000"的數據,是以Set結構保存的。這個值記錄了所有session數據應該被刪除的時間(即最新的一個session數據過期的時間)。
[plain] ?view plaincopy
127.0.0.1:6379>?keys?*?? 1)?"spring:session:expirations:1431577740000"?? 2)?"spring:session:sessions:e2cef3ae-c8ea-4346-ba6b-9b3b26eee578"?? 127.0.0.1:6379>?type?spring:session:sessions:e2cef3ae-c8ea-4346-ba6b-9b3b26eee578?? hash?? 127.0.0.1:6379>?type?spring:session:expirations:1431577740000?? set?? ?
[plain] ?view plaincopy
127.0.0.1:6379>?keys?*?? 1)?"spring:session:expirations:1431527520000"?? 2)?"spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b"?? 3)?"spring:session:sessions:11a69da6-138b-42bc-9916-60ae78aa55aa"?? 4)?"spring:session:sessions:0a51e2c2-4a3b-4986-a754-d886d8a5d42d"?? 5)?"spring:session:expirations:1431527460000"?? ?? 127.0.0.1:6379>?hkeys?spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b?? 1)?"maxInactiveInterval"?? 2)?"creationTime"?? 3)?"lastAccessedTime"?? 4)?"sessionAttr:attr1"?? ?? 127.0.0.1:6379>?hget?spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b?sessionAttr:attr1?? "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x03"?? ?? 127.0.0.1:6379>?hget?spring:session:sessions:59f3987c-d1e4-44b3-a83a-32079942888b?creationTime?? "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01MM\x94(\xec"?? 6.參考文章 ?
Spring Session 1.01 Reference
spring session入門
集群session共享機制
轉載于:https://www.cnblogs.com/davidwang456/articles/8994541.html
總結
以上是生活随笔 為你收集整理的利用spring session解决共享Session问题 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。