文章目录

  • 项目整体结构
  • 依赖
  • openfeign的一些配置
  • order-service-api
  • order-service
  • pay-service
  • 测试
  • 源码分析
  • 源码下载


项目整体结构

springboot WireMock springboot wiremock feign_springboot WireMock

说明:

  • 所有公共依赖都放在了父pom中,API接口抽离放在单独模块

依赖

<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

这里模拟支付服务调用订单服务,订单 order-service 会依赖于 order-service-api, pay-service 会依赖于 pay-service-api,同时因为要调用 订单服务,所以也会依赖于 order-service-api

springboot WireMock springboot wiremock feign_spring boot_02

springboot WireMock springboot wiremock feign_spring_03

这样做的好处是抽离了公共的接口,同事将model放在了公共API采用继承方式减少重复代码,不采用这种方式模块间的相互调用就会写重复的model和接口

openfeign的一些配置

feign:
  client:
    config:
    # 全局级别配置
      default:
        connectTimeout: 5000
        readTimeout: 5000
        # 只打印基本信息,包括请求方法、请求地址、响应状态码、请求时长
        loggerLevel: basic

我们也可以采用 @Configuration的 方式来配置 feign,如果配置文件和 @Configuration都配置了,配置文件将会覆盖@configuration 值,如果希望将优先级更改为@configuration,可以将 feign.client.default-to-properties 更改为 false
在 Feign 中,定义了四种日志级别:

  • NONE:不打印日志
  • BASIC:只打印基本信息,包括请求方法、请求地址、响应状态码、请求时长
  • HEADERS:在 BASIC 基础信息的基础之上,增加请求头、响应头
  • FULL:打印完整信息,包括请求和响应的所有信息。

order-service-api

结构:

springboot WireMock springboot wiremock feign_springboot WireMock_04

  • Order
@Data
public class Order {

    /**
     * 订单号
     */
    private String orderNo;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * 商品数量
     */
    private Integer orderCount;
}
  • OrderService
@RestController
@RequestMapping("/order")
public interface OrderService {

    @GetMapping(value = "/get-order",headers = {"Accept=application/json"})
    Order getOrder(@RequestParam("orderNo")String orderNo);
}

order-service

结构:

springboot WireMock springboot wiremock feign_openfeign_05

  • application.properties
server.port=8080
spring.application.name=order-service
  • OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order getOrder(String orderNo) {
        System.out.println("收到的订单号为: " + orderNo);
        Order order = new Order();
        order.setOrderNo("123456");
        order.setOrderCount(5);
        order.setPrice(new BigDecimal("52.6"));
        return order;
    }
}
  • OrderController
public class OrderController implements OrderService{

    @Autowired
    OrderService orderService;


    @Override
    public Order getOrder(String orderNo) {
        return orderService.getOrder("123");
    }
}

pay-service

目录结构:

springboot WireMock springboot wiremock feign_springboot WireMock_06

  • PayController
public class PayController implements PayService{


    @Autowired
    PayService payService;

   @Override
    public boolean pay() {
        payService.pay();
       return true;

    }
}
  • OrderServiceApi
@FeignClient(name = "order-service",url = "127.0.0.1:8080")
public interface OrderServiceApi extends OrderService {
}

这里name 是服务名 即订单服务,url是订单服务的访问地址,这里因为没有用到注册中心,所以需要写url

  • PayServiceImpl
@Service
public class PayServiceImpl implements PayService {


    @Autowired
    private OrderServiceApi orderServiceApi;



    @Override
    public boolean pay() {
        Order order = orderServiceApi.getOrder("123456");
        System.out.println("获取到订单号" + order.getOrderNo());
        return true;
    }
}

这里的url也是支持占位符的 类似 这种 url = "${order.service.baseUrl} 然后将地址写在配置文件中也是可以的

项目大致结构是这样,pay-service-api 和 order-service-api类似,感兴趣可以自己去源码查看

然后我们需要在 order-service 和 pay-service 的启动类开启使用Fegin的注解

springboot WireMock springboot wiremock feign_openfeign_07

测试

然后同时启动 order-service 和 pay-service,注意自己在 配置文件配置不同的端口,idea启动多个实例请查看我以前的博客教程

然后访问调用 支付接口

springboot WireMock springboot wiremock feign_springboot WireMock_08

springboot WireMock springboot wiremock feign_ide_09

springboot WireMock springboot wiremock feign_spring_10

这里可以看到调用Order-service 服务已经成功了

源码分析

首先我们 从 启动类的 @EnableFeignClients 注解分析

springboot WireMock springboot wiremock feign_spring boot_11


这里可以看到导入了一个配置类 EnableFeignClients那么很明显 EnableFeignClients 配置类会将FeignClient注解的类注册到Spring IOC容器里供我们使用,我们来看看看 EnableFeignClients 核心代码

在 registerDefaultConfiguration 方法中

springboot WireMock springboot wiremock feign_springboot WireMock_12


首先会去检测 EnableFeignClients 中是否配置 clients,如果没有配置则将 通过 @FeignClient 注解去找到相应的客户端,放入BeanDefinitionBuilder,然后根据BeanDefinitionBuilder得到beanDefinition,最后beanDefinition式注入到ioc容器

springboot WireMock springboot wiremock feign_ide_13


通过包扫描获取到 @FeignClient 的类的 beanDefinition

springboot WireMock springboot wiremock feign_spring_14

这里再调用 registerFeignClient 注入到IOC容器中

springboot WireMock springboot wiremock feign_ide_15


springboot WireMock springboot wiremock feign_spring boot_16

注入bean之后,通过jdk的代理,当请求Feign Client的方法时会被拦截,然后通过ReflectiveFeign类获取对象,代码如下:

public <T> T newInstance(Target<T> target) {
        Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
        Method[] var5 = target.type().getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method method = var5[var7];
            if (method.getDeclaringClass() != Object.class) {
                if (Util.isDefault(method)) {
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method, handler);
                } else {
                    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
                }
            }
        }

        InvocationHandler handler = this.factory.create(target, methodToHandler);
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
        Iterator var12 = defaultMethodHandlers.iterator();

        while(var12.hasNext()) {
            DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
            defaultMethodHandler.bindTo(proxy);
        }

        return proxy;
    }