>>>> 😜😜😜 Github: 👉 github.com/black-ant CASE Backup: 👉 gitee.com/antblack/ca…

A. The preface

As an open source framework, SpringCloud Config has many functions that are not suitable for business scenarios, so we need to achieve our business needs through custom rewriting. This article will look at how to customize the SpringCloud Config module

Two. Principle analysis

2.1 Processing Entry

The entry to SpringCloudConfig is EnvironmentController, which is processed when we enter the following address:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
Copy the code

Corresponding processing interface -> EnvironmentController

// The above modes correspond to the following interfaces:
@RequestMapping(path = "/{name}/{profiles:.*[^-].*}", produces = MediaType.APPLICATION_JSON_VALUE)
public Environment defaultLabel(@PathVariable String name, @PathVariable String profiles) 

@RequestMapping(path = "/{name}/{profiles:.*[^-].*}", produces = EnvironmentMediaType.V2_JSON)
public Environment defaultLabelIncludeOrigin(@PathVariable String name, @PathVariable String profiles) 

@RequestMapping(path = "/{name}/{profiles}/{label:.*}", produces = MediaType.APPLICATION_JSON_VALUE)
public Environment labelled(@PathVariable String name, @PathVariable String profiles, @PathVariable String label) 

@RequestMapping(path = "/{name}/{profiles}/{label:.*}", produces = EnvironmentMediaType.V2_JSON)
public Environment labelledIncludeOrigin(@PathVariable String name, @PathVariable String profiles,@PathVariable String label)
Copy the code

Main entry logic: getEnvironment

public Environment getEnvironment(String name, String profiles, String label, boolean includeOrigin) {
      
      // Format/normalize the parameters as the method name
      name = normalize(name);
      label = normalize(label);
      
      // Core logic to find the corresponding configuration
      Environment environment = this.repository.findOne(name, profiles, label, includeOrigin);

      // Null check
      if (!this.acceptEmpty && (environment == null || environment.getPropertySources().isEmpty())) {
         throw new EnvironmentNotFoundException("Profile Not found");
      }
      returnenvironment; }}Copy the code

2.2 Processing Logic

Main in EnvironmentEncryptorEnvironmentRepository processing logic, which is the core of our custom classes

Here is a look at the overall query system:

2.2.1 Delegate Search configuration

Note that the delegate can be rewritten by autowing, which means we can implement different configurations!

// C- 
public Environment findOne(String name, String profiles, String label, boolean includeOrigin) {
    
    // The core query point is also the customization point
   Environment environment = this.delegate.findOne(name, profiles, label, includeOrigin);
   if (this.environmentEncryptors ! =null) {
   
      for (EnvironmentEncryptor environmentEncryptor : environmentEncryptors) {
         // Decode the configurationenvironment = environmentEncryptor.decrypt(environment); }}return environment;
}
Copy the code

2.2.2 Git Processing Process

Corresponding Git queries using the MultipleJGitEnvironmentRepository and, of course, several other, here not to further:

PS: here, in turn, calls the multiple Repository, and ultimately is to call the superclass AbstractScmEnvironmentRepository for Locations

//C- AbstractScmEnvironmentRepository
public synchronized Environment findOne(String application, String profile, String label, boolean includeOrigin) {

   NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(),
         new NativeEnvironmentProperties());
         
   // Get Location, pull Git locally -> see 2.3
   Locations locations = getLocations(application, profile, label);
   delegate.setSearchLocations(locations.getLocations());
   
   / / call the native continue processing - > NativeEnvironmentRepository - > 2.2.3
   Environment result = delegate.findOne(application, profile, "", includeOrigin);
   
   result.setVersion(locations.getVersion());
   result.setLabel(label);
   
   return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(), getUri());
}
Copy the code

2.3 Download Git files

Git Plugin: Git Plugin: Git Plugin: Git Plugin

    <dependency>
        <groupId>org.eclipse.jgit</groupId>
        <artifactId>org.eclipse.jgit</artifactId>
        <version>5.13.201810200350.-r</version>
    </dependency>
Copy the code

In JGitEnvironmentRepository # getLocation link, there is a refresh operation

2.3.1 Refresh Git main process

public String refresh(String label) {
   Git git = null;
   
   // Pull Git files
   git = createGitClient();
   
   // Whether to pull or not
   if (shouldPull(git)) {
       FetchResult fetchStatus = fetch(git, label);
       if (this.deleteUntrackedBranches && fetchStatus ! =null) {
           deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git);
       }
   }

   checkout(git, label);
   tryMerge(git, label);
   
   return git.getRepository().findRef("HEAD").getObjectId().getName();

}
Copy the code

2.3.2 Pulling Git Files


// Step 1: createGitClient processIn this process the main2Git: -opengitRepository: -copyRepository:// Step 2: Git core pull process- copyRepository - > cloneToBasedir: from the distal clone project - cloneToBasedir - > getCloneCommandByCloneRepository: Get the clone command -clonetobasedir -> clone.call() to complete the clone processCopy the code

When I get here, I’ve downloaded it locally, and then I read it

2.2.3 NativeEnvironmentRepository processing

After obtaining the Location above, the delegate. FindOne chain call will continue, which will be called as follows:

  • C- NativeEnvironmentRepository # findOne
  • C – ConfigDataEnvironmentPostProcessor # applyTo: call EnvironmentPostProcessor for processing
  • C- ConfigDataEnvironmentPostProcessor # postProcessEnvironment
  • C – ConfigDataEnvironmentPostProcessor # getConfigDataEnvironment: build ConfigDataEnvironment
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection
       
         additionalProfiles)
        {
   return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
         additionalProfiles, this.environmentUpdateListener);
}

/ / PS: core, constructs the ConfigDataLocationResolvers in the constructor
this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);

Copy the code

2.4 File Scanning

Git data acquisition is divided into two steps:

  • Step 1: Pull configuration from remote to local
  • Step 2: Read the configuration locally

2.4.1 Scanning Main Process

// C- ConfigDataEnvironmentContributors
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
   
   ImportPhase is an enumeration containing the BEFORE_PROFILE_ACTIVATION and AFTER_PROFILE_ACTIVATION properties
   // BEFORE_PROFILE_ACTIVATION: Phase before starting the configuration file
   ImportPhase importPhase = ImportPhase.get(activationContext);
  
   ConfigDataEnvironmentContributors result = this;
   int processed = 0;
   
   // Process in an infinite loop until the path file is completely processed
   while (true) {
      // ConfigDataProperties contains a Location object
      ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
      if (contributor == null) {
         return result;
      }
      if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
         Iterable<ConfigurationPropertySource> sources = Collections
               .singleton(contributor.getConfigurationPropertySource());
         PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
               result, activationContext, true);
         Binder binder = new Binder(sources, placeholdersResolver, null.null.null);
         ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
         result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
               result.getRoot().withReplacement(contributor, bound));
         continue;
      }
      ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
            result, contributor, activationContext);
            
      // Prepare the Loader container
      ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
      
      // Get a list of all locations
      List<ConfigDataLocation> imports = contributor.getImports();
      
      Resolver (resolver, resolver, resolver, resolver, resolver
      Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
            locationResolverContext, loaderContext, imports);
        
      ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
            asContributors(imported));
      result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, result.getRoot().withReplacement(contributor, contributorAndChildren)); processed++; }}Copy the code

2.4.2 Resolve Resolve a path

// C- ConfigDataImporter # resolveAndLoad: This method is divided into three core steps

// Step 1: Obtain Profiles informationProfiles profiles = (activationContext ! =null)? activationContext.getProfiles() :null;

// Step 2: Resolved path
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);

/ / Step 3: load loading for Map < ConfigDataResolutionResult, ConfigData >
return load(loaderContext, resolved);

Copy the code

Resolved process can also be a bit tricky and I recommend looking at the picture here to outline the main logic:

Step 2-1: Resolved cycle location

private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext, Profiles profiles, List
       
         locations)
        {
   List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
   // Iterate over all locations
   for (ConfigDataLocation location : locations) {
      resolved.addAll(resolve(locationResolverContext, profiles, location));
   }
   return Collections.unmodifiableList(resolved);
}
Copy the code

Step 2-2: Loop through other resolve

Here is the parsing of resources in a variety of formats

List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
   // loop and judge can handle
   for(ConfigDataLocationResolver<? > resolver : getResolvers()) {if (resolver.isResolvable(context, location)) {
         returnresolve(resolver, context, location, profiles); }}}Copy the code

Step 2-3: Recycle all resources after getReference

// C- StandardConfigDataLocationResolver
private List<StandardConfigDataResource> resolve(Set<StandardConfigDataReference> references) {
   List<StandardConfigDataResource> resolved = new ArrayList<>();
   
   // reference Loops through reference
   for (StandardConfigDataReference reference : references) {
      resolved.addAll(resolve(reference));
   }
   return resolved;
}
Copy the code

Step 2-4: Loop through all configDatareSources

private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location, boolean profileSpecific,
      Supplier<List<? extends ConfigDataResource>> resolveAction) {
   // Note that 2-3 occurs at this point, when the final resource has been obtained
   List<ConfigDataResource> resources = nonNullList(resolveAction.get());
   List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());
   
   for (ConfigDataResource resource : resources) {
      resolved.add(new ConfigDataResolutionResult(location, resource, profileSpecific));
   }
   return resolved;
}
Copy the code

2.5 Load Process

2.5.1 Load Main Process

// C- ConfigDataImporter
private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext, List
       
         candidates)
        throws IOException {
   Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();
   
   // Apply the loop to all the candidates
   for (int i = candidates.size() - 1; i >= 0; i--) {
      ConfigDataResolutionResult candidate = candidates.get(i);
      
      / / get the location
      ConfigDataLocation location = candidate.getLocation();
      // Get all the resources
      ConfigDataResource resource = candidate.getResource();
      if (this.loaded.add(resource)) {
         try {
            // Call loaders to load resource
            ConfigData loaded = this.loaders.load(loaderContext, resource);
            if(loaded ! =null) {
               // This will all be placed in one mapresult.put(candidate, loaded); }}catch(ConfigDataNotFoundException ex) { handle(ex, location); }}}return Collections.unmodifiableMap(result);
}
Copy the code

!!!!!!!!!!!!!!!!!! Here I was pit of very miserable, a simple case how to load out, interested can see my following figure guess why, a very important point!!

2.5.2 loaders loading

The parent object here is mainly ConfigDataLoaders and the final call object is StandardConfigDataLoader

public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource)
      throws IOException, ConfigDataNotFoundException {
   
   // Ellipsis is empty and no Reource throws an exception
   StandardConfigDataReference reference = resource.getReference();
   Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(),
         Origin.from(reference.getConfigDataLocation()));
   String name = String.format("Config resource '%s' via location '%s'", resource,
         reference.getConfigDataLocation());
         
   // Finally load the YAML file with YamlPropertySourceLoaderList<PropertySource<? >> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);return new ConfigData(propertySources);
}
Copy the code

The final loaded PropertySources

At this point the property is officially loaded

conclusion

This article has been written for a long time, it is better to keep it simple, so the use of Native and attributes will be looked at later, and a flow chart will be contributed