打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
[互联网] 利用springsession解决共享Session问题 | 软件世界网

build.gradle文件里配置了Spring Session编译依赖的3个项目:
apply from: JAVA_GRADLEapply from: MAVEN_GRADLEapply 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了:
<dependency>    <groupId>org.springframework.session</groupId>    <artifactId>spring-session-data-redis</artifactId>    <version>1.0.1.RELEASE</version></dependency>

(2)第二步,编写一个配置类,用来启用RedisHttpSession功能,并向Spring容器中注册一个RedisConnectionFactory。
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 Configuration,来代替web.xml   public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {        @Override    protected Class<?>[] getRootConfigClasses() {        return new Class[]{Config1.class, Config2.class, RedisHttpSessionConfig.class};    }		//......}

(4)第四步,编写一个一个AbstractHttpSessionApplicationInitializer实现类,用于向Servlet容器中添加springSessionRepositoryFilter。
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容器中注册了一个SessionRepositoryFilterSessionRepositoryFilter的依赖关系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory)。

package org.springframework.session.data.redis.config.annotation.web.http;@Configuration@EnableSchedulingpublic 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过滤器链中。

package org.springframework.session.web.context;/** * Registers the {@link DelegatingFilterProxy} to use the * springSessionRepositoryFilter before any other registered {@link Filter}.  * * ...... */@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);//注册一个SessionRepositoryFilter		afterSessionRepositoryFilter(servletContext);	}	/**	 * Registers the springSessionRepositoryFilter	 * @param servletContext the {@link ServletContext}	 */	private void insertSessionRepositoryFilter(ServletContext servletContext) {		String filterName = DEFAULT_FILTER_NAME;//默认名字是springSessionRepositoryFilter		DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(filterName);//该Filter代理会在初始化时从Spring容器中查找springSessionRepositoryFilter,之后实际会使用SessionRepositoryFilter进行doFilter操作 				String contextAttribute = getWebApplicationContextAttribute();		if(contextAttribute != null) {			springSessionRepositoryFilter.setContextAttribute(contextAttribute);		}		registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);	}		//......}

SessionRepositoryFilter是一个优先级最高的javax.servlet.Filter,它使用了一个SessionRepositoryRequestWrapper类接管了Http Session的创建和管理工作。
注意下面给出的是简化过的示例代码,与spring-session项目的源代码有所差异。
@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);        }        // ...}

public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {        public SessionRepositoryRequestWrapper(HttpServletRequest original) {                super(original);        }        public HttpSession getSession() {                return getSession(true);        }        public HttpSession getSession(boolean createNew) {                // create an HttpSession implementation from Spring Session        }        // ... other methods delegate to the original HttpServletRequest ...}

(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实现类。
package org.springframework.web;@HandlesTypes(WebApplicationInitializer.class)public class SpringServletContainerInitializer implements ServletContainerInitializer {	/**	 * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}	 * implementations present on the application classpath.	 *	 * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},	 * Servlet 3.0+ containers will automatically scan the classpath for implementations	 * of Spring's {@code WebApplicationInitializer} interface and provide the set of all	 * such types to the {@code webAppInitializerClasses} parameter of this method.	 *	 * <p>If no {@code WebApplicationInitializer} implementations are found on the	 * classpath, this method is effectively a no-op. An INFO-level log message will be	 * issued notifying the user that the {@code ServletContainerInitializer} has indeed	 * been invoked but that no {@code WebApplicationInitializer} implementations were	 * found.	 *	 * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,	 * they will be instantiated (and <em>sorted</em> if the @{@link	 * org.springframework.core.annotation.Order @Order} annotation is present or	 * the {@link org.springframework.core.Ordered Ordered} interface has been	 * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}	 * method will be invoked on each instance, delegating the {@code ServletContext} such	 * that each instance may register and configure servlets such as Spring's	 * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},	 * or any other Servlet API componentry such as filters.	 *	 * @param webAppInitializerClasses all implementations of	 * {@link WebApplicationInitializer} found on the application classpath	 * @param servletContext the servlet context to be initialized	 * @see WebApplicationInitializer#onStartup(ServletContext)	 * @see AnnotationAwareOrderComparator	 */	@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数据过期的时间)。
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-9b3b26eee578hash127.0.0.1:6379> type spring:session:expirations:1431577740000set

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-32079942888b1) "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共享机制
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Spring-Session基于Redis管理Session【面试+工作】
基于Spring Session实现Session的分布式管理
spring-session Redis实现Session共享
在spring web应用中获得ApplicationContext的引用(2014-06-27)
Java中的Listener 监听器
SpringSession和Redis实现Session跨域
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服