1.主要依赖
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.4.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>${spring.session.version}</version> </dependency>2.写一个configuration来启用RedisHttpSession,在这个配置注册一个redis客户端的连接工厂Bean,供Spring Session用于与redis服务端交互.
package org.exam.config; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /** * Created by xin on 15/1/20. */ @EnableRedisHttpSession public class SessionConfig { @Bean public JedisConnectionFactory connectionFactory() { return new JedisConnectionFactory(); } }3.写一个Initializer,主要用于向应用容器添加springSessionRepositoryFilter,顺便注册一下HttpSessionEventPublisher监听,这个监听的作用发布HttpSessionCreatedEvent和HttpSessionDestroyedEvent事件
package org.exam.config; import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; /** * Created by xin on 15/1/20. */ public class SessionApplicationInitializer extends AbstractHttpSessionApplicationInitializer { @Override protected void afterSessionRepositoryFilter(ServletContext servletContext) { servletContext.addListener(new HttpSessionEventPublisher()); } }
4.将SessionConfig加入到org.exam.config.DispatcherServletInitializer#getRootConfigClasses,不要加到ServletConfigClasses,至于原因看http://blog.csdn.net/xiejx618/article/details/50603758文末
@Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] {AppConfig.class,SessionConfig.class}; }5.使用例子.
package org.exam.web; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * Created by xin on 15/1/7. */ @Controller public class DefaultController { @RequestMapping("/") public String index(Model model,HttpServletRequest request,String action,String msg){ HttpSession session=request.getSession(); if ("set".equals(action)){ session.setAttribute("msg", msg); }else if ("get".equals(action)){ String message=(String)session.getAttribute("msg"); model.addAttribute("msgFromRedis",message); } return "index"; } }得到这个被spring session包装过的session,像平常一样直接使用.
从redis删除存入去相关的值,再次请求localhost:8080/testweb/?action=get查看结果
redis:
a.查询所有key:keys命令,keys *
b.根据某个key删除,使用del命令
源码例子:
http://download.csdn.net/detail/xiejx618/9369518
使用redis集群的一个例子:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.7.1.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.4.2</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>1.1.1.RELEASE</version> </dependency>
#REDIS START redis.maxRedirections=10 redis.maxWaitMillis=1500 redis.maxTotal=2048 redis.minIdle=20 redis.maxIdle=200 redis.jedisClusterNodes=192.168.1.250:6380,192.168.1.250:6381,192.168.1.250:6382 #REDIS END
@Configuration @EnableRedisHttpSession public class HttpSessionConfig implements EnvironmentAware { private Environment env; @Bean public JedisConnectionFactory jedisConnectionFactory() { String[] jedisClusterNodes = env.getProperty("redis.jedisClusterNodes").split(","); RedisClusterConfiguration clusterConfig=new RedisClusterConfiguration(Arrays.asList(jedisClusterNodes)); clusterConfig.setMaxRedirects(env.getProperty("redis.maxRedirections",Integer.class)); JedisPoolConfig poolConfig=new JedisPoolConfig(); poolConfig.setMaxWaitMillis(env.getProperty("redis.maxWaitMillis",Integer.class)); poolConfig.setMaxTotal(env.getProperty("redis.maxTotal",Integer.class)); poolConfig.setMinIdle(env.getProperty("redis.minIdle",Integer.class)); poolConfig.setMaxIdle(env.getProperty("redis.maxIdle",Integer.class)); return new JedisConnectionFactory(clusterConfig,poolConfig); } @Override public void setEnvironment(Environment environment) { this.env=environment; } }
下面顺便跟踪下实现吧:
1.注册springSessionRepositoryFilter位置在:org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer#insertSessionRepositoryFilter,从org.springframework.web.filter.DelegatingFilterProxy#initDelegate可以看出会去找名为springSessionRepositoryFilter Bean的实现作为Filter的具体实现.
2.因为使用了@EnableRedisHttpSession,就会使用org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration,这个配置里注册的springSessionRepositoryFilter Bean就是SessionRepositoryFilter.即springSessionRepositoryFilter的实现为org.springframework.session.web.http.SessionRepositoryFilter
3.Filter每一次的请求都会调用doFilter,即调用SessionRepositoryFilter的父类OncePerRequestFilter的doFilter,此方法会调用SessionRepositoryFilter自身的doFilterInternal.这个方法如下:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository); SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,response); HttpServletRequest strategyRequest = httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse); try { filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } }4.从这里就知request经过了包装,httpSessionStrategy的默认值是new CookieHttpSessionStrategy(),可以猜测它结合了cookie来实现,当然里面的getSession方法也重写了.org.springframework.session.web.http.SessionRepositoryFilter.SessionRepositoryRequestWrapper#getSession(boolean)方法如下:
public HttpSession getSession(boolean create) { if(currentSession != null) { return currentSession; } String requestedSessionId = getRequestedSessionId(); if(requestedSessionId != null) { S session = sessionRepository.getSession(requestedSessionId); if(session != null) { this.requestedValidSession = true; currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); return currentSession; } } if(!create) { return null; } S session = sessionRepository.createSession(); currentSession = new HttpSessionWrapper(session, getServletContext()); return currentSession; }即上面的例子调用getSession会调用此方法来获取Session.而此Session是通过sessionRepository创建的,此处注入的是org.springframework.session.data.redis.RedisOperationsSessionRepository(sessionRepository的注册也是在org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration),而不是应用服务器本身去创建的.
可以继续看看org.springframework.session.data.redis.RedisOperationsSessionRepository#createSession
public RedisSession createSession() { RedisSession redisSession = new RedisSession(); if(defaultMaxInactiveInterval != null) { redisSession.setMaxInactiveIntervalInSeconds(defaultMaxInactiveInterval); } return redisSession; }这里new了一个RedisSession,继续看org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession#RedisSession()
RedisSession() { this(new MapSession()); delta.put(CREATION_TIME_ATTR, getCreationTime()); delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds()); delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime()); } RedisSession(MapSession cached) { Assert.notNull("MapSession cannot be null"); this.cached = cached; }这里又new了一个MapSession并赋给了cached变量,再看org.springframework.session.MapSession片段:
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800; private String id = UUID.randomUUID().toString(); private Map<String, Object> sessionAttrs = new HashMap<String, Object>(); private long creationTime = System.currentTimeMillis(); private long lastAccessedTime = creationTime; /** * Defaults to 30 minutes */ private int maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
从这里你可以基本猜测id就是sessionid,这个UUID就是区分不同的客户端的一个唯一标识,它会写入到客户端的cookie,session的有效时间是存在什么地方了,cached和delta都有存.最后就要看它怎么保存到redis里面去了.下面再看看如何保存到redis去:response是经过了SessionRepositoryResponseWrapper包装,SessionRepositoryResponseWrapper是OnCommittedResponseWrapper的子类,服务端一旦调用response.getWriter()就会触发org.springframework.session.web.http.OnCommittedResponseWrapper#getWriter
@Override public PrintWriter getWriter() throws IOException { return new SaveContextPrintWriter(super.getWriter()); }
private class SaveContextPrintWriter extends PrintWriter { private final PrintWriter delegate; public SaveContextPrintWriter(PrintWriter delegate) { super(delegate); this.delegate = delegate; } public void flush() { doOnResponseCommitted(); delegate.flush(); } public void close() { doOnResponseCommitted(); delegate.close(); }一旦调用out.flush或out.close都会触发doOnResponseCommitted()方法,
private void doOnResponseCommitted() { if(!disableOnCommitted) { onResponseCommitted(); disableOnResponseCommitted(); } else if(logger.isDebugEnabled()){ logger.debug("Skip invoking on"); } }回来org.springframework.session.web.http.SessionRepositoryFilter.SessionRepositoryResponseWrapper#onResponseCommitted
@Override protected void onResponseCommitted() { request.commitSession(); }再回来org.springframework.session.web.http.SessionRepositoryFilter.SessionRepositoryRequestWrapper#commitSession
private void commitSession() { HttpSessionWrapper wrappedSession = currentSession; if(wrappedSession == null) { if(isInvalidateClientSession()) { httpSessionStrategy.onInvalidateSession(this, response); } } else { S session = wrappedSession.session; sessionRepository.save(session); if(!requestedValidSession) { httpSessionStrategy.onNewSession(session, this, response); } } }终于看到sessionRepository调用save了