openFeign能做什么?
openFeign是一种声明式、模板化的HTTP客户端。在SpringCloud中使用openFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。
其用法就是编写一个接口,在接口上添加注解,如此就能轻而易举的调用远程服务
有如此强大的东西,我们肯定不能放过使用的机会,就像有时你有特殊的要求必须拉别的女孩的手,而此时有个中间人能帮你实现这个愿望,你拉别的女孩子的手就像拉自己女朋友的手一样方便
openFeign在微服务中的作用就像中间方一样,当你需要调用另一个微服务的接口时,使用openFeign就像调用本服务的接口一样丝滑
操作
既然是远程调用,那肯定至少得有2个微服务,consumer and provider。下面我们在consumer中记录以下使用步骤
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- 对JAXRS支持 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jaxrs</artifactId>
<version>10.0.0</version>
</dependency>
<!-- 调用soap webservice支持 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-soap</artifactId>
<version>10.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
启动类
在启动类上添加@EnableFeignClients注解
@SpringBootApplication
@EnableFeignClients
public class OpenFeignServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeignServiceApplication.class, args);
}
}
@EnableFeignClients工作原理
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
注解@EnableFeignClients告诉框架扫描所有使用注解@FeignClient定义的Feign客户端。它又通过注解@Import导入了类FeignClientRegistrar(feign客户端注册器)
创建openFeign-Client接口
分别写三个接口:GET请求、POST请求、SOAP请求
@FeignClient(name = "dynamicFeignClient", configuration = FeignClientsConfiguration.class)
public interface DynamicFeignClient {
// 发送GET请求
@RequestLine("GET")
@GetMapping("/product/{id}")
String getProductById(@PathVariable("id") Long id);
// 发送POST请求
@RequestLine("POST")
@Headers(value = "Content-Type: application/json;charset=UTF-8")
String requestPOST(URI baseUri, @RequestBody JSONObject args);
// 发送webservice-SOAP请求
@RequestLine("POST")
@Headers(value = "Content-Type: text/xml;charset=UTF-8")
String requestSOAP(URI baseUri, @RequestBody String params);
}
@FeignClient源码解析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";
String serviceId() default "";
String contextId() default "";
String name() default "";
String qualifier() default "";
String url() default "";
boolean decode404() default false;
Class<?>[] configuration() default {};
Class<?> fallback() default void.class;
Class<?> fallbackFactory() default void.class;
String path() default "";
boolean primary() default true;
@FeignClient注解解析
- name: 是一个任意得客户端名称,用于创SpringCloud LoadBalancer 客户端
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- configuration: Feign配置类,可自定义Feign的Encode,Decode,LogLevel,Contract
- fallback: 定义容错的类,当远程调用的接口失败或者超时的时候,会调用对应接口的容错逻辑,fallback执行的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类实例,通过此属性可以实现每个接口通用的容错逻辑,以达到减少重复的代码
- path: 定义当前FeignClient的统一前缀
创建请求类发送POST请求
public interface RequestClient {
String sendPost(String url, JSONObject request);
}
@Service
@Slf4j
@Import(FeignClientsConfiguration.class)
public class RequestClientImpl implements RequestClient {
private DynamicFeignClient client;
@Autowired
public RequestClientImpl(Decoder decoder, Encoder encoder) {
client = Feign.builder()
.decoder(decoder)
.encoder(encoder)
.options(new Request.Options(1000, 1000))
.target(Target.EmptyTarget.create(DynamicFeignClient.class));
}
@Override
public String sendPost(String url, JSONObject request) {
log.info("url:{},入参:{}", url, request);
String response;
try {
response = client.requestPOST(new URI(url), request);
} catch (FeignException feignException) {
throw new BusinessException("调用接口异常!地址:" + url);
} catch (URISyntaxException uriSyntaxException) {
throw new BusinessException("调用地址异常!地址:" + url);
}
log.info("url:{},出参:{}", url, response);
return response;
}
}
编写Controller请求提供者
@SneakyThrows
@PostMapping("/getOtherServiceData")
public JSONObject getOtherServiceData(@RequestBody JSONObject jsonObject) {
String responseString = client.sendPost("http://localhost:9907/remindMedication/getRemindMedication", jsonObject);
return JSONObject.parseObject(responseString);
}
使用postman进行测试
可以看到调用了远程服务的接口
优化
日志
openFeign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解openFeign中Http请求的细节。通过设置日志,可以对Feign接口的调用情况进行监控和输出
openFeign的日志级别主要有以下几种
- NONE: 默认的,不显示任何日志;
- BASIC: 仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS: 除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL: 除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
使用方式
第一种. 设置 Feign Logger Level
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
// 开启详细日志
return Logger.Level.FULL;
}
}
设置什么级别,根据情况而定
第二种. 在配置文件中给指定的FeignClient接口加指定的日志级别
@Autowired
public RequestClientImpl(Decoder decoder, Encoder encoder) {
client = Feign.builder()
.decoder(decoder)
.encoder(encoder)
.options(new Request.Options(5000, 5000))
.logLevel(Logger.Level.FULL)
.target(Target.EmptyTarget.create(DynamicFeignClient.class));
}
超时时间
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
在@FeignClient注解中,当设定了客户端名称后,便默认使用了Spring Cloud LoadBalancer进行负载均衡访问,在老版本中,集成的是Ribbon,它默认的响应时间是1s,可以通过ribbon.ReadTimeout和ribbon.ConnectTimeout来设置客户端超时时间
在新版本中,我们可以在options设置,例如,我们设置为1s
@Autowired
public RequestClientImpl(Decoder decoder, Encoder encoder) {
client = Feign.builder()
.decoder(decoder)
.encoder(encoder)
.options(new Request.Options(1000, 1000))
.logLevel(Logger.Level.FULL)
.target(Target.EmptyTarget.create(DynamicFeignClient.class));
}
并且在服务提供方延迟3s
然后再进行测试
Feign的工作原理
- 主程序入口添加了@EnableFeignClients注解开启对FeignClient扫描加载处理。根据FeignClient的开发规范,定义接口并加@FeignClients注解
- 当程序启动时,会进行包扫描,扫描所有@FeignClients注解的类,并且讲这些信息注入Spring IOC容器中,当定义的Feign接口中的方法被调用时,通过JDK的代理方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,改对象封装可HTTP请求需要的全部信息,如请求参数名,请求方法等信息都是再这个过程中确定的
- 然后RequestTemplate生成Request,然后把Request交给Client去处理,这里指的是Client可以把JDK原生的URLConnection,Apache的HttpClient,也可以是OKhttp,最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用