Feign 微服务调用 (nacos 作为注册中心)
Feign 是
spring Cloud Netflix
组件中的一量级Restful
的HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了Ribbon
和RestTemplate
,实现WebService
的面向接口编程。Feign 简化了
RestTemplate
代码,是声明式服务调用组件:核心就是像调用本地方法一样调用远程方法。让开发者无需关注,远程调用过程,和交互细节。Feign 本身并不支持
spring MVC
注解,它有一套自己的注解,为了更方便使用Spring Cloud
孵化OpenFeign
。并且支持spring mvc
的注解,例如:@RequestMapping、@PathVariable。openFeign 的
@FeignClient
可以解析Spring MVC
的@RequestMapping
注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。soringboot 2.0 以后基本使用
OpenFeign
特性
- Hystrix 和它的 Fallback
- HTTP 请求响应的压缩
- Ribbon 负载均衡客户端
- 可拔插的HTTP编码器和解码器
- 拔插的注解支持,包括Feign 注解 和JAX-RS 注解
文章目录
快速开始
项目预览
项目
pom 文件
父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 利用传递依赖,公共部分 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
</dependencies>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--注意:这里需要添加以下配置,否则可能会有各种依赖问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
goods 消费者
<parent>
<artifactId>springcloud-openfeig</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>openfeig-goods</artifactId>
<description>商品服务</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
order 生产者
<parent>
<artifactId>springcloud-openfeig</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>openfeig-order</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- openfeign 依赖的 服务调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
goods
server:
port: 8081
spring:
application:
name: openfeig-goods
cloud:
nacos:
server-addr: ...:8848
@RestController
public class GoodsController {
@GetMapping("hello/{message}")
public String hello(@PathVariable String message){
return "来自goods的消息 =="+ message;
}
}
order
server:
port: 8080
spring:
application:
name: openfeig-order
cloud:
nacos:
server-addr: ....:8848
@SpringBootApplication
//开启远程调用的注解 扫描,可以扫描 @FeignClient
@EnableFeignClients
public class OpenFeigOrderApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeigOrderApplication.class,args);
}
}
// 准备远程调用的接口
@FeignClient(name = "openfeig-goods")
public interface GoodsClienService {
@GetMapping("hello/{message}")
String hello(@PathVariable String message);
}
//提供对外暴露的 api
@RestController
public class OrderController {
//nacos 客户端信息
@Autowired
private LoadBalancerClient loadBalancerClient;
//调用的 service
@Autowired
private GoodsClienService cartService;
@GetMapping("add/{message}")
public String add(@PathVariable String message){
ServiceInstance choose = loadBalancerClient.choose("openfeig-goods");
return cartService.hello(message) +" "+ choose.getHost() + "--" + choose.getPort();
}
}
测试
@FeignClient ‼️
需要
@EnableFeignClients
来开启扫描当定义的Feign接口中的方法被调用时,通过JDK的代理方式,生成具体的
RequestTemplate
。这个对象中,封装了http需要的全部信息。参数、方法名等等
属性 | 说明 |
---|---|
name | 指定FeignClient等名称,如果项目使用了Ribbon,那么name属性会作为微服务等名称,用于服务发现 |
url | 一般用于调试,可以手动指定@FeignClient 调用的服务地址 |
decode404 | 当发生404错误时,如果该字段值为true,会调用decoder进行编码,否则抛出FeignException |
configuration | Feign 配置列,可以自定义Feign 的 Encoder、Decoder、LogLevel、Contract 也可以在配置文件中配置 |
fallback | 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现 @FeignClient 标记的接口 |
fallbackFactory | 工厂类,用于生成fallback类实例,通过这个属性可以实现每个接口通用的容错逻辑,减少重复的代码 |
path | 定义当前FeignClient 的统一前缀 |
Feign 日志开启
需要在配置文件中 logback.xml 把日志级别改为debug
# open feign 日志设置
logging:
level:
com.springcloud.study.service.ProviderClientService: debug
//全局的配置 日志配置类
@Configuration
public class FeignServiceConfig {
/*
Logger.Level 设置级别
NONE : 不记录任何信息
BASIC: 仅记录请求方法,URL以及响应状态码
HEADERS: 除了记录 BASIC级别信息外,还会记录请求和响应的头信息
FULL: 记录所有的请求于响应的明细,包括头信息,请求体,元数据
*/
@Bean
Logger.Level feignLogger(){
return Logger.Level.FULL;
}
}
//指定单独的配置 MyFeignConfig 是一个 日志的配置类
@FeignClient(configurtion = MyFeignConfig.class)
HTTP Client 替换
Feign 默认使用的是JDK原生的
URLConnection
发送HTTP请求,没有用链接池。对每个地址都会建立一个长链接。feign 的
HTTP
客户端支持3中框架HttpURLConnection、HttpClient、OkHttp
默认是 HttpURLConnection
修改
<!-- feign httpclient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<!-- 或者换成okhttp -->
<!-- 引入 Feign 对 Okhttp 的支持 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
feign:
httpclient:
# 开启httpclient
enabled: true
如果使用 okhttp 的可以自定义配置
okHttp 优势
- 支持SPDY,合并多个请求到同一个主机
- 使用链接池
- 使用GZIP压缩减少传输数据体积
- 缓存响应结果,减少重复请求
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
@Bean
public okhttp3.OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS) //链接超时时间
.readTimeout(60,TimeUnit.SECONDS) //读超时
.writeTimeout(60,TimeUnit.SECONDS) //写超时
.retryOnConnectionFailure(true) //自动重试
.connectionPool(new ConnectionPool()) //创建链接池 okhttp包的
.build();
}
}
参数传递
get 方式,
@PathVariable、@RequestParm
注解来接收post 方式 ,
@RequestBody
接收请求参数
特殊需求,Get方法传递了多参数
需要时实现Feign 的
RequestInterceptor
中的pally
进行统一处理
TODO 还是用 post 方法发送省事
实现Token 传递
认证鉴权的时候,使用JWT,或spring security 都需要拿到token
RequestInterceptor
拦截器,在feign 调用的时候,向请求头里添加需要传递的token基于上面的代码改动 注意token 大小写的问题
//生产端
@Component
public class FeignTokenAddInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
if(null==getHttpServletRequest()){
//此处省略日志记录
return;
}
//将获取Token对应的值往下面传
requestTemplate.header("oauthToken", getHeaders(getHttpServletRequest()).get("oauthtoken"));
}
private HttpServletRequest getHttpServletRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
/**
* Feign拦截器拦截请求获取Token对应的值
* @param request
* @return
*/
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
System.out.println("模拟token " + map.get("oauthtoken"));
return map;
}
}
@GetMapping("hello/{message}")
public String hello(@PathVariable("message") String message, HttpServletRequest req){
String oauthToken = req.getHeader("oauthToken");
System.out.println( "token =" + oauthToken);
return "来自goods的消息 =="+ message + oauthToken;
}