微服务—OpenFeign服务接口

前言

SpringCloud之 OpenFeign服务接口学习!


文章目录

一、Feign与OpenFeign概念

  • Feign是一个声明式的伪Http客户端(因为底层依然使用的是RestTemplate来实现,基于Http应用层协议实现),它的使用方法是通过在一个服务接口上面增加注解(可以使用SpringMVC的注解)。Feign也支持可插拔式的编码器和解码器。Spring Cloud对Feign进行了封装,使它支持了Spring MVC标准注解和 HttpMessageConverts,Feign可以与Ribbon和Eureka组合使用来支持负载均衡,Feign内部集成了Ribbon组件,可以实现请求的负载均衡。

  • 前面在使用 Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是实际操作时对于服务依赖的调用可能不只一处,往往会一个接口被多处调用,所以通常都会针对每个微服务自行封装一些客户端来包装。因此Feign在此基础上做了进一步的封装处理,由它来帮助我们实现依赖服务接口的定义,这样就能够实现服务端接口对应Feign接口,客户端只用调用Feign接口即可,Feign的实现只需要接口+注解就可以实现。

  • Feign还集成了Ribbon,并通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口并且以声明式的方法,来优雅而且简单的实现服务调用。

  • RestTemplateOpenFeign的区别
    RestTemplate的缺点包括:路径写死、不能自动转换响应结果为对应对象、必须集成Ribbon来实现负载均衡;
    OpenFeign 则以十分便捷的方式实现了服务间的通信,同时内部集成了Ribbon,具备负载均衡的特性。

  • FeignOpenFeign的区别
    微服务—OpenFeign服务接口

二、OpenFeign的使用

OpenFeign是集成在客户端,为客户请求微服务而生效的,同时由于它还支持SpringMVC注解。因此我们可以通过SpringMVC注解的方式结合微服务ID、微服务ip地址创建接口,这样商品服务端就只需要调用本地的接口即可实现目的。

  • pom.xml
    关键需要引入spring-cloud-starter-openfeign 依赖包
  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  • application.yml
server:
  port: 80
eureka:
  instance:
    prefer-ip-address: true
    instance-id: consumer-order-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureaka7002.com:7002/eureka
  • ConsumerController
    模拟客户端请求服务端,模拟了无参数、简单参数、复杂参数、对象作为参数
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Autowired
    PaymentClient paymentClient;
    /**
     * 用户查询单个订单信息
     * @param id
     * @return
     */
    @GetMapping("/getById/{id}")
    public ResultObj<Payment> getPayment(@PathVariable("id") Long id){
//        return restTemplate.getForObject(PAYMENT_URL+"/payment/getById/"+id, ResultObj.class);
        return paymentClient.getPaymentById(id);
    }

    @GetMapping("/all")
    public ResultObj<List<Payment>> getPaymentList(){
        return paymentClient.getAllPayment();
    }

    @GetMapping("/limit")
    public ResultObj<List<Payment>> getLimit(@RequestParam("limit") int limit){
        return paymentClient.getByLimit(limit);
    }

    @PostMapping("/create")
    public ResultObj createPayment(@RequestBody Payment payment){
//        int a= 2;
//        return paymentClient.createPayment(payment);
        Payment payment1 = new Payment(36L,"qqqqq");
        System.out.println("客户端:"+payment1);
        ResultObj resultObj = paymentClient.insertPayment(payment1);
        return resultObj;
    }
}
  • PaymentClient
    定义客户端相应的OpenFeign接口,客户端Controller通过调用这个接口的方法来实现访问目标微服务。
/**
 * 根据OpenFeign编写的客户端接口
 */
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentClient {

    /**
     * 根据ID获取指定服务
     * 注意参数作为路径时必须加上@PathVariable
     * @return
     */
    @GetMapping("/payment/getById/{id}")
    public ResultObj<Payment> getPaymentById(@PathVariable("id") Long id);

    @GetMapping("/payment/getAll")
    public ResultObj<List<Payment>> getAllPayment();

    /**
     * 方法对应多个普通参数
     * 注意参数作为路径时必须加上@RequestParam
     * @return
     */
    @GetMapping("/payment/queryAllByLimit")
    public ResultObj<List<Payment>> getByLimit(@RequestParam("limit") int limit);

    /**
     * 传递对象作为参数
     * 注意参数作为路径时必须加上@RequestBody
     * @return
     */
    @PostMapping("/payment/create")
    public ResultObj createPayment(@RequestBody Payment payment);

    @PostMapping(value = "/payment/insert")
    public ResultObj insertPayment(@RequestBody Payment payment);
}
  • PaymentController
    微服务端对应Controller
@RestController
@RequestMapping("/payment")
public class PaymentController {
    @Autowired
    PaymentService paymentService;
    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/getById/{id}")
    public ResultObj<Payment> selectOne(@PathVariable("id") Long id){
        return new ResultObj<Payment>("200","serverPort:"+serverPort,paymentService.queryById(id));
    }

    @GetMapping("/getAll")
    public ResultObj<List<Payment>> selectAll(){
        ResultObj<List<Payment>> ans = ResultObj.success(paymentService.queryAll());
        return ans;
    }

    @GetMapping("/queryAllByLimit")
    public ResultObj<List<Payment>> queryAll(@RequestParam(defaultValue = "0") int offset,
                                             @RequestParam(defaultValue = "5") int limit){
        return ResultObj.success(paymentService.queryAllByLimit(offset,limit));
    }

    @PostMapping("/insert")
    public ResultObj insertObj(@RequestBody Payment payment){
        System.out.println(payment);
        return ResultObj.success();
    }

    @PostMapping("/create")
    public ResultObj addData(@RequestBody Payment payment){
        System.out.println("success received Payment........");
        Payment ans = paymentService.insert(payment);
        if(ans != null){
            return ResultObj.success();
        }
        else
            return ResultObj.failure(Common.SERVER_ERROR);
    }
}

注意OpenFeign参数传递之数组和集合类型参数,在编写接口时,数组需要注上@RequestParam("数组名称")
如果有多个参数需要返回,一般先返回客户端json字符串,然后利用jackson工具来解析对应的json字符串从中获取数据。
微服务提供端

    @GetMapping("/testManyArgs")
    public ResultObj testManyArgs(){
        String res = null;
        Map<String,Object> ans = new HashMap<>();
        try {
            List<Payment> list = new ArrayList<>();
            list.add(new Payment(1L,"阿萨德"));
            list.add(new Payment(2L,"自行车"));
            list.add(new Payment(3L,"徐徐线程"));
            list.add(new Payment(4L,"水电费"));
            ans.put("list",list);
            ans.put("guigui","xxxx");
            ObjectMapper mapper = new ObjectMapper();
            res = mapper.writeValueAsString(ans);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return ResultObj.success(res);
    }

消费者处理端

    @GetMapping("/testManyArgs")
    public ResultObj testArgs(){
        ObjectMapper mapper = new ObjectMapper();
        ResultObj res = paymentClient.testManyArgs();
        String json = (String) res.getObject();
        try {
            Map<String,Object> tempMap = mapper.readValue(json, Map.class);
            String str = (String) tempMap.get("guigui");
            System.out.println(str);
            String objstr = mapper.writeValueAsString(tempMap.get("list"));
            List<Payment> paymentList = mapper.readValue(objstr, new TypeReference<List<Payment>>() {
            });
            for (Payment payment : paymentList) {
                System.out.println(payment);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResultObj.success();
    }

测试
微服务—OpenFeign服务接口

三、OpenFeign的超时处理

OpenFeign默认调用超时服务,使用OpenFeign组件在进行服务间通信时要求被调用服务必须在1s内给予响应,一旦服务执行业务逻辑时间超过1s,OpenFeign组件将直接报错。

修改OpenFeign超时时间:

# 修改某个具体服务的超时时间
feign.client.config.服务ID.connectTimeout=5000    #配置指定服务连接超时

feign.client.config.服务ID.readTimeout=5000       #配置指定服务等待超时

# 修改所有默认服务的超时时间
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000

四、OpenFeign日志展示

4.1 日志开启

默认OpenFeign是没有开启日志记录的,因此需要在配置文件中开启对于日志记录的支持,OpenFeign能够对于每个微服务申请一个日志记录功能。

  • 开启日志展示
    指定feign客户端所在包的日志级别:loggin.level.xxxx.xxx=debug
    开启指定服务日志展示:feign.client.config.PRODUCTS.loggerLevel=full
    开启全局服务的日志展示:feign.client.config.default.loggerLevel=full

  • feign的日志记录级别
    1)NONE:不记录任何日志;
    2)BASIC:仅仅记录请求方法,url,响应状态码及执行时间;
    3)HEADERS:记录Basic级别的基础上,记录请求和响应的Header;
    4)FULL:记录请求和响应的Header,body和元数据。

4.2 结果展示

微服务—OpenFeign服务接口

上一篇:POJ_1990 MooFest 【树状数组】


下一篇:“OpenFeign“ 调取第三方服务接口时出现的“Connection reset“ 解决方案