OpenFeign组件的介绍使用

Feign是Spring Cloud 提供的声明式、模板化的Http客户端,它主要的作用是简化远程调用,当通过RestTemplate调用其他服务的API时,所需要的参数须在请求的URL中拼接,这种方式容易出错又不美观而且效率低下,Fegin具体实现的方式为创建一个接口,并添加一个注解。

注意:Feign默认集成了Ribbon,所以Feign默认就实现了负载均衡。

Fegin的入门使用:

实际调用服务的流程图如上图所示

来搭建一个Fegin的工程,创建服务提供者feign_provider

pom依赖(引入nacos的注册中心以及Spring Boot的web启动器):

         <!--nacos客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

 Controller:

@RestController
@RequestMapping("/provider")
public class ProviderController {
    @Autowired
    private UserService userService;

    @RequestMapping("/getUserById/{id}")
    public User getUserById(@PathVariable Integer id){
        return userService.getUserById(id);
    }
}

Service接口:

public interface UserService {
    User getUserById(Integer id);
}

ServiceImpl:

@Service
public class UserServiceImpl implements UserService {

    @Override
    public User getUserById(Integer id) {
        System.out.println("one...");
        return new User(id,"张三",18);
    }
}

启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class FeignProviderApp {
    public static void main(String[] args) {
        SpringApplication.run(FeignProviderApp.class,args);
    }
}

 application.yaml配置:

server:
  port: 9090
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.40.141:8848
  application:
    name: feign-provider

创建interface工程(当做依赖被服务消费者所引用):

pom依赖(在interface中引入openfeign的依赖及项目的公共模块):

       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-openfeign</artifactId>
       </dependency>

接口:

@FeignClient(value = "feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
    @RequestMapping("/getUserById/{id}")
    User getUserById(@PathVariable("id") Integer id);
}

创建消费者工程fegin_consumer:

pom中引入Spring Boot的web启动器,interface工程以及nacos的注册中心

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

config配置类:


@Configuration
public class ConfigBean {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    public IRule iRule(){
        return new RandomRule();
    }
}

controller:

@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Autowired
    private UserFeign userFeign;

    @RequestMapping(value = "/getUserById/{id}")
    public User getUserById(@PathVariable Integer id) {
        return userFeign.getUserById(id);
    }
}

 启动类:


@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.bjpowernode")
public class ConsumerApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class,args);
    }
}

 application.yaml配置文件:

server:
  port: 80
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.40.141:8848
  application:
    name: feign-consumer

准备就绪,启动服务提供者和服务消费者,在浏览器中访问localhost/consumer/getUserById/2

测试:

 访问成功。

@EnableFeignClients注解的作用:

开启Feign的注解扫描,这个注解中引入了FeignClientsRegistrar这个类,在服务启动时,会调用它的registerFeignClients()方法扫描被@FeignClient注解的接口,再将这些接口注入到spring的ioc容器中。

其实现如下:

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		... ... ...
         //扫描feign接口   
		for (String basePackage : basePackages) {
            //扫描获取BeanDefinition集合
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
                //判断迭代并判断集合中的元素是否是AnnotatedBeanDefinition的实例,如果是则强转
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) 
                                                              candidateComponent;
                      //获得UserFeign的详细信息
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    //@FeignClient注解只能用在接口上,被@FeignClient注解的是否为接口
					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"));
                      //注入Feign接口到Spring容器中
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

Feign的执行流程:

SynchronousMethodHandler.invoke(): 当定义的的Feign接口中的方法被调用时,通过JDK的代理方式为Feign接口生成了一个动态代理类,当生成代理时,Feign会为每个接口方法创建一个RequestTemplate。该对象封装了HTTP请求需要的全部信息,如请url、参数,请求方式等信息都是在这个过程中确定的。

    public Object invoke(Object[] argv) throws Throwable {
        //创建一个RequestTemplate
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                //发出请求
                return this.executeAndDecode(template);
            } catch (RetryableException var8) {
                ... ... ...
            }
        }
    }

看一下RequestTemplate定义的属性:

package feign;

public final class RequestTemplate implements Serializable {
    ... ... ... ... ... ...
    private UriTemplate uriTemplate;
    private HttpMethod method;
    private Body body;
    ... ... ... ... ... ...
}    

当发起请求的时候,执行SynchronousMethodHandler.executeAndDecode();方法

通过RequestTemplate生成Request,然后把Request交给Client去处理,Client可以是JDK原生的URLConnection,Apache的HttpClient,也可以时OKhttp,最后Client结合Ribbon负载均衡发起服务调用。

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、如果是Restful风格的路径传参

则feign接口的形参必须使用@PathVarible接收,并且必须指定value值,否则报错

 启动出现如下错误:

 改为:

 成功启动!

 2.如果是get请求中的占位符拼接传参则需要使用@RequestParam注解接收

同样必须指定value,否则报错!

首先在在服务提供者,消费者以及feign的接口中添加如下方法:

feign_provider的controller:

    @RequestMapping(value = "/getUser")
    public User getUser(Integer id) {
        return userService.getUser(id);
    }

    @RequestMapping(value = "/addUser")
    public User addUser(@RequestBody User user) {
        return userService.addUser(user);
    }

service:

    @Override
    public User getUser(Integer id) {
        return new User(id,"李四",20);
    }

    @Override
    public User addUser(User user) {
        user.setName("王五");
        return user;
    }

  feign_consumer的controller:

    @RequestMapping(value = "/getUser")
    public User getUser(Integer id) {
        return userFeign.getUser(id);
    }

    @RequestMapping(value = "/addUser")
    public User addUser(User user) {
        return userFeign.addUser(user);
    }

feign接口:

    @RequestMapping("/getUser")
    User getUser(@RequestParam("id") Integer id);

    @RequestMapping("/addUser")
    User addUser(@RequestBody User user);

启动测试:

先测试getUser方法:

 再测试addUser方法:

 

 这里解释一下为什么fegin接口中要加@RequsetBody和@RequestParam注解

在Controller中,如果拼接传参的是一个对象的属性,只要变量名一致,可以不用加注解直接接收。但是使用feign实际上是用的http通信,服务消费者在调用feign获取服务提供者的服务时,fegin会获取到服务提供者的信息,包括ip端口号接口信息等,并且发送http请求,发送请求的时候会自动拼接url,而如果不使用注解直接发送请求,feign就不知道该如何拼接,比如上面的@PathVarible的例子中,为什么不指定value就会报错呢?feign帮助我们拼接url的时候,获取到了http://localhost:9090/provider/getUserById/{},最后的路径它就不知道该拼接什么。同理@RequestParam的作用也是一样。而为何要使用@RequestBody是因为java对象不能被http请求识别,使用该注解后,feign就会将java对象转换为json串并且处理后发送到服务提供者调用。

Feign的调优:

feign基于http通信协议,从通讯速度上来讲是不如rpc框架的如dubbo

那从速度上考虑为什么不使用更快的dubbo呢,引入feign的调优

主要有四个方面:

1.feign的日志:

 feign的日志是基于spring的日志的,所以开启feign日志之前要先配置spring的日志:

application.yaml配置:

feign:
 client:
  config:
	default:
	 loggerLevel: full
logging:
 level:
  com.bjpowernode.feign: debug

重启服务:然后再次访问localhost/consumer/addUser

 

 日志配置完毕

 2.feign的超时时间

feign的超时有两种配置方式,可以配置ribbon的超时时间,也可以配置feign的

如下:

配置ribbon超时

ribbon:
	ConnectTimeout: 5000 #请求连接的超时时间
	ReadTimeout: 5000 #请求处理的超时时间

配置feign超时:

feign:
 client:
  config:
	feign-provider:
	  ConnectTimeout: 5000 #请求连接的超时时间
	  ReadTimeout: 5000 #请求处理的超时时间

 默认feign的超时时间是一秒

3.feign的http连接池

连接池的配置非常简单,引入依赖:

        <dependency>
		    <groupId>io.github.openfeign</groupId>
		    <artifactId>feign-httpclient</artifactId>
		</dependency>

在配置文件中开启(默认开启可以不用配置)

feign:
  httpclient:
    enabled: true

 

 测试是否替换成功:

Debug模式下启动,在SynchronousMethodHandler.executeAndDecode()断点进入,查看client属性

如果这里显示ApacheHttpClient表示替换成功! 

4.fegin的压缩

在配置文件中开启fegin的gzip压缩

开启gzip压缩后,效率能提高近乎百分之70

server:
    compression:
        enabled: true

mime-types配置项默认指定了常用的格式,这里没有自定义,采用默认

重启项目测试

 配置成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值