Feign与项目多模块
本篇文章主要是记录了Feign的使用方式,并且重点讲述了使用Maven构建多模块项目,从而更好地适应微服务架构的软件开发模式。在服务调用的场景中,我们经常调用基于HTTP协议的服务,Feign封装了Http调用流程,更适合面向接口化的变成习惯。Feign底层使用了Ribbon作为负载均衡的客户端,而有关Ribbon的负载均衡的实现请见 《RestTemplate与负载均衡器》 。
一、Feign基本使用
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、启动类注解
@EnableFeignClients
3、声明伪RPC调用接口(因为本质还是HTTP)
@FeignClient(name = "SHOP-CLIENT")
public interface ShopClient {
@GetMapping("/shop/show")
String getShop();
}
4、注入接口对象并使用
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ShopClient shopClient;
@GetMapping
public String getOrder(){
return shopClient.getShop();
}
}
使用Feign可以让调用者无感知这是一个远程调用,获得与本地方法一致的体验
二、项目多模块改造
其实多模块改造主要就是为了复用,毕竟让调用者去声明Client端是不合理的,Client应该由服务端声明,也就是我自己知道自己提供了哪些服务,别人来使用这些服务即可,而不是别人看我的源代码才知道我提供哪些接口。另外像接口输入参数,输出参数也都是需要为调用者提供的。
商品服务有两个功能:查看所有商品、新增商品,商品在数据库中的定义如下:
@Data
@AllArgsConstructor
public class ShopInfo {
private String shopId;
private String shopName;
private Integer shopStock;
}
分别是商品ID、商品名称、商品库存等字段,我们需要给外界展示的是ID和名称,库存多少是没有必要展示费消费者的。同样的对于商城的管理系统来说,如果要添加新的商品,那么主键ID是没有必要让用户手动传入的,所以对于这两种情况分别就是此服务模块的输入参数和输出参数,分别定义对应的JavaBean:
@Data
public class ShopInfoInput {
private String shopName;
private Integer shopStock;
}
@Data
@AllArgsConstructor
public class ShopInfoOutput {
private String shopId;
private String shopName;
}
并且将这两个类放到shop-common模块中,作为商品服务的通用模块。接下来是逻辑的编写,应该放到shop-serivce模块中,shop-service和我们平时的SpringBoot工程无区别,实现的都是主体业务逻辑:
@RestController
@RequestMapping("/shop")
public class ShopController {
private static final List<ShopInfo> *list* = Arrays.*asList*(
new ShopInfo(UUID.*randomUUID*().toString(), "ThinkPad X1", 10),
new ShopInfo(UUID.*randomUUID*().toString(), "MacBook Air", 5),
new ShopInfo(UUID.*randomUUID*().toString(), "MacBook Pro", 20)
);
private static final CopyOnWriteArrayList<ShopInfo> *collect* = new CopyOnWriteArrayList<>(*list*);
@GetMapping("show")
public List<ShopInfoOutput> getAllShop(){
return *collect*.stream()
.map(x -> new ShopInfoOutput(x.getShopId(), x.getShopName()))
.collect(Collectors.*toList*());
}
@PostMapping("create")
public List<ShopInfoOutput> addOneShop(@RequestBody ShopInfoInput shopInfoInput){
*collect*.add(new ShopInfo(UUID.*randomUUID*().toString(), shopInfoInput.getShopName(), shopInfoInput.getShopStock()));
return *collect*.stream()
.map(x -> new ShopInfoOutput(x.getShopId(), x.getShopName()))
.collect(Collectors.*toList*());
}
}
@Data
@AllArgsConstructor
class ShopInfo {
private String shopId;
private String shopName;
private Integer shopStock;
}
同时配置好Eureka,在application.yml中:
server:
port: 8080
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: shop-client
最后需要把服务提供暴露给外界使用,所以直接使用Feign来完成shop-client模块的编写:
@FeignClient(name = "SHOP-CLIENT")
public interface ShopClient {
@GetMapping("/shop/show")
List<ShopInfoInput> getAllShop();
@PostMapping("/shop/create")
List<ShopInfoOutput> addOneShop(@RequestBody ShopInfoInput shopInfoInput);
}
通过上面的代码我们不难发现,项目被分成了三个模块,分别是shop-common、shop-service和shop-client,他们之间的依赖关系如下图所示:
所以,接下来介绍一下1个大工程的pom文件和3个小模块的pom文件,首先是父工程的pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/>
</parent>
<groupId>xpu.edu</groupId>
<artifactId>shop_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shop_server</name>
<description>Demo project for Spring Boot</description>
<!-- 共用的一些配置 -->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR4</spring-cloud.version>
<shop-common-version>0.0.1-SNAPSHOT</shop-common-version>
</properties>
<!-- 包含的子模块 -->
<modules>
<module>shop_client</module>
<module>shop_common</module>
<module>shop_service</module>
</modules>
<!-- 打包方式必须是pom -->
<packaging>pom</packaging>
<!-- 依赖管理 -->
<dependencyManagement>
<dependencies>
<!-- 公用模块 -->
<dependency>
<groupId>xpu.edu</groupId>
<artifactId>shop_common</artifactId>
<version>${shop-common-version}</version>
</dependency>
<!-- SpringCloud -->
<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>
父工程的还是基于SpringBoot,另外包含了一些公用配置,包含SpringCloud的版本等信息,还包含了公用模块的依赖。接下来看看shop-service模块:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>xpu.edu</groupId>
<artifactId>shop_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>shop_service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shop_service</name>
<dependencies>
<dependency>
<groupId>xpu.edu</groupId>
<artifactId>shop_common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这个模块和我们之前写的工程一致,各种必要的依赖(不要忘记公用模块shop-common),编译插件、SpringBoot插件。最后需要看的是shop-client模块,因为这个模块相当于是整个系统的使用手册:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>xpu.edu</groupId>
<artifactId>shop_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>shop_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shop_client</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>xpu.edu</groupId>
<artifactId>shop_common</artifactId>
</dependency>
</dependencies>
</project>
因为这个模块用到了@FeignClient(name = “SHOP-CLIENT”)、@GetMapping("/shop/show")、@PostMapping("/shop/create")等注解,所以需要引入spring-web、spring-cloud-openfeign-core等依赖,同样的公用模块需要引入,所以加上了shop-common这个模块的依赖。最后是shop-common的pom文件 :
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>xpu.edu</groupId>
<artifactId>shop_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>shop_common</artifactId>
<name>shop_common</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
其实就是引入了一个lombok,没啥其他的东西。
那么别的模块如何使用shop-server这个工程提供的服务呢?下面是一个order-server即订单服务。它也是一个多模块的项目,分为order-common、order-client、order-service。只是为了测试所以,order-common与order-client模块都是空的,我们直接使用order-service模块测试一下即可:
1、引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>edu.xpu</groupId>
<artifactId>order_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>order_service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order_service</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>xpu.edu</groupId>
<artifactId>shop_client</artifactId>
<version>${shop_client.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
</dependency>
<dependency>
<groupId>edu.xpu</groupId>
<artifactId>order_common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、添加注解,其实就是为了把FeignClient给添加到IOC容器中
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "xpu.edu.shop_client")
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
3、使用其他(shop-client)模块的FeignClient
@RestController
@RequestMapping("/")
public class TestShopClient {
@Autowired
private ShopClient shopClient;
@GetMapping
public String test(){
ShopInfoInput infoInput = new ShopInfoInput();
infoInput.setShopName("iPad Pro");
infoInput.setShopStock(100);
shopClient.addOneShop(infoInput);
return JSON.toJSONString(shopClient.getAllShop());
}
}
完整的代码请见:
https://github.com/zouchanglin/practic_code/tree/master/maven_test