>>>> 😜😜😜 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