本系列示例与胶水代码地址: https://github.com/HashZhang/spring-cloud-scaffold
入口类注解修改
之前的项目,我们也许会用@SpringCloudApplication
作为我们入口类的注解。这个注解包括:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
其中的@EnableDiscoveryClient
会启动服务发现的客户端,我们这里继续用Eureka,但是EurekaClient不需要这个注解,只要加上spring-cloud-starter-eureka-client
的依赖,就会启动EurekaClient。对于@EnableCircuitBreaker
这个注解,就比较麻烦了。我们引入了spring-cloud-starter-netflix-eureka-client
依赖,这个依赖,包含了hystrix
依赖,导致会自动启用hystrix
实现CircuitBreaker
接口,而我们并不想启用hystrix。并且,使用SpringCloud
的CircuitBreaker
的抽象接口,并不能完全使用resilience4j
的所有功能,spring-cloud
社区维护的resilience4j
的starter
功能还有适用性不如resilience4j
自己维护的starter
。
所以,** 我们这里改为使用@SpringBootApplication
作为入口类注解 **
open-feign 的兼容
升级后,多 FeignClient 类同一微服务,导致 feign 配置 bean 名称重复。当你的项目中存在多个不同的FeignClient
类使用同一个微服务名称的时候:
@FeignClient(value = "service-provider")
public interface UserService {
}
@FeignClient(value = "service-provider")
public interface RetailerService {
}
升级后,就会报错:
Spring Boot 2.1.x 版本之后默认不支持同名 bean,需要增加配置 spring.main.allow-bean-definition-overriding=true
。但是这是一个非常危险的配置,bean 覆盖开启,如果你定义了重复 Bean,你并不知情,这样可能导致你不确定用的是哪个 bean 导致业务问题。所以不推荐开启。
另一个解决办法是参考:Support Multiple Clients Using The Same Service
使用FeignClient
中的contextId
配置:
@FeignClient(value = "service-provider", contextId = "UserService")
public interface UserService {
}
@FeignClient(value = "service-provider", contextId = "RetailerService")
public interface RetailerService {
}
但是如果你这样的的FeignClient
非常的多,那么改起来也是比较麻烦。我们这里可以考虑使用类的全限定名作为 contextId。考虑在项目中创建两个和框架中的代码同名同路径的类,分别是org.springframework.cloud.openfeign.FeignClientFactoryBean
还有FeignClientsRegistrar
,相当于改了这两个类的源代码。
要实现的目的是:使用类的全限定名作为 contextId,这样不同类的 contextId 肯定不一样,实现了配置 bean 的名称唯一。同时修改feign配置读取,用name作为key去读取,而不是contextId,也就是能这样配置不同微服务的feign
配置:
feign.client.config.微服务名称.connectTimeout=1000
对于org.springframework.cloud.openfeign.FeignClientFactoryBean
,修改configureFeign方法:
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = this.applicationContext
.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
//使用name,而不用原来的contextId,我们要实现的是多个FeignClient通过contextId指定bean名称实现多个同微服务的FeignClient共存
//但是配置上,同一个微服务的FeignClient统一配置
configureUsingProperties(properties.getConfig().get(this.name),
builder);
} else {
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
//使用name,而不用原来的contextId,我们要实现的是多个FeignClient通过contextId指定bean名称实现多个同微服务的FeignClient共存
//但是配置上,同一个微服务的FeignClient统一配置
configureUsingProperties(properties.getConfig().get(this.name),
builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
对于org.springframework.cloud.openfeign.FeignClientsRegistrar
,修改registerFeignClients
:
for (String basePackage : basePackages) {
Set 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 attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
//使用类的名字作为contextId,实现不同类context不同名称
//这样就不用每个同微服务的feignclient都要填写不同的contextId了
String contextId = candidateComponent.getBeanClassName();
registerClientConfiguration(registry, contextId,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes, contextId);
}
}
}
详细源码,请参考:
- FeignClientFactoryBean.java
- FeignClientsRegistrar.java