Spring cloud configの使用と原理分析


Spring cloud config基本使用
Spring cloud configはhttpプロトコルに基づくリモート構成実装方式である.統一的な構成管理サーバによって構成管理を行い、クライアントはhttpsプロトコルによってサービスの構成情報をアクティブに引き出し、構成取得を完了する.
Spring cloud configの使用方法は非常に簡単で、spring cloud config serverのデフォルトの実装方法はgit管理構成であり、公式ドキュメントの紹介ではいくつかの使用方法が詳しく説明されています.gitのspring cloud config server実装方式を見てみましょう.
Spring cloud config server使用
SpringApplication実装、コードは以下の通りです.
@SpringBootApplication
@EnableConfigServer
public class SpringCloudConfigServer {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudConfigServer.class, args);
    }
}

application.propertiesプロファイル、コードは以下の通りです.
spring.application.name=configServer
server.port=8888
#      
spring.cloud.config.server.git.uri=\
${user.dir}/all-about-learn/garine-learn-spring-clound/garine-learn-config-server/src/main/resources/configs/

最も簡単な配置方式は以上のいくつかの項目を配置すると同時に、springを必要とする.cloud.config.server.git.uri構成のgit repoが作成され、中の構成ファイルも作成する必要があります.
ここではローカルgit repoを使用してspringに入ります.cloud.config.server.git.uri構成のディレクトリ、git bashはgit initコマンドを実行してrepoを作成し、同時に構成ファイルconfigを作成する.propertiesは、アプリケーション名configのアプリケーションのデフォルトのリクエストプロファイルの内容を表します.名前がconfig-dev.propertiesの場合、アプリケーション名configのアプリケーション開発環境のリクエストプロファイルの内容を表します.config.propertiesはgit repoにコミットしなければ読み込めません.
イニシエータ、アクセスhttp://localhost:8888/config/defaultconfigに読み込むことができる.propertiesの構成内容.
構成を要求するパラメータは、パスパラメータによって設定されます.
例:http://localhost:8888/{アプリケーション名}/{profile}/{label}
{labelブランチ、渡さないとデフォルトmaster
Spring cloud config client使用
まずbootstrap.propertiesファイルの構成を行います.
#  git       
spring.application.name=config
#        
spring.cloud.config.label=master
# dev          |  test       |  pro     
#  git       
#spring.cloud.config.profile=default
#            
spring.cloud.config.uri= http://localhost:8888/
server.port=8080

次にSpringApplication実装で、構成プロパティを読み込んでみます.
@SpringBootApplication
@RestController
public class SpringCloudConfigClient {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudConfigClient.class, args);
    }
    @Value("${name}")
    String name;

    @RequestMapping("/getName")
    public String getName(){
        return name;
    }
}

アクセスアドレスはconfig.propertiesの構成プロパティgarineを返します.
Spring cloud config server実現原理分析
@EnableConfigServer
まず、@EnableConfigServer注記を表示します.Enable注記プログラミングモデルは、通常、いくつかのbeanをアセンブリする目的を達成するために、あるコンフィギュレーションクラスを導入します.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {

}

ConfigServerConfiguration
@Configuration
public class ConfigServerConfiguration {
   class Marker {}

   @Bean
   public Marker enableConfigServerMarker() {
      return new Marker();
   }
}

ConfigServerConfigurationクラスにはbeanの組み立てがあまり実現されていません.ここでは折衷方式を利用して、必要な自動構成を導入します.次のクラスを見てください.Markerが唯一参照されている場所はConfigServerAutoConfigurationクラスです.
ConfigServerAutoConfiguration
@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
      ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {

}

@ConditionalOnBean(ConfigServerConfiguration.Marker.class)は、ConfigServerConfiguration.Markerのインスタンスがアセンブリされている場合にのみ、ConfigServerAutoConfigurationの処理が実行されることを示します.ここでは、5つの構成クラスが追加されています.config serverを分析し、EnvironmentRepositoryConfigurationクラスに重点を置きます.
EnvironmentRepositoryConfiguration
@Configuration
@EnableConfigurationProperties({ SvnKitEnvironmentProperties.class,
      JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultRepositoryConfiguration.class,
      SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class,
      DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {
}

ここで@Importには7種類のコンフィギュレーションクラスが導入されており、ドキュメントを見るとconfig serverのいくつかのインプリメンテーション方式gitのインプリメンテーション方式にちょうど対応するコンフィギュレーションクラスがGitRepositoryConfigurationであることがわかります.GitRepositoryConfigurationを例に分析します.
GitRepositoryConfiguration
@Configuration
@Profile("git")
class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {
}

GitRepositoryConfigurationはデフォルトの実装であり、DefaultRepositoryConfigurationのコードを表示していることがわかります.
@Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
   @Autowired
   private ConfigurableEnvironment environment;

   @Autowired
   private ConfigServerProperties server;

   @Autowired(required = false)
   private TransportConfigCallback customTransportConfigCallback;

   @Bean
   public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
           MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
         MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
      return gitEnvironmentRepositoryFactory.build(environmentProperties);
   }
}

最終的にはM u l t i p l e J GitEnvironmentRepositoryのbeanをアセンブリし、実際には各構成クラスの実装は最終的にEnvironmentRepositoryのサブクラスをアセンブリし、最終的にEnvironmentRepositoryのbeanを参照し、org.springframework.cloud.config.server.environment.EnvironmentRepository#findOneメソッドを使用して構成をクエリーする場所があると考えられます.
EnvironmentController
findOneメソッドを使用するクラスを検索してみます.org.springframework.cloud.config.server.environment.EnvironmentController#labelledで使用されています.また、この中にRestControllerが作成されています.クライアントがサービス側の構成を取得するエントリであると推測します.コードは次のように表示されます.
@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
      @PathVariable String label) {
   if (name != null && name.contains("(_)")) {
      // "(_)" is uncommon in a git repo name, but "/" cannot be matched
      // by Spring MVC
      name = name.replace("(_)", "/");
   }
   if (label != null && label.contains("(_)")) {
      // "(_)" is uncommon in a git branch name, but "/" cannot be matched
      // by Spring MVC
      label = label.replace("(_)", "/");
   }
   Environment environment = this.repository.findOne(name, profiles, label);
   if(!acceptEmpty && (environment == null || environment.getPropertySources().isEmpty())){
       throw new EnvironmentNotFoundException("Profile Not found");
   }
   return environment;
}

ここでのEnvironmentController#repository属性は、GitRepositoryConfigurationがインスタンス化したM u l t i p l e J G i t EnvironmentRepositoryであり、別の実装形態であれば別のEnvironmentRepositoryであることに注意してください」/{name}/{profiles}/{label:.*}パスパラメータはちょうど我々の要求方式に対応しているので、Config ServerはRestControllerを確立することによって読み出し構成要求を受信し、EnvironmentRepositoryを使用して構成クエリーを行い、org.springframework.cloud.config.environment.Environmentオブジェクトのjson列を返し、クライアントが受信時もorg.springframeworkに逆シーケンス化すべきであると推測する.cloud.config.environment.Environmentのインスタンスです.Environmentのプロパティ定義を参照してください.
private String name;

private String[] profiles = new String[0];

private String label;

private List propertySources = new ArrayList<>();

private String version;

private String state;

カスタムEnvironmentRepository実装を試みる
上記の分析から分かるように、すべての構成EnvironmentRepositoryのコンフィギュレーションは、EnvironmentRepositoryのbeanがないときに有効になり、カスタムのEnvironmentRepositoryのbeanを実現し、上書きできるシステムの実装を実現することができます.コードは以下の通りです.
@SpringBootApplication
@EnableConfigServer
public class SpringCloudDefineConfigServer {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudDefineConfigServer.class, args);
    }

    @Bean
    public EnvironmentRepository newEnvironmentRepository(){
        return new EnvironmentRepository() {
            @Override
            public Environment findOne(String application, String profile, String label) {
                Environment environment =new Environment(application, profile);
                List propertySourceList = environment.getPropertySources();
                Map map = new HashMap<>();
                map.put("name", "garine-define");
                PropertySource propertySource = new PropertySource("map", map);
                propertySourceList.add(propertySource);
                return environment;
            }
        };
    }
}

これにより、取得構成をカスタマイズできます.
Spring cloud config client実現原理分析
getRemoteEnvironment
前述したように、構成サーバrestインタフェースはEnvironmentのjson列を返しているが、client側の逆シーケンス化もEnvironmentであるべきであり、spring-cloud-config-clientパッケージがEnvironmentを使用している場所を検索し、この方法を発見した.
org.springframework.cloud.config.client.C o n f i g ServicePropertySourceLocator#getRemoteEnvironmentは、リモート・サーバの構成を取得する場所を目視します.コードは次のとおりです.
private Environment getRemoteEnvironment(RestTemplate restTemplate,
      ConfigClientProperties properties, String label, String state) {
   String path = "/{name}/{profile}";
   String name = properties.getName();
   String profile = properties.getProfile();
   String token = properties.getToken();
   int noOfUrls = properties.getUri().length;
   if (noOfUrls > 1) {
      logger.info("Multiple Config Server Urls found listed.");
   }

   Object[] args = new String[] { name, profile };
   if (StringUtils.hasText(label)) {
      if (label.contains("/")) {
         label = label.replace("/", "(_)");
      }
      args = new String[] { name, profile, label };
      path = path + "/{label}";
   }
   ResponseEntity response = null;

   for (int i = 0; i < noOfUrls; i++) {
      Credentials credentials = properties.getCredentials(i);
      String uri = credentials.getUri();
      String username = credentials.getUsername();
      String password = credentials.getPassword();

      logger.info("Fetching config from server at : " + uri);

      try {
         HttpHeaders headers = new HttpHeaders();
         addAuthorizationToken(properties, headers, username, password);
         if (StringUtils.hasText(token)) {
            headers.add(TOKEN_HEADER, token);
         }
         if (StringUtils.hasText(state) && properties.isSendState()) {
            headers.add(STATE_HEADER, state);
         }

         final HttpEntity entity = new HttpEntity<>((Void) null, headers);
         response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
               Environment.class, args);
      }
      catch (HttpClientErrorException e) {
         if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
            throw e;
         }
      }
      catch (ResourceAccessException e) {
         logger.info("Connect Timeout Exception on Url - " + uri
               + ". Will be trying the next url if available");
         if (i == noOfUrls - 1)
            throw e;
         else
            continue;
      }

      if (response == null || response.getStatusCode() != HttpStatus.OK) {
         return null;
      }

      Environment result = response.getBody();
      return result;
   }

   return null;
}

上のコードの主な操作は、要求構成アドレス列をつなぎ、必要なApplicationName、profile、labelパラメータを取得し、RestTemplateを利用してhttp要求を実行し、返されたjsonをEnvironmentに逆シーケンス化し、必要な構成情報を取得することです.
では問題ですが、clientはいつgetRemoteEnvironmentメソッドを呼び出したのか、boostrap contextで初期化フェーズを行うべきだと推測します.getRemoteEnvironmentでブレークポイントを打ってclientプログラムを再起動すると、以下の呼び出しリンクが表示されます.
  • org.springframework.boot.SpringApplication#run(java.lang.String…)
  • org.springframework.boot.SpringApplication#prepareContext
  • org.springframework.boot.SpringApplication#applyInitializers
  • org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration#initialize
  • org.springframework.cloud.config.client.ConfigServicePropertySourceLocator#locate
  • org.springframework.cloud.config.client.ConfigServicePropertySourceLocator#getRemoteEnvironment




  • したがって、spring起動時に構成情報がリモートでロードされることがわかります.SpringApplication#applyInitializersコードは、以下のようにすべてのinitializerを巡回して動作します.P r o p e r t y S o u r c e BootstrapConfigurationはその1つのinitializerです.
    protected void applyInitializers(ConfigurableApplicationContext context) {
       for (ApplicationContextInitializer initializer : getInitializers()) {
          Class> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
          Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
          initializer.initialize(context);
       }
    }

    spring-cloud-configが導入されると、P r e t y S o u r c e BootstrapConfiguration#propertySourceLocatorsにC o n f i g ServicePropertySourceLocatorインスタンスが追加されます.P r e t y S o u r c e BootstrapConfiguration#initializeでは、propertySourceLocatorsのlocateメソッドを巡回し、リモート・サービス構成情報を読み込みます.spring-cloud-configが導入されていない場合、propertySourceLoctorsは空の集合になります.コードは次のとおりです.
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
       CompositePropertySource composite = new CompositePropertySource(
             BOOTSTRAP_PROPERTY_SOURCE_NAME);
       AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
       boolean empty = true;
       ConfigurableEnvironment environment = applicationContext.getEnvironment();
       for (PropertySourceLocator locator : this.propertySourceLocators) {
          PropertySource> source = null;
          source = locator.locate(environment);
          if (source == null) {
             continue;
          }
          logger.info("Located property source: " + source);
          composite.addPropertySource(source);
          empty = false;
       }
       if (!empty) {
          MutablePropertySources propertySources = environment.getPropertySources();
          String logConfig = environment.resolvePlaceholders("${logging.config:}");
          LogFile logFile = LogFile.get(environment);
          if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
             propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
          }
          insertPropertySources(propertySources, composite);
          reinitializeLoggingSystem(environment, logConfig, logFile);
          setLogLevels(applicationContext, environment);
          handleIncludedProfiles(environment);
       }
    }

    P r o p e r t y S o u r c e BootstrapConfiguration#propertySourceLocators初期化
    @Autowired(required = false)
    private List propertySourceLocators = new ArrayList<>();

    上記のコードから分かるように、ここのpropertySourceLocatorsはコンテキストに直接注入して管理するPropertySourceLocatorの例なので、PropertySourceLocatorには必ず別の場所が初期化されています.
    ConfigServicePropertySourceLocatorの使用箇所を検索すると、org.springframework.cloud.configServiceBootstrapConfiguration#configServicePropertySourceメソッドには、次のコードのConfigServicePropertySourceLocatorのbeanが組み込まれています.
    @Configuration
    @EnableConfigurationProperties
    public class ConfigServiceBootstrapConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
    @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
    public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
       ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
             properties);
       return locator;
    }
       //........ 
    }

    org.springframework.cloud.config.client.C o n f i g ServiceBootstrapConfigurationはconfig clientのクラスで、spring cloud configを導入したときに導入し、使用箇所を検索してみると、spring cloud config clientパッケージの中のspring.factoriesにConfigServiceBootstrapConfigurationが導入されていて、spring boot自動アセンブリに詳しいことはよく知られていて、プログラムは自動的に追加されますspring.factoriesの構成クラスをロードします.
    つまりspring cloud config clientパッケージを導入すると、C o n f i g S v e r i c e BootstrapConfigurationクラスが自動的にロードされ、C o n f i g S v e rbootstrapConfigurationで構成されているbeanが自動的にアセンブリされ、C o n f i g S v e P r o pertySourceLocatorが自動的にインスタンス化されます.
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.config.client.ConfigClientAutoConfiguration
    
    # Bootstrap components
    org.springframework.cloud.bootstrap.BootstrapConfiguration=\
    org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
    org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration