真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網站制作重慶分公司

分布式環(huán)境下SpringSessionRedis底層原理-創(chuàng)新互聯(lián)

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 {
     }
 
   }
     // 該類主要作用就是用來更加當前環(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ā)展。
  1. RedisSessionConfiguration,RedisReactiveSessionConfiguration
  2. MongoSessionConfiguration,MongoReactiveSessionConfiguration
  3. JdbcSessionConfiguration
  4. HazelcastSessionConfiguration
  5. NoOpSessionConfiguration,NoOpReactiveSessionConfiguration

這些類都是在如下類中注冊

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, SessionRepositoryFilterfilter) {
     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
   publicSessionRepositoryFilterspringSessionRepositoryFilter(
       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));
         }
       }
     }
   }
 }

該過濾器的作用及工作流程總結如下:

  1. 使用自定義的Request,Response對象將這2個對象通過FilterChain#doFilter方法向后傳遞,供其它的過濾器使用。
  2. 其它過濾器在使用Session過程中都是使用的上一步中傳下來的自定義Request對象SessionRepositoryRequestWrapper
  3. 目標對象執(zhí)行完后返回時會繼續(xù)執(zhí)行FilterChain#doFilter剩下的代碼,也就是上面的SessionRepositoryRequestWrapper#commitSession方法,該方法的多用就是提交在后續(xù)的Filter或者目標對象(如:Controller)中對Session對象的操作,將這些信息提交多相應的存儲對象上,如:Redis或者JDBC等中。
3.3 Session數(shù)據(jù)存儲

這里我們查看關于
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元起,快前往官網查看詳情吧


分享名稱:分布式環(huán)境下SpringSessionRedis底層原理-創(chuàng)新互聯(lián)
文章出自:http://weahome.cn/article/cssecg.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部