声明式服务调用Feign
一、Feign介绍
1.什么是feign?
- feign是spring cloud提供的声明式的http客户端,工作在consumer端
- feign支持springmvc注解
- feign集成ribbon也支持负载均衡(restTemplate+ribbon=feign)
2.feign启动器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
二、Feign入门案例
2.1创建服务提供者feign_provider模块
<?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_parent</artifactId>
<groupId>com.bjpowernode</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign_provider-1</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>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 8090
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.19.132
application:
name: feign-provider
package com.bjpowernode.controller;
import com.bjpowernode.pojo.User;
import com.bjpowernode.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/provider")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
return userService.getUserById(id);
}
}
package com.bjpowernode.service;
import com.bjpowernode.pojo.User;
public interface UserService {
User getUserById(Integer id);
}
package com.bjpowernode.service;
import com.bjpowernode.pojo.User;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Override
public User getUserById(Integer id){
return new User(id, "王粪堆-1", 18);
}
}
package com.bjpowernode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class FeignProviderApp {
public static void main(String[] args) {
SpringApplication.run(FeignProviderApp.class, args);
}
}
2.2创建feign_interface接口模块
<?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_parent</artifactId>
<groupId>com.bjpowernode</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>feign_interface</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>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
package com.bjpowernode.feign;
import com.bjpowernode.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable("id") Integer id);
}
2.3创建服务消费者feign_consumer模块
<?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_parent</artifactId>
<groupId>com.bjpowernode</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ribbon_consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>springcloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.bjpowernode</groupId>
<artifactId>feign_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.19.132:8848
application:
name: feign-consumer
package com.bjpowernode.controller;
import com.bjpowernode.feign.UserFeign;
import com.bjpowernode.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping("/consumer")
public class UserController {
@Autowired
private UserFeign userFeign;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
System.out.println(userFeign.getClass());
return userFeign.getUserById(id);
}
}
package com.bjpowernode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignConsumerApp {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApp.class, args);
}
}
三、Feign原理
3.1 将Feign接口注入到Spring容器中
扫描feign接口生成代理类并交给spring容器管理
@EnableFeignClients开启feign接口扫描:FeignClientsRegistrar.registerFeignClients()扫描被@FeignClient标识的接口生成代理类并交给spring的容器管理
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
... ... ...
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)
candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
3.2 RequestTemplate封装请求信息
根据接口上的注解创建RequestTemplate
当controller调用feign代理类时,代理类会调用SynchronousMethodHandler.invoke()创建RequestTemplate(url、requestMethod、body)
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
return this.executeAndDecode(template);
} catch (RetryableException var8) {
... ... ...
}
}
}
package feign;
public final class RequestTemplate implements Serializable {
... ... ... ... ... ...
private UriTemplate uriTemplate;
private HttpMethod method;
private Body body;
... ... ... ... ... ...
}
3.3 发起请求
接着通过RequestTemplate创建Request,然后client(HttpClient、OkHttp、URLConnection)使用Request发送请求
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
response = this.client.execute(request, this.options);
} catch (IOException var15) {
... ... ...
throw FeignException.errorExecuting(request, var15);
}
}
四、feign接口三种传参方式
1.?传参
? @RequestParam(“”)【拼接?形式的url】
2.restful传参
@PathVariable("")【拼接restful形式的url】
3.pojo传参
? @RequestBody【获取请求体中的json串】
五、Feign请求超时
@Service
public class UserServiceImpl implements UserService {
@Override
public User getUser() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new User(1,"一直下雨天",18);
}
}
六、feign优化
6.1开启feign日志
feign虽然提供了日志增强功能,但是默然是不显示任何日志,不过开发者可以自己配置日志级别:
feign的日志级别如下:
NONE:不输出日志
BASIC:输出请求方法及url,响应的状态码及响应时间
HEADERS:输出请求和响应的头信息
FULL:输出请求和响应的请求头,消息体及元数据
在application.yml文件中配置日志级别
feign:
client:
config:
default:
loggerLevel: full
logging:
level:
com.bjpowernode.feign: debug
6.2 GZIP压缩
-
gzip 是一种数据格式,采用用 deflate 算法压缩 data;gzip 是一种流行的文件 压缩算法,应用十分广泛,尤其是在 Linux 平台。 -
当 Gzip 压缩到一个纯文本文件时,效果是非常明显的,大约可以减少 70% 以上的文件大小。 -
网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可 以加快网页加载的速度。网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏 览体验外,另一个潜在的好处是 Gzip 与搜索引擎的抓取工具有着更好的关系。例如 Google 就可以通过直接读取 gzip 文件来比普通手工抓取 更快地检索网页。
server:
compression:
enabled: true
feign:
compression:
request:
enabled: true
response:
enabled: true
6.3 http连接池
两台服务器建立HTTP 连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP 连接池可以节约大量的时间提示吞吐量。
添加依赖,不需要在application.yml配置开启,默认为开启
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
6.4 feign超时
feign超时优化方式一:
ribbon:
ConnectionTimeout: 5000
ReadTimeout: 5000
feign超时优化方式二:
feign:
client:
config:
feign-provider:
ConnectionTimeout: 5000
ReadTimeout: 5000
- 服务器建立
HTTP 连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP 连接池可以节约大量的时间提示吞吐量。 - 添加依赖,不需要在application.yml配置开启,默认为开启
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
6.4 feign超时
feign超时优化方式一:
ribbon:
ConnectionTimeout: 5000
ReadTimeout: 5000
feign超时优化方式二:
feign:
client:
config:
feign-provider:
ConnectionTimeout: 5000
ReadTimeout: 5000
|