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/>
</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>
<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
@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);
}
@RestController
public class OrderController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@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
logging:
level:
com.springcloud.study.service.ProviderClientService: debug
@Configuration
public class FeignServiceConfig {
@Bean
Logger.Level feignLogger(){
return Logger.Level.FULL;
}
}
@FeignClient(configurtion = MyFeignConfig.class)
HTTP Client 替换
Feign 默认使用的是JDK原生的 URLConnection 发送HTTP请求,没有用链接池。对每个地址都会建立一个长链接。
feign 的HTTP 客户端支持3中框架HttpURLConnection、HttpClient、OkHttp
默认是 HttpURLConnection
修改
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
feign:
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())
.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;
}
requestTemplate.header("oauthToken", getHeaders(getHttpServletRequest()).get("oauthtoken"));
}
private HttpServletRequest getHttpServletRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
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;
}
底部
|