Spring Cloud升级之路 - Hoxton - 2.入口类注解修改与OpenFeign的改造

2021-04-12 14:31:21 浏览数 (1)

本系列示例与胶水代码地址: https://github.com/HashZhang/spring-cloud-scaffold

入口类注解修改

之前的项目,我们也许会用@SpringCloudApplication作为我们入口类的注解。这个注解包括:

代码语言:javascript复制
@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。并且,使用SpringCloudCircuitBreaker的抽象接口,并不能完全使用resilience4j的所有功能,spring-cloud社区维护的resilience4jstarter功能还有适用性不如resilience4j自己维护的starter

所以,** 我们这里改为使用@SpringBootApplication作为入口类注解 **

open-feign 的兼容

升级后,多 FeignClient 类同一微服务,导致 feign 配置 bean 名称重复。当你的项目中存在多个不同的FeignClient类使用同一个微服务名称的时候:

代码语言:javascript复制
@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配置:

代码语言:javascript复制
@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配置:

代码语言:javascript复制
feign.client.config.微服务名称.connectTimeout=1000

对于org.springframework.cloud.openfeign.FeignClientFactoryBean,修改configureFeign方法:

代码语言:javascript复制
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:

代码语言:javascript复制
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

0 人点赞