由于互联网时代的到来,大量的互联网用户的涌入,便出现了很多单机无法满足的场景,毕竟单机的并发与性能是有局限性的。于是便催生了分布式应用,分布式服务的出现就必然要解决一个用户登录后的所有操作对后端的分布式服务的每台机器都是可见的。如果说第一次操作请求打在了服务器A上面,第二次请求打在了服务器B上面,同一个用户的操作要对服务器AB都是可见的,而不是说第二次到了服务器B上之后要重新登录与重复操作之前已经处理过的事情。毕竟如今的互联网更是分秒必争的
共享session的实现,对session进行持久化。例如常见的mysql、redis等等。那么我们来看下springboot+springmvc共享session的实现。我们以redis的实现与背景来逐步走进源码
springboot的自动配置为我们准备好了环境,用户仅需要简单的配置即可使用
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
# 设置session刷新ON_SAVE(表示在response commit前刷新缓存),IMMEDIATE(表示只要有更新,就刷新缓存)
spring.session.redis.flush-mode=on_save
# 选择使用redis 作为session存储
spring.session.store-type=redis
没错,就是简单的两个配置,我们就做到了session的共享。那么我们来看看springboot的自动配置都为我们做了什么?
度过上篇文章的同学应该记得jetty服务的SessionHandler句柄。没错看这个名字就知道是容器用来处理session的。没看过或者忘记了的同学可以跟着传荣门走一走哈哈https://blog.csdn.net/u010597819/article/details/90745546
看过的同学可以直接跟着我们继续来看下SessionHandler句柄的作用
protected void doStart() throws Exception
{
//check if session management is set up, if not set up HashSessions
final Server server=getServer();
...
synchronized (server)
{
//Get a SessionDataStore and a SessionDataStore, falling back to in-memory sessions only
if (_sessionCache == null)
{
SessionCacheFactory ssFactory = server.getBean(SessionCacheFactory.class);
setSessionCache(ssFactory != null?ssFactory.getSessionCache(this):new DefaultSessionCache(this));
SessionDataStore sds = null;
SessionDataStoreFactory sdsFactory = server.getBean(SessionDataStoreFactory.class);
if (sdsFactory != null)
sds = sdsFactory.getSessionDataStore(this);
else
sds = new NullSessionDataStore();
_sessionCache.setSessionDataStore(sds);
}
if (_sessionIdManager==null)
{
_sessionIdManager=server.getSessionIdManager();
if (_sessionIdManager==null)
{
//create a default SessionIdManager and set it as the shared
//SessionIdManager for the Server, being careful NOT to use
//the webapp context's classloader, otherwise if the context
//is stopped, the classloader is leaked.
ClassLoader serverLoader = server.getClass().getClassLoader();
try
{
Thread.currentThread().setContextClassLoader(serverLoader);
_sessionIdManager=new DefaultSessionIdManager(server);
server.setSessionIdManager(_sessionIdManager);
server.manage(_sessionIdManager);
_sessionIdManager.start();
}
finally
{
Thread.currentThread().setContextClassLoader(_loader);
}
}
// server session id is never managed by this manager
addBean(_sessionIdManager,false);
}
_scheduler = server.getBean(Scheduler.class);
if (_scheduler == null)
{
_scheduler = new ScheduledExecutorScheduler();
_ownScheduler = true;
_scheduler.start();
}
}
// Look for a session cookie name
if (_context!=null)
{
String tmp=_context.getInitParameter(__SessionCookieProperty);
if (tmp!=null)
_sessionCookie=tmp;
tmp=_context.getInitParameter(__SessionIdPathParameterNameProperty);
if (tmp!=null)
setSessionIdPathParameterName(tmp);
// set up the max session cookie age if it isn't already
if (_maxCookieAge==-1)
{
tmp=_context.getInitParameter(__MaxAgeProperty);
if (tmp!=null)
_maxCookieAge=Integer.parseInt(tmp.trim());
}
// set up the session domain if it isn't already
if (_sessionDomain==null)
_sessionDomain=_context.getInitParameter(__SessionDomainProperty);
// set up the sessionPath if it isn't already
if (_sessionPath==null)
_sessionPath=_context.getInitParameter(__SessionPathProperty);
tmp=_context.getInitParameter(__CheckRemoteSessionEncoding);
if (tmp!=null)
_checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
}
_sessionContext = new SessionContext(_sessionIdManager.getWorkerName(), _context);
_sessionCache.initialize(_sessionContext);
super.doStart();
}
@Configuration
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnClass(Session.class)
@ConditionalOnWebApplication
@EnableConfigurationProperties(SessionProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, MongoAutoConfiguration.class,
RedisAutoConfiguration.class })
@Import({ SessionConfigurationImportSelector.class, SessionRepositoryValidator.class })
public class SessionAutoConfiguration {
/**
* {@link ImportSelector} to add {@link StoreType} configuration classes.
*/
static class SessionConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
StoreType[] types = StoreType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = SessionStoreMappings.getConfigurationClass(types[i]);
}
return imports;
}
}
/**
* Bean used to validate that a {@link SessionRepository} exists and provide a
* meaningful message if that's not the case.
*/
static class SessionRepositoryValidator {
...
}
}
服务使用公司的cas服务,堆栈如下,可以看到在登录时通过cas拦截器获取session,getSession,如果session不存在则创建session
java.lang.Thread.State: RUNNABLE
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:391)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:217)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:279)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:279)
at org.pac4j.core.context.session.J2ESessionStore.getHttpSession(J2ESessionStore.java:24)
at org.pac4j.core.context.session.J2ESessionStore.get(J2ESessionStore.java:34)
at org.pac4j.core.context.session.J2ESessionStore.get(J2ESessionStore.java:19)
at org.pac4j.core.context.WebContext.getSessionAttribute(WebContext.java:94)
at com.....authentication.filters.CasSecurityFilter.doFilter(CasSecurityFilter.java:62)
父类OncePerRequestFilter的doFilter中回调该类的doFilterInternal方法
session共享:spring通过SessionRepositoryFilter该过滤器将Request请求封装为SessionRepositoryRequestWrapper类型,该类型Request创建session,由_HttpSessionStrategy策略选择session资源库_RedisOperationsSessionRepository对session进行持久化实现session的共享。当然还有我们的sessionHandler如果再没有cas等配置的情况下默认对session不做任何操作即NullSessionDataStore
jetty特性doScope、doHandle
* <p>For example if Scoped handlers A, B & C were chained together, then
* the calling order would be:</p>
* <pre>
* A.handle(...)
* A.doScope(...)
* B.doScope(...)
* C.doScope(...)
* A.doHandle(...)
* B.doHandle(...)
* C.doHandle(...)
* </pre>
*
* <p>If non scoped handler X was in the chained A, B, X & C, then
* the calling order would be:</p>
* <pre>
* A.handle(...)
* A.doScope(...)
* B.doScope(...)
* C.doScope(...)
* A.doHandle(...)
* B.doHandle(...)
* X.handle(...)
* C.handle(...)
* C.doHandle(...)
* </pre>