Feign源码解析一
前言
本文将基于SpringCloud中Feign模块源码,解析其原理,并理解以下重点
-
使用Feign开发微服务一般的项目结构是什么样的?
-
Feign是什么?使用Feign模块可以为微服务开发带来哪些便利?
-
FeignClient注解的作用是?原理是什么?
接下来会一一解答以上三个问题
正文
问题1问到使用Feign开发微服务,项目结构是什么?
我们知道目前主流的开发模式都是微服务,一个项目会被分为多个服务同时开发,服务和服务之间采用Http请求的方式调用,比如现在有一个服务A,需要给服务B调用,我们目前的做法是每个微服务
中都会定义两个子module
,一个spi
,一个service
spi这个module会打成一个jar
,用来给服务调用者依赖
,并利用Feign Client
实现类似本地调用的方式发起Http调用
而service这个module则是该微服务真实的业务代码
以及Controller层实现
- 项目结构
假设以order服务为例
整体包结构
order.png
order-spi
order-spi模块作为提供给依赖订单服务的其他服务使用
pom.xml
文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>order</artifactId>
<groupId>com.wilson</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-spi</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.20</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
这里引入了openFeign以及swagger依赖,openFeign依赖用于提供@FeignClient注解支持,swagger依赖则提供swagger相关的注解支持
代码结构
spi包下定义了所有提供给其他服务调用的接口,domain下定义入参,出参类
com.wilson.order.spi.OrderSpi
接口
package com.wilson.order.spi;
import io.swagger.annotations.ApiParam;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("platform-order")
public interface OrderSpi {
@GetMapping(value = "/order/{orderCode}")
String queryOrder(@ApiParam(value = "订单号", required = true) @PathVariable("orderCode") String orderCode);
}
至此order-spi整体结构完成,下面看order-service模块
-
order-service
模块
pom.xml
文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>order</artifactId>
<groupId>com.wilson</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>com.wilson</groupId>
<artifactId>order-spi</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
</dependency>
</dependencies>
</project>
pom中主要依赖以下:
-
order-spi
-
web
-
eureka-client
代码结构
application.yml
文件
# 监听端口
server:
port: 8080
# 注册中心地址,我本地机器启动的一个注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:8716/eureka
# 和order-spi的注解@FeignClient value保持一致,即注册到eureka的应用名
spring:
application:
name: platform-order
OrderServiceApplication
类
package com.wilson.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author shuyuan
* @since 2019/6/4
*/
@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
最后看下OrderController代码实现
package com.wilson.order.controller;
import com.wilson.order.spi.OrderSpi;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author shuyuan
* @since 2019/6/4
*/
@RestController
public class OrderController implements OrderSpi {
/**
* 查询订单支付状态
* @param orderCode 订单号
* @return 订单支付状态
*/
@Override
@GetMapping(value = "/order/{orderCode}")
public String queryOrder(@PathVariable("orderCode") String orderCode) {
// 可以直接在这一层写业务逻辑,也可以再调用一层OrderService来完成订单服务
// 1. 参数校验
// 2. 查询订单数据库
// 3. 返回订单支付状态
return "SUCCESS";
}
}
另外提一点,微服务开发中,我们应该始终将内部异常捕获后封装成统一的返回值给到服务调用者
,不同的异常返回不同的错误码(errorCode
)和错误描述(errorMsg
),可以利用一个GlobalExeceptionHandler实现,网上随便搜一下一大堆,这里不在赘述
至此整个order-service的代码实现也完成了,下面我们会新建一个用户服务,模拟调用订单服务完成订单支付状态的查询接口调用
user-service
用户微服务
定义一个用户微服务,用来模拟用feignClient的方式调用订单微服务接口
-
user-service.png项目结构
-
pom.xml
文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>order</artifactId>
<groupId>com.wilson</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>user-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>user-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 需要调用order服务,依赖order-spi -->
<dependency>
<groupId>com.wilson</groupId>
<artifactId>order-spi</artifactId>
<version>0.0.1-SNAPSHOT</version>
</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</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意这里依赖了order-spi,以及eureka-client
applicetion.yml文件
server:
port: 8086
# 注册中心地址,我本地机器启动的一个注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:8716/eureka
# 注册到eureka的应用名
spring:
application:
name: platform-user
UserServiceApplication启动类
package com.wilson.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients(basePackages = {"com.wilson.order.spi"})
@EnableEurekaClient
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
注意这里用到了@EnableFeignClients
注解,该注解的意思是启用扫描作为FeignClient的接口
-
UserController
定义一个Controller用于测试user服务的http请求,内部处理调用order服务的feignClient接口
package com.wilson.userservice.controller;
import com.wilson.order.spi.OrderSpi;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author shuyuan
* @since 2019/6/6
*/
@RestController
public class UserController {
@Resource
private OrderSpi orderSpi;
@RequestMapping("/user/{userId}/order/{orderId}")
public String getOrderSuccessByUserId(@PathVariable("userId") String userId, @PathVariable("orderId") String orderId) {
// 调用userService 查询user信息
// 调用feignClient order-spi查询订单信息
return orderSpi.queryOrder(orderId);
}
}
分别启动user-service以及order-service两个服务,以及本地启动的eureka注册中心
启动后的效果如下
eureka.png
至此整个基础工作搭建完毕,我们发起user服务暴露的http请求测试以下效果
打开浏览器访问
http://localhost:8086/user/1/order/2
为避免篇幅过长,我会在下一篇博文分析Feign调用原理