preface

Recently business department has so a business scenario, we use had their own micro service registry, they have some service interface to call brother department interface, they set a service call plan, business services registered them directly to the brothers department of registry, and then walk RPC calls, registry is to use nacos brothers department.

At first business department r&d was introduced directly in POM.xml

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
Copy the code

Then the project starts with the following error

Field registration in org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration$ServiceRegistryEndpointConfiguration required a single bean, but 2 were found:
	- nacosRegistration: defined by method 'nacosRegistration' in class path resource [com/alibaba/cloud/nacos/NacosDiscoveryAutoConfiguration.class]
	- eurekaRegistration: defined in BeanDefinition defined in class path resource [org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration.class]

Copy the code

The way the business unit works is that every time a release is made, they manually comment out the eureka client dependencies if they want to include the sibling microservices.

Later, the business department raised a requirement to our department that POM introduce multiple registry clients and the project should be started normally

Demand analysis

From the project anomaly analysis

Field registration in org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration$ServiceRegistryEndpointConfiguration required a single bean, but 2 were found:
	- nacosRegistration: defined by method 'nacosRegistration' in class path resource [com/alibaba/cloud/nacos/NacosDiscoveryAutoConfiguration.class]
	- eurekaRegistration: defined in BeanDefinition defined in class path resource [org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration.class]

Copy the code

You can see that the default service registry in springcloud supports only a single service registry. So our solution was either to extend springcloud’s source code to support multiple registries, or to tell springcloud to choose the one we wanted when there were multiple registry clients

In this article, we choose the relatively easy option, and when there are multiple registry clients, we tell springcloud which registry we want to choose

Implementation scheme

At present, as long as the basic open source projects with SpringBoot integration, it can be said that most of the use of automatic assembly, so our solution is also from automatic assembly

Front knowledge

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
		if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

Copy the code
	private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
		long startTime = System.nanoTime();
		String[] candidates = StringUtils.toStringArray(configurations);
		boolean[] skip = new boolean[candidates.length];
		boolean skipped = false;
		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
			invokeAwareMethods(filter);
			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
			for (int i = 0; i < match.length; i++) {
				if(! match[i]) { skip[i] =true;
					candidates[i] = null;
					skipped = true; }}}if(! skipped) {return configurations;
		}
		List<String> result = new ArrayList<>(candidates.length);
		for (int i = 0; i < candidates.length; i++) {
			if (!skip[i]) {
				result.add(candidates[i]);
			}
		}
		if (logger.isTraceEnabled()) {
			int numberFiltered = configurations.size() - result.size();
			logger.trace("Filtered " + numberFiltered + " auto configuration class in "
					+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
		}
		return new ArrayList<>(result);
	}
Copy the code

These two code fragments, one is the autowiring code fragment, the other is the filtering candidate which does not need to autowiring

Solution a: use AutoConfigurationImportFilter + custom logo

Implementation principle: when the logo for nacos, by excluding eureka AutoConfigurationImportFilter automatic assembly; Conversely, the automatic assembly of NACOS is excluded

1. Core implementation

public class RegistrationCenterAutoConfigurationImportFilter implements AutoConfigurationImportFilter.EnvironmentAware {

    private Environment environment;

    /** * Because springboot is automatically assembled, all spring-configured classes are loaded into the candidate collection by default. * So when we configure nacOS, we need to exclude other registries, such as Eureka, from the candidate collection@param autoConfigurationClasses
     * @param autoConfigurationMetadata
     * @return* /
    @Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        Set<String> activeProfiles = Arrays.stream(environment.getActiveProfiles()).collect(Collectors.toSet());

        if(activeProfiles.contains(RegistrationCenterConstants.ACTIVE_PROFILE_NACOS)){
            return excludeMissMatchRegistrationAutoConfiguration(autoConfigurationClasses, new String[]{RegistrationCenterConstants.EUREKA_AUTO_CONFIGURATION_CLASSES});

        }else if (activeProfiles.contains(RegistrationCenterConstants.ACTIVE_PROFILE_EUREKA)){
            return excludeMissMatchRegistrationAutoConfiguration(autoConfigurationClasses, newString[]{RegistrationCenterConstants.NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASSES,RegistrationCenterConstants.NACOS_DISCOV ERY_CLIENT_AUTO_CONFIGURATION_CLASSES,RegistrationCenterConstants.NACOS_DISCOVERY_ENDPOINT_AUTO_CONFIGURATION_CLASSES});  }return new boolean[0];
    }

    private boolean[] excludeMissMatchRegistrationAutoConfiguration(String[] autoConfigurationClasses,String[] needExcludeRegistrationAutoConfigurationClasse) {
        Map<String,Boolean> needExcludeRegistrationAutoConfigurationClzMap = initNeedExcludeRegistrationAutoConfigurationClzMap(needExcludeRegistrationAutoConfigurationClasse);
        boolean[] match = new boolean[autoConfigurationClasses.length];
        for (int i = 0; i < autoConfigurationClasses.length; i++) { String autoConfigurationClz = autoConfigurationClasses[i]; match[i] = ! needExcludeRegistrationAutoConfigurationClzMap.containsKey(autoConfigurationClz); }return match;
    }

    private Map<String,Boolean> initNeedExcludeRegistrationAutoConfigurationClzMap(String[] needExcludeRegistrationAutoConfigurationClasse){
        Map<String,Boolean> needExcludeRegistrationAutoConfigurationClzMap = new HashMap<>();
        for (String needExcludeRegistrationAutoConfigurationClz : needExcludeRegistrationAutoConfigurationClasse) {
            needExcludeRegistrationAutoConfigurationClzMap.put(needExcludeRegistrationAutoConfigurationClz,false);
        }

        return needExcludeRegistrationAutoConfigurationClzMap;

    }

    @Override
    public void setEnvironment(Environment environment) {
         this.environment = environment; }}Copy the code

2. Configure spring.factories

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
com.github.lybgeek.registration.autoconfigure.filter.RegistrationCenterAutoConfigurationImportFilter

Copy the code

Option 2: Use application-${specify registry identity} + spring.profiles.active

1. Disable other registry clients in the registry files you want to activate

For example, applium-nacos. yml disables Eureka

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

# Disable eureka client auto-registration
eureka:
  client:
    enabled: false
Copy the code

2. Activate the registry you want to register

spring:
  application:
    name: springboot-registration-client
  profiles:
    active: nacos
Copy the code

conclusion

Personally, I recommend plan 2 because it has the least change. Option 1 is suitable for registries that do not specify whether to activate the registry switch. Second, if we want to exclude some open source autowiring components, we can also consider option 1

The demo link

Github.com/lyb-geek/sp…