SpringCloud学习之路四(Feign)

1,254 阅读8分钟

今天我们来聊聊Springcloud全家桶中的第三个组件Feign(声明式的web service客户端),那么他的作用是什么呢?他和Ribbon有什么区别呢?

目前,在Spring cloud 中服务之间通过restful方式调用有两种方式

  • restTemplate+Ribbon :通过服务名称远程调用服务
  • feign:通过接口和注解远程调用服务

1、Feign是什么?

Feign是一个声明式的web service客户端,它使得编写web service客户端更为容易。创建接口,为接口添加注解,即可使用Feign。Feign可以使用Feign注解或者JAX-RS注解。Spring Cloud为Feign添加了Spring MVC的注解支持,并整合了Ribbon和Eureka来为使用Feign时提供负载均衡。 feign让微服务之间的调用变得更简单了。

Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去調用(RestTemplate是对HTTP请求的封装处理)

而 Feign 是一個使用起來更加方便的 HTTP 客戶端,它用起來就好像調用本地方法一樣,完全感覺不到是調用的远程方法

总结起来就是:发布到注册中心的服务方接口,是 HTTP 的,也可以不用 Ribbon 或者 Feign,直接浏览器一样能够访问

只不过 Ribbon 或者 Feign 调用起来要方便一些,最重要的是:它俩都支持软负载均衡

Feign集成了Ribbon

注意:spring-cloud-starter-feign 里面已经包含了 spring-cloud-starter-ribbon(Feign 中也使用了 Ribbon)

从实践上看Feign只需要定义服务绑定接口且以声明式的注解方法,采用feign的方式更优雅(feign内部也使用了ribbon做负载均衡)。

2、Feign怎么使用?

  • 1、在我们之前的springcloud-api(给各个服务提供pojo)这个服务中引入feign依赖
<!--Feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
  • 2、在springcloud-api中编写DeptClientService接口,方便其他服务属性注入调用

在该接口中配置@FeignClient(value = "springcloud-provider")注解,value值为服务名,可以被其他服务直接调用,通过客户端去找springcloud-provider服务名,进行负载均衡

package com.baoji.springcloud.service;

import com.baoji.springcloud.pojo.Dept;
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 java.util.List;

@Component  //此注解是将此接口注入到spring中
@FeignClient(value = "springcloud-provider")  //可以被其他服务直接调用,通过客户端去找springcloud-provider服务名,进行负载均衡
public interface DeptClientService {
    @GetMapping("/dept/get/{id}")
    public Dept quaryById(@PathVariable("id") Long id);
    @GetMapping("/dept/list")
    public List<Dept> queryAll();
    @PostMapping("/dept/add")
    public boolean addDept(Dept dept);
}
  • 3、创建一个springcloud-consume-feign-80服务消费者(feign方式)的服务(所有配置和springcloud-consume-80的配置基本类似,复制过来即可)

在该消费者服务中添加feign依赖

 <!--Feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

将api服务业务接口直接属性注入到该服务中,方便控制层的调用

package com.baoji.springcloud.controller;

import com.baoji.springcloud.pojo.Dept;
import com.baoji.springcloud.service.DeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 javax.annotation.Resource;
import java.util.List;

@RestController
public class DeptConsumerController {

    //自动属性注入service
   @Resource
    private DeptClientService service = null;

    //消费者请求的地址
    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        //服务器返回对应的对象
        return service.quaryById(id);
    }

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        //返回post类型的对象,三个参数(请求的地址,拼接的参数,返回响应的字节码)
        return service.addDept(dept);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        //返回get类型的对象,两个参数(url,返回响应的字节码)
       return service.queryAll();
    }
}

编写启动类

此类多了个@EnableFeignClients(basePackages = {"com.baoji.springcloud"}) 注解,去扫描哪些需要调用业务服务接口的注解包

package com.baoji.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;

// Ribbon和Eureka整合之后, 客户端可以直接调用,不用关心IP和端口号
@SpringBootApplication
@EnableEurekaClient  //eureka客户端注解  使用的是eureka发现功能
//Feign集成了Ribbon,通过Ribbon实现了负载均衡,通过Feign实现了远程调用
@EnableFeignClients(basePackages = {"com.baoji.springcloud"})  //去扫描哪些需要调用业务接口注解包
public class FeignConsume_80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignConsume_80.class,args);
    }
}

3、Feign和Ribbon调用服务在具体实现上的不同?

  • 1、我们来看看Ribbon实现服务调用的controller层

Ribbon实现原理:

Riggon是使用Spring中提供的RestTemplate对象来实现远程调用,RestTemplate对象对Http请求进行了封装处理,给消费者服务通过RestTemplate对象访问生产者服务提供了一套模板,它是根据服务名来调用生产者,根据restTemplat对象调用底层获取服务提供者的一系列方法来实现服务调用

package com.baoji.springcloud.controller;

import com.baoji.springcloud.pojo.Dept;
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;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class DeptConsumerController {
    //消费者:不应该有service层
    //RestTemplate  供我们直接调用就可以  直接手动注册到spring中
    //参数(url,请求路径:Map,Class<T> responseType)
    @Autowired
    private RestTemplate restTemplate;  //提供多种快捷访问Http服务的方法,简单的restFul服务模板
    //请求路径前固定的url
   // private static final String REST_URL_PREFIX = "http://localhost:8080";
    //用ribbon实现负载均衡时,我们消费者访问的地址就是一个变量,通过服务名来访问,
    // 这个服务名存在Eureka的多个注册中心里,通过Ribbon负载均衡机制获取它到底调用哪个生产者
    private static final String REST_URL_PREFIX = "http://springcloud-provider";


    //消费者请求的地址
    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        //服务器返回对应的对象
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
    }

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        //返回post类型的对象,三个参数(请求的地址,拼接的参数,返回响应的字节码)
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        //返回get类型的对象,两个参数(url,返回响应的字节码)
       return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
    }
}
  • 2、接下来我们来看Reign方式服务调用的controller层

Feign实现原理:

简单来说,Feign的实现比较简单,它集成了Rebbon,它只要编写一个接口和使用注解即可,它是将所有的业务内容写在一个接口中,服务消费者通过属性注入该接口,去调用该接口中的方法即可完成远程服务间的调用,这样就感觉到远程服务间的调用和之前的本地Controller层调用service层的实现一样简单

service接口:

package com.baoji.springcloud.service;

import com.baoji.springcloud.pojo.Dept;
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 java.util.List;

@Component  //此注解是将此接口注入到spring中
@FeignClient(value = "springcloud-provider")  //可以被其他服务直接调用,通过客户端去找springcloud-provider服务名,进行负载均衡
public interface DeptClientService {
    @GetMapping("/dept/get/{id}")
    public Dept quaryById(@PathVariable("id") Long id);
    @GetMapping("/dept/list")
    public List<Dept> queryAll();
    @PostMapping("/dept/add")
    public boolean addDept(Dept dept);
}

消费者controller类

就是单纯的属性注入service,服务消费者调用service里面的方法即可。

package com.baoji.springcloud.controller;

import com.baoji.springcloud.pojo.Dept;
import com.baoji.springcloud.service.DeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 javax.annotation.Resource;
import java.util.List;

@RestController
public class DeptConsumerController {

    //自动属性注入service
   @Resource
    private DeptClientService service = null;

    //消费者请求的地址
    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        //服务器返回对应的对象
        return service.quaryById(id);
    }

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        //返回post类型的对象,三个参数(请求的地址,拼接的参数,返回响应的字节码)
        return service.addDept(dept);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        //返回get类型的对象,两个参数(url,返回响应的字节码)
       return service.queryAll();
    }
}

启动类:

启动类中加入@EnableFeignClients(basePackages = {"com.baoji.springcloud"})注解,告诉服务消费者去扫描哪些需要调用业务接口注解包即可

package com.baoji.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;

// Ribbon和Eureka整合之后, 客户端可以直接调用,不用关心IP和端口号
@SpringBootApplication
@EnableEurekaClient  //eureka客户端注解  使用的是eureka发现功能
//Feign集成了Ribbon,通过Ribbon实现了负载均衡,通过Feign实现了远程调用
@EnableFeignClients(basePackages = {"com.baoji.springcloud"})  //去扫描哪些需要调用业务接口注解包
public class FeignConsume_80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignConsume_80.class,args);
    }
}

总体来说:Feign内部集成了Rebbon,使用简单,只需要将业务写在接口中,服务消费者调用该接口即可,将远程间的服务调用类似的简化成了本地的mvc架构方式调用,控制层调用业务层,但是总的来说,微服务是使用Ribbon实现负载均衡,feign实现远程间的服务调用,feign的使用,更好的体现了java面向接口编程,是面向接口的远程调用,ribbon+restTemplate是一种面向服务的远程调用,两者方式不同,使用时采取自愿,都会用最好!!!



好了,今天Feign组件的学习就到这了,今天你将get到Feign的基本概念、怎么使用Feign、Fiegn和Ribbon进行远程调用服务的区别,接下来你将学习到微服务中Hystrix服务熔断与降级的内容,当服务炸掉之后,微服务是怎么保证安全的呢?期待下一篇博客吧!记得点赞👍+关注哦!!!