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

文章目录

    • 简介
    • 原生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();@Overridepublic Exception decode(String methodKey, Response response) {// wrapper 401 to RetryableException in order to retryif (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;}@Overridepublic 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 requeste.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 worksprivate String getNewToken() {return "newToken";}@Overridepublic 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 {@Overridepublic 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 redirectionApi 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> {@Overridepublic GitHub create(Throwable cause) {return new GitHub() {@Overridepublic 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 {@Beanpublic 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();}@Beanpublic UserClient UserClient(){feignBuilder.options(new Request.Options(2000, 5000));return feignBuilder.target(UserClient.class, "https://xxxx");}@Beanpublic 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;@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}@Overridepublic 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 interfaceAnnotationMetadata 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) {@Overrideprotected 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;@Overridepublic void afterPropertiesSet() throws Exception {Assert.hasText(this.url, "url must be set");}@Overridepublic 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 valuesClient 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;}@Overridepublic Object getObject() throws Exception {return feign().target(type, url);}@Overridepublic Class<?> getObjectType() {return this.type;}@Overridepublic 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 {@Beanpublic 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();}@Beanpublic Encoder encoder() {return new JacksonEncoder();}@Beanpublic 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.mzph.cn/news/117005.shtml

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

相关文章

初识Kubernetes

一、k8s的由来及其技术运用 1.1 k8s的简介 Kubernetes&#xff0c;词根源于希腊语的 舵手、飞行员。在国内又称k8s&#xff08;因为k和s之间有8个字母&#xff0c;所以得名。“国内程序员的幽默”&#xff09;。用于自动部署、扩展和管理“容器化&#xff08;containerized…

【软件安装】Linux系统中安装MySQL数据库服务

这篇文章&#xff0c;主要介绍如何在Linux系统中安装MySQL数据库服务。 目录 一、Linux安装MySQL 1.1、下载MySQL安装包 1.2、解压MySQL安装包 1.3、更改存放目录 1.4、创建用户组和用户 1.5、创建数据目录data 1.6、创建my.cnf配置文件 1.7、初始化数据库 1.8、添加m…

云计算要学习哪些技术?

学习云计算需要涉及多个技术领域和相关的工具、平台和框架。以下是一个详细的介绍&#xff0c;帮助您了解学习云计算所需的技术。 1. 虚拟化技术 虚拟化是云计算的基础&#xff0c;因此了解虚拟化技术至关重要。学习虚拟化技术时&#xff0c;需要掌握以下知识点&#xff1a; …

C# 使用 LibUsbDotNet 实现 USB 设备检测

国庆节回来后的工作内容&#xff0c;基本都在围绕着各种各样的硬件展开&#xff0c;这无疑让本就漫长的 “七天班” &#xff0c;更加平添了三分枯燥&#xff0c;我甚至在不知不觉中学会了&#xff0c;如何给打印机装上不同尺寸的纸张。华为的 Mate 60 发布以后&#xff0c;人群…

RK3568平台开发系列讲解(应用篇)串口应用编程之串口介绍

🚀返回专栏总目录 文章目录 一、串口介绍1.1、数据传输方式1.2、数据格式1.3、波特率1.4、硬件流控制和软件流控制1.5、错误检测1.6、串口编程二、串口设备节点介绍沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 串口设备是嵌入式开发中最常用的外设之一,通过串口…

图论01-【无权无向】-图的基本表示-邻接矩阵/邻接表

文章目录 1. 代码仓库2. 图的基本表示的比较3. 邻接矩阵&#xff1a;Array和TreeSet3.1 图示3.2 Array主要代码解析3.3 测试输出3.4 使用TreeSet的代码 4. 邻接表&#xff1a;LinkedList4.1 图示4.2 LinkedList主要代码解析4.3 测试输出 5. 完整代码5.1 邻接表 - Array5.2 邻接…

数据库MongoDB

MongoDB记录是一个文档&#xff0c;由一个字段和值对组成的数据结构&#xff0c;文档类似于JSON对象。 一个文档认为就是一个对象&#xff0c;字段的数据类型是字符型&#xff0c;值除了使用基本类型外&#xff0c;还可以包括其他文档&#xff0c;普通数组和文档数组。 一、…

rust学习——方法 Method

文章目录 方法 Method定义方法self、&self 和 &mut self方法名跟结构体字段名相同 带有多个参数的方法关联函数多个 impl 定义为枚举实现方法 rust 结构体与枚举的区别回答1回答2 方法 Method 从面向对象语言过来的同学对于方法肯定不陌生&#xff0c;class 里面就充斥…

【proteus】8086仿真/汇编:创建项目并添加汇编代码文件

1.创建好新项目 2.点击source code 弹出VSM 3. 4.注意两个都不勾选 可以看到schematic有原理图出现 5. 再次点击source code 6.project/project settings&#xff0c;取消勾选embed 7. add 8.输入文件名保存后&#xff1a; 注意&#xff1a;proteus不用写dos的相关语句 。

C++第一篇--关键字以及命名空间

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 &#x1f384; 前言 …

31一维信号滤波(限幅滤波、中值滤波、均值滤波、递推平均滤波),MATLAB程序已调通,可直接运行。

一维信号滤波&#xff08;限幅滤波、中值滤波、均值滤波、递推平均滤波&#xff09;&#xff0c;MATLAB程序已调通&#xff0c;可直接运行。 31matlab、中值滤波、信号处理 (xiaohongshu.com)

螺旋矩阵[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个m行n列的矩阵matrix&#xff0c;请按照顺时针螺旋顺序&#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 示例 2&#xf…

性能优化:JIT即时编译与AOT提前编译

优质博文&#xff1a;IT-BLOG-CN 一、简介 JIT与AOT的区别&#xff1a; 两种不同的编译方式&#xff0c;主要区别在于是否处于运行时进行编译。 JIT:Just-in-time动态(即时)编译&#xff0c;边运行边编译&#xff1a;在程序运行时&#xff0c;根据算法计算出热点代码&#xf…

【题解 单调队列优化dp】 简单的加法乘法计算题

题目描述&#xff1a; 分析&#xff1a; 由于对于每一步而言&#xff0c;我们都需要的是最小步数 所以我们很显然的可以写出一个dp方程&#xff1a; 设 f [ i ] f[i] f[i]表示达到i时的最小步数 我们有两种操作&#xff0c;也就是说我们可以通过一下两种方式转移过来&#xff…

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET “/**“

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET "/**" 问题发现问题解决 问题发现 WebTestClient 是 Spring WebFlux 框架中提供的用于测试 Web 请求的客户端工具。它可以不用启动服务器&#xff0c;模拟发送 HTTP 请求并验证服务器的响…

力扣刷题 day54:10-24

1.十进制整数的反码 每个非负整数 N 都有其二进制表示。例如&#xff0c; 5 可以被表示为二进制 "101"&#xff0c;11 可以用二进制 "1011" 表示&#xff0c;依此类推。注意&#xff0c;除 N 0 外&#xff0c;任何二进制表示中都不含前导零。 二进制的反…

CPU眼里的C/C++:1.2 查看变量和函数在内存中的存储位置

写一个很简单的 c 代码&#xff0c;打印一些“地址”&#xff0c; 也就是变量、函数的“存储位置”&#xff1a;当程序被加载到内存后&#xff0c;它们具体是存在哪里&#xff0c;可以用精确的数值来表示&#xff0c;这就是内存地址。 https://godbolt.org/z/Ghh9ThY5Y #inc…

Java基础篇 | Java8流式编程

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色专栏&#xf…

自然语言处理---Transformer模型

Transformer概述 相比LSTM和GRU模型&#xff0c;Transformer模型有两个显著的优势&#xff1a; Transformer能够利用分布式GPU进行并行训练&#xff0c;提升模型训练效率。 在分析预测更长的文本时&#xff0c;捕捉间隔较长的语义关联效果更好。 Transformer模型的作用 基于seq…

Ai写作创作系统ChatGPT网站源码+图文搭建教程+支持GPT4.0+支持ai绘画(Midjourney)/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…