如何系列 如何玩转远程调用之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
功能图

架构图

依赖
<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使用
设置超时等
- 熔断器配置:你可以设置熔断器的相关属性,如
circuitBreakerErrorThresholdPercentage、circuitBreakerSleepWindowInMilliseconds、circuitBreakerRequestVolumeThreshold等,以控制熔断器的行为。 - 线程池配置:如果你在 Hystrix 命令中使用了线程池隔离,你可以设置线程池的相关属性,如
coreSize、maxQueueSize、keepAliveTimeMinutes等。 - 超时属性:除了设置总的执行超时时间,你还可以设置
executionTimeoutEnabled、executionIsolationStrategy等超时相关属性。 - 命令名和组名:你可以自定义命令的名称和分组名称,通过
andCommandKey和andCommandGroup方法来设置。 - 并发属性:你可以设置命令执行的并发性相关属性,如
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流程如下:
- 项目加载:在项目的启动阶段,EnableFeignClients 注解扮演了“启动开关”的角色,它使用 Spring 框架的 Import 注解导入了 FeignClientsRegistrar 类,开始了OpenFeign 组件的加载过程。
- 扫包:FeignClientsRegistrar 负责 FeignClient 接口的加载,它会在指定的包路径下扫描所有的 FeignClients 类,并构造 FeignClientFactoryBean 对象来解析FeignClient 接口。
- 解析 FeignClient 注解:FeignClientFactoryBean 有两个重要的功能,一个是解析FeignClient 接口中的请求路径和降级函数的配置信息;另一个是触发动态代理的构造过程。其中,动态代理构造是由更下一层的 ReflectiveFeign 完成的。
- 构建动态代理对象: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
相关文章:
如何系列 如何玩转远程调用之OpenFegin+SpringBoot(非Cloud)
文章目录 简介原生Fegin示例基础契约日志重试编码器/解码器自定义解码器 请求拦截器响应拦截器表单文件上传支持错误解码器断路器指标metrics客户端 配合SpringBoot(阶段一)配合SpringBoot(阶段二)1.EnableLakerFeignClients2.Lak…...
Python学习第2天-安装pycharm
文章目录 前言一、下载二、安装1.选择安装目录2.安装配置 总结 前言 好用的工具可以极大地提高生产力,开发Python推荐使用jetbrains全家桶的pycharm。 一、下载 通过官网下载安装包。 二、安装 1.选择安装目录 2.安装配置 一路Next,安装完成 总结 …...
等电位连接器行业应用综合方案
等电位连接器的原理 等电位连接器的原理是利用气体放电管或压敏电阻等非线性元件,当连接器两端的电位差大于所限峰值电压时,连接器导通,迫使连接器两端不同接地体电位基本相等,消除接地体间放电现象,从而避免了由于地…...
内裤洗衣机有用吗?最好用的四款内衣洗衣机测评
相信很多小伙伴往往会因为懒而不想洗内衣,又或者洗内衣时经常会洗不干净!这时就很有必要入手一台内衣洗衣机了,当我们洗完澡时,顺手把内衣放入洗衣机内,一键启动即可把我们的内衣洗得干干净净!同时还可以为…...
足底筋膜炎能自愈吗
什么是足底筋膜炎 足底筋膜炎是足底的肌腱或者筋膜发生无菌性炎症所致。最常见症状是脚跟的疼痛与不适,压痛点常在足底近足跟处,有时压痛较剧烈,且持续存在。晨起时疼痛感觉明显,行走过度时疼痛感加剧,严重患者甚至站…...
牛客网刷题-(3)
🌈write in front🌈 🧸大家好,我是Aileen🧸.希望你看完之后,能对你有所帮助,不足请指正!共同学习交流. 🆔本文由Aileen_0v0🧸 原创 CSDN首发🐒 如…...
Centos7 安装 Etcd
Github上下载并解压安装包 wget https://github.com/coreos/etcd/releases/download/v3.4.10/etcd-v3.4.10-linux-amd64.tar.gz tar xzvf etcd-v3.4.10-linux-amd64.tar.gz mv etcd-v3.4.10-linux-amd64 /opt/etcd解压后是一些文档和两个二进制文件etcd和etcdctl。etcd是serve…...
powerjob基于springboot2.1.6.RELEASE版本的问题研究
项目背景:基于第三代框架的集成问题,如果对于powerjob不熟悉的朋友,可以参考官方文档PowerJob 简介 语雀 关于语雀 23 日故障的公告 (qq.com) 简单插一句,针对语雀文档故障的心得,数据恢复,完整性&#…...
【AI视野·今日CV 计算机视觉论文速览 第270期】Wed, 18 Oct 2023
AI视野今日CS.CV 计算机视觉论文速览 Wed, 18 Oct 2023 Totally 60 papers 👉上期速览✈更多精彩请移步主页 Daily Computer Vision Papers 4K4D: Real-Time 4D View Synthesis at 4K Resolution Authors Zhen Xu, Sida Peng, Haotong Lin, Guangzhao He, Jiaming …...
uni-app小程序,uview-ui组件样式无法穿透修改的解决办法
1.首先设置以下选项.该选项的作用是让微信小程序允许样式穿透. 在需要改动的文件内加上 options: { styleIsolation: shared } 2.然后再使用vue的样式穿透写法. ::v-deep .类样式{} 或者 /deep/ .类样式{}...
Codeforces Round 515
Portal. C. Books Queries Portal. sol. D. Boxes Packing Portal. 把从左至右删物品转化为从右至左加物品。模拟即可。 #include <bits/stdc.h> using namespace std;const int maxn2e55; int a[maxn];int main() {int n,m,k;cin>>n>>m>>k;for(…...
Linux shell编程学习笔记15:定义数组、获取数组元素值和长度
一、 Linux shell 脚本编程中的数组概述 数组是一种常见的数据结构。跟大多数编程语言一样,大多数Linux shell脚本支持数组,但对数组的支持程度各不相同,比如数组的维度,是支持一维数组还是多维数组?再如,…...
k8s部署kafka,并使用zookeeper做注册中心
kafka在3.x版本后增加KRaft作为自己的注册中心,可以不依赖外部的zk;这里上一篇已经部署好了zk,kafka依然使用zk作为注册中心。 这里使用kafka是为集成zipkin收发微服务接口链路日志数据,只需要部署1个实列即可够用。 编写脚本yam…...
关于Nginx缓存
Nginx缓存 一般情况下系统用到的缓存有三种 服务端缓存: 缓存存在后端服务器,如redis代理缓存: 缓存存储在代理服务器或中间件,内容从后端服务器获取,保存在本地客户端缓存: 缓存在浏览器什么时候会出现3…...
为什么Open3D可视化TensorFlow张量速度超慢
问题描述 在使用Open3D可视化TensorFlow张量表示的点云时速度超慢 原因分析 可能是因为Open3D没有针对tf.Tensor做优化,也可能是tf.Tensor本身没有对张量的操作做优化,所以可能如果要在CPU中计算,numpy可能性能更好。 解决方案 open3d.u…...
使用element-UI Cascader组件,实现第一级单选选,第二级,第三级,子级可以多选
最近开发过程中,遇到需求测一个需求,就是级联选择器,需要多选;但是第一级是单选; 既要单选又要复选。参照网上内容,自己整理了一下功能实现; 如下图: 思路:1.把第一层的…...
防止消息丢失与消息重复——Kafka可靠性分析及优化实践
系列文章目录 上手第一关,手把手教你安装kafka与可视化工具kafka-eagle Kafka是什么,以及如何使用SpringBoot对接Kafka 架构必备能力——kafka的选型对比及应用场景 Kafka存取原理与实现分析,打破面试难关 防止消息丢失与消息重复——Kafka可…...
【Linux】Linux中Crontab(定时任务)命令详解及使用教程
文章目录 前言1.使用yum命令安装Crontab:2.查看Crontab状态:3.添加定时任务:4.查看任务列表:5.Crontab相关命令:6.部分脚本无法执行问题:7.Crontab默认调度任务:8.注意清理系统用户的邮件日志&a…...
计算机毕设 flink大数据淘宝用户行为数据实时分析与可视化
文章目录 0 前言1、环境准备1.1 flink 下载相关 jar 包1.2 生成 kafka 数据1.3 开发前的三个小 tip 2、flink-sql 客户端编写运行 sql2.1 创建 kafka 数据源表2.2 指标统计:每小时成交量2.2.1 创建 es 结果表, 存放每小时的成交量2.2.2 执行 sql &#x…...
8.2 矢量图层点要素单一符号使用一
文章目录 前言单一符号(Single symbol)渲染简单标记(Simple Marker)QGis代码实现 SVG标记(SVG marker)QGis代码实现 总结 前言 上一篇教程对矢量图层符号化做了一个整体介绍,并以点图层为例介绍了可以使用的渲染器&am…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
