文章目录

  • SpringCloud 分布式基础篇
  • 一、openfeign是什么?
  • 二、快速开始
  • 1. 引入依赖
  • 2. 调用者服务创建接口
  • 3.启用openfeign
  • 三、传递token
  • 四、异步丢失上下文
  • 1.RequestContextHolder
  • 2. 异步调用
  • 3.解决方式
  • 五、传递文件流



SpringCloud 分布式基础篇

一、openfeign是什么?

openfeign是一个声明式的Web服务客户端。它使编写Web服务客户端更容易。
为了简化开发,OpenFeign整合了Ribbon和Hystrix,并且可以不通过RestTemplate就可以调用其他服务。
本章暂时不介绍Ribbon和Hystrix。

二、快速开始

1. 引入依赖

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

2. 调用者服务创建接口

@FeignClient(“stores”) 调用nacos服务中心中stores服务 也可以用path参数指定相同部分uri@FeignClient(value = "stores", path = "/api/demo/stores")

@FeignClient("stores")
public interface StoreClient {
    //该方法为stores服务暴露的接口签名,即stores服务controller层的每个接口头部代码
    //如果FeignClient指定path,则value直接写后面部分即可,即 value = "/stores"
    @RequestMapping(method = RequestMethod.GET, value = "/api/demo/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.GET, value = "/api/demo/stores")
    Page<Store> getStores(Pageable pageable);

    @RequestMapping(method = RequestMethod.POST, value = "/api/demo/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

3.启用openfeign

在调用者的启动文件添加注解@EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

三、传递token

  • openfeign调用时会丢失请求头。
  • openfeign请求服务时会自动创建一个新的请求,虽然最开始的入口请求会有请求头,但是当openfeign实际触发的时候会新建一个请求,这个请求是不带任何header信息的。
  • openfeign在创建请求对象的过程中会查询该服务是否有拦截器,拦截器会对请求对象进行丰富,那么答案就在这里

    在调用方服务添加拦截器
/**
 * 同步token 如果使用了熔断器,暂时配置关闭feign.hystrix.enabled=false 否则取不到attributes.
 *
 * @author: Mr.wanter
 * @since 2021-04-06 14:08
 */
@Configuration
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (null != attributes) {
            HttpServletRequest request = attributes.getRequest();
            if (null != request) {
            	//获取请求头
                String token = request.getHeader("token");
                if (null != token) {
                    token = StringUtils.removeStart(token, "Bearer ");
                    //设置新请求的请求头
                    template.header("token", new String[]{token});
                }
            }
        }
    }

}

spring cloud本地启动的服务如何与远程网关部署的开发服务互相调用 springcloud远程调用流程_主线程

四、异步丢失上下文

当我们使用异步处理openfeign请求时,我们会发现拦截器中ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();为空。

1.RequestContextHolder

RequestContextHolder为spring为我们提供的上下文工具类。
查看源码我们发现,他的底层是基于ThreadLocal实现的。即获取的数据为主线程的上下文信息。

public abstract class RequestContextHolder {
    private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");

2. 异步调用

任何异步调用方式都是以开启新线程的方式进行异步调用。new Thread()CompletableFuture.runAsync()

spring cloud本地启动的服务如何与远程网关部署的开发服务互相调用 springcloud远程调用流程_子线程_02

3.解决方式

  1. 在主线程获取一次上下文
    RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); 异步处理中将主线程的上下文添加到异步线程中
    RequestContextHolder.setRequestAttributes(attributes);
  2. 线程共享:在主线程执行RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);true表示子线程共享主线程上下文。
    说明:这种共享方式适合用于主线程等待子线程完成任务后再结束的情况,否则主线程先于子线程结束时主线程的request销毁,子线程还是共享不了主线程的request属性。