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

简介: Spring Cloud升级之路 - Hoxton - 2.入口类注解修改与OpenFeign的改造
本系列示例与胶水代码地址: 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。并且,使用SpringCloudCircuitBreaker的抽象接口,并不能完全使用resilience4j的所有功能,spring-cloud社区维护的resilience4jstarter功能还有适用性不如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<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());
            //使用类的名字作为contextId,实现不同类context不同名称
            //这样就不用每个同微服务的feignclient都要填写不同的contextId了
            String contextId = candidateComponent.getBeanClassName();
            registerClientConfiguration(registry, contextId,
                    attributes.get("configuration"));
            registerFeignClient(registry, annotationMetadata, attributes, contextId);
        }
    }
}


详细源码,请参考:

相关文章
|
22小时前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
22小时前
|
移动开发 前端开发 NoSQL
ruoyi-nbcio从spring2.7.18升级springboot到3.1.7,java从java8升级到17(二)
ruoyi-nbcio从spring2.7.18升级springboot到3.1.7,java从java8升级到17(二)
44 0
|
22小时前
|
监控 安全 Java
Spring cloud原理详解
Spring cloud原理详解
17 0
|
22小时前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
22小时前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
21 2
|
22小时前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
19 2
|
22小时前
|
消息中间件 负载均衡 Java
【Spring Cloud 初探幽】
【Spring Cloud 初探幽】
16 1
|
22小时前
|
安全 Java Docker
|
23小时前
|
Java 开发者 微服务
Spring Cloud原理详解
【5月更文挑战第4天】Spring Cloud是Spring生态系统中的微服务框架,包含配置管理、服务发现、断路器、API网关等工具,简化分布式系统开发。核心组件如Eureka(服务发现)、Config Server(配置中心)、Ribbon(负载均衡)、Hystrix(断路器)、Zuul(API网关)等。本文讨论了Spring Cloud的基本概念、核心组件、常见问题及解决策略,并提供代码示例,帮助开发者更好地理解和实践微服务架构。此外,还涵盖了服务通信方式、安全性、性能优化、自动化部署、服务网格和无服务器架构的融合等话题,揭示了微服务架构的未来趋势。
35 6
|
22小时前
|
JSON Java Apache
Spring Cloud Feign 使用Apache的HTTP Client替换Feign原生httpclient
Spring Cloud Feign 使用Apache的HTTP Client替换Feign原生httpclient

热门文章

最新文章