Feign 简介与使用入门,请求、响应压缩,日志记录
目录Feign 声明式web服务客户端feign 声明式web客户端使用eurekaserver_changsha(注册中心)eureka-client-food(服务提供者)feign-client-cat(服务消费者)浏览器访问微服务调用测试Feign 请求与响应压缩 与 日志Feign 声明式web服务客户端spring-cloud-openfeign ...
Feign 声明式web服务客户端
spring-cloud-openfeign 官网:Spring Cloud OpenFeign spring cloud 官方 2.1.x 文档:Spring Cloud OpenFeign feign Github 开源地址:https://github.com/OpenFeign/feign。 |
1、feign 是一个声明式 Web 服务客户端/http 客户端,它使编写 Web 服务客户端更加容易,要使用 feign,请创建一个接口并对其进行注释。它具有可插拔的注解支持,包括外部注解和 JAX-RS 注解。Feign 还支持可插拔的编码器和解码器。
2、Spring Cloud 增加了对 Spring MVC 注解的支持,并支持使用 Spring Web 中默认使用的 HttpMessageConverters,Spring Cloud 集成了 Ribbon 和 Eureka,在使用 Feign 时提供一个负载均衡的 http 客户端。
3、虽然直接使用 org.springframework.web.client.RestTemplate 也可以实现微服务之间的 http 调用,但是 feign 作为一个独立的库,更具有优势,它使得调用远程微服务的 API 就如同调用自己本地的 API 一样。
4、How to Include Feign?按着官网文档介绍,使用 feign 很简单,分为如下几步:
1)服务请求/调用/消费方在 pom.xml 文件中导入 feign 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2)服务请求/调用/消费方在启动类上加上 @org.springframework.cloud.openfeign.EnableFeignClients 注解开启 feignClient 客户端:
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3)服务请求/调用/消费方创建一个接口,@FeignClient 表示此接口为 feign 客户端,"stores" 为服务提供者的微服务名称,可以从注册中心看到,接口中的方法就是服务提供者 Cotroller 层的方法。其中 @RequestMapping 请求方式必须与服务提供者提供的方式一致,value 是请求路径,如果对方设置了应用上下文,则 value 中也要加上,方法名称可以自定义,不过建议与服务提供者一致。
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}
4)然后服务请求/调用/消费方可以在自己的 Controller 中调用上面接口中的方法,表面上好像调用自己的 API,实际上会通过微服务名称和路径调用远程微服务接口。官网文档:Spring Cloud OpenFeign
注意事项 & 温馨提示
注意事项点 | 示例 |
---|---|
接口提供方接口方法参数比调用方定义的多,但是参数为非必传,调用方可以正常调用。 比如接口提供方后期新加了参数,但是接口调用方无法同步修改,此时定为非必传时,传与不传都可以兼容。 | |
接口提供方接口方法参数比调用方定义的多,且参数为必传,提供方接口方法无法执行,会提示缺少参数,但是调用方也不会抛异常。 | |
接口提供方接口方法参数比调用方定义的少,调用方可以正常调用。 比如接口提供方后期减少了参数,但是接口调用方无法同步修改,此时多传了不影响。 接口提供方还可以在任意位置获取对方传过来的查询参数: RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); |
Feign 声明式web客户端使用
1、使用非常简单,开发环境为:Java JDK 1.8 + Spring Boot 2.1.3 + Spring Cloud Greenwich.SR1 + IDEA 2018。
2、准备三个微服务应用:eurekaserver_changsha 应用作 eureka 服务端,用于服务注册;eureka-client-food 应用提供服务;feign-client-cat 应用作为服务请求者,请求 eureka-client-food 提供的服务(接口)
3、操作流程:用户从浏览器访问 feign-client-cat 、feign-client-cat 应用内部调用 eureka-client-food 微服务,然后返回数据。
eurekaserver_changsha(注册中心)
1、pom.xml 文件核心内容如下(详细源码地址:GitHub - wangmaoxiong/feign_first: Feign 简介及基础使用):
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2、全局配置文件内容如下(详细源码地址:GitHub - wangmaoxiong/feign_first: Feign 简介及基础使用):
server:
port: 9393
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 60000 #驱逐计时器扫描失效服务间隔时间。(单位毫秒,默认 60*1000)
instance:
hostname: localhost
client:
register-with-eureka: false #禁用自己向自己注册
fetch-registry: false #不同步其他的 Eureka Server节点的数据
service-url: #Eureka Client 与 Eureka Server 交互的地址
default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/
3、启动类上添加 @EnableEurekaServer:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @EnableEurekaServer:开启 eureka 服务
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaserverChangshaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaserverChangshaApplication.class, args);
}
}
注册中心提供服务注册,内容不多。
eureka-client-food(服务提供者)
1、pom.xml 文件核心内容如下(详细源码地址:GitHub - wangmaoxiong/feign_first: Feign 简介及基础使用):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
...
2、全局配置文件内容如下(详细源码地址:https://github.com/wangmaoxiong/feign_first):
server:
port: 9395 #服务器端口
servlet:
context-path: /food #应用访问上下文
spring:
application:
name: eureka-client-food #微服务名称
eureka:
client:
service-url:
defaultZone: http://localhost:9393/eureka/ #eureka 服务器地址
instance:
prefer-ip-address: true # IP 地址代替主机名注册
instance-id: changSha-food # 微服务实例id名称
3、服务提供者提供的服务就是 http 访问的接口,所以创建一个 Controller 层,提供访问接口,其中提供了不同参数的访问方式,以达到基本满足日常开发的需要:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import wmx.com.eurekaclient_food.pojo.Person;
import java.io.IOException;
import java.time.LocalDateTime;
/**
* 菜谱
*
* @author wangmaoxiong
*/
@RestController
public class Cuisine {
private static final Logger logger = LoggerFactory.getLogger(Cuisine.class);
/**
* 获取湘菜菜谱数据。访问地址:http://localhost:9395/food/getHunanCuisine?uuid=98389uou8309adko990
*
* @return
*/
@GetMapping("getHunanCuisine")
public String getHunanCuisine(String uuid) {
logger.info("获取湖南菜谱,uuid = {}", uuid);
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;//以 json 格式返回
ArrayNode arrayNode = nodeFactory.arrayNode()
.add("辣椒炒肉")
.add("剁椒鱼头")
.add("蚂蚁上树")
.add(StringUtils.trimToNull(uuid));
return arrayNode.toString();
}
/**
* 根据 id 删除:http://localhost:9395/food/deleteDataById?id=980890
*
* @param id
* @return
*/
@GetMapping("deleteDataById")
public String deleteDataById(@RequestParam(value = "id") Integer id) {
logger.info("根据 id 删除,id = {}", id);
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = nodeFactory.objectNode();//以 json 格式返回
objectNode.put("id", id);
objectNode.put("code", 200);
objectNode.put("message", "delete success");
return objectNode.toString();
}
/**
* 更新:http://localhost:9395/food/updateData/889uuo65eud99?data=name_zhangsan,age_33
*
* @param uid :使用路径变量
* @param data :使用普通的请求参数
* @return
*/
@RequestMapping(value = "updateData/{uid}", method = RequestMethod.GET)
public String updateData(@PathVariable("uid") String uid, String data) {
logger.info("更新数据,uid = {}, data = {}", uid, data);
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = nodeFactory.objectNode(); //以 json 格式返回
objectNode.put("code", 200);
objectNode.put("message", "更新成功");
objectNode.put("uid", uid);
objectNode.put("data", data);
return objectNode.toString();
}
/**
* 保存数据:http://localhost:9395/food/saveData?type=saveing
*
* @param jsonData :使用请求正文(json 格式)传入,数据在请求体中,如:{"id":9527,"name":"华安","order":100000},页面必须传入
* @param type :使用普通的 key-value 格式传入,数据在请求透中
* @return
*/
@PostMapping("saveData")
public String saveData(@RequestBody String jsonData, @RequestParam String type) {
logger.info("保存数据,jsonData = {}, type = {}", jsonData, type);
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = nodeFactory.objectNode();//以 json 格式返回
try {
objectNode.put("code", 200);
objectNode.put("message", "保存成功");
objectNode.put("type", type);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(jsonData);
objectNode.set("jsonDta", jsonNode);
} catch (IOException e) {
e.printStackTrace();
}
return objectNode.toString();
}
/**
* 测试 get 方式复杂对象调用:http://localhost:9395/food/updatePerson?pid=100&pname=张三&age=33
*
* @param person
* @return
*/
@GetMapping("updatePerson")
public Person updatePerson(Person person) {
logger.info("更新数据 person = {}", person);
person.setBirthday(LocalDateTime.now());
return person;
}
/**
* 测试 post 方式复杂对象调用:http://localhost:9395/food/updatePerson2?pid=100&pname=张三&age=33
* 1、对于复杂对象,不建议使用 @RequestBody 在请求体中传递数据,因为 feignClient 客户端调用时会很难处理
* 2、如果非得要使用 @RequestBody ,则建议使用 String 类型,而不是复杂对象
* 3、也可以如下所示,使用请求头传递参数,这样 feignClient 客户端可以将复杂对象拆成属性调用
*
* @param person
* @return
*/
@PostMapping("updatePerson2")
public Person updatePerson2(Person person) {
logger.info("更新数据(post) person = {}", person);
person.setBirthday(LocalDateTime.now());
return person;
}
}
feign-client-cat(服务消费者)
1、pom.xml 文件核心内容如下(详细源码地址:GitHub - wangmaoxiong/feign_first: Feign 简介及基础使用):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
...
2、全局配置文件内容如下(详细源码地址:https://github.com/wangmaoxiong/feign_first):
server:
port: 9394 #服务器端口
servlet:
context-path: /cat #应用访问上下文
spring:
application:
name: feign-client-cat #微服务名称
eureka:
client:
service-url:
defaultZone: http://localhost:9393/eureka/ #eureka 服务器地址
instance:
prefer-ip-address: true # IP 地址代替主机名注册
instance-id: feign-cat # 微服务实例id名称
feign:
name:
food: eureka-client-food #服务提供方的服务名称,自定义配置。
3、启动类代码如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @EnableFeignClients:开启 feign 客户端
* @EnableEurekaClient:开启 eureka 客户端,可以不写,默认就是开启的
*/
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class FeignClientCatApplication {
public static void main(String[] args) {
SpringApplication.run(FeignClientCatApplication.class, args);
}
}
4、提供 feign 客户端接口如下:
1)@FeignClient 注解的 vaule 和 name 其实是一个属性,互相使用了别名,完全等价。值为服务提供方的服务名称。
2)@FeignClient(name = "${feign.name.food}"):推荐方式,服务提供方的服务名称从配置文件读取。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import wmx.com.feign_client_cat.pojo.Person;
import java.time.LocalDateTime;
/**
* 1、@FeignClient :声明接口为 feign 客户端,value 值为被请求的微服务名称(注册中心可以看到,配置文件中的spring.application.name属性值),
* value 可以省略如 @FeignClient("eureka-client-food")。推荐方式:@FeignClient(name = "${feign.name.food}")
* 2、@FeignClient 接口中的方法名称可以自定义,但建议保持与对方一致,请求方式必须一致,请求路径记得带上服务提供方上下文路径(如果有的话)
* 有些细节需要注意,下面注释中有说明
*/
@FeignClient(value = "eureka-client-food")
public interface FoodFeignClient {
/**
* 获取湘菜菜谱数据
* 1、"food" 是被请求应用的上下文件路径,一并写在方法上
* 2、@GetMapping 也可以拆开写成:@RequestMapping(value = "food/getHunanCuisine", method = RequestMethod.GET)
* 3、参数为字符串时,如果没加 @RequestParam("uuid") 请求参数注解,则请求时会抛异常如下:
* feign.FeignException: status 405/404 reading FoodFeignClient#getHunanCuisine(String)
* @return
* @GetMapping("getHunanCuisine")
*/
@GetMapping("food/getHunanCuisine")
public String getHunanCuisine(@RequestParam("uuid") String uuid);
/**
* 根据 id 删除
* 1、@RequestMapping 也可以换成 @GetMapping("food/deleteDataById")
* 2、经过实测参数为整形时,@RequestParam("id") 此时可加可不加,但都建议加上
* @param id
* @return
*/
@RequestMapping(value = "food/deleteDataById", method = RequestMethod.GET)
public String deleteDataById(@RequestParam("id") Integer id);
/**
* 更新
* 1、@PathVariable("uid") 注解可以不写 value 属性
* 2、再次提醒:对于 String 参数,服务提供者方法上有没有加 @RequestParam,feignClient 客户端都需要加上,否则调用失败,抛异常:
* feign.FeignException: status 405 reading FoodFeignClient#updateData(String,String)
* @param uid :使用路径变量
* @param data :使用普通的请求参数
* @return
*/
@GetMapping(value = "food/updateData/{uid}")
public String updateData(@PathVariable("uid") String uid, @RequestParam String data);
/**
* 保存数据,post 请求。再次提醒 food 是服务提供者应用上下文件路径
* @return
*/
@PostMapping("food/saveData")
public String saveData(@RequestBody String jsonData, @RequestParam String type);
/**
* 测试复杂对象调用
* 1、对于 get 请求,参数为复杂对象时,feignClient 中如果直接使用 public Person updatePerson(Person person); 会抛异常:
* feign.FeignException: status 404 reading FoodFeignClient#updatePerson(Person)
* 2、抛异常的原因是对于get方式复杂对象传递时,虽然已经指明了是 get 方式,但是 feign 还是会以 post 方式传递,导致调用失败
* 3、解决方式1:可以和服务提供者协商,转换成 String 的方式进行传递;解决方式2:将复杂对象(Pojo) 拆成简单的属性,如下所示
* @return
*/
@GetMapping("food/updatePerson")
public Person updatePerson(@RequestParam Integer pid,
@RequestParam String pname,
@RequestParam Integer age,
@RequestParam LocalDateTime birthday);
/**
* 测试 post 方式复杂对象调用
* 1、与 get 方式差不多,当服务提供者使用 updatePerson2(Person person) 时,feign 客户端不能这么直接传递
* 虽然 post 方式这样直传不会报错,但是对方接收不到数据,所以仍然只能拆成简单的属性进行传递
* 2、如果对方要求的是 @RequestBody ,则此时这样写会直接抛异常,推荐解决方式是使用 String 类型传递
* @return
*/
@PostMapping("food/updatePerson2")
public Person updatePerson2(@RequestParam Integer pid,
@RequestParam String pname,
@RequestParam Integer age,
@RequestParam LocalDateTime birthday);
}
5、@FeignClient 客户端接口中的方法请求方式要求与服务提供者一致,然而服务消费者自己控制层的调用方式是不受约束的,可以自己随意设置,控制层调用代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import wmx.com.feign_client_cat.feignClient.FoodFeignClient;
import wmx.com.feign_client_cat.pojo.Person;
import javax.annotation.Resource;
@RestController
public class CatController {
private static final Logger logger = LoggerFactory.getLogger(CatController.class);
//注入 feign 客户端实例。有了 feign 之后,调用远程微服务就如同调用自己本地的方法一样简单
//@FeignClient 客户端接口中的方法请求方式要求与服务提供者一致,然而服务消费者自己控制层的调用方式是不受约束的
@Resource
private FoodFeignClient foodFeignClient;
/**
* 查询湖南菜谱:http://localhost:9394/cat/getHunanCuisine?uuid=77884934euei000pp
* @param uuid
* @return
*/
@GetMapping("getHunanCuisine")
public String getHunanCuisine(String uuid) {
logger.info("查询湖南菜谱,uuid = {}", uuid);
String result = this.foodFeignClient.getHunanCuisine(uuid);
return result;
}
/**
* 根据 id 删除:http://localhost:9394/cat/deleteDataById?id=980890
* @param id
* @return
*/
@GetMapping("deleteDataById")
public String deleteDataById(@RequestParam(value = "id") Integer id) {
logger.info("根据 id 删除,id = {}", id);
String result = this.foodFeignClient.deleteDataById(id);
return result;
}
/**
* 更新:http://localhost:9394/cat/updateData/889uuo65eud99?data=name_zhangsan,age_33
* @param uid :使用路径变量
* @param data :使用普通的请求参数
* @return
*/
@RequestMapping(value = "updateData/{uid}", method = RequestMethod.GET)
public String updateData(@PathVariable("uid") String uid, String data) {
logger.info("更新数据,uid = {}, data = {}", uid, data);
String result = this.foodFeignClient.updateData(uid, data);
return result;
}
/**
* 保存数据:http://localhost:9394/cat/saveData?type=saveing
* @param jsonData :使用请求正文(json 格式)传入,数据在请求体中,如:{"id":9527,"name":"华安","order":100011}
* @param type :使用普通的 key-value 格式传入,数据在请求透中
* @return
*/
@PostMapping("saveData")
public String saveData(@RequestBody String jsonData, @RequestParam String type) {
logger.info("保存数据,jsonData = {}, type = {}", jsonData, type);
String result = this.foodFeignClient.saveData(jsonData, type);
return result;
}
/**
* 测试 get 方式复杂对象调用:http://localhost:9394/cat/updatePerson?pid=100&pname=张三&age=33
* @param person
* @return
*/
@GetMapping("updatePerson")
public Person updatePerson(Person person) {
logger.info("updatePerson(get) person = {}", person);
return this.foodFeignClient.updatePerson(person.getPid(),
person.getPname(),
person.getAge(),
person.getBirthday());
}
/**
* 测试 post 方式复杂对象调用:http://localhost:9394/cat/updatePerson2
* 1、@RequestBody:表示参数通过请求体传递,且为 json 格式,所以前端必须设置请求类型:Content-Type: application/json
* @param person :{"pid":9334,"pname":"华雄","age":35}
* @return
*/
public Person updatePerson2(@RequestBody Person person) {
logger.info("updatePerson(post) person = {}", person);
return this.foodFeignClient.updatePerson2(person.getPid(),
person.getPname(),
person.getAge(),
person.getBirthday());
}
}
详细源码地址:https://github.com/wangmaoxiong/feign_first
浏览器访问微服务调用测试
1、顺序启动 eureka 注册中心、服务提供者、服务消费者,然后从浏览器请求 feign-client-cat,如果它能从 eureka-client-food(服务提供者)获取数据并返回,则说明成功。
2、因为有 post 请求,所以在 firefox 浏览器上安装使用 https://addons.mozilla.org/zh-CN/firefox/addon/restclient/ 插件进行访问测试:
Feign 请求与响应压缩 与 日志
Feign 请求与响应压缩
1、Feign request/response compression:可以为 feign 请求或响应 使用 gzip 压缩,压缩设置与为 web 服务器的设置类似,允许选择压缩媒体类型和最小请求阈值长度。哪边使用 feign 就配置在哪边。
feign.compression.request.enabled=true #开启 feign 请求压缩,默认 false
feign.compression.response.enabled=true #开启 feign 响应压缩,默认 false
feign.compression.request.mime-types=text/xml,application/xml,application/json #设置 feign 请求压缩类型
feign.compression.request.min-request-size=2048 #开启 feign 请求压缩阈值,超过此值才进行压缩,默认 2048
2、亲身经历过一次 feign 调用报错,服务 A 调用服务 B,debug 可以看到 B 的方法能正常进入,对方也没有报错,但是对方返回后,服务 A 这边直接报错如下,说是无法解析返回的内容,最后是服务A这边的 feign.compression.response.enabled 设置为 false 才得以解决。
2023-05-16 14:08:03 WARN [http-nio-7071-exec-9] o.s.cloud.tsf.route.util.TsfRouteInterceptUtil:114 tsf route, handle request tsf route rule , target service name is null, route not work.
2023-05-16 14:08:03 ERROR [http-nio-7071-exec-9] grp.aop.InterfaceRequestErrorAndPerformanceLog:91 ReturnData grp.basic3.busi.controller.BasExpCriController3.auditByData(String,String,List) 接口调用失败! 参数为:[003001, null, [{id=bbe12bb2-8c6d-446c-b524-7549ddd2a185, exp_cri_id=237484f1-f6d3-43e5-b5cd-e8b10305047e, exp_cri_code=Z01002, exp_cri_name=测试-24版本, exp_cri_class_id=596, exp_cri_class_code=3, exp_cri_class=暂定标准, exp_cri_class_name=暂定标准, exp_cri_class_codename=3 暂定标准, mof_div_code=210000000, fiscal_year=2024, unit=个, form_id=10576, form_code=1, form=定额, form_name=定额, form_codename=1 定额, exp_cri_val=60000, exp_eco_cls_id=null, exp_eco_cls_code=null, exp_eco_cls_name=null, exp_eco_cls_codename= , start_date=2023-05-16 11:36:56, end_date=2099-12-31 00:00:00, is_enabled=1, update_time=2023-05-16 11:36:56, is_deleted=2, create_time=2023-05-16 11:36:56, remark=, parent_id=0, is_leaf=1, level_no=1, create_user_id=211464028, create_user_code=21000000300111, create_user_name=鞠鑫, update_user_id=211464028, update_user_code=21000000300111, update_user_name=鞠鑫, exp_cri_busi_cate_id=dc4e5583-11e8-47a8-b9c6-7d3704fa1967, exp_cri_busi_cate_code=Z01, exp_cri_busi_cate_name=暂定标准, exp_cri_busi_cate_codename=Z01 暂定标准, audit_id=bbe12bb2-8c6d-446c-b524-7549ddd2a185, agency_id=12, is_end=2, create_menu_id=1c7b82de5f424c84b2f9c5eb53c8fcd8, ele_id=null, ele_code=null, ele_name=null, ele_codename= , bill_no=E003001-0, agency_code=003001, agency_name=中共辽宁省直属机关工作委员会本级, is_confirmed=2, mof_div_name=辽宁省本级, biz_key=237484f1-f6d3-43e5-b5cd-e8b10305047e, dep_bgt_eco_id=null, dep_bgt_eco_code=null, dep_bgt_eco_name=null, dep_bgt_eco_codename= , is_last_inst=2, rownum_=1, agency_codename=003001 中共辽宁省直属机关工作委员会本级}]]
feign.codec.DecodeException: Error while extracting response for type [class grp.pt.core.ReturnData] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens; nested exception is com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
at [Source: (PushbackInputStream); line: 1, column: 2]
at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:174) ~[feign-core-9.7.0.jar!/:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:134) ~[feign-core-9.7.0.jar!/:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:77) ~[feign-core-9.7.0.jar!/:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:102) ~[feign-core-9.7.0.jar!/:na]
at com.sun.proxy.$Proxy238.updateIsenabled(Unknown Source) ~[na:na]
at grp.bgt.pm.service.PmEstImateFeignService.updateIsenabled(PmEstImateFeignService.java:27) ~[bgt-com-3.2.0.jar!/:na]
at grp.basic3.busi.service.AbstractBasExpCriService3.auditByData(AbstractBasExpCriService3.java:733) ~[classes!/:3.2.0(build20230418)_TSF]
at grp.basic3.busi.service.AbstractBasExpCriService3$$FastClassBySpringCGLIB$$9b31790f.invoke(<generated>) ~[classes!/:3.2.0(build20230418)_TSF]
Feign 日志记录
1、Feign logging:Feign 日志默认是不开启的,可以通过配置进行开启,如下所示,logging.level 表示日志级别,后面跟着 feign 客户端接口的完整类名,或者它的包名,日志记录只响应 debug 级别,所以值只能是 debug。
logging:
level:
wmx.com.feign_client_cat.feignClient.FoodFeignClient: debug
2、上面的配置开启之后表示 feign 可以记录日志了,但是具体怎么记录,还需要在配置类(@Configuration)中进行指定记录级别:
NONE:无日志记录(默认)
BASIC:基本,只记录请求方法和 url 以及响应状态代码和执行时间。
HEADERS: 头,记录基本信息以及请求和响应头。
FULL: 完整,记录请求和响应的头、正文和元数据。
3、例下面将 logger.level 设置为 full 级别:
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SysConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
4、当再次访问 feign-client-cat 微服务,它内部调用 eureka-client-food 时,控制台打印日志记录如下:
2019-11-04 17:35:36.768 DEBUG 8912 --- [nio-9394-exec-1] w.c.f.feignClient.FoodFeignClient : [FoodFeignClient#updateData] <--- HTTP/1.1 200 (268ms)
2019-11-04 17:35:36.768 DEBUG 8912 --- [nio-9394-exec-1] w.c.f.feignClient.FoodFeignClient : [FoodFeignClient#updateData] content-length: 89
2019-11-04 17:35:36.768 DEBUG 8912 --- [nio-9394-exec-1] w.c.f.feignClient.FoodFeignClient : [FoodFeignClient#updateData] content-type: text/plain;charset=UTF-8
2019-11-04 17:35:36.768 DEBUG 8912 --- [nio-9394-exec-1] w.c.f.feignClient.FoodFeignClient : [FoodFeignClient#updateData] date: Mon, 04 Nov 2019 09:35:36 GMT
2019-11-04 17:35:36.768 DEBUG 8912 --- [nio-9394-exec-1] w.c.f.feignClient.FoodFeignClient : [FoodFeignClient#updateData]
2019-11-04 17:35:36.770 DEBUG 8912 --- [nio-9394-exec-1] w.c.f.feignClient.FoodFeignClient : [FoodFeignClient#updateData] {"code":200,"message":"更新成功","uid":"889uuo65eud99","data":"name_zhangsan,age_33"}
2019-11-04 17:35:36.770 DEBUG 8912 --- [nio-9394-exec-1] w.c.f.feignClient.FoodFeignClient : [FoodFeignClient#updateData] <--- END HTTP (89-byte body)
动态 feign 接口调用
1、有时候需要根据配置的服务名称、Url、参数来远程调用,这样就没办法提前定义Feign接口,当然此时直接使用其他 Http 库调用也是可以的,比如 Apache HttpClient、RestTemplate、OkHttpClient 等等。
2、本文使用 FeignClientBuilder 也可以实现 Feign 动态接口调用。
package grp.basic3.dynamicfeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*动态feign接口,调用示例:
* dynamicFeignClient.executePostApi("bgt-basic-server","/seFeignBasPerson/genAssistPersonsYSBZ",new HashMap());
* dynamicFeignClient.executePostApi("bgt-basic-server","/debt/queryBasBondLoan",new ArrayList());
*/
@Component
public class DynamicFeignClient {
@Autowired
private DynamicFeignFactory<IDynamicFeignService> dynamicFeignFactory;
/**
*
* @param feignName 如:基础库 bgt-basic-server
* @param url 如:/aa/bb
* @param params 如:具体参数
* @return
*/
public Object executePostApi(String feignName, String url, Object params) {
IDynamicFeignService dynamicService = dynamicFeignFactory.getFeignClient(IDynamicFeignService.class, feignName);
return dynamicService.executePostFeign(url, params);
}
}
package grp.basic3.dynamicfeign;
import org.springframework.cloud.openfeign.FeignClientBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class DynamicFeignFactory<T> {
private FeignClientBuilder feignClientBuilder;
public DynamicFeignFactory(ApplicationContext appContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
public T getFeignClient(final Class<T> type, String serviceId) {
return this.feignClientBuilder.forType(type, serviceId).build();
}
}
package grp.basic3.dynamicfeign;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public interface IDynamicFeignService {
@PostMapping("{url}")
Object executePostFeign(@PathVariable("url") String url, @RequestBody Object params);
}
更多推荐
所有评论(0)