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 {
}
}
// 該類主要作用就是用來更加當前環(huán)境下的所有類型的*SessionConfiguration
// 如:RedisSessionConfiguration,JdbcSessionConfiguration等。
// 2 核心Session配置對象
static class ServletSessionConfigurationImportSelector extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.SERVLET);
}
}
}
2 核心Session配置對象在上每一步中會獲取容器中所有注冊的*SessionConfiguration。
目前成都創(chuàng)新互聯(lián)公司已為千余家的企業(yè)提供了網站建設、域名、虛擬主機、網站運營、企業(yè)網站設計、麗水網站維護等服務,公司將堅持客戶導向、應用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。這些類都是在如下類中注冊
final class SessionStoreMappings {
private static final MapMAPPINGS;
static {
Mapmappings = 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配置類上面列出了系統(tǒng)中所有的*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對象就創(chuàng)建注冊了。
RedisIndexedSessionRepository類繼承自SessionRepository接口。
3 核心過濾器 3.1 過濾器注冊class SessionRepositoryFilterConfiguration {
// 這里的SessionRepositoryFilter是核心的處理Session的過濾器
// 而關于該種過濾器的注冊方式可參考SpringSecurity.md文檔
@Bean
FilterRegistrationBean>sessionRepositoryFilterRegistration(
SessionProperties sessionProperties, SessionRepositoryFilter>filter) {
FilterRegistrationBean>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 {
// 注入了在上一步中創(chuàng)建的核心Session存儲對象RedisIndexedSessionRepository
// 該過濾器對象會被注冊到Servlet容器中
@Bean
publicSessionRepositoryFilter extends Session>springSessionRepositoryFilter(
SessionRepositorysessionRepository) {
SessionRepositoryFiltersessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
}
}
3.2 過濾器核心方法接下來查看該過濾器的一些核心方法
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilterextends 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 {
// 這就就是觸發(fā)講所有向Session中存入的對象保存到對應的實現(xiàn)中(如:Redis或JDBC)
wrappedRequest.commitSession();
}
}
}
接著查看
SessionRepositoryRequestWrapper包裝類中重寫的幾個核心方法
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
// 提交Session中的數(shù)據(jù)保存到具體的實現(xiàn)中,如(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對象就是該實現(xiàn)
private final class HttpSessionWrapper extends HttpSessionAdapter{
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中操作數(shù)據(jù)核心方法是setAttribute,getAttribute
HttpSessionWrapper繼承HttpSessionWrapper
class HttpSessionAdapterimplements HttpSession {
// MapSession 內部維護了一個Map集合,專門用來存數(shù)據(jù)的
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));
}
}
}
}
}
該過濾器的作用及工作流程總結如下:
這里我們查看關于
SessionRepositoryRequestWrapper#commitSession方法的執(zhí)行。
根據(jù)上面還是以Redis實現(xiàn)為例,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, 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 Mapdelta = new HashMap<>();
private void save() {
saveChangeSessionId();
// 這里是核心
saveDelta();
}
private void saveDelta() {
if (this.delta.isEmpty()) {
return;
}
String sessionId = getId();
// 將所有的數(shù)據(jù)保存到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);
}
Mapindexes = 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);
}
}
// 將數(shù)據(jù)存儲完成后將delta集合清空(這里可以避免重復提交數(shù)據(jù))
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);
}
}
完畢!??!
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧