Feign的使用

参考谷粒商城

1、简介

Feign 是一个声明式的 HTTP 客户端,它的目的就是让远程调用更加简单。Feign 提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。
Feign 整合了 Ribbon(负载均衡)和 Hystrix(服务熔断),可以让我们不再需要显式地使用这两个组件。

SpringCloudFeign 在 NetflixFeign 的基础上扩展了对 SpringMVC 注解的支持,在其实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。简化了SpringCloudRibbon 自行封装服务调用客户端的开发量。

2、使用

2.1 引入依赖

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

要使用远程调用,必须先在nacos中注册服务,能使feign找到被调用的服务

2.2 需要远程调用的服务开启 feign 功能

#在springboot启动类上添加该注解
#basePackages为告诉springboot扫描哪个包下的远程调用
@EnableFeignClients(basePackages = "com.atguigu.gulimall.pms.feign")

2.3 声明远程接口

@FeignClient(“gulimall-ware”):声明该接口需要远程调用哪个服务 gulimall-ware为被调用服务的服务名
该接口中的每一个方法都是被远程调用服务中的完整方法签名,(包含完整请求地址)

@FeignClient("gulimall-ware")
public interface WareFeignService {
	@PostMapping("/ware/waresku/skus")
	public Resp<List<SkuStockVo>> skuWareInfos(@RequestBody List<Long> skuIds);
}

2.4 远程调用原理

比如有一个A服务,需要调用B服务中的一个接口:
B服务接口如下:

    /**
     * 保存
     */
    @PostMapping("/save")
    // @RequiresPermissions("coupon:spubounds:save")
    public R save(@RequestBody SpuBoundsEntity spuBounds){
		spuBoundsService.save(spuBounds);
        return R.ok();
    }

A服务创建一个远程调用接口

@FeignClient("gulimall-coupon")
public interface CouponFeignService {
    @PostMapping("/coupon/spubounds/save") // 此处必须是被调用服务接口的完整路径
    public R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
}

调用原理:

  • 1、A服务的@RequestBody将SpuBoundTo 对象转为json数据。
  • 2、feign根据@FeignClient(“gulimall-coupon”)在服务注册中心找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。将上一步转的json数据放在请求体里,发送请求。
  • 3、被调用服务收到请求。获得请求体里的json数据,B服务的@RequestBody SpuBoundsEntity spuBounds会将请求体里的json数据转为SpuBoundsEntity ,(SpuBoundTo 内的 字段和SpuBoundsEntity 内的字段必须保持一致)

参数传输总结:只要json数据模型是兼容的,双方服务无需使用同一个TO

3、解决远程调用丢失请求头信息

在远程调用中,由于feign会在远程调用之前会构造一个新请求去远程调用,而新请求内没有我们从浏览器携带过来的各种头信息,所有就需要在feign构造请求的时候,调用的拦截器中解决请求头丢失的问题。

3.1、同一线程下远程调用丢失请求头信息

@Configuration
public class GuliFeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return new RequestInterceptor() {

            @Override
            public void apply(RequestTemplate requestTemplate) {
                // 1、使用RequestContextHolder拿到刚进来的这个请求
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = attributes.getRequest();
                // 同步请求头数据,Cookie
                System.out.println("feign远程调用之前先进行RequestInterceptor.apply");
                String cookie = request.getHeader("Cookie");
                // 给新请求同步了老请求的cookie
                requestTemplate.header("Cookie", cookie);
            }
        };
    }
}

3.1、不同线程下异步远程调用丢失请求头信息

上面的拦截器设置请求头时,获取的是当前主线程的请求信息,但是如果是异步线程远程调用下,则异步线程在拦截器中获取不到主线程的请求信息,如下:

    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
        // 1、远程查询所有的收货地址
        CompletableFuture<List<MemberAddressVo> > addressFuture = CompletableFuture.supplyAsync(() -> {
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
            return address;
        }, threadPoolExecutor);

        // 2、远程查询购物车所有选中的购物项
        // feign在远程调用之前要构造请求,构造请求会调用很多拦截器
        CompletableFuture<List<OrderItemVo> > orderItemFuture = CompletableFuture.supplyAsync(() -> {
            List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
            return currentUserCartItems;
        }, threadPoolExecutor);

        // 3、查询用户积分
        Integer integration = memberResponseVo.getIntegration();
        orderConfirmVo.setIntegration(integration);
        // 4、其他数据自动计算
        // todo 5、防重令牌
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(addressFuture, orderItemFuture);
        voidCompletableFuture.get();
        List<MemberAddressVo> memberAddressVos = addressFuture.get();
        List<OrderItemVo> orderItemVos = orderItemFuture.get();
        orderConfirmVo.setAddress(memberAddressVos);
        orderConfirmVo.setItems(orderItemVos);
        return orderConfirmVo;
    }

所以,我们在异步远程调用时,需要手动设置异步线程的上下文与主线程同步 保证在feign的拦截器中可以使用到主线程的信息

@Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();

        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();

        // 获取主线程的上下文
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 1、远程查询所有的收货地址
        CompletableFuture<List<MemberAddressVo> > addressFuture = CompletableFuture.supplyAsync(() -> {
            // 设置异步线程的上下文与主线程同步 保证在feign的拦截器中可以使用到主线程的信息
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
            return address;
        }, threadPoolExecutor);


        // 2、远程查询购物车所有选中的购物项
        // feign在远程调用之前要构造请求,构造请求会调用很多拦截器
        CompletableFuture<List<OrderItemVo> > orderItemFuture = CompletableFuture.supplyAsync(() -> {
            // 设置异步线程的上下文与主线程同步 保证在feign的拦截器中可以使用到主线程的信息
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
            return currentUserCartItems;
        }, threadPoolExecutor);


        // 3、查询用户积分
        Integer integration = memberResponseVo.getIntegration();
        orderConfirmVo.setIntegration(integration);
        // 4、其他数据自动计算
        // todo 5、防重令牌

        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(addressFuture, orderItemFuture);
        voidCompletableFuture.get();
        List<MemberAddressVo> memberAddressVos = addressFuture.get();
        List<OrderItemVo> orderItemVos = orderItemFuture.get();
        orderConfirmVo.setAddress(memberAddressVos);
        orderConfirmVo.setItems(orderItemVos);
        return orderConfirmVo;
    }

此时在feign的拦截器中,就能获取到主线程的请求信息了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值