1 自動裝配
public class SessionAutoConfiguration {
// SessionRepositoryFilterConfiguration用來配置核心的過濾器
// 3 核心過濾器
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@Import({ ServletSessionRepositoryValidator.class, SessionRepositoryFilterConfiguration.class })
static class ServletSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ ServletSessionRepositoryImplementationValidator.class,
ServletSessionConfigurationImportSelector.class })
static class ServletSessionRepositoryConfiguration {
}
}
// 該類主要作用就是用來更加目前環境下的所有類型的*SessionConfiguration
// 如:RedisSessionConfiguration,JdbcSessionConfiguration等。
// 2 核心Session配置對象
static class ServletSessionConfigurationImportSelector extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.SERVLET);
}
}
}
2 核心Session配置對象
在上每一步中會擷取容器中所有注冊的*SessionConfiguration。
- RedisSessionConfiguration,RedisReactiveSessionConfiguration
- MongoSessionConfiguration,MongoReactiveSessionConfiguration
- JdbcSessionConfiguration
- HazelcastSessionConfiguration
- NoOpSessionConfiguration,NoOpReactiveSessionConfiguration
這些類都是在如下類中注冊
final class SessionStoreMappings {
private static final Map<StoreType, Configurations> MAPPINGS;
static {
Map<StoreType, Configurations> mappings = new EnumMap<>(StoreType.class);
mappings.put(StoreType.REDIS,
new Configurations(RedisSessionConfiguration.class, RedisReactiveSessionConfiguration.class));
mappings.put(StoreType.MONGODB,
new Configurations(MongoSessionConfiguration.class, MongoReactiveSessionConfiguration.class));
mappings.put(StoreType.JDBC, new Configurations(JdbcSessionConfiguration.class, null));
mappings.put(StoreType.HAZELCAST, new Configurations(HazelcastSessionConfiguration.class, null));
mappings.put(StoreType.NONE,
new Configurations(NoOpSessionConfiguration.class, NoOpReactiveSessionConfiguration.class));
MAPPINGS = Collections.unmodifiableMap(mappings);
}
}
2.1 注冊Session配置類
上面列出了系統中所有的*SessionConfiguration配置類,那具體該注冊哪一個?
回到上面的ServletSessionConfigurationImportSelector中
進入ServletSessionConfigurationImportSelector#selectImports方法:
abstract static class SessionConfigurationImportSelector implements ImportSelector {
protected final String[] selectImports(WebApplicationType webApplicationType) {
// 這裡就是疊代上面登記的所有*SessionConfiguration類
return Arrays.stream(StoreType.values())
.map((type) -> SessionStoreMappings.getConfigurationClass(webApplicationType, type))
.toArray(String[]::new);
}
}
擷取到所有的配置類後,如何進行選擇該注冊哪一個配置類?這裡我們打開*SessionConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisSessionConfiguration {
@Configuration(proxyBeanMethods = false)
public static class SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(DataSource.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(JdbcSessionProperties.class)
class JdbcSessionConfiguration {
@Configuration(proxyBeanMethods = false)
static class SpringBootJdbcHttpSessionConfiguration extends JdbcHttpSessionConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(MongoOperations.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
class MongoSessionConfiguration {
@Configuration
public static class SpringBootMongoHttpSessionConfiguration extends MongoHttpSessionConfiguration {
}
}
@Conditional(ServletSessionCondition.class)
@ConditionalOnMissingBean(SessionRepository.class)
class HazelcastSessionConfiguration {
}
@Conditional(ServletSessionCondition.class)
@ConditionalOnMissingBean(SessionRepository.class)
class NoOpSessionConfiguration {
}
這些類每一種存儲類型它都有相應的注冊條件,隻有滿足條件的才能被注冊。
注意:
這些類是通過ImportSelector導入進行注冊的,這時候就需要注意了,如果一個類是通過@Import導入的,那麼隻有導入的這個類能被注冊,該類的内部配置類才能被注冊,反之,被導入的不能被注冊,那麼這個類的内部配置類也不會被注冊。如下RedisSessionConfiguration,如果這個類不能被注冊,那麼内部類SpringBootRedisHttpSessionConfiguration也不能被注冊。
class RedisSessionConfiguration {
@Configuration(proxyBeanMethods = false)
public static class SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
@Autowired
public void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties) {
Duration timeout = sessionProperties.getTimeout();
if (timeout != null) {
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
}
setRedisNamespace(redisSessionProperties.getNamespace());
setFlushMode(redisSessionProperties.getFlushMode());
setSaveMode(redisSessionProperties.getSaveMode());
setCleanupCron(redisSessionProperties.getCleanupCron());
}
}
}
如果一個配置類本身在容器啟動的時候就能被容器掃描到,那麼如果該類即便不能被注冊,但是他的内部配置類還是可以被注冊的。如下情況:
@Configuration
@ConditionalOnProperty(prefix = "s", name = "n", havingValue = "1", matchIfMissing = false)
public class InnerConfiguration {
public InnerConfiguration() {
System.out.println("===============") ;
}
@Configuration
static class Inner {
public Inner() {
System.out.println("--------------") ;
}
}
}
如果上面的類内被容器啟動的時候掃描到,但是這個類本身沒有滿足條件不能被注冊,但是它的内部配置類Inner還是會被容器掃描到進行注冊的。因為容器啟動的時候會掃描啟動類所在的包及其子包下的所有*.class檔案,Inner這個内部類也是一個class檔案。
再看ServletSessionCondition條件注冊類
class ServletSessionCondition extends AbstractSessionCondition {
ServletSessionCondition() {
super(WebApplicationType.SERVLET);
}
}
abstract class AbstractSessionCondition extends SpringBootCondition {
private final WebApplicationType webApplicationType;
protected AbstractSessionCondition(WebApplicationType webApplicationType) {
this.webApplicationType = webApplicationType;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Session Condition");
Environment environment = context.getEnvironment();
StoreType required = SessionStoreMappings.getType(this.webApplicationType,
((AnnotationMetadata) metadata).getClassName());
if (!environment.containsProperty("spring.session.store-type")) {
return ConditionOutcome.match(message.didNotFind("property", "properties")
.items(ConditionMessage.Style.QUOTE, "spring.session.store-type"));
}
try {
Binder binder = Binder.get(environment);
// 将spring.session.store-type配置屬性綁定到StoreType枚舉對象上
return binder.bind("spring.session.store-type", StoreType.class)
// 判斷配置的類型是否與目前處理的類上的相同。
.map((t) -> new ConditionOutcome(t == required,
message.found("spring.session.store-type property").items(t)))
.orElse(ConditionOutcome.noMatch(message.didNotFind("spring.session.store-type property").atAll()));
}
}
}
2.2 注冊Session存儲對象
這裡以Redis為例,上面的SpringBootRedisHttpSessionConfiguration繼承RedisHttpSessionConfiguration類進入
@Configuration(proxyBeanMethods = false)
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
// 注冊一個SessionRepository類型的Session存儲對象
@Bean
public RedisIndexedSessionRepository sessionRepository() {
// ...
}
}
到這裡最為關鍵的一個SessionRepository對象就建立注冊了。
RedisIndexedSessionRepository類繼承自SessionRepository接口。
3 核心過濾器
3.1 過濾器注冊
class SessionRepositoryFilterConfiguration {
// 這裡的SessionRepositoryFilter是核心的處理Session的過濾器
// 而關于該種過濾器的注冊方式可參考SpringSecurity.md文檔
@Bean
FilterRegistrationBean<SessionRepositoryFilter<?>> sessionRepositoryFilterRegistration(
SessionProperties sessionProperties, SessionRepositoryFilter<?> filter) {
FilterRegistrationBean<SessionRepositoryFilter<?>> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(getDispatcherTypes(sessionProperties));
registration.setOrder(sessionProperties.getServlet().getFilterOrder());
return registration;
}
}
在2.2中RedisHttpSessionConfiguration繼承自SpringHttpSessionConfiguration進入該類
@Configuration(proxyBeanMethods = false)
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
// 注入了在上一步中建立的核心Session存儲對象RedisIndexedSessionRepository
// 該過濾器對象會被注冊到Servlet容器中
@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
}
}
3.2 過濾器核心方法
接下來檢視該過濾器的一些核心方法
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
// 核心就是這裡,分别自定義了Request,Response對象進行了重新包裝
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
response);
try {
// 将自定義的Request,Response向下傳遞,這在使用了Spring Security就非常友善了。
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
// 這就就是觸發講所有向Session中存入的對象儲存到對應的實作中(如:Redis或JDBC)
wrappedRequest.commitSession();
}
}
}
接着檢視SessionRepositoryRequestWrapper包裝類中重寫的幾個核心方法
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
// 送出Session中的資料儲存到具體的實作中,如(Redis,JDBC等)
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
} else {
S session = wrappedSession.getSession();
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.save(session);
String sessionId = session.getId();
if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) {
SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId);
}
}
}
// 當通過HttpServletRequest擷取HttpSession對象的時候就是調用的該方法了。
@Override
public HttpSessionWrapper getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
S requestedSession = getRequestedSession();
if (requestedSession != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
currentSession.markNotNew();
setCurrentSession(currentSession);
return currentSession;
}
} else {
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
if (!create) {
return null;
}
// session = MapSession該對象内部維護了一個Map集合
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
// 這又是自定義的Session對象
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
@Override
public HttpSessionWrapper getSession() {
return getSession(true);
}
// 實際操作的Session對象就是該實作
private final class HttpSessionWrapper extends HttpSessionAdapter<S> {
HttpSessionWrapper(S session, ServletContext servletContext) {
super(session, servletContext);
}
@Override
public void invalidate() {
super.invalidate();
SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
setCurrentSession(null);
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
}
}
}
像Session中操作資料核心方法是setAttribute,getAttribute
HttpSessionWrapper繼承HttpSessionWrapper
class HttpSessionAdapter<S extends Session> implements HttpSession {
// MapSession 内部維護了一個Map集合,專門用來存資料的
private S session;
public Object getAttribute(String name) {
return this.session.getAttribute(name);
}
public void setAttribute(String name, Object value) {
checkState();
// 調用MapSession對象方法,擷取内部Map中的值資訊
Object oldValue = this.session.getAttribute(name);
// 調用MapSession對象方法,将鍵值存入到内部維護的Map中
this.session.setAttribute(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue)
.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
}
}
if (value instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
}
}
}
}
}
該過濾器的作用及工作流程總結如下:
- 使用自定義的Request,Response對象将這2個對象通過FilterChain#doFilter方法向後傳遞,供其它的過濾器使用。
- 其它過濾器在使用Session過程中都是使用的上一步中傳下來的自定義Request對象SessionRepositoryRequestWrapper
- 目标對象執行完後傳回時會繼續執行FilterChain#doFilter剩下的代碼,也就是上面的SessionRepositoryRequestWrapper#commitSession方法,該方法的多用就是送出在後續的Filter或者目标對象(如:Controller)中對Session對象的操作,将這些資訊送出多相應的存儲對象上,如:Redis或者JDBC等中。
3.3 Session資料存儲
這裡我們檢視關于SessionRepositoryRequestWrapper#commitSession方法的執行。
根據上面還是以Redis實作為例,Session的存儲對象是RedisIndexedSessionRepository
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
} else {
S session = wrappedSession.getSession();
clearRequestedSessionCache();
// 儲存session裡的資訊
SessionRepositoryFilter.this.sessionRepository.save(session);
}
}
}
RedisIndexedSessionRepository對象
public class RedisIndexedSessionRepository
implements FindByIndexNameSessionRepository<RedisIndexedSessionRepository.RedisSession>, MessageListener {
public void save(RedisSession session) {
session.save();
if (session.isNew) {
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
session.isNew = false;
}
}
}
RedisSession對象
final class RedisSession implements Session {
// 該Map中存了所有的Session資訊
private Map<String, Object> delta = new HashMap<>();
private void save() {
saveChangeSessionId();
// 這裡是核心
saveDelta();
}
private void saveDelta() {
if (this.delta.isEmpty()) {
return;
}
String sessionId = getId();
// 将所有的資料儲存到Redis中。
getSessionBoundHashOperations(sessionId).putAll(this.delta);
String principalSessionKey = getSessionAttrNameKey(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
String securityPrincipalSessionKey = getSessionAttrNameKey(SPRING_SECURITY_CONTEXT);
if (this.delta.containsKey(principalSessionKey) || this.delta.containsKey(securityPrincipalSessionKey)) {
if (this.originalPrincipalName != null) {
String originalPrincipalRedisKey = getPrincipalKey(this.originalPrincipalName);
RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey)
.remove(sessionId);
}
Map<String, String> indexes = RedisIndexedSessionRepository.this.indexResolver.resolveIndexesFor(this);
String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME);
this.originalPrincipalName = principal;
if (principal != null) {
String principalRedisKey = getPrincipalKey(principal);
RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(principalRedisKey)
.add(sessionId);
}
}
// 将資料存儲完成後将delta集合清空(這裡可以避免重複送出資料)
this.delta = new HashMap<>(this.delta.size());
// 下面就是更新key的過期時間
Long originalExpiration = (this.originalLastAccessTime != null)
? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli() : null;
RedisIndexedSessionRepository.this.expirationPolicy.onExpirationUpdated(originalExpiration, this);
}
}
完畢!!!
SpringBoot對Spring MVC都做了哪些事?(一)
SpringBoot對Spring MVC都做了哪些事?(四)
SpringBoot對Spring MVC都做了哪些事?(二)
SpringBoot對Spring MVC都做了哪些事?(三)
Spring IOC容器對Bean執行個體化的過程詳解源碼分析
Spring注解@Qualifier這種用法你知道嗎?
Spring中Aware接口實作原了解析
Spring Cloud Nacos 開啟權限驗證
Spring Cloud Gateway應用詳解1之謂詞
Spring AOP實作原理源碼詳細分析