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,他们之间的依赖关系如下图所示:
所以,接下来介绍一下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}
完整的代码请见:
https://github.com/zouchanglin/practic_code/tree/master/maven_test