美文网首页
Feign源码解析一

Feign源码解析一

作者: 0爱上1 | 来源:发表于2019-06-06 10:02 被阅读0次

    前言

    本文将基于SpringCloud中Feign模块源码,解析其原理,并理解以下重点

    • 使用Feign开发微服务一般的项目结构是什么样的?

    • Feign是什么?使用Feign模块可以为微服务开发带来哪些便利?

    • FeignClient注解的作用是?原理是什么?

    接下来会一一解答以上三个问题


    正文

    问题1问到使用Feign开发微服务,项目结构是什么?

    我们知道目前主流的开发模式都是微服务,一个项目会被分为多个服务同时开发,服务和服务之间采用Http请求的方式调用,比如现在有一个服务A,需要给服务B调用,我们目前的做法是每个微服务中都会定义两个子module,一个spi,一个service

    spi这个module会打成一个jar,用来给服务调用者依赖,并利用Feign Client实现类似本地调用的方式发起Http调用

    而service这个module则是该微服务真实的业务代码以及Controller层实现

    • 项目结构
      假设以order服务为例

    整体包结构


    order.png
    • order-spi

    order-spi模块作为提供给依赖订单服务的其他服务使用

    pom.xml文件

    <?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>order</artifactId>
            <groupId>com.wilson</groupId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>order-spi</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <dependency>
                <groupId>io.swagger</groupId>
                <artifactId>swagger-annotations</artifactId>
                <version>1.5.20</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    </project>
    

    这里引入了openFeign以及swagger依赖,openFeign依赖用于提供@FeignClient注解支持,swagger依赖则提供swagger相关的注解支持

    代码结构

    order-spi.png

    spi包下定义了所有提供给其他服务调用的接口,domain下定义入参,出参类

    com.wilson.order.spi.OrderSpi接口

    package com.wilson.order.spi;
    
    import io.swagger.annotations.ApiParam;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient("platform-order")
    public interface OrderSpi {
    
        @GetMapping(value = "/order/{orderCode}")
        String queryOrder(@ApiParam(value = "订单号", required = true) @PathVariable("orderCode") String orderCode);
    }
    

    至此order-spi整体结构完成,下面看order-service模块


    • order-service模块

    pom.xml文件

    <?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>order</artifactId>
            <groupId>com.wilson</groupId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>order-service</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>com.wilson</groupId>
                <artifactId>order-spi</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    </project>
    

    pom中主要依赖以下:

    1. order-spi

    2. web

    3. eureka-client

    代码结构

    order-service.png

    application.yml文件

    # 监听端口
    server:
      port: 8080
    
    # 注册中心地址,我本地机器启动的一个注册中心
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8716/eureka
    
    # 和order-spi的注解@FeignClient value保持一致,即注册到eureka的应用名
    spring:
      application:
        name: platform-order
    

    OrderServiceApplication

    package com.wilson.order;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    /**
     * @author shuyuan
     * @since 2019/6/4
     */
    @SpringBootApplication
    @EnableEurekaClient
    public class OrderServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceApplication.class, args);
        }
    }
    

    最后看下OrderController代码实现

    package com.wilson.order.controller;
    
    import com.wilson.order.spi.OrderSpi;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author shuyuan
     * @since 2019/6/4
     */
    @RestController
    public class OrderController implements OrderSpi {
    
        /**
         * 查询订单支付状态
         * @param orderCode 订单号
         * @return 订单支付状态
         */
        @Override
        @GetMapping(value = "/order/{orderCode}")
        public String queryOrder(@PathVariable("orderCode") String orderCode) {
    
            // 可以直接在这一层写业务逻辑,也可以再调用一层OrderService来完成订单服务
            // 1. 参数校验
    
            // 2. 查询订单数据库
    
            // 3. 返回订单支付状态
            return "SUCCESS";
        }
    }
    

    另外提一点,微服务开发中,我们应该始终将内部异常捕获后封装成统一的返回值给到服务调用者,不同的异常返回不同的错误码(errorCode)和错误描述(errorMsg),可以利用一个GlobalExeceptionHandler实现,网上随便搜一下一大堆,这里不在赘述

    至此整个order-service的代码实现也完成了,下面我们会新建一个用户服务,模拟调用订单服务完成订单支付状态的查询接口调用


    user-service用户微服务

    定义一个用户微服务,用来模拟用feignClient的方式调用订单微服务接口

    • 项目结构

      user-service.png
    • pom.xml文件

    <?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">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <artifactId>order</artifactId>
            <groupId>com.wilson</groupId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
    
        <artifactId>user-service</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>user-service</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <!-- 需要调用order服务,依赖order-spi -->
            <dependency>
                <groupId>com.wilson</groupId>
                <artifactId>order-spi</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    注意这里依赖了order-spi,以及eureka-client

    • applicetion.yml文件
    server:
      port: 8086
    
    # 注册中心地址,我本地机器启动的一个注册中心
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8716/eureka
    
    # 注册到eureka的应用名
    spring:
      application:
        name: platform-user
    
    • UserServiceApplication启动类
    package com.wilson.userservice;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @EnableFeignClients(basePackages = {"com.wilson.order.spi"})
    @EnableEurekaClient
    @SpringBootApplication
    public class UserServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(UserServiceApplication.class, args);
        }
    
    }
    

    注意这里用到了@EnableFeignClients注解,该注解的意思是启用扫描作为FeignClient的接口

    • UserController
      定义一个Controller用于测试user服务的http请求,内部处理调用order服务的feignClient接口
    package com.wilson.userservice.controller;
    
    import com.wilson.order.spi.OrderSpi;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * @author shuyuan
     * @since 2019/6/6
     */
    @RestController
    public class UserController {
    
        @Resource
        private OrderSpi orderSpi;
    
        @RequestMapping("/user/{userId}/order/{orderId}")
        public String getOrderSuccessByUserId(@PathVariable("userId") String userId, @PathVariable("orderId") String orderId) {
    
            // 调用userService 查询user信息
    
            // 调用feignClient order-spi查询订单信息
            return orderSpi.queryOrder(orderId);
        }
    }
    

    分别启动user-service以及order-service两个服务,以及本地启动的eureka注册中心

    启动后的效果如下


    eureka.png

    至此整个基础工作搭建完毕,我们发起user服务暴露的http请求测试以下效果

    打开浏览器访问
    http://localhost:8086/user/1/order/2

    http.png

    为避免篇幅过长,我会在下一篇博文分析Feign调用原理

    相关文章

      网友评论

          本文标题:Feign源码解析一

          本文链接:https://www.haomeiwen.com/subject/zhokxctx.html