Spring Cloud Learning Notes: Using Feign to Implement Declarative Service Calls

Keywords: Java Spring Maven Apache

brief introduction

Feign is a declarative Web Service client, which simplifies the writing of Web Service clients. Compared with Ribbon+RestTemplate, developers only need to call HTTP API through simple interfaces and annotations. It supports Spring MVC annotations and JAX-RS annotations, as well as pluggable encoders and decoders. Integrating Eureka, Ribbon and Hystrix, it has a series of convenient functions, such as pluggable, annotation-based, load balancing, service fuse, etc.

Project introduction

  1. sc-parent, parent module (see Spring Cloud Learning Notes (1): Eureka Registry)
  2. sc-eureka, Registry (see Spring Cloud Learning Notes (1): Eureka Registry)
  3. sc-provider, provider (see Spring Cloud Learning Notes (1): Eureka Registry)
  4. sc-consumer-feign, consumer based on Feign declarative invocation

Consumers Based on Feign Declarative Calls

1. Create the sub-module project sc-consumer-feign, pom.xml under the parent module:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.cf</groupId>
    <artifactId>sc-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>sc-consumer-feign</artifactId>
  
  <dependencies>
    <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>
  </dependencies>
</project>

2. Create the startup class feign.FeignApplication:

package feign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class FeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignApplication.class, args);
    }
}

3. Create a Feign declarative interface: feign.inter.BookService

package feign.inter;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient("sc-provider")
public interface BookService {
    
    @GetMapping("/book/list")
    public String getBookList();
}

4. Create Controller that invokes provider services:

package provider.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/book")
@RestController
public class BookController {
    @GetMapping("/list")
    public String getBookList(){
        return "[\"Java Beginning to Abandoning\",\"C++Beginning to Abandoning\",\"Python Beginning to Abandoning\",\"C Beginning to Abandoning\"]";
    }
}

5. Create application.yml:

server:
  port: 8084

spring:
  application:
    name: sc-consumer-feign
    
eureka:
  client:
    registerWithEureka: false
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/

6. Start the registry sc-eureka, provider sc-provider, consumer sc-consumer-feign in turn, and visit http://localhost:8084/feign/getBook List:

Feign is based on Ribbon implementation and has Ribbon load balancing features. It can replace the invoked provider service with sc-provider-random (see Spring Cloud Learning Notes (2): Using Ribbon Load Balancing ) To test.

Requests with parameters

The example above does not involve parameter passing. Next, test how to use Feign to construct requests with parameters. First, make the following changes to the provider and consumer:

//Provider Controller adds two parameters and prints them to the console.
@RequestMapping("/book")
@RestController
public class BookController {
    @GetMapping("/list")
    public String getBookList(String param1, Integer param2){
        System.out.println(param1 + ":" + param2);
        return "[\"Java Beginning to Abandoning\",\"C++Beginning to Abandoning\",\"Python Beginning to Abandoning\",\"C Beginning to Abandoning\"]";
    }
}


//Consumer Feign Interface and Controller Add Parameters
@FeignClient("sc-provider")
public interface BookService {
    @GetMapping("/book/list")
    public String getBookList(String param1, Integer param2);
}

@RequestMapping("/feign")
@RestController
public class FeignController {
    @Autowired
    private BookService bookService;
    
    @GetMapping("/getBookList")
    public String getBookList(){
        return bookService.getBookList("Java", 520);
    }
}

Start the registry sc-eureka, provider sc-provider, consumer sc-consumer-feign in turn, and start the consumer sc-consumer-feign will fail:
java.lang.IllegalStateException: Method has too many Body parameters: public abstract java.lang.String feign.inter.BookService.getBookList(java.lang.String,java.lang.Integer)

Solution 1

Change the Feign interface and add the @RequestParam annotation for the parameter:

@FeignClient("sc-provider")
public interface BookService {
    @GetMapping("/book/list")   
    public String getBookList(@RequestParam("param1") String param1, @RequestParam("param2") Integer param2);
}

Solution 2

Encapsulate the parameters into Map and change the consumer Feign interface and Controller:

@FeignClient("sc-provider")
public interface BookService {
    @GetMapping("/book/list")
    public String getBookList(@RequestParam Map<String, Object> paramMap);
}

@RequestMapping("/feign")
@RestController
public class FeignController {
    @Autowired
    private BookService bookService;
    
    @GetMapping("/getBookList")
    public String getBookList(){
        Map<String,Object> paramMap = new HashMap<String, Object>();
        paramMap.put("param1", "Java");
        paramMap.put("param2", 520);
        return bookService.getBookList(paramMap);
    }
}

In the case of many parameters, this method can simplify the writing of Feign interface.

Parameters for custom types

OpenFeign's @QueryMap annotation supports the use of custom types for GET parameter mapping. Because @QueryMap and Spring are incompatible, Spring Cloud OpenFeign provides an equivalent @Spring QueryMap annotation, which can be used for parameter mapping of custom types and Map types. The following will use the custom type Params as a parameter and the @SpringQueryMap annotation to process the parameter mapping of the custom type.

1. Create Params-like classes in providers and consumers respectively (you can build a common module and then add dependencies in providers and consumers):

public class Params {
    private String param1;
    
    private Integer param2;

    public String getParam1() {
        return param1;
    }

    public void setParam1(String param1) {
        this.param1 = param1;
    }

    public Integer getParam2() {
        return param2;
    }

    public void setParam2(Integer param2) {
        this.param2 = param2;
    }
    
    @Override
    public String toString() {
        return "Params [param1=" + param1 + ", param2=" + param2 + "]";
    }

    public Params(String param1, Integer param2) {
        this.param1 = param1;
        this.param2 = param2;
    }

    public Params() {}
}

2. Change provider and consumer related classes

//Provider
@RequestMapping("/book")
@RestController
public class BookController {
    @GetMapping("/list")
    public String getBookList(Params params){
        System.out.println(params.toString());
        return "[\"Java Beginning to Abandoning\",\"C++Beginning to Abandoning\",\"Python Beginning to Abandoning\",\"C Beginning to Abandoning\"]";
    }
}

//Consumer
@FeignClient("sc-provider")
public interface BookService {
    @GetMapping("/book/list")
    public String getBookList(@SpringQueryMap Params params);
}

@RequestMapping("/feign")
@RestController
public class FeignController {
    @Autowired
    private BookService bookService;
    
    @GetMapping("/getBookList")
    public String getBookList(){    
        Params params = new Params("Java", 520);
        return bookService.getBookList(params);
    }
}

3. Start the registry sc-eureka, provider sc-provider, consumer sc-consumer-feign in turn, and visit http://localhost:8084/feign/getBook List. The provider console output:

Params [param1=Java, param2=520]

Posted by scotmcc on Thu, 12 Sep 2019 02:20:08 -0700