基于Eureka搭建Springcloud微服务-15.OpenFeign高级用法
原创2020年6月28日大约 5 分钟约 1551 字
15.OpenFeign高级用法
15.1.章节内容概述
本章节涉及主要内容有:
15.1.章节内容概述
15.2.章节内容大纲
15.3.搭建服务消费者
15.4.测试OpenFeign高级用法
具体每个小节中包含的内容可使通过下面的章节内容大纲进行查看。
15.2.章节内容大纲
15.3.搭建服务消费者
15.3.1.模块简介
集成了OpenFeign高级用法服务消费者,包括OpenFeign拦截器和OpenFeign动态服务名调用,启动端口: 80
15.3.2.模块目录结构
springcloud-consumer-loadbalance-openfeign-configuration-openfeign-senior-order80
|-- src
| •-- main
| |-- java
| | •-- org
| | •-- openatom
| | •-- springcloud
| | |-- config
| | | |-- DynamicFeignClientFactoryConfig.java
| | | •-- OpenFeignConfig.java
| | |-- controller
| | | |-- OrderConsumerController.java
| | | •-- OrderConsumerControllerDynamicFeignClientFactory.java
| | |-- interceptor
| | | •-- FeignClientRequestInterceptor.java
| | |-- service
| | | |-- PaymentServiceOpenFeign.java
| | | •-- PaymentServiceOpenFeignDynamicFeignClientFactory.java
| | •-- OrderServiceConsumerLoadBalanceOpenFeignDynamicServiceName80.java
| •-- resources
| •-- application.yml
•-- pom.xml
15.3.3.创建模块
在父工程(springcloud-eureka)中创建一个名为springcloud-consumer-loadbalance-openfeign-configuration-openfeign-senior-order80的maven模块,注意:当前模块创建成功后,在父工程pom.xml中<modules></modules>中会自动生成有关当前模块的信息
15.3.4.编写模块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>springcloud-eureka</artifactId>
<groupId>org.openatom</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-loadbalance-openfeign-configuration-openfeign-senior-order80</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入公共的工程-->
<dependency>
<groupId>org.openatom</groupId>
<artifactId>springcloud-api-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<!--热部署需要加这个-->
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<!--把当前插件repackage命令和maven的package绑定-->
<id>repackage-original</id>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources/</directory>
<includes>
<!--不区分环境:直接加载application.yml配置文件-->
<include>application.yml</include>
</includes>
</resource>
</resources>
</build>
</project>
15.3.5.编写模块application.yml
server:
port: 80
spring:
application:
name: SPRINGCLOUD-CONSUMER-LOADBALANCE-OPENFEIGN-CONFIGURATION-OPENFEIGN-SENIOR-ORDER80 #注意:服务名不要出现_
devtools:
restart:
enabled: true
logging: #Spring运行日志配置
level: info
eureka:
client:
register-with-eureka: true #表示是否将自己注册进EurekaServer默认为true。
fetchRegistry: true #是否从EurekaServer抓取已有的注册信息,默认为true。服务提供端是单节点无所谓,是集群必须设置为true才能配合ribbon使用负载均衡,否则报异常No instances available for SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER
service-url:
#单机版
defaultZone: http://localhost:7001/eureka
#集群版
#defaultZone: http://eureka7002:7002/eureka,http://eureka7003:7003/eureka,http://eureka7004:7004/eureka
instance:
instance-id: ${spring.application.name} #Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容
#当前微服务所在部署机器ip
ip-address: localhost
prefer-ip-address: false #访问路径可以显示IP地址,点击Eureka仪表盘中Instances currently registered with Eureka.Status显示的内容地址栏是否显示IP地址
#服务提供端信息
service:
provider:
provider-1: &provider-1 SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER #服务提供端名称
#某个/某些服务的Ribbon配置
*provider-1: #服务提供端名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #Ribbon负载均衡规则类所在的路径,自带七种规则,也可以是自定位规则的类所在的路径
#对OpenFeign进行单独配置
feign:
client:
config:
#这里填具体的服务名称(也可以填default,表示对所有服务生效)
*provider-1: #服务提供端名称
#connectTimeout和readTimeout这两个得一起配置才会生效
connectTimeout: 5000 #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
readTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间
15.3.6.编写模块service实现类
PaymentServiceOpenFeign.java
package org.openatom.springcloud.service;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 使用系统自带的OpenFeignClient发起调用
*/
@Component
@FeignClient(name="SPRINGCLOUD-PROVIDER-PAYMENT-SERVICE-CLUSTER")
public interface PaymentServiceOpenFeign {
@PostMapping(value = "/provider/payment/create")
CommonResult create(@RequestBody Payment payment);
@GetMapping(value = "/provider/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
/**
* 使用拦截器替换路由服务提供端URL中的占位符
* @param id
* @return
*/
@GetMapping(value = "/provider/$targetStr/get/{id}")
CommonResult<Payment> getPaymentByIdReplaceRouter(@PathVariable("id") Long id);
@GetMapping(value = "/provider/payment/openfeign/timeout")
String getPaymentByIdTimeout();
}
PaymentServiceOpenFeignDynamicFeignClientFactory.java
package org.openatom.springcloud.service;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 使用自定义的自带的OpenFeignClient发起调用
*/
@Service
public interface PaymentServiceOpenFeignDynamicFeignClientFactory {
@PostMapping(value = "/provider/payment/create")
CommonResult create(@RequestBody Payment payment);
@GetMapping(value = "/provider/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
/**
* 使用拦截器替换路由服务提供端URL中的占位符
* @param id
* @return
*/
@GetMapping(value = "/provider/$targetStr/get/{id}")
CommonResult<Payment> getPaymentByIdReplaceRouter(@PathVariable("id") Long id);
@GetMapping(value = "/provider/payment/openfeign/timeout")
String getPaymentByIdTimeout();
}
15.3.7.编写模块config
OpenFeignConfig.java
package org.openatom.springcloud.config;
import feign.Logger;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class OpenFeignConfig {
/**
* NONE:默认的,不显示任何日志;
* BASIC:仅记录请求方法、URL、响应状态码及执行时间;
* HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
* FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
* @return
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
DynamicFeignClientFactoryConfig.java
package org.openatom.springcloud.config;
import org.springframework.cloud.openfeign.FeignClientBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
/**
* 当前使用的是yml中配置的负载均衡策略
* @param <T>
*/
@Configuration
public class DynamicFeignClientFactoryConfig<T> {
private FeignClientBuilder feignClientBuilder;
public DynamicFeignClientFactoryConfig(ApplicationContext appContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
public T getFeignClient(final Class<T> type, String serviceId) {
return this.feignClientBuilder.forType(type, serviceId).build();
}
}
15.3.8.编写模块interceptor
package org.openatom.springcloud.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
/**
* @Description Feign发起远程调用之前的请求拦截器
* 注意:不管是系统自带的FeignClient还是用户自定义的FeignClietn执行时这个拦截器都会生效
**/
@Slf4j
@Configuration
public class FeignClientRequestInterceptor implements RequestInterceptor {
/**
* @description: 将traceId设置到请求头
*/
@Override
public void apply(RequestTemplate template) {
String url = template.url();
log.info("替换前的url:" + url);
if (url.contains("$targetStr")) {
url = url.replace("$targetStr", replaceRouter());
template.uri(url);
}
log.info("替换后的url:" + url);
}
/**
* 替换router中的字符串
* @return
*/
private CharSequence replaceRouter() {
return "payment";
}
}
15.3.9.编写模块controller
OrderConsumerController.java
package org.openatom.springcloud.controller;
import lombok.extern.slf4j.Slf4j;
import org.openatom.springcloud.service.PaymentServiceOpenFeign;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;
import javax.annotation.Resource;
/**
* DEV环境测试用这个Controller,可以动态获取服务名
*/
@RestController
@Slf4j
public class OrderConsumerController {
//单机版
// public static final String PAYMENT_URL = "http://localhost:8001";
// public static final String PAYMENT_URL = "http://localhost:8002";
@Resource
private PaymentServiceOpenFeign paymentServiceOpenFeign;
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return paymentServiceOpenFeign.create(payment);
}
/**
* 测试url:
* http://localhost/consumer/payment/get/1
* @param id
* @return
*/
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return paymentServiceOpenFeign.getPaymentById(id);
}
/**
* 使用拦截器替换路由服务提供端URL中的占位符
* 测试url:
* http://localhost/consumer/payment/replace_router/get/1
* @param id
* @return
*/
@GetMapping("/consumer/payment/replace_router/get/{id}")
public CommonResult<Payment> getPaymentReplaceRouter(@PathVariable("id") Long id) {
return paymentServiceOpenFeign.getPaymentByIdReplaceRouter(id);
}
@GetMapping("/consumer/payment/openfeign/timeout")
public String getPaymentByIdTimeout() {
return paymentServiceOpenFeign.getPaymentByIdTimeout();
}
}
OrderConsumerControllerDynamicFeignClientFactory.java
package org.openatom.springcloud.controller;
import lombok.extern.slf4j.Slf4j;
import org.openatom.springcloud.entities.CommonResult;
import org.openatom.springcloud.entities.Payment;
import org.openatom.springcloud.config.DynamicFeignClientFactoryConfig;
import org.openatom.springcloud.service.PaymentServiceOpenFeignDynamicFeignClientFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* 多环境测试用这个Controller,可以动态获取服务名
*/
@RestController
@Slf4j
public class OrderConsumerControllerDynamicFeignClientFactory {
//单机版
// public static final String PAYMENT_URL = "http://localhost:8001";
// public static final String PAYMENT_URL = "http://localhost:8002";
//集群版
//从配置文件中动态获取远程调用地址
@Value("${service.provider.provider-1}")
private String SERVICE_PROVIDER_NAME;
@Autowired
private DynamicFeignClientFactoryConfig<PaymentServiceOpenFeignDynamicFeignClientFactory> client;
/**
* 获取具体的Service接口
* @return
*/
public PaymentServiceOpenFeignDynamicFeignClientFactory getPaymentService() {
return client.getFeignClient(PaymentServiceOpenFeignDynamicFeignClientFactory.class,
SERVICE_PROVIDER_NAME.toUpperCase());
}
@GetMapping("/consumer/dynamic/payment/create")
public CommonResult<Payment> create(Payment payment) {
return this.getPaymentService().create(payment);
}
/**
* 测试url:
* http://localhost/consumer/dynamic/payment/get/1
* @param id
* @return
*/
@GetMapping("/consumer/dynamic/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return this.getPaymentService().getPaymentById(id);
}
/**
* 使用拦截器替换路由服务提供端URL中的占位符
* 测试url:
* http://localhost/consumer/dynamic/payment/replace_router/get/1
* @param id
* @return
*/
@GetMapping("/consumer/dynamic/payment/replace_router/get/{id}")
public CommonResult<Payment> getPaymentReplaceRouter(@PathVariable("id") Long id) {
return this.getPaymentService().getPaymentByIdReplaceRouter(id);
}
@GetMapping("/consumer/dynamic/payment/openfeign/timeout")
public String getPaymentByIdTimeout() {
return this.getPaymentService().getPaymentByIdTimeout();
}
}
15.3.10.编写模块主启动类
package org.openatom.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 1.使用OpenFeign完成远程调用,如果要配置负载均衡策略,和Ribbon配置负载均衡策略方式相同
* 本微服务主要测试OpenFeign的功能,所以采用YML文件配置Ribbon的负载均衡策略
* 2.OpenFeign是对Ribbon和RestTemplate的封装,所以配置负载均衡方式同Ribbon配置负载均衡方式,而且不需要在容器中手动注入ResTemplate对象
* 3.OpenFeign YML文件配置实现远程调用,但不是完全将服务信息配置在YML中,只是在YML中写一些增强的配置,相关的服务中仍然要写服务名,@FeignClient(name="SPRING-CLOUD-PROVIDER-CONSUL-PAYMENT-SERVICE")
* 4.对每个微服务单独进行配置,如连接超时时间配置、读取超时时间配置,YML没有把OpenFegin的配置和对Ribbon的配置写在一起
* 5.开启OpenFeign增强日志后可以看到Http调用的详细信息
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] <--- HTTP/1.1 200 (59ms)
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] connection: keep-alive
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] content-type: application/json
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] date: Tue, 31 May 2022 19:51:37 GMT
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] keep-alive: timeout=60
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] transfer-encoding: chunked
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById]
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] {"code":200,"message":"查询成功,serverPort: 8006","data":{"id":1,"serial":"15646546546"}}
* 2022-06-01 03:51:37.176 DEBUG 16792 --- [p-nio-80-exec-1] o.o.s.services.PaymentServiceOpenFeign : [PaymentServiceOpenFeign#getPaymentById] <--- END HTTP (94-byte body)
*/
@EnableEurekaClient //添加@EnableEurekaClient好像没什么用,但是还是加上
@SpringBootApplication
@EnableFeignClients
public class OrderServiceConsumerLoadBalanceOpenFeignDynamicServiceName80 {
public static void main(String[] args) {
SpringApplication.run(OrderServiceConsumerLoadBalanceOpenFeignDynamicServiceName80.class, args);
}
}
15.4.测试OpenFeign高级用法
15.4.1.测试动态服务名
在浏览器中访问
非动态服务名调用(服务名配置在Service中使用@FeignClient配置)
http://localhost/consumer/payment/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第三次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的RandomRule(随机策略),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的
动态服务名调用(服务名配置在application.yml中配置)
http://localhost/consumer/dynamic/payment/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第三次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的RandomRule(随机策略),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的
从配置文件中动态获取服务名有什么好处?
有时候服务名可能会出现在代码中的两个地方,分别是@FeignClient中application.yml中,这意味着如果修改了服务名就要同时修改@FeignClient中application.yml中的配置,如果OpenFeign发起调用时,直接从application.yml中读取服务名称,那么当服务名发生了变化时,只需要修改application.yml中这一个地方的配置即可
15.4.2.测试openfeign拦截器
在浏览器中访问
非动态服务名调用(服务名配置在Service中使用@FeignClient配置)
http://localhost/consumer/payment/replace_router/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第三次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的RandomRule(随机策略),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的
动态服务名调用(服务名配置在application.yml中配置)
http://localhost/consumer/dynamic/payment/replace_router/get/1
第一次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
第二次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第三次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8002","data":{"id":1,"serial":"15646546546"}}
第四次访问返回结果
{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"15646546546"}}
可以看到四次访问返回的结果中,四次返回结果是没有规律的,因为采用的RandomRule(随机策略),实际返回结果可能不是上面的情况,但是一定是随机进行服务调用的
评论