feign拦截器--RequestInterceptor实现数据共享

在使用feign做服务间调用的时候,如何修改请求的头部或编码信息呢,可以通过实现RequestInterceptor接口的apply方法,feign在发送请求之前都会调用该接口的apply方法,所以我们也可以通过实现该接口来记录请求发出去的时间点。

以下是参考文章的内容

RequestInterceptor

feign-core-10.2.3-sources.jar!/feign/RequestInterceptor.java

public interface RequestInterceptor {
 
  /**
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}
  • RequestInterceptor接口定义了apply方法,其参数为RequestTemplate;它有一个抽象类为BaseRequestInterceptor,还有几个实现类分别为BasicAuthRequestInterceptor、FeignAcceptGzipEncodingInterceptor、FeignContentGzipEncodingInterceptor

BasicAuthRequestInterceptor

feign-core-10.2.3-sources.jar!/feign/auth/BasicAuthRequestInterceptor.java

 
public class BasicAuthRequestInterceptor implements RequestInterceptor {
 
  private final String headerValue;
 
  /**
   * Creates an interceptor that authenticates all requests with the specified username and password
   * encoded using ISO-8859-1.
   *
   * @param username the username to use for authentication
   * @param password the password to use for authentication
   */
  public BasicAuthRequestInterceptor(String username, String password) {
    this(username, password, ISO_8859_1);
  }
 
  /**
   * Creates an interceptor that authenticates all requests with the specified username and password
   * encoded using the specified charset.
   *
   * @param username the username to use for authentication
   * @param password the password to use for authentication
   * @param charset the charset to use when encoding the credentials
   */
  public BasicAuthRequestInterceptor(String username, String password, Charset charset) {
    checkNotNull(username, "username");
    checkNotNull(password, "password");
    this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset));
  }
 
  /*
   * This uses a Sun internal method; if we ever encounter a case where this method is not
   * available, the appropriate response would be to pull the necessary portions of Guava's
   * BaseEncoding class into Util.
   */
  private static String base64Encode(byte[] bytes) {
    return Base64.encode(bytes);
  }
 
  @Override
  public void apply(RequestTemplate template) {
    template.header("Authorization", headerValue);
  }
}
  • BasicAuthRequestInterceptor实现了RequestInterceptor接口,其apply方法往RequestTemplate添加名为Authorization的header

BaseRequestInterceptor

spring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/encoding/BaseRequestInterceptor.java

 
public abstract class BaseRequestInterceptor implements RequestInterceptor {
 
    /**
     * The encoding properties.
     */
    private final FeignClientEncodingProperties properties;
 
    /**
     * Creates new instance of {@link BaseRequestInterceptor}.
     * @param properties the encoding properties
     */
    protected BaseRequestInterceptor(FeignClientEncodingProperties properties) {
        Assert.notNull(properties, "Properties can not be null");
        this.properties = properties;
    }
 
    /**
     * Adds the header if it wasn't yet specified.
     * @param requestTemplate the request
     * @param name the header name
     * @param values the header values
     */
    protected void addHeader(RequestTemplate requestTemplate, String name,
            String... values) {
 
        if (!requestTemplate.headers().containsKey(name)) {
            requestTemplate.header(name, values);
        }
    }
 
    protected FeignClientEncodingProperties getProperties() {
        return this.properties;
    }
 
}
  • BaseRequestInterceptor定义了addHeader方法,往requestTemplate添加非重名的header

FeignAcceptGzipEncodingInterceptor

spring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingInterceptor.java

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
 
    /**
     * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
     * @param properties the encoding properties
     */
    protected FeignAcceptGzipEncodingInterceptor(
            FeignClientEncodingProperties properties) {
        super(properties);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void apply(RequestTemplate template) {
 
        addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER,
                HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
    }
 
}
  • FeignAcceptGzipEncodingInterceptor继承了BaseRequestInterceptor,它的apply方法往RequestTemplate添加了名为Accept-Encoding,值为gzip,deflate的header

FeignContentGzipEncodingInterceptor

spring-cloud-openfeign-core-2.2.0.M1-sources.jar!/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingInterceptor.java

public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor {
 
    /**
     * Creates new instance of {@link FeignContentGzipEncodingInterceptor}.
     * @param properties the encoding properties
     */
    protected FeignContentGzipEncodingInterceptor(
            FeignClientEncodingProperties properties) {
        super(properties);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void apply(RequestTemplate template) {
 
        if (requiresCompression(template)) {
            addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER,
                    HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
        }
    }
 
    /**
     * Returns whether the request requires GZIP compression.
     * @param template the request template
     * @return true if request requires compression, false otherwise
     */
    private boolean requiresCompression(RequestTemplate template) {
 
        final Map<String, Collection<String>> headers = template.headers();
        return matchesMimeType(headers.get(HttpEncoding.CONTENT_TYPE))
                && contentLengthExceedThreshold(headers.get(HttpEncoding.CONTENT_LENGTH));
    }
 
    /**
     * Returns whether the request content length exceed configured minimum size.
     * @param contentLength the content length header value
     * @return true if length is grater than minimum size, false otherwise
     */
    private boolean contentLengthExceedThreshold(Collection<String> contentLength) {
 
        try {
            if (contentLength == null || contentLength.size() != 1) {
                return false;
            }
 
            final String strLen = contentLength.iterator().next();
            final long length = Long.parseLong(strLen);
            return length > getProperties().getMinRequestSize();
        }
        catch (NumberFormatException ex) {
            return false;
        }
    }
 
    /**
     * Returns whether the content mime types matches the configures mime types.
     * @param contentTypes the content types
     * @return true if any specified content type matches the request content types
     */
    private boolean matchesMimeType(Collection<String> contentTypes) {
        if (contentTypes == null || contentTypes.size() == 0) {
            return false;
        }
 
        if (getProperties().getMimeTypes() == null
                || getProperties().getMimeTypes().length == 0) {
            // no specific mime types has been set - matching everything
            return true;
        }
 
        for (String mimeType : getProperties().getMimeTypes()) {
            if (contentTypes.contains(mimeType)) {
                return true;
            }
        }
 
        return false;
    }
 
}
  • FeignContentGzipEncodingInterceptor继承了BaseRequestInterceptor,其apply方法先判断是否需要compression,即mimeType是否符合要求以及content大小是否超出阈值,需要compress的话则添加名为Content-Encoding,值为gzip,deflate的header

小结

  • RequestInterceptor接口定义了apply方法,其参数为RequestTemplate;它有一个抽象类为BaseRequestInterceptor,还有几个实现类分别为BasicAuthRequestInterceptor、FeignAcceptGzipEncodingInterceptor、FeignContentGzipEncodingInterceptor
  • BasicAuthRequestInterceptor实现了RequestInterceptor接口,其apply方法往RequestTemplate添加名为Authorization的header
  • BaseRequestInterceptor定义了addHeader方法,往requestTemplate添加非重名的header;FeignAcceptGzipEncodingInterceptor继承了BaseRequestInterceptor,它的apply方法往RequestTemplate添加了名为Accept-Encoding,值为gzip,deflate的header;FeignContentGzipEncodingInterceptor继承了BaseRequestInterceptor,其apply方法先判断是否需要compression,即mimeType是否符合要求以及content大小是否超出阈值,需要compress的话则添加名为Content-Encoding,值为gzip,deflate的header


值得注意的是:该拦截器所在的线程不是controller层接收到请求的线程,因为feign是利用多个线程池来发送请求的,所以,主线程中的ThreadLocal对象在这里失效了,解决方案如下(参考资料:https://bbs.huaweicloud.com/blogs/106521):
https://stackoverflow.com/questions/34719809/unreachable-security-context-using-feign-requestinterceptor 里面提到了使用 HystrixRequestVariableDefault,通过该类的注释可以知道,这个是Hystrix 为自身执行的线程提供的一个类似于ThreadLocal 的类,但是它与ThreadLocal 的不同之处在于,该Locals 的作用范围提升到了这个User Request Scope 级别,通过其注解可知,它是通过父类线程与子类线程共享的方式来共用该Locals 中的信息;所以,达到了User Request 线程与Hystrix 线程共用 attributes 的目的;由此,可以猜测,Hystrix 的线程是由当前Request 线程所创建的子线程;不过,使用的过程中,需要注意的是,HystrixRequestVariable 必须在每一个请求开始的时候进行初始化;也就是说,我们可以将 Request Context 中的有用信息存储到HystrixRequestVariableDefault中达到与Hystrix Context 共享信息;也就实现了Request Context 中的属性与Hystrix Context 之间共享的目的。

总的来说就是用HystrixRequestVariableDefault代替ThreadLocal来作为同一个请求不同环节传递变量,ThreadLocal作用于同一个线程,HystrixRequestVariableDefault作用于同一个请求。

关于HystrixRequestVariableDefault的更多说明,可以直接看该类源码注释。

具体做法看:https://bbs.huaweicloud.com/blogs/106521

实战:实现请求之间cookie共享

@Configuration
public class CookieConfig {
    // feign请求时将被调用,将原来的request中cookie添加到feign请求的header中,实现cookie共享
    @Bean
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {

                //获取请求上下文对象
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                // 判断请求是否存在
                if(requestAttributes!=null){
                    HttpServletRequest request = requestAttributes.getRequest();
                    if(request != null){
                        // 把cookie信息实现共享
                        String cookie = request.getHeader("Cookie");
                        requestTemplate.header("Cookie",cookie);
                    }
                }

            }
        };
    }

}
// Service方法
@Override
    public OrderConfirmVo confirmOrder() {

        // 获取请求上下文信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 异步请求之前,共享请求数据
        RequestContextHolder.setRequestAttributes(requestAttributes);
        // 远程调用用户授权认证,用户中心服务,查询用户数据
        List<UserReceiveAddressVo> userReceiveAddressVos = authRemoteClient.addressList(userInfo.getId());
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`feign-spring-mvc-starter` 是一个 Feign 的扩展,它支持使用 Spring MVC 注解来定义和调用 REST 服务。使用 `feign-spring-mvc-starter`,你可以像使用 Spring MVC 控制器一样定义 Feign 客户端,从而更方便地进行 REST 服务的开发。 在使用 `feign-spring-mvc-starter` 之前,你需要先了解 Feign 和 Spring MVC 的基本概念和用法。 Feign 是一个声明式的 Web 服务客户端,它可以帮助你更方便地定义和调用 REST 服务。Feign 的基本使用方法是定义一个接口,用于描述 REST 服务的 API,然后使用 Feign 注解来声明这个接口。 Spring MVC 是一个基于 Java 的 Web 框架,它提供了一组注解和 API,用于处理 Web 请求和响应。 `feign-spring-mvc-starter` 将 Feign 和 Spring MVC 结合起来,使你可以使用 Spring MVC 注解来定义和调用 REST 服务。使用 `feign-spring-mvc-starter`,你可以更方便地使用 Feign 来调用 REST 服务。 以下是一个使用 `feign-spring-mvc-starter` 的示例: 1. 添加 Maven 依赖 在 pom.xml 文件中添加以下依赖项: ```xml <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-spring-mvc</artifactId> <version>5.3.1</version> </dependency> ``` 2. 定义 Feign 接口 定义一个 Feign 接口,用于描述 REST 服务的 API。例如: ```java @FeignClient(name = "example-service") public interface ExampleClient { @GetMapping("/example") String getExample(); } ``` 在这个接口中,我们使用了 `@FeignClient` 注解来声明这个接口是一个 Feign 客户端,并指定了服务的名称。然后,我们定义了一个 `getExample()` 方法,用于调用 example-service 服务的 /example 路径。 3. 定义 Spring MVC 控制器 定义一个 Spring MVC 控制器,用于处理来自客户端的请求。例如: ```java @RestController public class ExampleController { private final ExampleClient exampleClient; public ExampleController(ExampleClient exampleClient) { this.exampleClient = exampleClient; } @GetMapping("/") public String index() { return exampleClient.getExample(); } } ``` 在这个控制器中,我们注入了 `ExampleClient`,并在 `index()` 方法中使用它来调用 example-service 服务的 /example 路径。 4. 运行应用程序 现在,你可以运行应用程序并访问 http://localhost:8080/ ,你应该会看到来自 example-service 服务的响应。 这就是一个使用 `feign-spring-mvc-starter` 的示例。使用 `feign-spring-mvc-starter`,你可以更方便地使用 Feign 来调用 REST 服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值