与单片应用程序相比,分布式系统中的服务在多台计算机上运行。为了让这些服务相互交互,我们需要某种进程间通信机制。在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>
@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>
}
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>
构建器
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客户端:
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的重试时,我们可以设置三个属性。
@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,则客户端是用于实现服务间通信的高级工具。