1.主要依赖
org.springframework.data
spring-data-redis
1.4.1.RELEASE
redis.clients
jedis
2.5.2
org.springframework.session
spring-session
${spring.session.version}
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集群的一个例子:
org.springframework.data
spring-data-redis
1.7.1.RELEASE
org.apache.commons
commons-pool2
2.4.2
redis.clients
jedis
2.8.1
org.springframework.session
spring-session
1.1.1.RELEASE
#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 sessionAttrs = new HashMap();
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了