spring session入门

参考资料:
http://projects.spring.io/spring-session/#quick-start
http://docs.spring.io/spring-session/docs/current-SNAPSHOT/reference/html5/guides/httpsession.html#httpsession-sample

spring session提供以下功能:
1.API and implementations for managing a user's session
2.HttpSession - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way
2.1.Clustered Sessions - Spring Session makes it trivial to support clustered sessions without being tied to an application container specific solution.
2.2.Multiple Browser Sessions - Spring Session supports managing multiple users' sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google).
2.3.RESTful APIs - Spring Session allows providing session ids in headers to work with RESTful APIs
3.WebSocket - provides the ability to keep the HttpSession alive when receiving WebSocket messages

仅是集群session功能,都是振奋人心的.spring session是通过filter嵌入去实现的(spring security也是使用这种方式),下面是个例子.

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,像平常一样直接使用.
6.测试.先启动redis服务端.
请求:localhost:8080/testweb/?action=set&msg=123   把123通过spring session set到redis去.
请求:localhost:8080/testweb/?action=get  从redis取出刚才存入的值.

从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了



你可能感兴趣的:(spring,spring,mvc,Security)