OpenFeign是什么?
OpenFeign是一个声明式的WebService客户端。使用OpenFeign能让编写Web Service客户端更加简单。使用时只需定义服务接口,然后在上面添加注解。OpenFeign也支持可拔插式的编码和解码器。spring cloud对feign进行了封装,使其支持MVC注解和HttpMessageConverts。和 eureka(服务注册中心)及 ribbon 组合可以实现负载均衡。在Spring Cloud中使用OpenFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样简单,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求,非常的方便。
OpenFeign能干啥?
- OpenFeign的设计宗旨是简化Java Http客户端的开发。Feign 在 RestTemplate 的基础上做了进一步的封装,由其来帮助我们定义和实现依赖服务接口。在OpenFeign的协助下,我们只需创建一个接口并使用注解的方式进行配置(类似于Dao接口上面的Mapper注解)即可完成对服务提供方的接口绑定,大大简化了调用客户端的开发量。
- OpenFeign集成了Ribbon,利用ribbon维护了服务列表,并且通过ribbon实现了客户端的负载均衡。与ribbon不同的是,通过OpenFeign只需要定义服务绑定接口,且以声明式的方法,优雅而简单的实现了服务调用。
客户端使用
添加 maven 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
代码示例
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Feign;
import feign.FeignException;
import feign.RequestLine;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import lombok.Data;
import org.springframework.core.ResolvableType;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* 测试Feign发送HTTP请求
*/
public class TestFeignClient {
public static void main(String[] args) {
DujitangClient client = Feign.builder()
.decoder(new MyDecoder())
.target(DujitangClient.class, "https://api.nextrt.com");
TauntResponse response = client.queryDujitang();
System.out.println(response);
}
static class MyDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
return new ObjectMapper().readValue(response.body().asInputStream(), ResolvableType.forType(type).getRawClass());
}
}
interface DujitangClient {
@RequestLine("GET /api/dutang")
TauntResponse queryDujitang();
}
@Data
public static class TauntResponse {
private String status;
private String msg;
private TauntInfo data;
private Long timestamp;
}
@Data
public static class TauntInfo {
private Integer id;
private String content;
}
}
默认使用 feign.Client.Default 客户端,底层为 java 中的 HttpURLConnection。
整合SpringMVC
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Feign;
import feign.FeignException;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import lombok.Data;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.core.ResolvableType;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* 测试Feign发送HTTP请求
*/
public class TestSpringFeignClient {
public static void main(String[] args) {
DujitangClient client = Feign.builder()
.decoder(new MyDecoder())
.contract(new SpringMvcContract())
.target(DujitangClient.class, "https://api.nextrt.com");
TauntResponse response = client.queryDujitang();
System.out.println(response);
}
static class MyDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
return new ObjectMapper().readValue(response.body().asInputStream(), ResolvableType.forType(type).getRawClass());
}
}
interface DujitangClient {
@RequestMapping("/api/dutang")
TauntResponse queryDujitang();
}
@Data
public static class TauntResponse {
private String status;
private String msg;
private TauntInfo data;
private Long timestamp;
}
@Data
public static class TauntInfo {
private Integer id;
private String content;
}
}
SpringMvcContract 可以支持 @RequestMapping,@PathVariable 等注解。
整合SpringBoot及OkHttp
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-ribbon</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
@EnableFeignClients
@SpringBootApplication
public class CnblogsBackUpApplication {
public static void main(String[] args) {
SpringApplication.run(CnblogsBackUpApplication.class, args);
}
}
public class FeignDefaultConfiguration {
@Bean
public Client feignClient() {
return new OkHttpClient(OkHttpUtility.getOkHttpClientInstance());
}
private static class OkHttpUtility {
private static final okhttp3.OkHttpClient okHttpClient;
public static okhttp3.OkHttpClient getOkHttpClientInstance() {
return okHttpClient;
}
static {
okHttpClient = new okhttp3.OkHttpClient.Builder()
.connectionPool(new ConnectionPool(50, 5L, TimeUnit.MINUTES))
.connectTimeout(30L, TimeUnit.SECONDS).readTimeout(30L, TimeUnit.SECONDS)
.build();
}
}
}
@FeignClient(url = "${baidu.url}", name = "baidu",
configuration = FeignDefaultConfiguration.class)
public interface BaiduService {
@GetMapping(value = "/s")
String baiduSearch(@RequestParam String wd);
}
将 默认的客户端实现 feign.Client.Default 替换为了 okhttp3.OkHttpClient。
原理分析
- @EnableFeignClients导入FeignClientsRegistrar,扫描包含@FeignClient注解的类
- 接口底层实现类为FeignClientFactoryBean
- 最终的方法执行拦截器为SynchronousMethodHandler
Feign组件分析
整合负载均衡
- FeignClientFactoryBean的getTarget()方法获取到类型为LoadBalancerFeignClient的Client
- 进入LoadBalancerFeignClient的execute()方法
- 进入LoadBalancerFeignClient的lbClient(),根据clientName获取
- 进入CachingSpringLoadBalancerFactory的create()方法
- SpringClientFactory的getLoadBalancer()方法,获取到ZoneAwareLoadBalancer类型的ILoadBalancer
- ZoneAwareLoadBalancer的balancers字段中已经包含了该clientName在注册中心对应的多个实例地址
- PollingServerListUpdater每隔30秒都会从注册中心拉取最新的数据并更新到ILoadBalancer中
- 通过DiscoveryEnabledNIWSServerList的obtainServersViaDiscovery()方法来从注册中心获取实例列表