文章目录
- 简介
- 原生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();
@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 范围内的响应都将触发ErrorDecoder
sdecode
方法,允许您处理响应、将失败包装到自定义异常中或执行任何其他处理。如果您想再次重试请求,请抛出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使用
设置超时等
- 熔断器配置:你可以设置熔断器的相关属性,如
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 {
@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流程如下:
- 项目加载:在项目的启动阶段,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;
@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