实现sessionfilter_Spring-Session实现Session共享实现原理以及源码解析

知其然,还要知其所以然 !

本篇介绍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应用上下文

contextConfigLocation

classpath*:spring/*xml

org.springframework.web.context.ContextLoaderListener

2.application-session.xml中,配置 RedisHttpSessionConfiguration的bean和JedisConnectionFactory的bean,web应用初始化加载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 SessionRepositoryFilter extends ExpiringSession> springSessionRepositoryFilter(

SessionRepository sessionRepository) {

SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(

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 sessionRedisTemplate(

RedisConnectionFactory connectionFactory) {

//实例化 RedisTemplate

RedisTemplate template = new RedisTemplate();

//设置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 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

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

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 implements HttpSession {

private S session;

final class RedisSession implements ExpiringSession {

4.save,实际是调用 RedisOperationsSessionRepository的 RedisOperations 操作

SessionRepositoryFilter.this.sessionRepository.save(session);

//this.sessionRepository = SessionRepository sessionRepository;

//--------------------------------

//这个RedisOperationsSessionRepository是之前就创建好的

public class RedisOperationsSessionRepository implements

FindByIndexNameSessionRepository,

MessageListener {

public interface FindByIndexNameSessionRepository

extends SessionRepository {

//---------------------------

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 里面介绍保存的详细过程,具体请看文档说明:

因为 RedisTemplate implements RedisOperations,实际进行操作的是RedisTemplate,RedisTemplate通过RedisConnection进行数据add和remove等

public class RedisTemplate

extends RedisAccessor

implements RedisOperations, BeanClassLoaderAware

总结

本系列到这里也就结束了,本次话的整个流程图,会上传到github上,使用Jude打开就可以看!

如果有什么地方写的不对或者有想和我一起探讨一下的,欢迎加我的QQ或者QQ群!

记录一个小点:

Spring Session + Redis实现分布式Session共享 有个非常大的缺陷, 无法实现跨域名共享session , 只能在单台服务器上共享session , 因为是依赖cookie做的 , cookie 无法跨域 pring Session一般是用于多台服务器负载均衡时共享Session的,都是同一个域名,不会跨域。你想要的跨域的登录,可能需要SSO单点登录。

参考博文

本系列教程

备注: 由于本人能力有限,文中若有错误之处,欢迎指正。

谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!

Java编程技术乐园:一个分享编程知识的公众号。跟着老司机一起学习干货技术知识,每天进步一点点,让小的积累,带来大的改变!

扫描关注,后台回复【秘籍】,获取珍藏干货! 99.9%的伙伴都很喜欢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值