知其然,还要知其所以然 !
本篇介绍Spring-Session的整个实现的原理。以及对核心的源码进行简单的介绍!
实现原理介绍
实现原理这里简单说明描述:
就是当Web服务器接收到http请求后,当请求进入对应的Filter进行过滤,将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!
整个实现流程和源码详细介绍
本次源码介绍基于上一篇内容,并且在保存Session的时候只会分析使用JedisConnectionFactory实现的RedisConnectionFactory !
1.SessionRepositoryFilter和JedisConnectionFactory注册过程
流程:
说明:
1.、启动WEB项目的时候,会读取web.xml,读取顺序content-param --> listener --> filter --> servlet
2.、ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息
3、初始化根web应用程序上下文。
4、SpringHttpSessionConfiguration注册 springSessionRepositoryFilter :bean,RedisHttpSessionConfiguration 注册 sessionRedisTemplate : bean 和 sessionRepository : bean
45、配置文件配置JedisConnectionFactory implements RedisConnectionFactory ,创建 jedisConnectionFactory bean
代码分析如下:
- web.xml ,加载了xml配置文件,并初始化web应用上下文
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/*xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2.application-session.xml中,配置 RedisHttpSessionConfiguration的bean和JedisConnectionFactory的bean,web应用初始化加载bean!
<!--创建一个Spring Bean的名称springSessionRepositoryFilter实现过滤器。
筛选器负责将HttpSession实现替换为Spring会话支持。在这个实例中,Spring会话得到了Redis的支持。-->
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<!--创建了一个RedisConnectionFactory,它将Spring会话连接到Redis服务器。我们配置连接到默认端口(6379)上的本地主机!-->
<!--集群Redis-->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<!--Redis-CLuster-->
<constructor-arg index="0" ref="redisClusterConfig"/>
<!--配置Redis连接池 ,可以不配置,使用默认就行!-->
<constructor-arg index="1" ref="jedisPoolConfig"/>
</bean>
/**
* 初始化根web应用程序上下文。
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
4.RedisHttpSessionConfiguration类图
RedisHttpSessionConfiguration继承了SpringHttpSessionConfiguration
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements EmbeddedValueResolverAware, ImportAware {
4.1 SpringHttpSessionConfiguration 创建一个名称为springSessionRepositoryFilter的bean
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(
sessionRepository);
sessionRepositoryFilter.setServletContext(this.servletContext);
if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
sessionRepositoryFilter.setHttpSessionStrategy(
(MultiHttpSessionStrategy) this.httpSessionStrategy);
}
else {
sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
}
return sessionRepositoryFilter;
}
4.2 创建RedisHttpSessionConfiguration#RedisTemplate bean的名称为sessionRedisTemplate
@Bean
public RedisTemplate<Object, Object> sessionRedisTemplate(
RedisConnectionFactory connectionFactory) {
//实例化 RedisTemplate
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
//设置key序列化 StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
//设置Hash key StringRedisSerializer
template.setHashKeySerializer(new StringRedisSerializer());
if (this.defaultRedisSerializer != null) {
template.setDefaultSerializer(this.defaultRedisSerializer);
}
//设置 connectionFactory。第五步创建的(实际connectionFactory加载过程和讲解过程顺序不一样)
template.setConnectionFactory(connectionFactory);
return template;
}
4.3 创建RedisHttpSessionConfiguration#RedisOperationsSessionRepository bean的名称为sessionRepository
@Bean
public RedisOperationsSessionRepository sessionRepository(
//使用sessionRedisTemplate bean
@Qualifier("sessionRedisTemplate") RedisOperations<Object, Object> sessionRedisTemplate,
ApplicationEventPublisher applicationEventPublisher) {
//實例化RedisOperationsSessionRepository
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
sessionRedisTemplate);
//設置applicationEventPublisher
sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
//設置最大的失效時間 maxInactiveIntervalInSeconds = 1800
sessionRepository
.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (this.defaultRedisSerializer != null) {
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
}
String redisNamespace = getRedisNamespace();
if (StringUtils.hasText(redisNamespace)) {
sessionRepository.setRedisKeyNamespace(redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
return sessionRepository;
}
- 创建 RedisConnectionFactory bean为 jedisConnectionFactory
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
2.SessionRepositoryFilter添加到FilterChain
流程:
说明:
1 2、在Servlet3.0规范中,Servlet容器启动时会自动扫描javax.servlet.ServletContainerInitializer的实现类,在实现类中我们可以定制需要加载的类。 通过注解@HandlesTypes(WebApplicationInitializer.class),让Servlet容器在启动该类时,会自动寻找所有的WebApplicationInitializer实现类。
2.1、insertSessionRepositoryFilter 方法通过filterName获取 SessionRepositoryFilter ,并创建了 new DelegatingFilterProxy(filterName);
3 4、然后将filter添加到FilterChain中
1.ServletContainerInitializer的实现类加载和通过注解@HandlesTypes(WebApplicationInitializer.class)实现类的加载
//加载实现类
@HandlesTypes(WebApplicationInitializer.class)
//SpringServletContainerInitializer实现ServletContainerInitializer
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//------------
2.AbstractHttpSessionApplicationInitializer实现WebApplicationInitializer进行加载
@Order(100)
public abstract class AbstractHttpSessionApplicationInitializer
implements WebApplicationInitializer {
2.1 onStartup
public void onStartup(ServletContext servletContext) throws ServletException {
beforeSessionRepositoryFilter(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
//添加Filter
insertSessionRepositoryFilter(servletContext);
afterSessionRepositoryFilter(servletContext);
}
2.1.1.insertSessionRepositoryFilter
/**
* 注册springSessionRepositoryFilter
* @param servletContext the {@link ServletContext}
*/
private void insertSessionRepositoryFilter(ServletContext servletContext) {
// DEFAULT_FILTER_NAME = "springSessionRepositoryFilter"
String filterName = DEFAULT_FILTER_NAME;
//通过filterName创建 DelegatingFilterProxy
DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(
filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSessionRepositoryFilter.setContextAttribute(contextAttribute);
}
//根据filterName和上下文添加Filter到FilterChain
registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);
}
- registerFilter
private void registerFilter(ServletContext servletContext,
boolean insertBeforeOtherFilters, String filterName, Filter filter) {
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
throw new IllegalStateException(
"Duplicate Filter registration for '" + filterName
+ "'. Check to ensure the Filter is only configured once.");
}
//是否支持异步,默认 true
registration.setAsyncSupported(isAsyncSessionSupported());
//得到DispatcherType springSessionRepositoryFilter
EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
//添加一个带有给定url模式的筛选器映射和由这个FilterRegistration表示的过滤器的分派器类型。 过滤器映射按照添加它们的顺序进行匹配。
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
"/*");
}
- addFilter将Filter添加到ServletContext中
public FilterRegistration.Dynamic addFilter(
String filterName, Filter filter);
3.SessionRepositoryFilter拦截过程
流程:
说明:
1、请求被DelegatingFilterProxy : 拦截到,然后执行doFilter方法,在doFilter中找到执行的代理类。
2、OncePerRequestFilter : 代理Filter执行doFilter方法,然后调用抽象方法doFilterInternal
3、SessionRepositoryFilter 继承了OncePerRequestFilter,实现了doFilterInternal,这个方法一个封装一个wrappedRequest,通过执行commitSession保存session信息到redis
1请求进来,被DelegatingFilterProxy 拦截到,在web.xml中进行了配置
1.1 执行doFilter
如果没有指定目标bean名称,请使用筛选器名称。
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 如果需要,延迟初始化委托。 necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
}
// 让委托执行实际的doFilter操作
invokeDelegate(delegateToUse, request, response, filterChain);
}
1.2 initDelegate
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
//可以获取到SessionRepositoryFilter [备注1]
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
//[备注1] 因为 :SessionRepositoryFilter是一个优先级最高的javax.servlet.Filter
/*
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends ExpiringSession>
extends OncePerRequestFilter {
*/
- delegate.doFilter();
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//代理去执行doFilter,代理为SessionRepositoryFilter
delegate.doFilter(request, response, filterChain);
}
2.1 OncePerRequestFilter#doFilter
public final void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!(request instanceof HttpServletRequest)
|| !(response instanceof HttpServletResponse)) {
throw new ServletException(
"OncePerRequestFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
boolean hasAlreadyFilteredAttribute = request
.getAttribute(this.alreadyFilteredAttributeName) != null;
if (hasAlreadyFilteredAttribute) {
//在不调用此过滤器的情况下进行…
filterChain.doFilter(request, response);
}
else {
// 调用这个过滤器…
request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);
try {
//doFilterInternal是个抽象方法
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// 删除此请求的“已过滤”请求属性。
request.removeAttribute(this.alreadyFilteredAttributeName);
}
}
}
- 执行SessionRepositoryFilter#doFilterInternal
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
//使用HttpServletRequest 、HttpServletResponse和servletContext创建一个SessionRepositoryRequestWrapper
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
//使用CookieHttpSessionStrategy重新包装了 HttpServletRequest
HttpServletRequest strategyRequest = this.httpSessionStrategy
.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = this.httpSessionStrategy
.wrapResponse(wrappedRequest, wrappedResponse);
try {
//执行其他过滤器
filterChain.doFilter(strategyRequest, strategyResponse);
}
finally {
//保存session信息
wrappedRequest.commitSession();
}
}
4 .wrappedRequest.commitSession() 看下第四大点分析
4.SessionRepository保存session数据
流程:
说明:
1、提交session保存
2、获取当前session,这一步比较重要,获取了一个HttpSessionWrapper,这个HttpSessionWrapper替换了HTTPSession
3、wrappedSession获取当前的Session
4、使用 RedisTemplate 保存Session内容,并通过调用RedisConnection 使用它的实现类JedisClusterConnection获取redis连接
1.commitSession
/**
*使用HttpSessionStrategy将会话id写入响应。 *保存会话。
*/
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionStrategy
.onInvalidateSession(this, this.response);
}
}
else {
S session = wrappedSession.getSession();
SessionRepositoryFilter.this.sessionRepository.save(session);
if (!isRequestedSessionIdValid()
|| !session.getId().equals(getRequestedSessionId())) {
SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session,
this, this.response);
}
}
}
2.getCurrentSession
会话存储库请求属性名。
public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class
.getName();
private static final String CURRENT_SESSION_ATTR = SESSION_REPOSITORY_ATTR
+ ".CURRENT_SESSION";
private HttpSessionWrapper getCurrentSession() {
return (HttpSessionWrapper)
//获取session
getAttribute(CURRENT_SESSION_ATTR);
}
/**
* 此方法的默认行为是在包装请求对象上调用getAttribute(字符串名称)。
*/
public Object getAttribute(String name) {
//这里的request就是上面封装的
return this.request.getAttribute(name);
}
3 .wrappedSession.getSession
//返回 RedisSession
S session = wrappedSession.getSession();
//-------------------------
public S getSession() {
return this.session;
}
class ExpiringSessionHttpSession<S extends ExpiringSession> implements HttpSession {
private S session;
final class RedisSession implements ExpiringSession {
4.save,实际是调用 RedisOperationsSessionRepository的 RedisOperations 操作
SessionRepositoryFilter.this.sessionRepository.save(session);
//this.sessionRepository = SessionRepository<S> sessionRepository;
//--------------------------------
//这个RedisOperationsSessionRepository是之前就创建好的
public class RedisOperationsSessionRepository implements
FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
MessageListener {
public interface FindByIndexNameSessionRepository<S extends Session>
extends SessionRepository<S> {
//---------------------------
public void save(RedisSession session) {
//4.1saveDelta
session.saveDelta();
if (session.isNew()) {
//4.2调用
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
//4.3convertAndSend
//RedisOperations = this.sessionRedisOperations
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
session.setNew(false);
}
}
其中RedisOperationsSessionRepository 里面介绍保存的详细过程,具体请看文档说明:
Class RedisOperationsSessionRepository
因为 RedisTemplate implements RedisOperations,实际进行操作的是RedisTemplate,RedisTemplate通过RedisConnection进行数据add和remove等
public class RedisTemplate<K, V>
extends RedisAccessor
implements RedisOperations<K, V>, BeanClassLoaderAware
总结
本系列到这里也就结束了,本次话的整个流程图,会上传到github上,使用Jude打开就可以看!
如果有什么地方写的不对或者有想和我一起探讨一下的,欢迎加我的QQ或者QQ群!
记录一个小点:
Spring Session + Redis实现分布式Session共享 有个非常大的缺陷, 无法实现跨域名共享session , 只能在单台服务器上共享session , 因为是依赖cookie做的 , cookie 无法跨域 pring Session一般是用于多台服务器负载均衡时共享Session的,都是同一个域名,不会跨域。你想要的跨域的登录,可能需要SSO单点登录。
参考博文
【Spring】Spring Session的简单搭建与源码阅读
Spring Session解决分布式Session问题的实现原理
本系列教程
【第一篇】Spring-Session实现Session共享入门教程
【第二篇】Spring-Session实现Session共享Redis集群方式配置教程
【第三篇】Spring-Session实现Session共享实现原理以及源码解析
本系列的源码下载地址:learn-spring-session-core
备注: 由于本人能力有限,文中若有错误之处,欢迎指正。
谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!