背景:

Spring-Boot 2.0.8.RELEASE

Spring-Cloud 2.0.4.RELEASE

OpenFeign 2.0.4.RELEASE

JDK 1.8

启动类:

package com.xxx.tfb;  import java.sql.SQLException;  import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication; import org.h2.tools.Server; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType;  import com.xxx.framework.config.SecondDataSourceConfiguration;  import springfox.documentation.swagger2.annotations.EnableSwagger2;  @EnableEurekaClient @EnableFeignClients(basePackages="com.xxx") @EnableCircuitBreaker @EnableProcessApplication @EnableHystrix @SpringBootApplication @EnableSwagger2 @MapperScan(basePackages = { "com.xxx.**.mapper" }) @MapperScan(basePackages = { "com.xxx.**.oramapper" }) @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {SecondDataSourceConfiguration.class})}) @ComponentScan(basePackages = { "com.xxx.framework","com.xxx.workflow", 		"com.xxx.tradeengine","com.xxx.component", 		"com.xxx.controller", "com.xxx.bizservice", 		"com.xxx.rpc","com.xxx.dto.rpc", 		"com.xxx.fallbackservice","com.xxx.helper" }) @SuppressWarnings("all") public class TfbOrBizServiceApplication  { 	private static Log log=LogFactory.getLog(TfbOrBizServiceApplication.class); 	 	public static void main(String[] args) { 		SpringApplication.run(TfbOrBizServiceApplication.class, args); 	} }

Feign客户端:

package com.xxx.rpc;  import java.util.List;  import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;  import com.xxx.dto.tfb.function.UnitBizRequestDto; import com.xxx.dto.tfb.function.UnitBizResponseDto; import com.xxx.framework.dto.PaginationDto; import com.xxx.framework.web.controller.PageRequestDto; import com.xxx.framework.web.controller.ResponseDto;  @FeignClient(value = "tfb-biz-common-service-app", path="/tfb-biz-common-service-app") public interface CommonBizRpc { 	 	/** 	 * 获取交易日期 	 */ 	@RequestMapping(value = "/leftQuery/queryAcctDate", method = RequestMethod.POST) 	public ResponseDto<String> queryAcctDate(); }

使用的地方:

package com.xxx.tfb.component.or;  import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component;  import com.xxx.TradeDto; import com.xxx.bizenum.tfb.CommonEnum; import com.xxx.framework.utils.SpringContextUtil; import com.xxx.rpc.CommonBizRpc; import com.xxx.tradeengine.dto.DataContext; import com.xxx.tradeengine.parser.TradeComponent;  @Component @DependsOn("springContextUtil") public class OrOpenFxccDataInitComponentImpl implements TradeComponent { 	//公共交易服务 	CommonBizRpc commonBizRpc = SpringContextUtil.getBean("commonBizRpc"); 			 	@Override 	public void exec(DataContext dataContext) { 		//交易日期 		orDto.setTxDt(commonBizRpc.queryAcctDate().getData()); 	}  }

SpringContextUtil工具类:

 @Component public class SpringContextUtil implements ApplicationContextAware { 	private static ApplicationContext applicationContext; // Spring应用上下文环境       	/** 	 * 实现ApplicationContextAware接口的回调方法,设置上下文环境 	 *  	 * @param applicationContext @ 	 */ 	@Override 	public void setApplicationContext(ApplicationContext applicationContext) { 		SpringContextUtil.applicationContext = applicationContext; 	}  	/** 	 * 获取对象 	 *  	 * @param name 	 * @return Object 一个以所给名字注册的bean的实例 @ 	 */ 	@SuppressWarnings("unchecked") 	public static <T> T getBean(String name) { 		return (T) applicationContext.getBean(name); 	}  }

这样的配置,应用启动的时候报错了:

Constructor in com.xxx.tfb.component.or.OrOpenFxccDataInitComponentImpl required a bean named 'commonBizRpc' that could not be found.  Action:  Consider defining a bean named 'commonBizRpc' in your configuration.

报错的意思是不存在name为commonBizRpc的实例。已经在@EnableFeignClients(basePackages="com.xxx")配置了basePackages,而且可以确定该@FeignClient已经被扫描到了。

为什么提示找不到呢?

分析下源码:

@EnableFeignClients 注解:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients {

注意上面导入的FeignClientsRegistrar类,该类实现了将@FeignClient注解的接口加入到Spring管理的容器里:

	@Override 	public void registerBeanDefinitions(AnnotationMetadata metadata, 			BeanDefinitionRegistry registry) { 		registerDefaultConfiguration(metadata, registry); 		registerFeignClients(metadata, registry); 	} 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);         // className为带有包路径的类名 		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);          // 默认的别名         // 这里的name是服务方的名称,本示例为注册到Eureka里的服务名 		String alias = name + "FeignClient"; 		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();  		boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null  		beanDefinition.setPrimary(primary);          // 如果注解里加了qualifier,别名就使用qualifier里的名称 		String qualifier = getQualifier(attributes); 		if (StringUtils.hasText(qualifier)) { 			alias = qualifier; 		}  		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, 				new String[] { alias }); 		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); 	}

注意上面的registerFeignClients方法,交给Spring容器管理的实例的name默认是className,即带有包路径的name,本次示例为"com.xxx.rpc.CommonBizRpc",默认的别名:
        // 这里的name是服务方的名称,本示例为注册到Eureka里的服务名,tfb-biz-common-service-appFeignClient
        String alias = name + "FeignClient";

        // 如果注解里加了qualifier,别名就使用qualifier里的名称
        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

这样在注入的时候,要么使用的name是com.xxx.rpc.CommonBizRpc而不能是CommonBizRpc,或tfb-biz-common-service-appFeignClient(如果该服务注册多个客户端,可能会报错),或使用CommonBizRpc.class

SpringContextUtil.getBean();

@AutoWired也一样

为了方便使用,建议在每个@FeignClient里配置上qualifier,修改后的代码如下:

package com.xxx.rpc;   import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;   @FeignClient(value = "tfb-biz-common-service-app", path="/tfb-biz-common-service-app", qualifier="commonBizRpc") public interface CommonBizRpc { 	 	/** 	 * 获取交易日期 	 */ 	@RequestMapping(value = "/leftQuery/queryAcctDate", method = RequestMethod.POST) 	public ResponseDto<String> queryAcctDate(); }

注意事项:

1.@EnableFeignClients(basePackages="com.xxx") 如果不配置basePackages的话,那么默认只会扫描当前启动类包及其子包的@FeignClient注解

2.@FeignClient里的path最好写上,而且是服务端的contextPath(value = "tfb-biz-common-service-app", path="/tfb-biz-common-service-app", qualifier="commonBizRpc")