如何系列 如何玩转远程调用之OpenFegin+SpringBoot(非Cloud)

news2025/1/12 1:34:20

文章目录

    • 简介
    • 原生Fegin示例
      • 基础
      • 契约
      • 日志
      • 重试
      • 编码器/解码器
        • 自定义解码器
      • 请求拦截器
      • 响应拦截器
      • 表单文件上传支持
      • 错误解码器
      • 断路器
      • 指标metrics
      • 客户端
    • 配合SpringBoot(阶段一)
    • 配合SpringBoot(阶段二)
      • 1.EnableLakerFeignClients
      • 2.LakerFeignClientsRegistrar
      • 3.LakerFeignClientFactoryBean
      • 4.LakerFeignClient
      • 5.FeginConfig
      • 使用示例
    • 参考

简介

市面上都是 Spring Cloud + openfeign

想搞个Spring(boot) + openfeign

Github: https://github.com/OpenFeign/feign

Feign 10.x 及更高版本基于 Java 8 构建,应该适用于 Java 9、10 和 11。对于需要 JDK 6 兼容性的用户,请使用 Feign 9.x

功能图

架构图

img

依赖

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <version>13.0</version>
</dependency>

原生Fegin示例

基础

// 1.定义接口
interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

// 测试
public class MyApp {
    public static void main(String... args) {
        GitHub github = Feign.builder()
                .logLevel(Logger.Level.FULL)
                .options(new Request.Options(Duration.ofSeconds(2), Duration.ofSeconds(5), true))
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(GitHub.class, "https://api.github.com");
        List<Contributor> contributors = github.contributors("OpenFeign", "feign");
        for (Contributor contributor : contributors) {
            System.out.println(contributor.login + " (" + contributor.contributions + ")");
        }
    }
}

原生注解@RequestLine有额外的理解成本,我们一般不会使用

契约

从10.5.0版本开始提供了feign-spring4,来适配spring注解。
使用spring注解需要将contract契约设置为SpringContract。

<dependency>
   <groupId>io.github.openfeign</groupId>
   <artifactId>feign-spring4</artifactId>
   <version>13.0</version>
</dependency>

Feign 仅支持处理 java 接口(不支持抽象类或具体类)

方法注解

  • @RequestMapping
  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

参数注解

  • @PathVariable
  • @RequestParam
interface GitHub {
  @GetMapping("/repos/{owner}/{repo}/contributors") //改变
  List<Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
  @PostMapping("/repos/{owner}/{repo}/issues") //改变
  void createIssue(Issue issue, @PathVariable("owner") String owner, @PathVariable("repo") String repo);
}
public class MyApp {
    public static void main(String... args) {
        GitHub github = Feign.builder()
                .logLevel(Logger.Level.FULL)
                .contract(new SpringContract()) // 这里 SpringContract
                .options(new Request.Options(Duration.ofSeconds(2), Duration.ofSeconds(5), true))
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(GitHub.class, "https://api.github.com");
        List<Contributor> contributors = github.contributors("OpenFeign", "feign");
        for (Contributor contributor : contributors) {
            System.out.println(contributor.login + " (" + contributor.contributions + ")");
        }
    }
}

日志

<dependency>
   <groupId>io.github.openfeign</groupId>
   <artifactId>feign-sl4j</artifactId>
   <version>13.0</version>
</dependency>

SLF4JModule允许将 Feign 的日志记录定向到SLF4J,允许您轻松使用选择的日志记录记录(Logback、Log4J 等)

相当于 SLF4J 与 Feign 一起使用,满足 SLF4J 模块和您选择的 SLF4J 绑定添加到您的类路径中。然后,配置 Feign 使用 Slf4jLogger:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .logger(new Slf4jLogger())
                     .logLevel(Level.FULL)
                     .target(GitHub.class, "https://api.github.com");
  }
}

重试

默认情况下,Feign会自动重试IOExceptions,无论HTTP方法如何,都将其视为临时的与网络相关的异常,并重试从ErrorDecoder中抛出的任何RetryableException。要自定义此行为,请通过构建器注册自定义的Retryer实例。

以下示例展示了如何在收到401响应时使用ErrorDecoder和Retryer来刷新令牌并进行重试。

public class Example {
    public static void main(String[] args) {
        var github = Feign.builder()
                .decoder(new GsonDecoder())
                .retryer(new MyRetryer(100, 3))
                .errorDecoder(new MyErrorDecoder())
                .target(Github.class, "https://api.github.com");

        var contributors = github.contributors("foo", "bar", "invalid_token");
        for (var contributor : contributors) {
            System.out.println(contributor.login + " " + contributor.contributions);
        }
    }

    static class MyErrorDecoder implements ErrorDecoder {

        private final ErrorDecoder defaultErrorDecoder = new Default();

        @Override
        public Exception decode(String methodKey, Response response) {
            // wrapper 401 to RetryableException in order to retry
            if (response.status() == 401) {
                return new RetryableException(response.status(), response.reason(), response.request().httpMethod(), null, response.request());
            }
            return defaultErrorDecoder.decode(methodKey, response);
        }
    }

    static class MyRetryer implements Retryer {

        private final long period;
        private final int maxAttempts;
        private int attempt = 1;

        public MyRetryer(long period, int maxAttempts) {
            this.period = period;
            this.maxAttempts = maxAttempts;
        }

        @Override
        public void continueOrPropagate(RetryableException e) {
            if (++attempt > maxAttempts) {
                throw e;
            }
            if (e.status() == 401) {
                // remove Authorization first, otherwise Feign will add a new Authorization header
                // cause github responses a 400 bad request
                e.request().requestTemplate().removeHeader("Authorization");
                e.request().requestTemplate().header("Authorization", "Bearer " + getNewToken());
                try {
                    Thread.sleep(period);
                } catch (InterruptedException ex) {
                    throw e;
                }
            } else {
                throw e;
            }
        }

        // Access an external api to obtain new token
        // In this example, we can simply return a fixed token to demonstrate how Retryer works
        private String getNewToken() {
            return "newToken";
        }

        @Override
        public Retryer clone() {
            return new MyRetryer(period, maxAttempts);
        }
}

Retryers负责通过从方法continueOrPropagate(RetryableException e)返回true或false来确定是否应该进行重试;如果需要,将为每个Client执行创建一个Retryer实例,以便在每个请求之间维护状态。

如果决定重试不成功,将抛出最后一个RetryException。要抛出导致重试不成功的原始原因,请使用exceptionPropagationPolicy()选项构建您的Feign客户端。

编码器/解码器

  • GSON
  • Jackson
  • Moshi
  • SOAP
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
    <version>13.0</version>
</dependency>

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
    <version>13.0</version>
</dependency>
public class Example {
  public static void main(String[] args) {
      GitHub github = Feign.builder()
                     .encoder(new JacksonEncoder())
                     .decoder(new JacksonDecoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}
自定义解码器
public class MyJacksonDecoder extends JacksonDecoder {
    @Override
    public Object decode(Response response, Type type) throws IOException {
        if (response.body() == null) {
            return null;
        }
        if (type == String.class) {
            return StreamUtils.copyToString(response.body().asInputStream(), StandardCharsets.UTF_8);
        }
        return super.decode(response, type);
    }
}

请求拦截器

当您需要更改所有请求时,无论其目标是什么,您都需要配置一个RequestInterceptor. 例如,如果您充当中介,您可能想要传播标X-Forwarded-For头。

static class ForwardedForInterceptor implements RequestInterceptor {
  @Override public void apply(RequestTemplate template) {
    template.header("X-Forwarded-For", "origin.host.com");
  }
}

public class Example {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new ForwardedForInterceptor())
                 .target(Bank.class, "https://api.examplebank.com");
  }
}

响应拦截器

如果您需要将错误视为成功并返回结果而不是发送异常,那么您可以使用ResponseInterceptor

例如,Feign 包含一个简单的RedirectionInterceptor可用于从重定向响应中提取位置标头。

public interface Api {
  // returns a 302 response
  @RequestLine("GET /location")
  String location();
}

public class MyApp {
  public static void main(String[] args) {
    // Configure the HTTP client to ignore redirection
    Api api = Feign.builder()
                   .options(new Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, false))
                   .responseInterceptor(new RedirectionInterceptor())
                   .target(Api.class, "https://redirect.example.com");
  }
}

表单文件上传支持

<dependency>
   <groupId>io.github.openfeign.form</groupId>
   <artifactId>feign-form</artifactId>
</dependency>

错误解码器

ErrorDecoder如果您需要更多地控制处理意外响应,Feign 实例可以通过构建器注册自定义。

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .errorDecoder(new MyErrorDecoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

所有导致 HTTP 状态不在 2xx 范围内的响应都将触发ErrorDecodersdecode方法,允许您处理响应、将失败包装到自定义异常中或执行任何其他处理。如果您想再次重试请求,请抛出RetryableException. 这将调用注册的 Retryer.

断路器

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hystrix</artifactId>
    <version>13.0</version>
</dependency>

HystrixFeign配置Hystrix提供的断路器支持。

要将 Hystrix 与 Feign 一起使用,请将 Hystrix 模块添加到类路径中。然后使用HystrixFeign构建器:

public class Example {
  public static void main(String[] args) {
    MyService api = HystrixFeign.builder()
        .target(MyFeignClient.class, "http://remote-service-url", new MyFeignClientFallbackFactory());
  }
}

interface GitHub {
  @GetMapping("/repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
    
}    


public class MyFeignClientFallbackFactory implements FallbackFactory<GitHub> {
    @Override
    public GitHub create(Throwable cause) {
        return new GitHub() {
            @Override
            public List<Contributor> contributors(String owner, String repo) {
                return new ArrayList<>(); // 回退逻辑,可以返回默认值或错误消息
            }
        };
    }
}

对于异步或反应式使用,返回HystrixCommand<YourType>CompletableFuture<YourType>

上面配置callback使用

设置超时等

  1. 熔断器配置:你可以设置熔断器的相关属性,如 circuitBreakerErrorThresholdPercentagecircuitBreakerSleepWindowInMillisecondscircuitBreakerRequestVolumeThreshold 等,以控制熔断器的行为。
  2. 线程池配置:如果你在 Hystrix 命令中使用了线程池隔离,你可以设置线程池的相关属性,如 coreSizemaxQueueSizekeepAliveTimeMinutes 等。
  3. 超时属性:除了设置总的执行超时时间,你还可以设置 executionTimeoutEnabledexecutionIsolationStrategy 等超时相关属性。
  4. 命令名和组名:你可以自定义命令的名称和分组名称,通过 andCommandKeyandCommandGroup 方法来设置。
  5. 并发属性:你可以设置命令执行的并发性相关属性,如 executionIsolationSemaphoreMaxConcurrentRequests
public class MyApp {
    public static void main(String... args) {


        GitHub github = HystrixFeign.builder()
                .logLevel(Logger.Level.FULL)
                // 设置超时等
                .setterFactory((target, method) -> HystrixCommand.Setter
                        .withGroupKey(HystrixCommandGroupKey.Factory.asKey(target.name()))
                        .andCommandKey(HystrixCommandKey.Factory.asKey(method.getName()))
                        .andCommandPropertiesDefaults(
                                HystrixCommandProperties.Setter()
                                        .withExecutionTimeoutInMilliseconds(5000) // 设置执行超时时间
                                        .withCircuitBreakerRequestVolumeThreshold(20) // 设置熔断器请求数阈值
                                        .withCircuitBreakerSleepWindowInMilliseconds(10000) // 设置熔断器休眠窗口
                        )
                        .andThreadPoolPropertiesDefaults(
                                HystrixThreadPoolProperties.Setter()
                                        .withCoreSize(10) // 设置线程池核心大小
                                        .withMaxQueueSize(100) // 设置线程池队列大小
                        ))
                .contract(new SpringContract())
                .options(new Request.Options(Duration.ofSeconds(2), Duration.ofSeconds(5), true))
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(GitHub.class, "https://api.github.com", new MyFeignClientFallbackFactory());
        List<Contributor> contributors = github.contributors("OpenFeign", "feign");
        for (Contributor contributor : contributors) {
            System.out.println(contributor.login + " (" + contributor.contributions + ")");
        }
    }
}

指标metrics

默认情况下,feign不会收集任何指标。

但是,可以向任何假客户端添加指标收集功能。

指标功能提供了一流的指标API,用户可以利用该API来深入了解请求/响应生命周期。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-micrometer</artifactId>
    <version>13.0</version>
</dependency>
public class MyApp {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                         .addCapability(new MicrometerCapability())
                         .target(GitHub.class, "https://api.github.com");

    github.contributors("OpenFeign", "feign");
    // metrics will be available from this point onwards
  }
}

Hystrix指标监控

hystrix.execution{event=failure,group=https://api.github.com,key=contributors,terminal=false} throughput=0.033333/s
hystrix.execution{event=fallback_missing,group=https://api.github.com,key=contributors,terminal=true} throughput=0.033333/s
hystrix.execution{event=exception_thrown,group=https://api.github.com,key=contributors,terminal=false} throughput=0.033333/s
hystrix.execution.terminal{group=https://api.github.com,key=contributors} throughput=0.033333/s
hystrix.threadpool.tasks.cumulative.count{key=https://api.github.com,type=completed} throughput=0.016667/s
hystrix.threadpool.tasks.cumulative.count{key=https://api.github.com,type=scheduled} throughput=0.016667/s

客户端

  • OkHttpClient: 实现 SPDY 和更好的网络控制
  • RibbonClient: 智能路由和弹性功能
  • Http2Client(java11): 实现HTTP/2的Java11新客户端
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>13.0</version>
</dependency>

<dependency>
   <groupId>io.github.openfeign</groupId>
   <artifactId>feign-httpclient</artifactId>
</dependency>

OpenFeign默认Http客户端是HttpURLConnection(JDK自带的Http工具),该工具不能配置连接池,生产中使用时性能较差,故我们配置自己的Apache HttpClient连接池。(当然Open Feign也有OkHttp的适配)

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .client(new OkHttpClient())
                     .target(GitHub.class, "https://api.github.com");
  }
}

配合SpringBoot(阶段一)

就是把上面的Bean变为Spring Bean去托管,示例代码如下

@Configuration
public class FeginConfig {

    @Bean
    public Feign.Builder feignBuilder() {
        return Feign.builder()
                .options(new Request.Options(Duration.ofSeconds(2), Duration.ofSeconds(5), true));
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
    public Feign.Builder feignHystrixBuilder() {
        return HystrixFeign
                .builder();
    }

    @Bean
    @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = false)
    public Client feignClient() {
        return new OkHttpClient();
    }
    @Bean
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    public Client feignClient() {
        return new ApacheHttpClient();
    }


    @Bean
    public UserClient UserClient(){
        feignBuilder
                .options(new Request.Options(2000, 5000));
        return feignBuilder.target(UserClient.class, "https://xxxx");
    }


    @Bean
    public OrgClient OrgClient(){
        feignBuilder
                .options(new Request.Options(30000, 50000));
        return feignBuilder.target(OrgClient.class, "https://xxxx");
    }
    
}

配合SpringBoot(阶段二)

  • https://github.com/spring-cloud/spring-cloud-openfeign

参考Spring Cloud Fegin流程如下

  1. 项目加载:在项目的启动阶段,EnableFeignClients 注解扮演了“启动开关”的角色,它使用 Spring 框架的 Import 注解导入了 FeignClientsRegistrar 类,开始了OpenFeign 组件的加载过程。
  2. 扫包FeignClientsRegistrar 负责 FeignClient 接口的加载,它会在指定的包路径下扫描所有的 FeignClients 类,并构造 FeignClientFactoryBean 对象来解析FeignClient 接口。
  3. 解析 FeignClient 注解FeignClientFactoryBean 有两个重要的功能,一个是解析FeignClient 接口中的请求路径和降级函数的配置信息;另一个是触发动态代理的构造过程。其中,动态代理构造是由更下一层的 ReflectiveFeign 完成的。
  4. 构建动态代理对象ReflectiveFeign 包含了 OpenFeign 动态代理的核心逻辑,它主要负责创建出 FeignClient 接口的动态代理对象。ReflectiveFeign 在这个过程中有两个重要任务,一个是解析 FeignClient 接口上各个方法级别的注解,将其中的远程接口URL、接口类型(GET、POST 等)、各个请求参数等封装成元数据,并为每一个方法生成一个对应的 MethodHandler 类作为方法级别的代理;另一个重要任务是将这些MethodHandler 方法代理做进一步封装,通过 Java 标准的动态代理协议,构建一个实现了 InvocationHandler 接口的动态代理对象,并将这个动态代理对象绑定到FeignClient 接口上。这样一来,所有发生在 FeignClient 接口上的调用,最终都会由它背后的动态代理对象来承接。

1.EnableLakerFeignClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(LakerFeignClientsRegistrar.class)
public @interface EnableLakerFeignClients {

    /**
     * Base packages to scan for annotated components.
     * @return base packages
     */
    String[] basePackages() default {};
}

2.LakerFeignClientsRegistrar

public class LakerFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private ResourceLoader resourceLoader;


    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerFeignClients(metadata, registry);

    }


    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Set<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableLakerFeignClients.class.getName());
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(LakerFeignClient.class));
        Set<String> basePackages = getBasePackages(metadata);
        for (String basePackage : basePackages) {
            candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
        }

        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
                // verify annotated class is an interface
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
                Map<String, Object> attributes = annotationMetadata
                        .getAnnotationAttributes(LakerFeignClient.class.getCanonicalName());
                String className = annotationMetadata.getClassName();
                registerFeignClient(className, attributes, registry);
            }
        }
    }


    private void registerFeignClient(String className, Map<String, Object> attributes,
                                     BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(LakerFeignClientFactoryBean.class);
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("type", className);
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setPrimary(false);
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, null);
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }


    private String resolve(String value) {
        if (StringUtils.hasText(value) && this.resourceLoader instanceof ConfigurableApplicationContext) {
            return ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment()
                    .resolvePlaceholders(value);
        }
        return value;
    }

    private String getUrl(Map<String, Object> attributes) {
        String url = resolve((String) attributes.get("url"));
        if (StringUtils.hasText(url)) {
            if (!url.contains("://")) {
                url = "https://" + url;
            }
            try {
                new URL(url);
            } catch (MalformedURLException e) {
                throw new IllegalArgumentException(url + " is malformed", e);
            }
        }
        return url;
    }


    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableLakerFeignClients.class.getCanonicalName());

        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        return basePackages;
    }
}

3.LakerFeignClientFactoryBean

@Data
class LakerFeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    private Class<?> type;

    private String url;


    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.hasText(this.url, "url must be set");
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.applicationContext = context;
    }

    protected Feign.Builder feign() {
        Feign.Builder builder = get(Feign.Builder.class)
                .contract(new SpringContract())
                // required values
                .encoder(get(Encoder.class))
                .decoder(get(Decoder.class));

        // optional values
        Client client = getOptional(Client.class);
        if (client != null) {
            builder.client(client);
        }
        Logger.Level level = getOptional(Logger.Level.class);
        if (level != null) {
            builder.logLevel(level);
        }
        Retryer retryer = getOptional(Retryer.class);
        if (retryer != null) {
            builder.retryer(retryer);
        }
        ErrorDecoder errorDecoder = getOptional(ErrorDecoder.class);
        if (errorDecoder != null) {
            builder.errorDecoder(errorDecoder);
        }
        Request.Options options = getOptional(Request.Options.class);
        if (options != null) {
            builder.options(options);
        }

        Map<String, RequestInterceptor> requestInterceptors = getOptionals(RequestInterceptor.class);
        if (requestInterceptors != null) {
            builder.requestInterceptors(requestInterceptors.values());
        }

        return builder;
    }

    protected <T> T get(Class<T> type) {
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, type).length > 0) {
            return BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, type);
        } else {
            throw new IllegalStateException("No bean found of type " + type);
        }
    }

    protected <T> T getOptional(Class<T> type) {
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, type).length > 0) {
            return BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, type);
        }
        return null;
    }

    protected <T> Map<String, T> getOptionals(Class<T> type) {
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, type).length > 0) {
            return BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, type);
        }
        return null;
    }

    @Override
    public Object getObject() throws Exception {
        return feign().target(type, url);
    }

    @Override
    public Class<?> getObjectType() {
        return this.type;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}

4.LakerFeignClient

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LakerFeignClient {

    String url() default "";
}

5.FeginConfig

@Configuration
public class FeginConfig {

    @Bean
    public Feign.Builder feignBuilder() {
        return Feign.builder()
                .options(new Request.Options(Duration.ofSeconds(2), Duration.ofSeconds(5), true));
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
    public Feign.Builder feignHystrixBuilder() {
        return HystrixFeign
                .builder();
    }

    @Bean
    @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
    public Client feignClient() {
        return new OkHttpClient();
    }

    @Bean
    public Encoder encoder() {
        return new JacksonEncoder();
    }
    @Bean
    public Decoder decoder() {
        return new JacksonDecoder();
    }
}

使用示例

// 1.启用 EnableLakerFeignClients
@SpringBootApplication
@EnableLakerFeignClients(basePackages = "com.laker.admin")
public class EasyAdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(EasyAdminApplication.class, args);
    }
    
// 或者
@Configuration
@EnableLakerFeignClients(basePackages = "com.laker.admin")
public class FeginConfig {
}
// 2.定义接口
@LakerFeignClient(url ="https://api.github.com")
public interface GitHub {
  @GetMapping("/repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
  @PostMapping("/repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @PathVariable("owner") String owner, @PathVariable("repo") String repo);
}
// 3.调用示例
@Autowired
GitHub gitHub;
    
List<Contributor>  contributors = gitHub.contributors("lakernote","easy-admin");

参考

  • https://www.infoq.cn/article/c9rk1mg0erk5mfqps4wz

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1128063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spring cloud Eureka集群模式搭建(IDEA中运行)《一》

spring cloud Eureka集群模式搭建&#xff08;IDEA中运行&#xff09; 新建springboot 工程工程整体目录配置文件IDEA中部署以jar包形式启动总结 新建springboot 工程 新建一个springboot 工程&#xff0c;命名为&#xff1a;eureka_server。 其中pom.xml文件为&#xff1a; …

Mask Free VIS笔记(CVPR2023 不需要mask标注的实例分割)

paper: Mask-Free Video Instance Segmentation github 一般模型学instance segmentation都是要有mask标注的&#xff0c; 不过mask标注既耗时又枯燥&#xff0c;所以paper中仅用目标框的标注来实现实例分割。 主要针对视频的实例分割。 之前也有box-supervised实例分割&…

去除QPushButton边框上的白点

使用border:3px solid #35FFFAF0; 出现上面一行border上白点。 使用border:3px solid rgb(89,87,84); 没有白点。

1.java环境搭建与eclipse安装和配置

JDK&#xff08;JAVA开发工具包&#xff09;&#xff1a;提供给java开发人员使用的&#xff0c;其中包含了java的开发工具&#xff0c;也包括了JRE所以安装了JDK,就不用单独安装JTE了&#xff0c;其中的开发工具&#xff1a;编译工具(javac.exe) 打包工具(jar.exe)等JRE(JAVA运…

什么年代了还在手工写接口测试文档吗?

01 前言 接口文档&#xff0c;顾名思义就是对接口说明的文档。好的接口文档包含了对接口URL&#xff0c;参数以及输出内容的说明&#xff0c;我们参照接口文档就能编写出一个个的测试用例。而且接口文档详细的话&#xff0c;测试用例编写起来就会比较简单&#xff0c;不容易…

MES 漫谈123

我们从Know-How出发 Know&#xff1a;什么是 MES 制造执行系统MES是一套工具&#xff0c;旨在支持产品达到预期的质量、安全和合规水平&#xff0c;以及生产的预期性能水平。MES是支持工厂质量标准和企业卓越运营计划的关键要素。在工厂层面&#xff0c;MES不是通过“最后一天…

Telegram 引入了国产小程序容器技术

Telegram 宣布为其开发者提供了一项“能够在 App 中运行迷你应用”的新功能&#xff08; 迷你应用即 Mini App&#xff0c;下文中以“小程序”代替&#xff09;。 在一篇博客文章中&#xff0c;Telegram 的开发者写到“小程序提供了可替代互联网网站的灵活界面&#xff08;cre…

DataX 数据迁移

1、前期准备 Linux系统 Python&#xff08;最好是2&#xff09; Jdk 1.8以上 2、安装Python2 --更新软件包 sudo apt update --安装python2 sudo apt install python2 --查看python版本 python2 --version 3、下载DataX Linux下载DataX wget http://datax-opensource.o…

攻防世界-Ph0en1x-100

第一次独立使用frida解安卓题&#xff0c;没分析代码 Steps 使用jadx打开apk分析主要代码 最主要的就是这个if判断了&#xff0c;安装apk后&#xff0c;有一个输入框和一个check按钮&#xff0c;会根据输入的结果Toast&#xff1a;Success or Failed。 getSecret(getFlag()).eq…

深入了解JavaScript中的AJAX和HTTP请求

在现代Web开发中&#xff0c;AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;和HTTP请求被广泛应用于实现动态交互式网页。本文将深入探讨AJAX的概念、工作原理以及使用方法。 什么是AJAX&#xff1f; AJAX是一种利用JavaScript和HTTP请求与服务器进行异步通信的…

基于公开数据集,5 分钟生成个性可视化数据报告

云布道师 简介&#xff1a; 本次活动将基于内置电商、广告、出行、汽车、国内生产总值等公开数据集&#xff0c;通过DataWorks 与 MaxCompute 搭建可视化数据报告。 活动时间 2023 年 10 月 8 日-2023 年 11 月 10 日 参赛者首先前往参赛页面领取产品免费资源&#xff0c;依…

Redis基于布隆过滤器解决缓存穿透问题(15)

Redis基于布隆过滤器解决缓存穿透问题 1.布隆过滤器基本介绍2.布隆过滤器的优缺点3.布隆过滤器的原理4.缓存穿透问题5.解决Redis缓存穿透问题 1.布隆过滤器基本介绍 布隆过滤器适用于判断某个数据是否在集合中存在&#xff0c;可能存在一定的误判&#xff0c; Bloom Filter基本…

教育行业如何通过互联网推广品牌?媒介盒子告诉你

近年来&#xff0c;国民对教育的重视程度日趋上升&#xff0c;教育行业也日益壮大&#xff0c;数字化时代的来临也使教育行业推广品牌的方式更加多样化&#xff0c;接下来媒介盒子就和大家分享&#xff1a;教育行业如何通过互联网推广品牌。 一、 发布软文进行品牌推广 数字…

docker-compose安装ES7.14和Kibana7.14(有账号密码)

一、docker-compose安装ES7.14.0和kibana7.14.0 1、下载镜像 1.1、ES镜像 docker pull elasticsearch:7.14.0 1.2、kibana镜像 docker pull kibana:7.14.0 2、docker-compose安装ES和kibana 2.1、创建配置文件目录和文件 #创建目录 mkdir -p /home/es-kibana/config mkdir…

“它经济”盛行,宠物食品行业如何做好口碑营销

口碑营销能够为企业带来潜在优势&#xff0c;让企业实现可持续发展&#xff0c;好的口碑能够提升品牌的传播速度&#xff0c;作为宠物食品行业&#xff0c;更需要营造良好口碑&#xff0c;才能获得源源不断的客户&#xff0c;那么如何打造口碑呢&#xff1f;数字化时代下&#…

前端请求后台接口失败处理逻辑

前后分离项目&#xff0c;前端为uni-app&#xff08;vue2&#xff09;&#xff0c;后台为java 后台api设置存在问题&#xff0c;部分公共接口为开放非登录用户访问权限 导致前台打开首页后立即跳转到登录提示页 怀疑是开了uni-app开发代理服务器&#xff0c;导致访问的代理服务…

openGaussDatakit让运维如丝般顺滑!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

全国三维数字化创新设计大赛湖北赛区省赛成功举办

须弥芥子&#xff0c;数字如海。10月14日—15日&#xff0c;2023 年数字科技文化节——第16届全国三维数字化创新设计大赛湖北赛区省赛暨产教联合体大会在武汉软件工程职业学院成功举行。 &#xff08;大赛全体专家领导合影&#xff09; 全国三维数字化创新设计大赛组委会副秘…

《论文阅读》LORA:大型语言模型的低秩自适应 2021

《论文阅读》LORA: LOW-RANK ADAPTATION OF LARGE LAN-GUAGE MODELS 前言简介现有方法模型架构优点前言 今天为大家带来的是《LORA: LOW-RANK ADAPTATION OF LARGE LAN-GUAGE MODELS》 出版: 时间:2021年10月16日 类型:大语言模型的微调方法 关键词: 作者:Edward Hu,…

大型电商系统的订单设计

前言&#xff1a;电商系统需要满足商品、订单、支付、会员、优惠券、秒杀、拼团、砍价、分销、积分等多种经营需求。其中订单模块是比较核心复杂的&#xff0c;需要架构师在上面下不少功夫。 0、电商系统业务架构图 电商系统&#xff0c;一般包括前台商城系统及后台管理系统&am…