Spring Cloud学习笔记(6)- 声明式的远程调用(Feign)

245 阅读4分钟

Spring Cloud学习笔记(6)- 声明式的远程调用(Feign)


在上一章中讲到利用RestTemplate来实现对服务的远程调用,我们来回顾一下这段代码:

/**
 * @author: bruce.hong
 * @time: 2021/7/9 下午 4:49
 */
@RequestMapping("/movies")
@RestController
public class MovieController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/users/{id}")
    public User findById(@PathVariable Long id) {
        // 这里用到了RestTemplate的占位符能力
        User user = this.restTemplate.getForObject(
                "http://microservice-provider-user/users/{id}",
                User.class,
                id
        );
        // ...电影微服务的业务...
        return user;
    }
}

现在作为一个程序员,我们平心而论如果没有完整注释或者完整的API文档说明,在下一个倒霉蛋接受到这段代码的时候估计十有八九是猜不到这段代码真正的作用的。一个完整请求路径实际上是一种契约化的转义,但是这种转义如果需要被通读,是需要辅助文档或者说明的。在一个遍地强迫症的码农环境中,这种代码实际上是很难接受的。我们需要易于维护和理解代码,来降低项目整体的开发和维护成本。

Feign是什么

Feign是有Netfix开发的模板化、声明式访问的HTTP客户端,灵感来自Retrofit、JAXRS-2.0以及WebSocket。Feign可帮助我们更加便捷、优雅地调用HTTP API。

在Spring Cloud中,使用Feign非常简单仅需创建接口,并在接口上添加注解即可。

Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。Spring Cloud对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便。

构建一个利用Feign访问接口的实例

新建一个Spring Boot项目,添加如下一些依赖:

<?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>testfeign</artifactId>
        <groupId>org.bruce.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>clients</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.bruce.demo</groupId>
            <artifactId>entity</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- 引入Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>
        <!--必须引入完整的Feign 包 否则会引起注入失败错误-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>
    </dependencies>
</project>

特别需要注意的是:

<!--必须引入完整的Feign 包 否则会引起注入失败错误-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>

注意:很多时候如果我们不手动修改pom文件,通过IDEA智能提示添加的包是spring-cloud-openfeign-core,而不是Feign完整包,会导致相关错误。

引入Feign包后,在相关Application代码中添加相关Feign注解:

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
@EntityScan("org.bruce.demo.entities.*")
public class TestFeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestFeignApplication.class, args);
    }
}

以上代码中我们将当前微服务添加两个特殊注解:

  • @EntityScan("org.bruce.demo.entities.*"):由于微服务项目存在相关实体在多个模块中多次引用的问题,故而一般在构建微服务项目时,一般将实体类独立封装为一个模块,由于跨模块引用,故而添加EntityScan注解来保证实体类一定被服务感知。
  • @EnableFeignClients:该注解标注了当前应用程序是一个Feign客户端,可能存在对其他服务的声明式引用。

添加Feign声明式调用接口:

@Component
@FeignClient(name="feignservice")
public interface UserFeignService {

    @GetMapping("/users/{id}")
    User findById(@PathVariable("id") Long id);
}

以上代码就实现了一个典型Feign调用接口,有以下一些注意事项:

  • @FeignClient(name="feignservice"):该注解标注当前接口是一个Feign客户端,其调用的远程服务名称是feignservice。注意:这里的远程服务名称即注册在Eureka或者Nacos中的服务名称,一般会在application.yaml中配置。
  • @GetMapping("/users/") User findById(@PathVariable("id") Long id):该代码标注了调用远程服务中的具体URL地址和值传递方式,即声明式调用。

添加相关Controller代码:

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private UserFeignService userService;

    @GetMapping("/users")
    public User getUserById( Long userId) {
        return userService.findById(userId);
    }
}

在Controller中只需要注入相关Feign接口,就可以直接远程调用相关服务。添加相关程序运行配置如下所示:

server:
  # 指定Tomcat端口
  port: 9201
spring:
  jpa:
    # 让hibernate打印执行的SQL
    show-sql: true
  application:
    name: feignclient
feign:
  okhttp:
    enabled: true
logging:
  level:
    root: INFO
    # 配置日志级别,让hibernate打印出执行的SQL参数
    org.hibernate: INFO
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.BasicExtractor: TRACE

运行调用远端接口,结果如下所示:

{"id":1,"username":"account1","name":"张三","age":20,"balance":100.00}

具体代码请见参见 :

Bruce.hong的阿里云

Feign和RestTemplate的恩怨情仇

在实际开发中,RestTemplate由于其灵活性实际上有些时候是会让项目开发变得更为灵活,但是我们更推荐使用feign方式,因为易于维护和管理,也不便于更快速的构建和完成一个项目,关于Feign还有很多需要注意的相关知识点,请关注下一节相关讲解!