Spring Session是Spring的项目之一,GitHub地址:https://github.com/spring-projects/spring-session。
Spring Session提供了一套创建和管理Servlet HttpSession的方案。Spring Session提供了集群Session(Clustered Sessions)功能,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。
下面是来自官网的特性介绍:
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、集成SpringSession的4个正确姿势
(1)导入相关jar/配置相关依赖
(2)编写一个配置类,用来启用RedisHttpSession功能,并向Spring容器中注册一个RedisConnectionFactory。/或者通过xml配置
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注解类的方式等等)。 / 或者通过xml配置
//该类采用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。 / 或者通过xml配置
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
public class SpringSessionInitializer extends AbstractHttpSessionApplicationInitializer {
}
4、SpringSession原理
(1)前面集成spring-sesion的第二步中,编写了一个配置类RedisHttpSessionConfig,它包含注解@EnableRedisHttpSession,并通过@Bean注解注册了一个RedisConnectionFactory到Spring容器中。
而@EnableRedisHttpSession注解通过Import,引入了RedisHttpSessionConfiguration配置类。该配置类通过@Bean注解,向Spring容器中注册了一个SessionRepositoryFilter。
(SessionRepositoryFilter的依赖关系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory)
package org.springframework.session.data.redis.config.annotation.web.http;
@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoaderAware {
//......
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接口,会被自动加载。
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 数据?