SpringCloud实战微服务之OpenFeign


RestTemplate的缺点

//并不优雅的代码
restTemplate.getForObject("http://book- service/bsn?sn=" + sn,Book.class);

Feign与OpenFeign

 ◆Feign是一个开源声明式WebService客户端, 用于简化服务通信;

 ◆Feign采用“接口+注解”方式开发,屏蔽了网络通信的细节;

 ◆OpenFeign是SpringCloud对Feign的增强, 支持Spring MVC注解;

一、OpenFeign初体验

(1)创建OpenFeign项目

 

 

 

 

pom.xml如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 4     <modelVersion>4.0.0</modelVersion>
 5     <parent>
 6         <groupId>org.springframework.boot</groupId>
 7         <artifactId>spring-boot-starter-parent</artifactId>
 8         <version>2.2.2.RELEASE</version>
 9         <relativePath/> <!-- lookup parent from repository -->
10     </parent>
11     <groupId>com.angei</groupId>
12     <artifactId>eureka-client-openfeign</artifactId>
13     <version>0.0.1-SNAPSHOT</version>
14     <name>eureka-client-openfeign</name>
15     <description>Demo project for Spring Boot</description>
16 
17     <properties>
18         <java.version>1.8</java.version>
19         <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
20     </properties>
21 
22     <dependencies>
23         <dependency>
24             <groupId>org.springframework.boot</groupId>
25             <artifactId>spring-boot-starter-web</artifactId>
26         </dependency>
27         <dependency>
28             <groupId>org.springframework.cloud</groupId>
29             <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
30         </dependency>
31         <dependency>
32             <groupId>org.springframework.cloud</groupId>
33             <artifactId>spring-cloud-starter-openfeign</artifactId>
34         </dependency>
35 
36         <dependency>
37             <groupId>org.springframework.boot</groupId>
38             <artifactId>spring-boot-starter-test</artifactId>
39             <scope>test</scope>
40             <exclusions>
41                 <exclusion>
42                     <groupId>org.junit.vintage</groupId>
43                     <artifactId>junit-vintage-engine</artifactId>
44                 </exclusion>
45             </exclusions>
46         </dependency>
47     </dependencies>
48 
49     <dependencyManagement>
50         <dependencies>
51             <dependency>
52                 <groupId>org.springframework.cloud</groupId>
53                 <artifactId>spring-cloud-dependencies</artifactId>
54                 <version>${spring-cloud.version}</version>
55                 <type>pom</type>
56                 <scope>import</scope>
57             </dependency>
58         </dependencies>
59     </dependencyManagement>
60 
61     <build>
62         <plugins>
63             <plugin>
64                 <groupId>org.springframework.boot</groupId>
65                 <artifactId>spring-boot-maven-plugin</artifactId>
66             </plugin>
67         </plugins>
68     </build>
69 
70 </project>

(2)通过添加@EnableFeignClients的方式开启OpenFeign:

 1 import org.springframework.boot.SpringApplication;
 2 import org.springframework.boot.autoconfigure.SpringBootApplication;
 3 import org.springframework.cloud.openfeign.EnableFeignClients;
 4 
 5 @SpringBootApplication
 6 @EnableFeignClients //启用OpenFeign
 7 public class EurekaClientOpenfeignApplication {
 8 
 9     public static void main(String[] args) {
10         SpringApplication.run(EurekaClientOpenfeignApplication.class, args);
11     }
12 
13 }

(3)配置application.yml:

spring:
  application:
    name: eureka-client-openfeign
eureka:
  client:
    service-url:
      defaultZone:
        http://admin:admin@server1:8761/eureka/, http://admin:admin@server2:8762/eureka/, http://admin:admin@server3:8763/eureka/

(4)创建实体类Book:

 1 public class Book {
 2 
 3     private String sn;
 4     private String name;
 5     private String desc;
 6 
 7     public Book() {
 8     }
 9 
10     public Book(String sn, String name, String desc) {
11         this.sn = sn;
12         this.name = name;
13         this.desc = desc;
14     }
15 
16     public String getSn() {
17         return sn;
18     }
19 
20     public void setSn(String sn) {
21         this.sn = sn;
22     }
23 
24     public String getName() {
25         return name;
26     }
27 
28     public void setName(String name) {
29         this.name = name;
30     }
31 
32     public String getDesc() {
33         return desc;
34     }
35 
36     public void setDesc(String desc) {
37         this.desc = desc;
38     }
39 
40     public String toString() {
41         return "编号:" + this.sn + "\t书名:" + this.name + "\t描述信息:" + this.desc;
42     }
43 }

(5)相比于使用RestTemplate+@loadBalanced的组合时,通过getForObject()方法中配置url = http://eureka-provider/bsn?sn="+sn的方式获取另一个微服务的信息。

注意对比下面OpenFeign的使用方式:

项目结构图:

创建MemberService接口:

 1 import com.angei.eurekaclientopenfeign.entity.Book;
 2 import org.springframework.cloud.openfeign.FeignClient;
 3 import org.springframework.web.bind.annotation.GetMapping;
 4 import org.springframework.web.bind.annotation.RequestParam;
 5 
 6 //使用RestTemplate+@loadBalanced时:http://eureka-provider/bsn?sn="+sn
 7 @FeignClient(name="eureka-provider")  8 public interface MemberService {
 9 
10     @GetMapping("/bsn")
11     public Book findBySn(@RequestParam("sn") String sn);
12 }

创建MemberController:

 1 import com.angei.eurekaclientopenfeign.Service.MemberService;
 2 import com.angei.eurekaclientopenfeign.entity.Book;
 3 import org.springframework.web.bind.annotation.GetMapping;
 4 import org.springframework.web.bind.annotation.RestController;
 5 
 6 import javax.annotation.Resource;
 7 
 8 @RestController
 9 public class MemberController {
10 
11     @Resource
12     private MemberService service;
13 
14     @GetMapping("/borrow")
15     public String borrowBook(String sn) {
16         Book book = service.findBySn(sn);
17         return "【" + book.getName() + "】" + "借阅成功! 服务窗口:" + book.getDesc();
18     }
19 }

配置服务端口号(10000):

效果展示:

调用Eureka-client-openFeign的相关API:

 

二、OpenFeign的工作原理及其负载均衡策略

OpenFeign提供了许多负载均衡策略,其中一部分列举如下:

 

 

 全局配置如下:

 1 import com.netflix.loadbalancer.IRule;
 2 import com.netflix.loadbalancer.RandomRule;
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import org.springframework.cloud.openfeign.EnableFeignClients;
 6 import org.springframework.context.annotation.Bean;
 7 
 8 @SpringBootApplication
 9 @EnableFeignClients //启用OpenFeign
10 public class EurekaClientOpenfeignApplication {
11 
12     @Bean
13     public IRule ribbonRule(){
14         return new RandomRule();
15  }
16 
17     public static void main(String[] args) {
18 
19         SpringApplication.run(EurekaClientOpenfeignApplication.class, args);
20     }
21 
22 }

 

三、OpenFeign通信日志

OpenFeign开启通信日志

    ◆基于SpringBoot的logback输出,默认为debug级别

 ◆设置项: 在配置文件中通过feign.client.config.微服务id.loggerLevel进行配置;

 ◆微服务id: 若为default代表全局默认配置。

附:
输出级别:日志可以以何种级别输出:
    logger.trace("=====trace=====");
    logger.debug("=====debug=====");
    logger.info("=====info=====");
    logger.warn("=====warn=====");
    logger.error("=====error=====");
日志输出级别: TRACE > DEBUG > INFO > WARN > ERROR。

通信日志输出格式

 ◆NONE:不输出任何通信日志;

 ◆BASIC:只包含URL、请求方法、状态码、执行时间;

 ◆HEADERS:在BASIC基础上,额外包含请求与响应头;

 ◆FULL:包含请求与响应内容最完整的信息;

推荐:在测试的时候使用FULL,但是在线上的时候使用BASIC或者HEADERS即可。

  

 

 

  

  

 

四、替换OpenFeign通信组件

OpenFeign通信组件

 ◆OpenFeign基于JDK原生URLConnection提供Http通信;

 ◆OpenFeign支持Apache HttpClient与Square OkHttp;

 ◆SpringCloud按条件自动加载应用通信组件;

应用条件

 ◆Maven引入feign-okhttp或者feign-httpclient依赖;

 ◆设置feign.[httpclient|okhttp].enabled=true;

 

4.1 使用OkHttp

(1)添加feign-okhttp的依赖

<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

(2)在application.yml进行相关的配置

feign:
  okhttp:
    enabled: true

 

4.2 使用HttpClient

 (1)添加feign-httpclient的依赖

<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

 (2)在application.yml进行相关的配置

 1 spring:
 2   application:
 3     name: eureka-client-openfeign
 4 eureka:
 5   client:
 6     service-url:
 7       defaultZone:
 8         http://admin:admin@server1:8761/eureka/, http://admin:admin@server2:8762/eureka/, http://admin:admin@server3:8763/eureka/
 9 feign:
10   okhttp:
11     enabled: false
12   httpclient:
13     enabled: true

注意:okhttp和httpClient两者不可兼得!

 

五、OpenFeign多参数传递

POST方式传递对象使用@RequestBody注解描述参数

GET方式将对象转换为Map后,利用@RequestParam注解描述

微服务Eureka-provider的BookController代码更新至如下

 1 import com.angei.eurekaprovider.entity.Book;
 2 import org.springframework.web.bind.annotation.GetMapping;
 3 import org.springframework.web.bind.annotation.PostMapping;
 4 import org.springframework.web.bind.annotation.RequestBody;
 5 import org.springframework.web.bind.annotation.RestController;
 6 
 7 import javax.servlet.http.HttpServletRequest;
 8 import java.util.ArrayList;
 9 import java.util.List;
10 
11 @RestController
12 public class BookController {
13 
14 
15     @GetMapping("/bsn")
16     public Book findBookBySN(String sn, HttpServletRequest request) {
17         Book book = null;
18         if ("111".equals(sn)) {
19             book = new Book("111", "图书1", "服务器端口:" + request.getLocalPort());
20         } else if ("222".equals(sn)) {
21             book = new Book("222", "图书2", "服务器端口:" + request.getLocalPort());
22         } else if ("333".equals(sn)) {
23             book = new Book("333", "图书3", "服务器端口:" + request.getLocalPort());
24         }
25         return book;
26 
27     }
28 
29     @GetMapping("/get") 30     public List<Book> searchBook(Book book) {
31         //创建结果集对象
32         List ans = new ArrayList<Book>();
33         if ("08".equals(book.getSn()) && "漫画类".equals(book.getDesc())) {
34             ans.add(new Book("008", "哆啦A梦", "漫画类"));
35             ans.add(new Book("008", "钢铁侠", "漫画类"));
36             ans.add(new Book("008", "千与千寻", "漫画类"));
37         }
38         return ans;
39     }
40 
41     @PostMapping("/post") 42     public String createBook(@RequestBody Book book) {
43         return book.getName() + "添加成功!";
44     }
45 }

5.1 使用Get方式进行多参数传递

微服务Eureka-client的MemberService代码:

 1 import com.angei.eurekaclientopenfeign.entity.Book;
 2 import org.springframework.cloud.openfeign.FeignClient;
 3 import org.springframework.web.bind.annotation.GetMapping;
 4 import org.springframework.web.bind.annotation.PostMapping;
 5 import org.springframework.web.bind.annotation.RequestBody;
 6 import org.springframework.web.bind.annotation.RequestParam;
 7 
 8 import java.util.List;
 9 import java.util.Map;
10 
11 //http://eureka-provider/bsn?sn="+sn
12 @FeignClient(name = "eureka-provider")
13 public interface MemberService {
14 
15     @GetMapping("/bsn")
16     public Book findBySn(@RequestParam("sn") String sn);
17 
18     @GetMapping("/get")
19     public List<Book> searchBook(@RequestParam Map book);
20 
21 }

注意:上面的正确写法就是Map book,因为get请求对Book类型无法识别,所以折中方案就是Map。

微服务Eureka-client的MemberController代码:

 1 import com.angei.eurekaclientopenfeign.Service.MemberService;
 2 import com.angei.eurekaclientopenfeign.entity.Book;
 3 import org.springframework.web.bind.annotation.GetMapping;
 4 import org.springframework.web.bind.annotation.PostMapping;
 5 import org.springframework.web.bind.annotation.RestController;
 6 import javax.annotation.Resource;
 7 import java.util.HashMap;
 8 import java.util.List;
 9 import java.util.Map;
10 
11 @RestController
12 public class MemberController {
13 
14     @Resource
15     private MemberService service;
16 
17     @GetMapping("/borrow")
18     public String borrowBook(String sn) {
19         Book book = service.findBySn(sn);
20         return "【" + book.getName() + "】" + "借阅成功! 服务窗口:" + book.getDesc();
21     }
22 
23     @GetMapping("/get")
24     public List<Book> searchBook() {
25         Map param = new HashMap<>();
26         param.put("sn", "08");//注意:这里的Key值必须和对应实体类的属性名相同
27         param.put("desc", "漫画类");//注意:这里的Key值必须和对应实体类的属性名相同
28         List<Book> ans = service.searchBook(param);
29         return ans;
30     }
31 }

效果显示:

 

5.2 使用Post方式进行多参数传递

微服务Eureka-client的MemberService代码:

 1 import com.angei.eurekaclientopenfeign.entity.Book;
 2 import org.springframework.cloud.openfeign.FeignClient;
 3 import org.springframework.web.bind.annotation.GetMapping;
 4 import org.springframework.web.bind.annotation.PostMapping;
 5 import org.springframework.web.bind.annotation.RequestBody;
 6 import org.springframework.web.bind.annotation.RequestParam;
 7 
 8 import java.util.List;
 9 import java.util.Map;
10 
11 //http://eureka-provider/bsn?sn="+sn
12 @FeignClient(name = "eureka-provider")
13 public interface MemberService {
14 
15     @GetMapping("/bsn")
16     public Book findBySn(@RequestParam("sn") String sn);
17 
18     @PostMapping("/post") 19     public String createBook(@RequestBody Book book);
20 
21 }

微服务Eureka-client的MemberController代码:

import com.angei.eurekaclientopenfeign.Service.MemberService;
import com.angei.eurekaclientopenfeign.entity.Book;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class MemberController {

    @Resource
    private MemberService service;

    @GetMapping("/borrow")
    public String borrowBook(String sn) {
        Book book = service.findBySn(sn);
        return "【" + book.getName() + "】" + "借阅成功! 服务窗口:" + book.getDesc();
    }

    @PostMapping("/post")
    public String createBook() {
        Book b = new Book();
        b.setSn("100");
        b.setName("《一千零一夜》");
        b.setDesc("经典巨作");
        String result = service.createBook(b);
        return result;
    }
}

效果显示:

 

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM