Feign Declarative service invocation

Learning goals

What is Feign

Feign is a lightweight RESTful HTTP client for Spring Cloud Netflix. Feign is an open source framework that implements load balancing and Rest invocation. It encapsulates Ribbon and RestTemplate. The interface – oriented programming of WebService is realized, which further reduces the coupling degree of the project. The article is reprinted with music bytes

Feign has a built-in Ribbon for client load balancing calls to services in the service registry.

Feign itself does not support Spring MVC annotations, it has its own annotations, and for ease of use, Spring Cloud incubates OpenFeign.

Feign is a declarative, templated HTTP client (available only in Consumer).

Feign supports annotations and usage in the official documentation: github.com/OpenFeign/f… Or spring. IO official documentation

Feign is used by using Feign’s annotations to define an interface that can be invoked to invoke the services of the service registry.

What problems does Feign solve

Feign is designed to make it easier to write JAVA HTTP clients. Feign simplifies RestTemplate code and implements Ribbon load balancing, making code simpler and less client-called. Feign is the preferred solution for load balancing. Just create an interface and add annotations to it.

Feign is a declarative service invocation component that, at its core, invokes remote methods as if they were local methods, with no awareness of remote HTTP requests.

  • It solves the problem of having a developer call a remote interface as if it were a local method, with no sense that it was a remote method, much less an HTTP request. You don’t have to worry about the details of remote interactions, and you don’t have to worry about distributed environment development.

  • Like Dubbo, the Consumer calls the Provider interface methods directly, without having to parse the returned data through the normal Http Client construction request.

Feign vs OpenFeign

OpenFeign is a Spring Cloud annotation that supports Spring MVC on top of Feign, such as @requestMapping, @PathVariable, etc.

OpenFeign’s @FeignClient can parse the interface under @RequestMapping annotation of SpringMVC and generate implementation classes through dynamic proxy, which can perform load balancing and invoke services.

Feign Primer case

Feign-demo polymerization project. SpringBoot 2.2.4.RELEASE, Spring Cloud hoxton.sr1

Feign can be used in the following steps:

  • Service consumers add Feign dependencies;
  • To create a service layer interface, add@FeignClientThe annotation declares the service that needs to be invoked;
  • The business-layer abstraction method uses SpringMVC annotations to configure service addresses and parameters.
  • Start class addition@EnableFeignClientsAnnotations activate the Feign component.

Create a project

PS: Service consumers access services through the Eureka registry, or through the Ribbon point-to-point direct connection mode.

We create an aggregation project and use the Eureka registry to illustrate Feign, first creating a POM parent project.

Add the dependent

pom.xml


      
<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>

    <groupId>com.xxxx</groupId>
    <artifactId>feign-demo</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <! -- Inherits spring-boot-starter-parent dependency -->
    <! -- Use inheritance mode, implement reuse, conform to inheritance can be used -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4. RELEASE</version>
    </parent>

    <! -- Define the dependency version number centrally, but do not introduce the dependency version number, when the subproject uses the declared dependency, can not add the dependency version number, so that the unified management project uses the dependency version number.
    <properties>
        <! -- Spring Cloud hoxton.sr1 dependencies -->
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
    </properties>

    <! Project dependency management The parent project only declares dependencies. The child project needs to specify the required dependencies (omit version information).
    <dependencyManagement>
        <dependencies>
            <! -- Spring Cloud dependencies -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>
Copy the code

Registry Eureka-Server

The registry is constructed in a cluster mode. In this paper, two nodes are eureka-Server and Eureka-Server02.

Create a project

The procedure for creating eureka-Server and Eureka-server02 is the same.

Add the dependent

The dependency configurations of eureka-server and Eureka-server02 are the same.

pom.xml


      

<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>

    <groupId>com.xxxx</groupId>
    <artifactId>eureka-server</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <! -- Inheriting parent dependencies -->
    <parent>
        <groupId>com.xxxx</groupId>
        <artifactId>feign-demo</artifactId>
        <version>1.0 the SNAPSHOT</version>
    </parent>

    <! -- Project dependencies -->
    <dependencies>
        <! -- Netflix Eureka Server dependencies -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <! Spring Boot Web dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <! -- Spring Boot test dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>
Copy the code

The configuration file

Eureka – server application. Yml

server:
  port: 8761 # port

spring:
  application:
    name: eureka-server # Application name (same under cluster)

Configure the Eureka Server registry
eureka:
  instance:
    hostname: eureka01            If the host name is not specified, the host name will be obtained based on the OS host name
    prefer-ip-address: true       Whether to register with an IP address
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    Set service registry address to point to another registry
    service-url:                  The registered address exposed by the registry
      defaultZone: http://localhost:8762/eureka/
Copy the code

Eureka – server02 application. Yml

spring:
  application:
    name: eureka-server # Application name (same under cluster)

# port
server:
  port: 8762

Configure the Eureka Server registry
eureka:
  instance:
    hostname: eureka02            If the host name is not specified, the host name will be obtained based on the OS host name
    prefer-ip-address: true       Whether to register with an IP address
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    Set service registry address to point to another registry
    service-url:                  The registered address exposed by the registry
      defaultZone: http://localhost:8761/eureka/
Copy the code

Start the class

The startup classes of Eureka-server and Eureka-server02 are the same.

EurekaServerApplication.java

package com.xxxx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
// Enable EurekaServer annotations
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }}Copy the code

Service-provider Indicates the service provider

The service provider is constructed in cluster mode, and two nodes are used in this paper, namely service-provider and service-provider02.

Create a project

The creation process of service-provider and service-provider02 is the same.

Add the dependent

The dependency configurations of service-provider and service-provider02 are the same.

pom.xml


      

<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>

    <groupId>com.xxxx</groupId>
    <artifactId>service-provider</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <! -- Inheriting parent dependencies -->
    <parent>
        <groupId>com.xxxx</groupId>
        <artifactId>feign-demo</artifactId>
        <version>1.0 the SNAPSHOT</version>
    </parent>

    <! -- Project dependencies -->
    <dependencies>
        <! -- Netflix eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <! Spring Boot Web dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <! -- Lombok dependency -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <! -- Spring Boot test dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>
Copy the code

The configuration file

Service – the provider application. Yml

server:
  port: 7070 # port

spring:
  application:
    name: service-provider # Application name (same under cluster)

Configure the Eureka Server registry
eureka:
  instance:
    prefer-ip-address: true       Whether to register with an IP address
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
Copy the code

Service – provider02 application. Yml

spring:
  application:
    name: service-provider # Application name (same under cluster)

# port
server:
  port: 7071

Configure the Eureka Server registry
eureka:
  instance:
    prefer-ip-address: true       Whether to register with an IP address
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
Copy the code

Entity class

Service-provider and service-Provider02 have the same entity class.

Product.java

package com.xxxx.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}
Copy the code

Write the service

The service codes of service-provider and service-provider02 are the same.

ProductService.java

package com.xxxx.service;

import com.xxxx.pojo.Product;

import java.util.List;

/** ** ** /
public interface ProductService {

    /** ** query the product list **@return* /
    List<Product> selectProductList(a);

}
Copy the code

ProductServiceImpl.java

package com.xxxx.service.impl;

import com.xxxx.pojo.Product;
import com.xxxx.service.ProductService;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

/** ** ** /
@Service
public class ProductServiceImpl implements ProductService {

    /** ** query the product list **@return* /
    @Override
    public List<Product> selectProductList(a) {
        return Arrays.asList(
                new Product(1.Huawei Mobile.1.5800D),
                new Product(2."Lenovo Notebook".1.6888D),
                new Product(3."Xiaomi Tablet".5.2020D)); }}Copy the code

Control layer

Service-provider and service-provider02 have the same control layer.

ProductController.java

package com.xxxx.controller;

import com.xxxx.pojo.Product;
import com.xxxx.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /** ** query the product list **@return* /
    @GetMapping("/list")
    public List<Product> selectProductList(a) {
        returnproductService.selectProductList(); }}Copy the code

Start the class

Service-provider and service-Provider02 have the same startup class.

ServiceProviderApplication.java

package com.xxxx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
// Enable the EurekaClient annotation, which is enabled by default in the current version if the Eureka registry is configured
//@EnableEurekaClient
public class ServiceProviderApplication {

    public static void main(String[] args) { SpringApplication.run(ServiceProviderApplication.class, args); }}Copy the code

Service-consumer

Create a project

Create a service-consumer service consumer project under the parent project.

Add the dependent

Service consumers add OpenFeign dependencies.

pom.xml


      

<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>

    <groupId>com.xxxx</groupId>
    <artifactId>service-consumer</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <! -- Inheriting parent dependencies -->
    <parent>
        <groupId>com.xxxx</groupId>
        <artifactId>feign-demo</artifactId>
        <version>1.0 the SNAPSHOT</version>
    </parent>

    <! -- Project dependencies -->
    <dependencies>
        <! -- Netflix eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <! -- Spring Cloud OpenFeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <! Spring Boot Web dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <! -- Lombok dependency -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <! -- Spring Boot test dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>
Copy the code

The configuration file

application.yml

server:
  port: 9090 # port

spring:
  application:
    name: service-consumer # app name

Configure the Eureka Server registry
eureka:
  client:
    register-with-eureka: false         # Whether to register yourself in the registry. Default is true
    registry-fetch-interval-seconds: 10 # indicates how often the Eureka Client pulls registration information from the server. The default is 30 seconds
    service-url:                        Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
Copy the code

Entity class

Product.java

package com.xxxx.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}
Copy the code

Order.java

package com.xxxx.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {

    private Integer id;
    private String orderNo;
    private String orderAddress;
    private Double totalPrice;
    private List<Product> productList;

}
Copy the code

Consumer services

ProductService.java

package com.xxxx.service;

import com.xxxx.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

// Declare the service to be invoked
@FeignClient("service-provider")
public interface ProductService {

    /** ** query the product list **@return* /
    // Configure the service address and parameters to be invoked
    @GetMapping("/product/list")
    List<Product> selectProductList(a);

}
Copy the code

OrderService.java

package com.xxxx.service;

import com.xxxx.pojo.Order;

public interface OrderService {

    /** * select order ** from primary key@param id
     * @return* /
    Order selectOrderById(Integer id);

}
Copy the code

OrderServiceImpl.java

package com.xxxx.service.impl;

import com.xxxx.pojo.Order;
import com.xxxx.service.OrderService;
import com.xxxx.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private ProductService productService;

    /** * select order ** from primary key@param id
     * @return* /
    @Override
    public Order selectOrderById(Integer id) {
        return new Order(id, "order-001"."China".22788D, productService.selectProductList()); }}Copy the code

Control layer

OrderController.java

package com.xxxx.controller;

import com.xxxx.pojo.Order;
import com.xxxx.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /** * select order ** from primary key@param id
     * @return* /
    @GetMapping("/{id}")
    public Order selectOrderById(@PathVariable("id") Integer id) {
        returnorderService.selectOrderById(id); }}Copy the code

Start the class

ServiceConsumerApplication.java

package com.xxxx;

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

@SpringBootApplication
// Enable the EurekaClient annotation, which is enabled by default in the current version if the Eureka registry is configured
//@EnableEurekaClient
// Enable the FeignClients annotation
@EnableFeignClients
public class ServiceConsumerApplication {

    public static void main(String[] args) { SpringApplication.run(ServiceConsumerApplication.class, args); }}Copy the code

access

The current operating results are as follows:

Feign Load balancing

Feign encapsulates the Ribbon and naturally integrates load balancing functions. By default, the Ribbon uses polling. How do I modify a load balancing policy? This is consistent with the configuration explained in the previous section of the Ribbon.

global

Inject load balancing policy objects into the startup class or configuration class. All service requests use this policy.

@Bean
public RandomRule randomRule(a) {
    return new RandomRule();
}
Copy the code

local

Modify the load balancing policy specified in the configuration file. Format: service application name. Ribbon. NFLoadBalancerRuleClassName

Load balancing policy
# service-provider specifies the name of the invoked service
service-provider:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Copy the code

Feign Requests parameter transmission

GET

Accept request parameters using the @PathVariable annotation or the @requestParam annotation.

Service provider

ProductService.java

/** * select * from primary key@param id
 * @return* /
Product selectProductById(Integer id);
Copy the code

ProductServiceImpl.java

/** * select * from primary key@param id
 * @return* /
@Override
public Product selectProductById(Integer id) {
    return new Product(id, "Refrigerator".1.2666D);
}
Copy the code

ProductController.java

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /** * select * from primary key@param id
     * @return* /
    @GetMapping("/{id}")
    public Product selectProductById(@PathVariable("id") Integer id) {
        returnproductService.selectProductById(id); }}Copy the code

Service consumer

ProductService.java

package com.example.service;

import com.example.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

// Declare the service to be invoked
@FeignClient("service-provider")
public interface ProductService {

    /** * select * from primary key@return* /
    @GetMapping("/product/{id}")
    Product selectProductById(@PathVariable("id") Integer id);

}
Copy the code

OrderServiceImpl.java

package com.example.service.impl;

import com.example.pojo.Order;
import com.example.service.OrderService;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private ProductService productService;

    /** * select order ** from primary key@param id
     * @return* /
    @Override
    public Order selectOrderById(Integer id) {
        return new Order(id, "order-003"."China".2666D,
                Arrays.asList(productService.selectProductById(5))); }}Copy the code

access

The results are as follows:

POST

Use the @requestBody annotation to receive request parameters.

Service provider

ProductService.java

/** * select * from primary key@param id
 * @return* /
Product queryProductById(Integer id);

/** ** new merchandise **@param product
 * @return* /
Map<Object, Object> createProduct(Product product);
Copy the code

ProductServiceImpl.java

/** * select * from primary key@param id
 * @return* /
@Override
public Product queryProductById(Integer id) {
    return new Product(id, "Refrigerator".1.2666D);
}

/** ** new merchandise **@param product
 * @return* /
@Override
public Map<Object, Object> createProduct(Product product) {
    System.out.println(product);
    return new HashMap<Object, Object>() {{
        put("code".200);
        put("message"."New success");
    }};
}
Copy the code

ProductController.java

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /** * select * from primary key@param id
     * @return* /
    @PostMapping("/single")
    public Product queryProductById(@RequestBody Integer id) {
        return productService.queryProductById(id);
    }

    /** ** new merchandise **@param product
     * @return* /
    @PostMapping("/save")
    public Map<Object, Object> createProduct(@RequestBody Product product) {
        returnproductService.createProduct(product); }}Copy the code

The article is reprinted with music bytes