转载

在Spring Cloud中使用OPENFEIGN技巧与最佳实践

与单片应用程序相比,分布式系统中的服务在多台计算机上运行。为了让这些服务相互交互,我们需要某种进程间通信机制。在OpenFeign的帮助下,我将解释如何启动对另一个服务的同步调用。

OpenFign 是Netflix的一个声明性HTTP客户端,它简化了我们与其他服务交互的方式。当我们决定由于众多原因分解我们的模块时,我们必须寻找一种方法来处理我们的进程间通信。

建立

要使用OpenFeign,我们需要将它添加到我们的类路径中:

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

Spring Cloud Starter有很多开箱即用的东西。如果您提供自己的弹性或负载平衡库,则只需添加所需的必要依赖项即可。

使用Spring包装器或OpenFeign本身的语法是不同的。为了让您的Spring上下文知道它必须搜索OpenFeign客户端,我们只需添加@EnableFeignClients。您可以将此批注添加到任何使用@Configuration,@SpringBootApplication或@SpringCloudApplication注释的类中。

在我们的类路径上启用OpenFeign后,我们可以开始添加OpenFeign客户端。定义这些客户端时,我们有两种解决方案供您选择。OpenFeign库(为我们提供了基础但非常可定制的OpenFeign客户端)以及Spring库(为云工具添加了一些额外的库)。

SPRING

@FeignClient(value = <font>"auth"</font><font>, fallback = FallbackAuthClient.<b>class</b>, configuration = FeignConfig.<b>class</b>)
<b>public</b> <b>interface</b> AuthClient { 
    @RequestMapping(method = RequestMethod.GET, value = </font><font>"checkToken"</font><font>)
    <b>boolean</b> isTokenValid(@RequestHeader(</font><font>"Authorization"</font><font>) String token);
}
</font>
  • @FeignClient:是Spring识别OpenFeign客户端的注释,OpenFign客户端必须是接口,因为它是自我声明的。
  • value/name:是将用于创建 Ribbon 负载均衡器的Feign客户端的名称,然后可以使用服务发现或固定的服务器列表将其链接到目标应用程序。当您不使用Ribbon时,您还可以使用url属性将客户端指向目标应用程序。
  • fallback:如果启用了 Hystrix, 则可以实现回退方法,如下:
@Component
<b>public</b> <b>class</b> FallbackAuthClient implements AuthClient {
    @Override
    <b>public</b> <b>boolean</b> isTokenValid(@RequestHeader(<font>"Authorization"</font><font>) String token) {
        <b>return</b> false;
    }
</font>

}

  • configuration:用于记录,拦截器等额外配置...更多内容如下。
  • @RequestMapping:Spring Cloud增加了对Spring MVC注释的支持,使用Spring Web默认的HttpMessageConverter。

OPENFEIGN

要创建OpenFeign客户端,我们需要一个接口和一个Feign构建器,后者是告诉接口它是一个OpenFeign客户端。

<b>public</b> <b>interface</b> AuthClient {
    @RequestLine(<font>"GET /auth"</font><font>)
    @Headers({</font><font>"Authorization: {token}"</font><font>, </font><font>"Accept: application/hal+json"</font><font>})
    <b>boolean</b> isValid(@Param(</font><font>"token"</font><font>) String token);
}
</font>
  • @RequestLine:定义与之通信的动词和URI路径。
  • @Headers:定义请求附带的请求标头。

构建器

OpenFign为我们的客户提供了类似于构建器的模式。当我们想要自定义时,我们只需将自己的自定义添加到构建器中。要查看构建器的工作原理,让我们创建一个客户端bean并返回一个Feign构建器。让构建器知道他必须以哪个接口进行通信是很重要的。第二个参数很可能是所有请求开始的基本URL。借助于从yml或属性文件中获取您的URL @Value

@Value(<font>"${base.url}"</font><font>)
<b>private</b> String baseServerUrl;

@Bean
AuthClient authClient() {
    <b>return</b> Feign.builder()
            .target(AuthClient.<b>class</b>, baseServerUrl);
}
</font>

不同种类的HTTP客户端

OpenFeign的默认HTTP客户端HttpUrlConnection用于执行其HTTP请求。您可以配置其他客户端(ApacheHttpClient,OkHttpClient,...),如下所示:

@Bean
AuthClient authClient() {
    <b>return</b> Feign.builder()
            .client(<b>new</b> ApacheHttpClient())
            .target(AuthClient.<b>class</b>, baseServerUrl);
}

OKHTTPCLIENT

OkHttp是一个默认有效的HTTP客户端:

  • HTTP / 2支持允许对同一主机的所有请求共享套接字。
  • 连接池减少了请求延迟(如果HTTP / 2不可用)。
  • 透明GZIP缩小了下载大小。
  • 响应缓存完全避免网络重复请求

APACHEHTTPCLIENT

使用ApacheHttpClient默认客户端的优点是可以使用ApacheHttpClient请求发送更多标头,例如。Content-Length,这是一些服务器期望的。

启用相互SSL

所有这些客户端都支持相互SSL。为了实现这一目标ApacheHttpClient,我们必须创建一个HttpClient构建SSL上下文的方法。当SSL上下文有效时,我们将其包含在ApacheHttpClient里面,以便与OpenFeign兼容。

<b>public</b> ApacheHttpClient createHttpClient() throws SSLException {
    HttpClient httpClient = HttpClients.custom()
            .setSSLSocketFactory(createSSLContext())
            .build();
    <b>return</b> <b>new</b> ApacheHttpClient(httpClient);
}

将其添加到构建器:

@Bean
AuthClient authClient() {
    <b>return</b> Feign.builder()
            .client(createHttpClient())
            .target(AuthClient.<b>class</b>, baseServerUrl);
}

重试

当我们想在通信中保持一些弹性时,我们可以在OpenFeign客户端中设置重试机制。如果其他服务无法访问,我们将再次尝试,直到它运行正常或者达到您在配置中设置的最大尝试次数为止。当我们想要使用OpenFeign的重试时,我们可以设置三个属性。

  • period:触发​​重试前需要多长时间
  • maxPeriod:这是触发重试之前可以花多长时间的最大值
  • maxAttempts:客户端在失败之前可以触发多少次重试
@Value(<font>"${retry.period:3000}"</font><font>)
<b>private</b> <b>int</b> period;

@Value(</font><font>"${retry.maxPeriod:30000}"</font><font>)
<b>private</b> <b>int</b> maxPeriod;

@Value(</font><font>"${retry.maxAttempts:5}"</font><font>)
<b>private</b> <b>int</b> maxAttempts;

@Bean
AuthClient authClient() {
    <b>return</b> Feign.builder()
            .retryer(<b>new</b> Retryer.Default(period, maxPeriod, maxAttempts))
            .target(AuthClient.<b>class</b>, baseServerUrl);
}
</font>

拦截请求

如果您需要一些基本授权,自定义标头或客户端的每个请求中的一些额外信息,我们可以使用拦截器。这在每个请求都需要这些额外信息的情况下变得非常有用。要添加拦截器,我们只需添加一个返回OpenFeign拦截器的额外方法。

<b>private</b> RequestInterceptor requestInterceptor() {
    <b>return</b> requestTemplate -> {
        requestTemplate.header(<font>"user"</font><font>, username);
        requestTemplate.header(</font><font>"password"</font><font>, password);
        requestTemplate.header(</font><font>"Accept"</font><font>, ContentType.APPLICATION_JSON.getMimeType());
    };
}
</font>

为了启用自定义,我们将拦截器添加到构建器。

@Bean
AuthClient authClient() {
    <b>return</b> Feign.builder()
            .requestInterceptor(requestInterceptor())
            .target(AuthClient.<b>class</b>, baseServerUrl);
}

保护您的API

当我们想在服务之间添加安全层时,有几个解决方案需要关注。以下是OpenFign可以处理的一些内容。

1. 基本安全:如果要发送基本凭据,只需为OpenFeign客户端添加 拦截器 并添加用户名和密码即可。

2. 对于仅承载令牌通信,您可以在方法调用的请求标头中将其传递下来。

<font><i>//Spring</i></font><font>
@RequestMapping(method = RequestMethod.GET, value = </font><font>"checkToken"</font><font>)
<b>boolean</b> isTokenValid(@RequestHeader(</font><font>"Authorization"</font><font>) String token);

</font><font><i>//OpenFeign</i></font><font>
@RequestLine(</font><font>"GET /auth"</font><font>)
@Headers({</font><font>"Authorization: {token}"</font><font>, </font><font>"Accept: application/hal+json"</font><font>})
<b>boolean</b> isValid(@Param(</font><font>"token"</font><font>) String token);
</font>

3. OAUTH2: 此链接提供了有关OAuth2与OpenFeign: OAuth 2拦截器 的使用的良好解释 。

创建SOAP客户端

除了JSON编码器和解码器,您还可以启用对XML的支持。如果您必须与SOAP第三方API集成,OpenFign支持它。有关如何在OpenFeign 文档 中使用它的详细解释。

使用错误解码器处理错误

OpenFeign API提供了一个ErrorDecoder处理来自服务器的错误响应。由于我们可以获得许多错误,因此我们需要一个可以相应处理每个错误的地方。ErrorDecoder必须将OpenFeign 添加到客户端对象的配置中,如下面的代码所示。

@Bean
MyClient myClient() {
   <b>return</b> Feign.builder()
           .errorDecoder(errorDecoder())
           .target(MyClient.<b>class</b>, <url>);
}

不是在decode方法中抛出异常,而是ErrorDecoder向Feign返回一个例外,Feign会为你抛出它。默认错误解码器ErrorDecoder.Default总是抛出一个FeignException。问题是一个FeignException不包含很多结构信息,它只是一个普通版RuntimeException,只包含带有字符串化等响应主体的消息,无法解释该异常,也无法重新抛出更多功能异常例如UserNotFoundException。

错误解码器

要处理错误,我们必须查看这些错误的结构。从错误的结构结构中,我们构建自己的异常并抛出它,ControllerAdvice类才可以处理我们的异常。

<b>public</b> <b>class</b> CustomErrorDecoder implements ErrorDecoder {
    @Override
    <b>public</b> Exception decode(String methodKey, Response response) {
        CustomException ex = <b>null</b>;
        <b>try</b> {
            <b>if</b> (response.body() != <b>null</b>) {
                ex = createException(response);
            }
        } <b>catch</b> (IOException e) {
            log.warn(<font>"Failed to decode CustomException"</font><font>, e);
        }
        ex = ex != <b>null</b> ? ex : <b>new</b> CustomException(</font><font>"Failed to decode CustomException"</font><font>, errorStatus(methodKey, response).getMessage());
        <b>return</b> ex;
    }
    
    <b>private</b> CustomExceptionException createException(Response response) throws IOException {
        String body = Util.toString(response.body().asReader());
        List<ErrorResource> errorMessages = createMessage(body);
        <b>return</b> createCustomException(body, errorMessages);
    }
    
    <b>private</b> List<ErrorResource> createMessage(String body) {
        <b>return</b> read(body, </font><font>"$.errors"</font><font>);
    }
    
    <b>private</b> CustomException createCustomException(String body, List<ErrorResource> errors) {
        CustomException ex = <b>new</b> CustomException();
        ex.setErrors(errors);
        <b>int</b> status = read(body, </font><font>"$.status"</font><font>);
        ex.setStatus(Integer.toString(status));
        ex.setTitle(read(body, </font><font>"$.title"</font><font>));
        <b>return</b> ex;
    }
}
</font>

警告: 由于多种原因,使用已检查的异常和Feign有点棘手。可以在中返回已检查的异常ErrorDecoder,但为了避免使用Java UndeclaredThrowableException,您必须将其添加到Feign界面中的方法签名中。然而,这样做会导致Sonar抱怨,因为没有实际的代码会抛出异常。

结论

这些是我使用OpenFign的经历,我喜欢它的简单性。如果您选择Spring包装器或OpenFeign,则客户端是用于实现服务间通信的高级工具。

原文  https://www.jdon.com/50638
正文到此结束
Loading...