Feign与项目多模块

本篇文章主要是记录了Feign的使用方式,并且重点讲述了使用Maven构建多模块项目,从而更好地适应微服务架构的软件开发模式。在服务调用的场景中,我们经常调用基于HTTP协议的服务,Feign封装了Http调用流程,更适合面向接口化的变成习惯。Feign底层使用了Ribbon作为负载均衡的客户端,而有关Ribbon的负载均衡的实现请见 《RestTemplate与负载均衡器》

一、Feign基本使用

1、引入依赖

1<dependency>
2  <groupId>org.springframework.cloud</groupId>
3  <artifactId>spring-cloud-starter-openfeign</artifactId>
4</dependency>

2、启动类注解

1@EnableFeignClients

3、声明伪RPC调用接口(因为本质还是HTTP)

1@FeignClient(name = "SHOP-CLIENT")
2public interface ShopClient {
3    @GetMapping("/shop/show")
4    String getShop();
5}

4、注入接口对象并使用

 1@RestController
 2@RequestMapping("/order")
 3public class OrderController {
 4
 5    @Autowired
 6    private ShopClient shopClient;
 7
 8    @GetMapping
 9    public String getOrder(){
10       return shopClient.getShop();
11    }
12}

使用Feign可以让调用者无感知这是一个远程调用,获得与本地方法一致的体验

二、项目多模块改造

其实多模块改造主要就是为了复用,毕竟让调用者去声明Client端是不合理的,Client应该由服务端声明,也就是我自己知道自己提供了哪些服务,别人来使用这些服务即可,而不是别人看我的源代码才知道我提供哪些接口。另外像接口输入参数,输出参数也都是需要为调用者提供的。

商品服务有两个功能:查看所有商品、新增商品,商品在数据库中的定义如下:

1@Data
2@AllArgsConstructor
3public class ShopInfo {
4    private String shopId;
5    private String shopName;
6    private Integer shopStock;
7}

分别是商品ID、商品名称、商品库存等字段,我们需要给外界展示的是ID和名称,库存多少是没有必要展示费消费者的。同样的对于商城的管理系统来说,如果要添加新的商品,那么主键ID是没有必要让用户手动传入的,所以对于这两种情况分别就是此服务模块的输入参数和输出参数,分别定义对应的JavaBean:

 1@Data
 2public class ShopInfoInput {
 3    private String shopName;
 4    private Integer shopStock;
 5}
 6 
 7@Data
 8@AllArgsConstructor
 9public class ShopInfoOutput {
10    private String shopId;
11    private String shopName;
12}

并且将这两个类放到shop-common模块中,作为商品服务的通用模块。接下来是逻辑的编写,应该放到shop-serivce模块中,shop-service和我们平时的SpringBoot工程无区别,实现的都是主体业务逻辑:

 1@RestController
 2@RequestMapping("/shop")
 3public class ShopController {
 4  private static final List<ShopInfo> *list* = Arrays.*asList*(
 5      new ShopInfo(UUID.*randomUUID*().toString(), "ThinkPad X1", 10),
 6      new ShopInfo(UUID.*randomUUID*().toString(), "MacBook Air", 5),
 7      new ShopInfo(UUID.*randomUUID*().toString(), "MacBook Pro", 20)
 8      );
 9  private static final CopyOnWriteArrayList<ShopInfo> *collect* = new CopyOnWriteArrayList<>(*list*);
10
11  @GetMapping("show")
12  public List<ShopInfoOutput> getAllShop(){
13    return *collect*.stream()
14        .map(x -> new ShopInfoOutput(x.getShopId(), x.getShopName()))
15        .collect(Collectors.*toList*());
16  }
17
18  @PostMapping("create")
19  public List<ShopInfoOutput> addOneShop(@RequestBody ShopInfoInput shopInfoInput){
20    *collect*.add(new ShopInfo(UUID.*randomUUID*().toString(), shopInfoInput.getShopName(), shopInfoInput.getShopStock()));
21    return *collect*.stream()
22        .map(x -> new ShopInfoOutput(x.getShopId(), x.getShopName()))
23        .collect(Collectors.*toList*());
24  }
25 }
26 
27 @Data
28 @AllArgsConstructor
29 class ShopInfo {
30   private String shopId;
31   private String shopName;
32   private Integer shopStock;
33 }

同时配置好Eureka,在application.yml中:

1server:
2  port: 8080
3eureka:
4  client:
5    service-url:
6      defaultZone: http://localhost:8761/eureka/
7spring:
8  application:
9    name: shop-client

最后需要把服务提供暴露给外界使用,所以直接使用Feign来完成shop-client模块的编写:

1@FeignClient(name = "SHOP-CLIENT")
2public interface ShopClient {
3
4    @GetMapping("/shop/show")
5    List<ShopInfoInput> getAllShop();
6
7    @PostMapping("/shop/create")
8    List<ShopInfoOutput> addOneShop(@RequestBody ShopInfoInput shopInfoInput);
9}

通过上面的代码我们不难发现,项目被分成了三个模块,分别是shop-common、shop-service和shop-client,他们之间的依赖关系如下图所示:

mark

所以,接下来介绍一下1个大工程的pom文件和3个小模块的pom文件,首先是父工程的pom文件:

 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    
 6    <!-- Spring Boot版本 -->
 7    <parent>
 8        <groupId>org.springframework.boot</groupId>
 9        <artifactId>spring-boot-starter-parent</artifactId>
10        <version>2.3.0.RELEASE</version>
11        <relativePath/>
12    </parent>
13    
14    <groupId>xpu.edu</groupId>
15    <artifactId>shop_server</artifactId>
16    <version>0.0.1-SNAPSHOT</version>
17    <name>shop_server</name>
18    <description>Demo project for Spring Boot</description>
19    
20    <!-- 共用的一些配置 -->
21    <properties>
22        <java.version>1.8</java.version>
23        <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
24        <shop-common-version>0.0.1-SNAPSHOT</shop-common-version>
25    </properties>
26
27    <!-- 包含的子模块 -->
28    <modules>
29        <module>shop_client</module>
30        <module>shop_common</module>
31        <module>shop_service</module>
32    </modules>
33    <!-- 打包方式必须是pom -->
34    <packaging>pom</packaging>
35
36    <!-- 依赖管理 -->
37    <dependencyManagement>
38        <dependencies>
39            <!-- 公用模块 -->
40            <dependency>
41                <groupId>xpu.edu</groupId>
42                <artifactId>shop_common</artifactId>
43                <version>${shop-common-version}</version>
44            </dependency>
45            <!-- SpringCloud -->
46            <dependency>
47                <groupId>org.springframework.cloud</groupId>
48                <artifactId>spring-cloud-dependencies</artifactId>
49                <version>${spring-cloud.version}</version>
50                <type>pom</type>
51                <scope>import</scope>
52            </dependency>
53        </dependencies>
54    </dependencyManagement>
55</project>

父工程的还是基于SpringBoot,另外包含了一些公用配置,包含SpringCloud的版本等信息,还包含了公用模块的依赖。接下来看看shop-service模块:

 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>xpu.edu</groupId>
 7        <artifactId>shop_server</artifactId>
 8        <version>0.0.1-SNAPSHOT</version>
 9    </parent>
10    <artifactId>shop_service</artifactId>
11    <version>0.0.1-SNAPSHOT</version>
12    <name>shop_service</name>
13
14    <dependencies>
15        <dependency>
16            <groupId>xpu.edu</groupId>
17            <artifactId>shop_common</artifactId>
18        </dependency>
19        <dependency>
20            <groupId>org.springframework.boot</groupId>
21            <artifactId>spring-boot-starter</artifactId>
22        </dependency>
23
24        <dependency>
25            <groupId>org.springframework.boot</groupId>
26            <artifactId>spring-boot-starter-web</artifactId>
27        </dependency>
28
29        <dependency>
30            <groupId>org.projectlombok</groupId>
31            <artifactId>lombok</artifactId>
32        </dependency>
33
34        <dependency>
35            <groupId>org.springframework.cloud</groupId>
36            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
37        </dependency>
38
39        <dependency>
40            <groupId>com.google.code.gson</groupId>
41            <artifactId>gson</artifactId>
42        </dependency>
43
44        <dependency>
45            <groupId>org.springframework.boot</groupId>
46            <artifactId>spring-boot-starter-test</artifactId>
47            <scope>test</scope>
48            <exclusions>
49                <exclusion>
50                    <groupId>org.junit.vintage</groupId>
51                    <artifactId>junit-vintage-engine</artifactId>
52                </exclusion>
53            </exclusions>
54        </dependency>
55    </dependencies>
56
57    <build>
58        <plugins>
59            <plugin>
60                <groupId>org.apache.maven.plugins</groupId>
61                <artifactId>maven-compiler-plugin</artifactId>
62                <configuration>
63                    <source>1.8</source>
64                    <target>1.8</target>
65                    <encoding>UTF-8</encoding>
66                </configuration>
67            </plugin>
68            <plugin>
69                <groupId>org.springframework.boot</groupId>
70                <artifactId>spring-boot-maven-plugin</artifactId>
71            </plugin>
72        </plugins>
73    </build>
74</project>

这个模块和我们之前写的工程一致,各种必要的依赖(不要忘记公用模块shop-common),编译插件、SpringBoot插件。最后需要看的是shop-client模块,因为这个模块相当于是整个系统的使用手册:

 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>xpu.edu</groupId>
 7        <artifactId>shop_server</artifactId>
 8        <version>0.0.1-SNAPSHOT</version>
 9    </parent>
10    <artifactId>shop_client</artifactId>
11    <version>0.0.1-SNAPSHOT</version>
12    <name>shop_client</name>
13
14    <packaging>jar</packaging>
15
16    <dependencies>
17        <dependency>
18            <groupId>org.springframework.cloud</groupId>
19            <artifactId>spring-cloud-openfeign-core</artifactId>
20        </dependency>
21
22        <dependency>
23            <groupId>org.springframework</groupId>
24            <artifactId>spring-web</artifactId>
25        </dependency>
26
27        <dependency>
28            <groupId>xpu.edu</groupId>
29            <artifactId>shop_common</artifactId>
30        </dependency>
31    </dependencies>
32</project>

因为这个模块用到了@FeignClient(name = “SHOP-CLIENT”)、@GetMapping("/shop/show")、@PostMapping("/shop/create")等注解,所以需要引入spring-web、spring-cloud-openfeign-core等依赖,同样的公用模块需要引入,所以加上了shop-common这个模块的依赖。最后是shop-common的pom文件 :

 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>xpu.edu</groupId>
 7        <artifactId>shop_server</artifactId>
 8        <version>0.0.1-SNAPSHOT</version>
 9    </parent>
10    <artifactId>shop_common</artifactId>
11    <name>shop_common</name>
12    <packaging>jar</packaging>
13    <dependencies>
14        <dependency>
15            <groupId>org.projectlombok</groupId>
16            <artifactId>lombok</artifactId>
17        </dependency>
18    </dependencies>
19</project>

其实就是引入了一个lombok,没啥其他的东西。

那么别的模块如何使用shop-server这个工程提供的服务呢?下面是一个order-server即订单服务。它也是一个多模块的项目,分为order-common、order-client、order-service。只是为了测试所以,order-common与order-client模块都是空的,我们直接使用order-service模块测试一下即可:

1、引入依赖

 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>edu.xpu</groupId>
 7        <artifactId>order_server</artifactId>
 8        <version>0.0.1-SNAPSHOT</version>
 9    </parent>
10    <artifactId>order_service</artifactId>
11    <version>0.0.1-SNAPSHOT</version>
12    <name>order_service</name>
13    <description>Demo project for Spring Boot</description>
14    <dependencies>
15        <dependency>
16            <groupId>org.springframework.cloud</groupId>
17            <artifactId>spring-cloud-starter-openfeign</artifactId>
18        </dependency>
19        <dependency>
20            <groupId>xpu.edu</groupId>
21            <artifactId>shop_client</artifactId>
22            <version>${shop_client.version}</version>
23        </dependency>
24        <dependency>
25            <groupId>com.alibaba</groupId>
26            <artifactId>fastjson</artifactId>
27            <version>1.2.67</version>
28        </dependency>
29        <dependency>
30            <groupId>edu.xpu</groupId>
31            <artifactId>order_common</artifactId>
32        </dependency>
33
34        <dependency>
35            <groupId>org.springframework.boot</groupId>
36            <artifactId>spring-boot-starter</artifactId>
37        </dependency>
38
39        <dependency>
40            <groupId>org.springframework.boot</groupId>
41            <artifactId>spring-boot-starter-web</artifactId>
42        </dependency>
43
44        <dependency>
45            <groupId>org.projectlombok</groupId>
46            <artifactId>lombok</artifactId>
47        </dependency>
48
49        <dependency>
50            <groupId>org.springframework.cloud</groupId>
51            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
52        </dependency>
53
54        <dependency>
55            <groupId>org.springframework.boot</groupId>
56            <artifactId>spring-boot-starter-test</artifactId>
57            <scope>test</scope>
58            <exclusions>
59                <exclusion>
60                    <groupId>org.junit.vintage</groupId>
61                    <artifactId>junit-vintage-engine</artifactId>
62                </exclusion>
63            </exclusions>
64        </dependency>
65    </dependencies>
66
67    <build>
68        <plugins>
69            <plugin>
70                <groupId>org.apache.maven.plugins</groupId>
71                <artifactId>maven-compiler-plugin</artifactId>
72                <configuration>
73                    <source>1.8</source>
74                    <target>1.8</target>
75                    <encoding>UTF-8</encoding>
76                </configuration>
77            </plugin>
78            <plugin>
79                <groupId>org.springframework.boot</groupId>
80                <artifactId>spring-boot-maven-plugin</artifactId>
81            </plugin>
82        </plugins>
83    </build>
84</project>

2、添加注解,其实就是为了把FeignClient给添加到IOC容器中

1@SpringBootApplication
2@EnableDiscoveryClient
3@EnableFeignClients(basePackages = "xpu.edu.shop_client")
4public class OrderServiceApplication {
5
6    public static void main(String[] args) {
7        SpringApplication.run(OrderServiceApplication.class, args);
8    }
9}

3、使用其他(shop-client)模块的FeignClient

 1@RestController
 2@RequestMapping("/")
 3public class TestShopClient {
 4    @Autowired
 5    private ShopClient shopClient;
 6
 7    @GetMapping
 8    public String test(){
 9        ShopInfoInput infoInput = new ShopInfoInput();
10        infoInput.setShopName("iPad Pro");
11        infoInput.setShopStock(100);
12        shopClient.addOneShop(infoInput);
13        return JSON.toJSONString(shopClient.getAllShop());
14    }
15}

mark

完整的代码请见:

https://github.com/zouchanglin/practic_code/tree/master/maven_test