前段时间业务部门有这么一个业务场景,他们自己微服务注册中心是用eureka,他们有一些服务接口要调用兄弟部门的接口,他们定了一个服务调用方案,业务部门直接把他们服务注册到兄弟部门的注册中心,然后走rpc调用,兄弟部门注册中心是用nacos。
一开始业务部门研发直接在在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>
然后项目启动报了如下错
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]
业务部门是这么解决的,每次发版时,如果是要纳入兄弟部门的微服务,他们就先手动注释掉eureka的客户端依赖。
后来业务部门就向我们部门提了一个需求,pom引入多个注册中心客户端,项目也要能正常启动
从项目异常分析
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]
可以看出springcloud默认的服务注册是只支持单服务注册中心。因此我们解决的方案要么扩展springcloud源码,让它支持多注册中心,要么就是告诉springcloud当存在多种注册中心客户端时,选择一种我们想要的注册中心客户端
本文就选实现相对容易的方案,当存在多种注册中心客户端时,我们告诉springcloud,我们想选的注册中心
目前基本只要和springboot集成的开源项目,可以说大部分使用了自动装配,因此我们的解决思路也是从自动装配搞起
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); }
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); }
这两个代码片段,一个是自动装配的代码片段,一个是过滤候选哪些不需要进行自动装配
实现的原理: 当自定的标识为nacos,通过AutoConfigurationImportFilter排除eureka的自动装配;反之排除nacos的自动装配
1、核心实现
public class RegistrationCenterAutoConfigurationImportFilter implements AutoConfigurationImportFilter, EnvironmentAware { private Environment environment; /** * 因为springboot自动装配,默认会把spring.factories的配置的类先全部加载到候选集合中, * 因此当我们代码配置启用nacos,则需把其他注册中心,比如eureka先从候选集合排除 * @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, new String[]{RegistrationCenterConstants.NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASSES,RegistrationCenterConstants.NACOS_DISCOVERY_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; } }
2、配置spring.factories
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ com.github.lybgeek.registration.autoconfigure.filter.RegistrationCenterAutoConfigurationImportFilter
1、在要激活的注册中心的文件禁用其他注册中心客户端
比如appliication-nacos.yml禁用eureka
spring: cloud: nacos: discovery: server-addr: localhost:8848 # 禁用eureka客户端自动注册 eureka: client: enabled: false
2、激活想要注册的注册中心
spring: application: name: springboot-registration-client profiles: active: nacos
这两种方案个人是比较推荐方案二,因为改动最小。方案一比较适用于没有提供是否需要激活注册中心开关的注册中心。其次如果我们要排除某些开源自动装配的组件,也可以考虑用方案一
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-registrationcenter-switch