From c4cf557e30b6f82917aeb53fa634db0267cfc8d3 Mon Sep 17 00:00:00 2001 From: DerekYRC <15521077528@163.com> Date: Sun, 10 Apr 2022 20:35:26 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90Feign=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mini-spring-cloud-openfeign/pom.xml | 5 + .../openfeign/FeignAutoConfiguration.java | 17 +++ .../openfeign/FeignClientFactoryBean.java | 19 ++- .../openfeign/FeignClientSpecification.java | 55 +++++++++ .../openfeign/FeignClientsConfiguration.java | 46 +++++++ .../github/cloud/openfeign/FeignContext.java | 16 +++ .../ribbon/LoadBalancerFeignClient.java | 112 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 2 + 8 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignAutoConfiguration.java create mode 100644 mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientSpecification.java create mode 100644 mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientsConfiguration.java create mode 100644 mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignContext.java create mode 100644 mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/ribbon/LoadBalancerFeignClient.java create mode 100644 mini-spring-cloud-openfeign/src/main/resources/META-INF/spring.factories diff --git a/mini-spring-cloud-openfeign/pom.xml b/mini-spring-cloud-openfeign/pom.xml index 99a980a..c2146ea 100644 --- a/mini-spring-cloud-openfeign/pom.xml +++ b/mini-spring-cloud-openfeign/pom.xml @@ -60,6 +60,11 @@ org.springframework.boot spring-boot-starter-web + + + cn.hutool + hutool-all + \ No newline at end of file diff --git a/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignAutoConfiguration.java b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignAutoConfiguration.java new file mode 100644 index 0000000..64b88b8 --- /dev/null +++ b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignAutoConfiguration.java @@ -0,0 +1,17 @@ +package com.github.cloud.openfeign; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author derek(易仁川) + * @date 2022/4/9 + */ +@Configuration +public class FeignAutoConfiguration { + + @Bean + public FeignContext feignContext() { + return new FeignContext(); + } +} diff --git a/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientFactoryBean.java b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientFactoryBean.java index dd9a0a5..5aa76cc 100644 --- a/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientFactoryBean.java +++ b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientFactoryBean.java @@ -1,7 +1,11 @@ package com.github.cloud.openfeign; -import com.github.cloud.openfeign.support.SpringMvcContract; +import feign.Client; +import feign.Contract; import feign.Feign; +import feign.Target.HardCodedTarget; +import feign.codec.Decoder; +import feign.codec.Encoder; import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.ApplicationContext; @@ -23,9 +27,18 @@ public class FeignClientFactoryBean implements FactoryBean, ApplicationC @Override public Object getObject() throws Exception { + FeignContext feignContext = applicationContext.getBean(FeignContext.class); + Encoder encoder = feignContext.getInstance(contextId, Encoder.class); + Decoder decoder = feignContext.getInstance(contextId, Decoder.class); + Contract contract = feignContext.getInstance(contextId, Contract.class); + Client client = feignContext.getInstance(contextId, Client.class); + return Feign.builder() - .contract(new SpringMvcContract()) - .target(type, "http://localhost:1234"); + .encoder(encoder) + .decoder(decoder) + .contract(contract) + .client(client) + .target(new HardCodedTarget<>(type, contextId, "http://" + contextId)); } @Override diff --git a/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientSpecification.java b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientSpecification.java new file mode 100644 index 0000000..93427ff --- /dev/null +++ b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientSpecification.java @@ -0,0 +1,55 @@ +package com.github.cloud.openfeign; + +import org.springframework.cloud.context.named.NamedContextFactory; + +import java.util.Arrays; +import java.util.Objects; + +/** + * @author derek(易仁川) + * @date 2022/4/9 + */ +public class FeignClientSpecification implements NamedContextFactory.Specification { + + private String name; + + private Class[] configuration; + + public FeignClientSpecification(String name, Class[] configuration) { + this.name = name; + this.configuration = configuration; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public Class[] getConfiguration() { + return configuration; + } + + public void setConfiguration(Class[] configuration) { + this.configuration = configuration; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FeignClientSpecification that = (FeignClientSpecification) o; + return name.equals(that.name) && Arrays.equals(configuration, that.configuration); + } + + @Override + public int hashCode() { + int result = Objects.hash(name); + result = 31 * result + Arrays.hashCode(configuration); + return result; + } +} diff --git a/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientsConfiguration.java b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientsConfiguration.java new file mode 100644 index 0000000..f85e312 --- /dev/null +++ b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignClientsConfiguration.java @@ -0,0 +1,46 @@ +package com.github.cloud.openfeign; + +import com.github.cloud.openfeign.ribbon.LoadBalancerFeignClient; +import com.github.cloud.openfeign.support.SpringMvcContract; +import feign.Client; +import feign.Contract; +import feign.codec.Decoder; +import feign.codec.Encoder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 配置feign的核心API + * + * @author derek(易仁川) + * @date 2022/4/9 + */ +@Configuration +public class FeignClientsConfiguration { + + @Bean + @ConditionalOnMissingBean + public Encoder encoder() { + return new Encoder.Default(); + } + + @Bean + @ConditionalOnMissingBean + public Decoder decoder() { + return new Decoder.Default(); + } + + @Bean + @ConditionalOnMissingBean + public Contract contract() { + return new SpringMvcContract(); + } + + @Bean + @ConditionalOnMissingBean + public Client client(LoadBalancerClient loadBalancerClient) { + return new LoadBalancerFeignClient(loadBalancerClient, new Client.Default(null, null)); + } +} diff --git a/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignContext.java b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignContext.java new file mode 100644 index 0000000..8b2b8f8 --- /dev/null +++ b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/FeignContext.java @@ -0,0 +1,16 @@ +package com.github.cloud.openfeign; + +import org.springframework.cloud.context.named.NamedContextFactory; + +/** + * 为每个feign客户端创建一个应用上下文(ApplicationContext),隔离每个feign客户端的配置 + * + * @author derek(易仁川) + * @date 2022/4/9 + */ +public class FeignContext extends NamedContextFactory { + + public FeignContext() { + super(FeignClientsConfiguration.class, "feign", "feign.client.name"); + } +} diff --git a/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/ribbon/LoadBalancerFeignClient.java b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/ribbon/LoadBalancerFeignClient.java new file mode 100644 index 0000000..c347068 --- /dev/null +++ b/mini-spring-cloud-openfeign/src/main/java/com/github/cloud/openfeign/ribbon/LoadBalancerFeignClient.java @@ -0,0 +1,112 @@ +package com.github.cloud.openfeign.ribbon; + +import com.netflix.client.ClientException; +import com.netflix.client.ClientRequest; +import com.netflix.client.IResponse; +import feign.Client; +import feign.Request; +import feign.Response; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * 具备负载均衡能力的feign client + * + * @author derek(易仁川) + * @date 2022/4/9 + */ +public class LoadBalancerFeignClient implements Client { + + private LoadBalancerClient loadBalancerClient; + + private Client delegate; + + public LoadBalancerFeignClient(LoadBalancerClient loadBalancerClient, Client delegate) { + this.loadBalancerClient = loadBalancerClient; + this.delegate = delegate; + } + + @Override + public Response execute(Request request, Request.Options options) throws IOException { + //客户端负载均衡 + URI original = URI.create(request.url()); + String serviceId = original.getHost(); + //选择服务实例 + ServiceInstance serviceInstance = loadBalancerClient.choose(serviceId); + //重建请求URI + URI uri = loadBalancerClient.reconstructURI(serviceInstance, original); + + Response response = delegate.execute(new RibbonRequest(request, uri).toRequest(), options); + return new RibbonResponse(uri, response).getResponse(); + } + + private static class RibbonRequest extends ClientRequest { + + private Request request; + + public RibbonRequest(Request request, URI uri) { + this.request = request; + setUri(uri); + } + + @SuppressWarnings("deprecation") + private Request toRequest() { + return Request.create(request.httpMethod(), getUri().toASCIIString(), new HashMap<>(), + request.body(), StandardCharsets.UTF_8); + } + } + + private static class RibbonResponse implements IResponse { + + private final URI uri; + + private final Response response; + + protected RibbonResponse(URI uri, Response response) { + this.uri = uri; + this.response = response; + } + + @Override + public Object getPayload() throws ClientException { + return response.body(); + } + + @Override + public boolean hasPayload() { + return response.body() != null; + } + + @Override + public boolean isSuccess() { + return response.status() == 200; + } + + @Override + public URI getRequestedURI() { + return uri; + } + + @Override + public Map getHeaders() { + return new HashMap<>(); + } + + @Override + public void close() throws IOException { + if (response != null && response.body() != null) { + response.body().close(); + } + } + + public Response getResponse() { + return response; + } + } +} diff --git a/mini-spring-cloud-openfeign/src/main/resources/META-INF/spring.factories b/mini-spring-cloud-openfeign/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..cc5e1f6 --- /dev/null +++ b/mini-spring-cloud-openfeign/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.github.cloud.openfeign.FeignAutoConfiguration \ No newline at end of file