背景:
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")
热门文章
- 动物疫苗价格一览表图 动物疫苗价格一览表图片
- .NET桌面程序集成Web网页开发的十种解决方案_在线工具
- Vue前端js循环遍历数组八种方法总结最新
- 1月24日|V2ray/Shadowrocket/SSR/Clash每天更新20.1M/S免费节点订阅链接,付费节点订阅推荐
- 附近的动物医院(附近动物医院地址电话)
- 宠物领养市场分析 宠物领养市场分析报告
- Android 白天黑夜模式切换适配及引起的Activity销毁重启解决
- 2月14日|SSR/Clash/Shadowrocket/V2ray每天更新21.1M/S免费节点订阅链接,付费节点订阅推荐
- 2月8日|Clash/Shadowrocket/V2ray/SSR每天更新21M/S免费节点订阅链接,付费节点订阅推荐
- 2月25日|Clash/Shadowrocket/SSR/V2ray每天更新21.8M/S免费节点订阅链接,付费节点订阅推荐