大数跨境
0
0

高级开发!手写OpenFeign,很简单,一看就会

高级开发!手写OpenFeign,很简单,一看就会 Spring全家桶实战案例
2025-12-12
0
导读:高级开发!手写OpenFeign,很简单,一看就会
Spring Boot 3实战案例锦集PDF电子书已更新至130篇!
🎉🎉《Spring Boot实战案例合集》目前已更新196个案例,我们将持续不断的更新。文末有电子书目录。

💪💪永久更新承诺

我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务

💌💌如何获取
订阅我们的合集点我订阅,并通过私信联系我们,我们将第一时间将电子书发送给您。

→ 现在就订阅合集

环境:SpringBoot3.4.2



1. 简介

项目开发中常需调用其他微服务或第三方 API,传统 HTTP 客户端(如 RestTemplate、HttpClient、RestClient)存在代码冗余、配置繁琐、缺乏服务治理能力等问题。OpenFeign 作为声明式 HTTP 客户端,通过接口定义和注解驱动(如 @FeignClient),将远程调用抽象为本地方法调用,我们只需定义接口,无需手动处理连接池、重试、负载均衡等底层细节。

虽然 OpenFeign 提供了开箱即用的声明式 HTTP 客户端能力,但理解其底层原理并手写简化版实现,能帮助我们深入掌握动态代理、服务调用的核心逻辑。

本篇文章我们将从零开始手写一个简化版OpenFeign,深入解析声明式HTTP客户端的核心实现原理。如下是最终示例:

@RemoteClient(    name = "userClient"    url = "http://localhost:8080"    configuration = UserClientConfiguration.class)public interface UserClient {
  @GetMapping("/api/{id}")  public String query(@PathVariable Long id, @RequestParam String state)  ;
  @PostMapping("/api/save")  public Map<StringObjectsave(@RequestBody Map<StringObject> body) ;}
2.实战案例
2.1 自定义注解
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface RemoteClient {  /**绝对URL*/  String url() ;  /**唯一标识*/  String name() ;  /**自定义配置*/  Class<?> configuration() default void.class ;}
我们将通过上面的注解进行声明我们定义的接口,与OpenFeign对比相对要简单很多,没有其它更多的配置。
2.2 定义@Enable功能
我们将通过@Enable实现容器启动的时候自动扫描使用@RemoteClient注解的接口,然后将其注册为Bean。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Import(RemoteClientRegistrar.class)public @interface EnableRemoteClient {   /**扫描的包*/  @AliasFor("basePackages")  String[] value() default {} ;
  String[] basePackages() default {};}

该注解通过 value 或 basePackages 属性指定要扫描的包路径(非必填)。若未显式定义,则默认以当前使用该注解的类所在包作为扫描起点。

在该注解中最关键的还是 @Import 注解导入的类 RemoteClientRegistrar ,该类完成了包下类的扫描及注册功能。
public class RemoteClientRegistrar implements ImportBeanDefinitionRegistrar {  private final Environment environment;  public RemoteClientRegistrar(Environment environment) {    this.environment = environment;  }
  @Override  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {    registerFeignClients(metadata, registry) ;  }  // 注册@RemoteClient标注的接口,而这里我们是通过注册 RemoteClientFactoryBean 方式实现对接口创建代理  public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {    LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();    ClassPathScanningCandidateComponentProvider scanner = getScanner() ;    scanner.addIncludeFilter(new AnnotationTypeFilter(RemoteClient.class)) ;    Set<String> basePackages = this.getPackagesToScan(metadata) ;    for (String basePackage : basePackages) {      candidateComponents.addAll(scanner.findCandidateComponents(basePackage));    }    for (BeanDefinition candidateComponent : candidateComponents) {      if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {        AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();        Assert.isTrue(annotationMetadata.isInterface(), "@RemoteClient 注解的类必须是接口");        Map<StringObject> attributes = annotationMetadata.getAnnotationAttributes(RemoteClient.class.getCanonicalName()) ;
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(RemoteClientFactoryBean.class) ;        definition.addPropertyValue("url", attributes.get("url")) ;        definition.addPropertyValue("name", attributes.get("name")) ;        definition.addPropertyValue("type", className) ;        registry.registerBeanDefinition(className, definition.getBeanDefinition()) ;
        Object configuration = attributes.get("configuration") ;        if (configuration != void.class) {          this.registerClientConfiguration(registry, attributes.get("name"), configuration);        }      }    }  }  // 将@RemoteClient注解中配置的configuration类注册为bean  private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RemoteClientSpecification.class);    builder.addConstructorArgValue(name);    builder.addConstructorArgValue(configuration);    registry.registerBeanDefinition(name + "." + RemoteClientSpecification.class.getSimpleName(),        builder.getBeanDefinition());  }  // 自定义组件扫描  protected ClassPathScanningCandidateComponentProvider getScanner() {    return new ClassPathScanningCandidateComponentProvider(falsethis.environment) {      @Override      protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {        boolean isCandidate = false;        if (beanDefinition.getMetadata().isIndependent()) {          if (!beanDefinition.getMetadata().isAnnotation()) {            isCandidate = true;          }        }        return isCandidate;      }    };  }  // 获取要扫描的包(如果没有定义包路径,则会根据当前使用住的类所在的包作为起点)  private Set<StringgetPackagesToScan(AnnotationMetadata metadata) {    AnnotationAttributes attributes = AnnotationAttributes      .fromMap(metadata.getAnnotationAttributes(EnableRemoteClient.class.getName()));    Set<String> packagesToScan = new LinkedHashSet<>();    for (String basePackage : attributes.getStringArray("basePackages")) {      String[] tokenized = StringUtils.tokenizeToStringArray(          this.environment.resolvePlaceholders(basePackage),          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);      Collections.addAll(packagesToScan, tokenized);    }    if (packagesToScan.isEmpty()) {      String packageName = ClassUtils.getPackageName(metadata.getClassName());      return Collections.singleton(packageName);    }    return packagesToScan;  }}

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读195
粉丝0
内容832