基于Spring Cloud集成OpenFeign应用feign客户端调用微服务
记录:387
场景:在基于Spring Cloud微服务架构,使用nacos实现微服务注册和配置,使用OpenFeign实现微服务之间Restful接口调用。
官网:https://spring.io/projects/spring-cloud-openfeign
源码:https://github.com/OpenFeign/feign
技术文档:https://docs.spring.io/spring-cloud-openfeign/docs/
1.初始化准备
1.1准备nacos
Nacos版本:Nacos 2.1.1。
(1)启动和登录nacos
启动命令:sh startup.sh -m standalone
地址:http://127.0.0.1:8848/nacos
用户名/口令:nacos/nacos
(2)创建命名空间
命名空间ID:aa3eebb6-daa2-4db8-9a29-03dd8a17db15
命名空间名:hub
1.2创建Maven工程
使用IntelliJ IDEA创建Maven工程。
(1)微服务名称
名称:hub-example-302-feign
(2)微服务groupId和artifactId
groupId: com.hub
artifactId: hub-example-302-feign
(3)微服务核心模块版本
spring-boot 2.6.3
spring-cloud 2021.0.1
spring-cloud-alibaba 2021.0.1.0
2.修改pom.xml
修改pom.xml,引入项目依赖Jar和管理Jar包。
2.1修改pom.xml文件
(1)spring-cloud-openfeign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
解析:引入sentinel,本例使用sentinel作为微服务熔断回调。
(2)nacos依赖
<!--nacos注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.1.0</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
2.2全量pom.xml文件
全量pom.xml文件请参考附录:2.11.1全量pom.xml文件
3.创建bootstrap.yml文件和配置nacos
3.1创建bootstrap.yml
server:
port: 18302
servlet:
context-path: /hub-302-feign
spring:
application:
name: hub-example-302-feign
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:18848
username: nacos
password: nacos
namespace: aa3eebb6-daa2-4db8-9a29-03dd8a17db15
group: DEFAULT_GROUP
config:
server-addr: 127.0.0.1:18848
username: nacos
password: nacos
namespace: aa3eebb6-daa2-4db8-9a29-03dd8a17db15
group: DEFAULT_GROUP
file-extension: yaml
shared-configs:
- dataId: hub-example01-config.yml
group: DEFAULT_GROUP
refresh: true
3.2配置nacos
Data ID:hub-example01-config.yml
Group:DEFAULT_GROUP
配置格式:YAML
配置内容:
spring:
main:
allow-bean-definition-overriding: true
jackson:
time-zone: GMT+8
server:
max-http-header-size: 51200
feign:
client:
config:
default:
connectTimeout: 20000
readTimeout: 90000
compression:
request:
enabled: true
response:
enabled: true
sentinel:
enabled: true
httpclient:
enabled: false
okhttp:
enabled: true
解析:feign.sentinel.enabled,必须为true,否则feign的FallbackFactory实现类不会生效。
4.创建启动类
4.1创建包
com.hub.example.domain:微服务使用到的DTO等实体类。
com.hub.example.controller:Controller类,发布Restful接口。
4.2启动类
包名:com.hub.example。
启动类:HubExampleFeignApplication。
(1)内容
@SpringBootApplication
@ComponentScan(basePackages = "com.hub.example.*")
@EnableFeignClients(basePackages = "com.hub.example.*")
public class HubExampleFeignApplication {
public static void main(String[] args) {
SpringApplication.run(HubExampleFeignApplication.class, args);
}
}
(2)解析
@SpringBootApplication,SpringBoot标记启动类的注解。
@ComponentScan,扫描指定的包,将组件加载到IOC容器中。
@EnableFeignClients,扫描指定Feign接口。
5.编写Controller代码
5.1Controller代码
@RestController
@RequestMapping("/hub/example/city")
@RefreshScope
@Slf4j
public class CityController {
@Autowired
private CityService cityService;
@PostMapping("/queryCityByCityId")
public ResultObj<CityDTO> queryCityByCityId(String cityId) {
return cityService.queryCityByCityId(cityId);
}
}
6.编写Service代码
6.1接口
public interface CityService {
ResultObj<CityDTO> queryCityByCityId(String cityId);
}
6.2实现类
@Service
public class CityServiceImpl implements CityService {
@Autowired
private CityFeignService cityFeignService;
@Override
public ResultObj<CityDTO> queryCityByCityId(String cityId) {
return cityFeignService.queryCityByCityId(cityId);
}
}
7.编写feign接口和回调类以及配置类代码
7.1编写feign接口
(1)代码
@FeignClient(contextId = "cityFeignService",
value = "hub-example-301-nacos",
fallbackFactory = CityFeignServiceFallbackFactory.class,
configuration={FeignConfiguration.class})
public interface CityFeignService {
@PostMapping("/hub-301-nacos/hub/example/city/queryCityByCityId")
ResultObj<CityDTO> queryCityByCityId(String cityId);
}
(2)解析
@FeignClient,标记是Feign接口。contextId,指定接口唯一标识。value,指定使用Feign接口调用的微服务名称,必须在nacos已注册。fallbackFactory,指定Feign接口调用报错时的回调类。这个类在使用熔断和降级机制时,都会回调。configuration,指定当前微服务需要传递到下一个微服务的配置信息,比如请求头信息传递到下一个微服务。
@PostMapping("/hub-301-nacos/hub/example/city/queryCityByCityId"),指定调用目标微服务的全路径。
7.2编写回调类FallbackFactory
回调类继承feign的FallbackFactory接口,并实现方法。
@Slf4j
@Component
public class CityFeignServiceFallbackFactory implements FallbackFactory<CityFeignService> {
@Override
public CityFeignService create(Throwable cause) {
log.info("调用FallbackFactory的create方法.");
CityFeignServiceFallbackImpl impl = new CityFeignServiceFallbackImpl();
impl.setThrowable(cause);
return impl;
}
}
7.3编写回调类实现类
回调实现类,实际就是实现feign接口,方法也是给回调类是使用。
@Slf4j
@Component
public class CityFeignServiceFallbackImpl implements CityFeignService {
private Throwable throwable;
@Override
public ResultObj<CityDTO> queryCityByCityId(String cityId) {
log.error("CityFeignService调用微服务失败.");
this.throwable.printStackTrace();
return ResultObj.fail("CityFeignService调用微服务失败.");
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}
}
7.4编写配置类
(1)RequestInterceptor接口实现代码
@Configuration
public class FeignConfiguration implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
RequestAttributes reqAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) reqAttributes).getRequest();
String cityCode = request.getHeader("cityCode");
requestTemplate.header("cityCode", cityCode);
}
}
(2)RequestInterceptor功能
编写配置类,就是实现feign的feign.RequestInterceptor接口。RequestInterceptor实际上就是一个拦截器。
功能就是使用feign接口调用微服务前,可以在请求对象中添加配置,比如添加请求头信息,传递到下一个微服务。
(3)RequestInterceptor功能和使用场景
场景:浏览器->微服务A->微服务B,调用路径中,请求头cityCode需传递到微服务A和微服务B。
默认情况下,从浏览器到微服务A的请求头信息,在微服务A调用微服务B时,请求头信息不会被传递微服务B,如需传递,就必须实现feign.RequestInterceptor接口。
8.支撑对象
8.1CityDTO
@Data
public class CityDTO implements Serializable {
private Long cityId;
private String cityName;
private Double landArea;
private Long population;
private Double gross;
private String cityDescribe;
private String dataYear;
@JsonFormat(
pattern = "yyyy-MM-dd HH:mm:ss"
)
private Date updateTime;
}
8.2ResultObj
@Data
public class ResultObj<T> implements Serializable {
private int code;
private boolean success;
private String msg;
private T data;
private ResultObj(int code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
this.success = code == 200;
}
public static <T> ResultObj<T> data(int code, T data, String msg) {
return new ResultObj<>(code, data, msg);
}
}
9.使用Postman工具测试
使用Postman工具测试。
测试地址:http://127.0.0.1:18301/hub-301-nacos/hub/example/city/queryCityByCityId
设置请求头:cityCode=310001
测试入参:cityId=20230322
返回结果:
{
"code": 200,
"success": true,
"msg": "执行成功",
"data": {
"cityId": 20230322,
"cityName": "杭州",
"landArea": null,
"population": null,
"gross": null,
"cityDescribe": null,
"dataYear": null,
"updateTime": "2023-03-22 21:21:55"
}
}
10.在微服务A和微服B中分别获取请求头方式
10.1注入HttpServletRequest
在Controller中注入HttpServletRequest对象。
@Autowired
private HttpServletRequest request;
10.2在方法中获取请求头
log.info("cityCode=" + request.getHeader("cityCode"));
11.附录
11.1全量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>
<groupId>com.hub.example</groupId>
<artifactId>hub-example-302-feign</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
</parent>
<description>集成feign框架应用</description>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring.boot.maven.plugin.version>2.6.3</spring.boot.maven.plugin.version>
<spring.boot.version>2.6.3</spring.boot.version>
<spring.cloud.version>2021.0.1</spring.cloud.version>
<spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
<nacos.client.version>2.1.1</nacos.client.version>
<lombok.version>1.18.24</lombok.version>
<guava.version>30.1-jre</guava.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--nacos注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos.client.version}</version>
</dependency>
<!--feign配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.maven.plugin.version}</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
以上,感谢。
2023年3月22日