• FeignClient 报错: A bean with that name has already been defined and overriding is disabled.


    1. 错误信息

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    The bean 'xxx.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.
    
    Action:
    
    Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
    
    

    2. 原因

    多个FeignClient访问同一个目标服务,导致value值相同

    @FeignClient(value = "product.center", path = "/info")
    

    而在FeignClient的自动配置类中(FeignClientsRegistrar.class), 注册Bean时,导致BeanName相同,报错

    主要方法如下:

    	public void registerFeignClients(AnnotationMetadata metadata,
    			BeanDefinitionRegistry registry) {
    		ClassPathScanningCandidateComponentProvider scanner = getScanner();
    		scanner.setResourceLoader(this.resourceLoader);
    
    		Set<String> basePackages;
    
    		Map<String, Object> attrs = metadata
    				.getAnnotationAttributes(EnableFeignClients.class.getName());
    		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
    				FeignClient.class);
    		final Class<?>[] clients = attrs == null ? null
    				: (Class<?>[]) attrs.get("clients");
    		if (clients == null || clients.length == 0) {
    			scanner.addIncludeFilter(annotationTypeFilter);
    			basePackages = getBasePackages(metadata);
    		}
    		else {
    			final Set<String> clientClasses = new HashSet<>();
    			basePackages = new HashSet<>();
    			for (Class<?> clazz : clients) {
    				basePackages.add(ClassUtils.getPackageName(clazz));
    				clientClasses.add(clazz.getCanonicalName());
    			}
    			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
    				@Override
    				protected boolean match(ClassMetadata metadata) {
    					String cleaned = metadata.getClassName().replaceAll("\$", ".");
    					return clientClasses.contains(cleaned);
    				}
    			};
    			scanner.addIncludeFilter(
    					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    		}
    
    		for (String basePackage : basePackages) {
    			Set<BeanDefinition> candidateComponents = scanner
    					.findCandidateComponents(basePackage);
    			for (BeanDefinition candidateComponent : candidateComponents) {
    				if (candidateComponent instanceof AnnotatedBeanDefinition) {
    					// verify annotated class is an interface
    					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
    					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
    					Assert.isTrue(annotationMetadata.isInterface(),
    							"@FeignClient can only be specified on an interface");
    
    					Map<String, Object> attributes = annotationMetadata
    							.getAnnotationAttributes(
    									FeignClient.class.getCanonicalName());
    
    					String name = getClientName(attributes);
    					registerClientConfiguration(registry, name,
    							attributes.get("configuration"));
    
    					registerFeignClient(registry, annotationMetadata, attributes);
    				}
    			}
    		}
    	}
    
    	private void registerFeignClient(BeanDefinitionRegistry registry,
    			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    		String className = annotationMetadata.getClassName();
    		BeanDefinitionBuilder definition = BeanDefinitionBuilder
    				.genericBeanDefinition(FeignClientFactoryBean.class);
    		validate(attributes);
    		definition.addPropertyValue("url", getUrl(attributes));
    		definition.addPropertyValue("path", getPath(attributes));
    		String name = getName(attributes);
    		definition.addPropertyValue("name", name);
    		String contextId = getContextId(attributes);
    		definition.addPropertyValue("contextId", contextId);
    		definition.addPropertyValue("type", className);
    		definition.addPropertyValue("decode404", attributes.get("decode404"));
    		definition.addPropertyValue("fallback", attributes.get("fallback"));
    		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    
    		String alias = contextId + "FeignClient";
    		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    
    		boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
    																// null
    
    		beanDefinition.setPrimary(primary);
    
    		String qualifier = getQualifier(attributes);
    		if (StringUtils.hasText(qualifier)) {
    			alias = qualifier;
    		}
    
    		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
    				new String[] { alias });
    		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    	}
    

    registerFeignClients 方法中扫描到指定包下所有的FeignClient, 并在registerFeignClient中构建对应的BeanDefinition对象并注入到容器中,但是这里可以看见, BeanName 是使用的每个FeignClient的类名:

    String className = annotationMetadata.getClassName();
    //...
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
    				new String[] { alias });
    		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    

    所以不是FeignClient 本身的问题

    但是在registerFeignClients方法中,还有一行代码:

    String name = getClientName(attributes);
    registerClientConfiguration(registry, name,
    							attributes.get("configuration"));
    

    这里注入的是每个FeignClient的自定义configuration(即FeignClient的configuration属性定义的配置类)

    此类的名称取法如下:

    private String getClientName(Map<String, Object> client) {
    		if (client == null) {
    			return null;
    		}
    		String value = (String) client.get("contextId");
    		if (!StringUtils.hasText(value)) {
    			value = (String) client.get("value");
    		}
    		if (!StringUtils.hasText(value)) {
    			value = (String) client.get("name");
    		}
    		if (!StringUtils.hasText(value)) {
    			value = (String) client.get("serviceId");
    		}
    		if (StringUtils.hasText(value)) {
    			return value;
    		}
    
    		throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
    				+ FeignClient.class.getSimpleName());
    	}
    

    即可看见,在contextId 属性为空的情况下,不同的FeignClient 类,因为value值相同,所以beanName相同,生成方式如下:

    registry.registerBeanDefinition(
    				name + "." + FeignClientSpecification.class.getSimpleName(),
    				builder.getBeanDefinition());
    

    3. 解决

    正如错误提示信息一样, 可以加如下配置:

    spring.main.allow-bean-definition-overriding=true
    

    即当名称相同时,则覆盖

    这样可以解决问题,并且网上大多数都是这么解决的,但是这样可能会有一些问题:

    在上面的分析中, 因为注入每个FeignClient 的 configuration 配置信息类导致的,那么如果配置了可覆盖,那么所有的FeignClient 都是使用同一份配置,在平常使用时,如果没有配置configuration 属性,则没有问题,如果配置了,就不能使用这种方式;

    第二,配置全局的可覆盖可能会隐藏简单的,代价小的,在启动时就会报错的问题, 而因为覆盖的原因,导致运行时出现无法追溯的问题;

    推荐:

    根据源码可以看出,

    private String getClientName(Map<String, Object> client) {
    		if (client == null) {
    			return null;
    		}
    		String value = (String) client.get("contextId");
    		if (!StringUtils.hasText(value)) {
    			value = (String) client.get("value");
    		}
    		if (!StringUtils.hasText(value)) {
    			value = (String) client.get("name");
    		}
    		if (!StringUtils.hasText(value)) {
    			value = (String) client.get("serviceId");
    		}
    		if (StringUtils.hasText(value)) {
    			return value;
    		}
    
    		throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
    				+ FeignClient.class.getSimpleName());
    	}
    

    名称首要取的是contextId属性,如果不为空,,则不会使用value作为名称,所以只需要在value相同的 FeignClient 配置不同的 contextId 即可:

    @FeignClient(value = "product.center",contextId="search", path = "/info")
    
  • 相关阅读:
    西门子1200/1500 PLC FC/FB块的区别
    Monaco Editor --Web编辑器 自定义主题、代码提示等
    C# 强制GC垃圾回收
    C# 注册表操作类(查询、修改、删除)
    WinForm重绘Combobox控件无边框样式
    Http-server搭建本地服务
    C# 压缩解压文件夹
    递归获取当前节点和所有父节点
    递归获取当前父节点下的所有子集
    轻量级的通信引擎 StriveEngine
  • 原文地址:https://www.cnblogs.com/xjwhaha/p/15251904.html
Copyright © 2020-2023  润新知