Compare commits
21
Commits
v3.0.0
...
v2.2.7.RELEASE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10c6992935 | ||
|
|
d87375e835 | ||
|
|
cc9af41dfe | ||
|
|
9bb8b43d58 | ||
|
|
60558c46ef | ||
|
|
89db04d7d0 | ||
|
|
27a3967975 | ||
|
|
0c4c19bfcd | ||
|
|
c672d205d7 | ||
|
|
6b29a1f7df | ||
|
|
bbef08205d | ||
|
|
0799f3dd7c | ||
|
|
7ef5c0ce96 | ||
|
|
14914b4c97 | ||
|
|
6bfb1edcf1 | ||
|
|
01a3898e83 | ||
|
|
286360cf7b | ||
|
|
14eb8a7bb1 | ||
|
|
a79d82584e | ||
|
|
ae3e3efb26 | ||
|
|
e32b10b60f |
@@ -28,6 +28,8 @@ image:https://api.codacy.com/project/badge/Grade/97b04c4e609c4b4f86b415e4437a648
|
||||
:project-full-name: Spring Cloud OpenFeign
|
||||
:all: {asterisk}{asterisk}
|
||||
|
||||
:core_path: {project-root}/spring-cloud-openfeign-core
|
||||
|
||||
This project provides OpenFeign integrations for Spring Boot apps through autoconfiguration
|
||||
and binding to the Spring Environment and other Spring programming model idioms.
|
||||
|
||||
@@ -314,3 +316,7 @@ Go to `File` -> `Settings` -> `Other settings` -> `Checkstyle`. There click on t
|
||||
- `checkstyle.additional.suppressions.file` - this variable corresponds to suppressions in your local project. E.g. you're working on `spring-cloud-contract`. Then point to the `project-root/src/checkstyle/checkstyle-suppressions.xml` folder. Example for `spring-cloud-contract` would be: `/home/username/spring-cloud-contract/src/checkstyle/checkstyle-suppressions.xml`.
|
||||
|
||||
IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources.
|
||||
|
||||
== License
|
||||
|
||||
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/master/LICENSE.txt[here].
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report security vulnerabilities, please go to https://pivotal.io/security.
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.BUILD-SNAPSHOT</version>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-docs</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -19,3 +19,7 @@ include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/maste
|
||||
== Contributing
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]
|
||||
|
||||
== License
|
||||
|
||||
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/master/LICENSE.txt[here].
|
||||
|
||||
@@ -14,3 +14,5 @@
|
||||
:sc-ext: java
|
||||
:project-full-name: Spring Cloud OpenFeign
|
||||
:all: {asterisk}{asterisk}
|
||||
|
||||
:core_path: {project-root}/spring-cloud-openfeign-core
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
|===
|
||||
|Name | Default | Description
|
||||
|
||||
|feign.autoconfiguration.jackson.enabled | `false` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.
|
||||
|feign.circuitbreaker.enabled | `false` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.
|
||||
|feign.client.config | |
|
||||
|feign.client.decode-slash | true | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`.
|
||||
|feign.client.default-config | default |
|
||||
|feign.client.default-to-properties | true |
|
||||
|feign.compression.request.enabled | false | Enables the request sent by Feign to be compressed.
|
||||
|feign.compression.request.mime-types | [text/xml, application/xml, application/json] | The list of supported mime types.
|
||||
|feign.compression.request.min-request-size | 2048 | The minimum threshold content size.
|
||||
|feign.compression.response.enabled | false | Enables the response from Feign to be compressed.
|
||||
|feign.compression.response.useGzipDecoder | false | Enables the default gzip decoder to be used.
|
||||
|feign.httpclient.connection-timeout | 2000 |
|
||||
|feign.httpclient.connection-timer-repeat | 3000 |
|
||||
|feign.httpclient.disable-ssl-validation | false |
|
||||
|feign.httpclient.enabled | true | Enables the use of the Apache HTTP Client by Feign.
|
||||
|feign.httpclient.follow-redirects | true |
|
||||
|feign.httpclient.max-connections | 200 |
|
||||
|feign.httpclient.max-connections-per-route | 50 |
|
||||
|feign.httpclient.time-to-live | 900 |
|
||||
|feign.client.decode-slash | `true` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`.
|
||||
|feign.client.default-config | `default` |
|
||||
|feign.client.default-to-properties | `true` |
|
||||
|feign.compression.request.enabled | `false` | Enables the request sent by Feign to be compressed.
|
||||
|feign.compression.request.mime-types | `[text/xml, application/xml, application/json]` | The list of supported mime types.
|
||||
|feign.compression.request.min-request-size | `2048` | The minimum threshold content size.
|
||||
|feign.compression.response.enabled | `false` | Enables the response from Feign to be compressed.
|
||||
|feign.compression.response.useGzipDecoder | `false` | Enables the default gzip decoder to be used.
|
||||
|feign.httpclient.connection-timeout | `2000` |
|
||||
|feign.httpclient.connection-timer-repeat | `3000` |
|
||||
|feign.httpclient.disable-ssl-validation | `false` |
|
||||
|feign.httpclient.enabled | `true` | Enables the use of the Apache HTTP Client by Feign.
|
||||
|feign.httpclient.follow-redirects | `true` |
|
||||
|feign.httpclient.max-connections | `200` |
|
||||
|feign.httpclient.max-connections-per-route | `50` |
|
||||
|feign.httpclient.time-to-live | `900` |
|
||||
|feign.httpclient.time-to-live-unit | |
|
||||
|feign.hystrix.enabled | false | If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.
|
||||
|feign.okhttp.enabled | false | Enables the use of the OK HTTP Client by Feign.
|
||||
|feign.hystrix.enabled | `false` | If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.
|
||||
|feign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign.
|
||||
|
||||
|===
|
||||
@@ -24,3 +24,18 @@ public class WebApplication {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions. You can read more on how to contribute to the project https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/README.adoc#3-contributing[here].
|
||||
|
||||
## Community Support
|
||||
|
||||
* You can report issues through https://github.com/spring-cloud/spring-cloud-openfeign/issues[Github].
|
||||
* We monitor https://stackoverflow.com/[StackOverflow] for questions with the `spring-cloud-feign` tag.
|
||||
* You can contact our team at https://gitter.im/spring-cloud/spring-cloud[Gitter].
|
||||
|
||||
## Commercial Support
|
||||
|
||||
Commercial Support is provided as part of the https://spring.io/support[VMware Spring Runtime] offering.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ To use Feign create an interface and annotate it.
|
||||
It has pluggable annotation support including Feign annotations and JAX-RS annotations.
|
||||
Feign also supports pluggable encoders and decoders.
|
||||
Spring Cloud adds support for Spring MVC annotations and for using the same `HttpMessageConverters` used by default in Spring Web.
|
||||
Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
|
||||
Spring Cloud integrates Ribbon and Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
|
||||
|
||||
[[netflix-feign-starter]]
|
||||
=== How to Include Feign
|
||||
@@ -55,7 +55,7 @@ public interface StoreClient {
|
||||
}
|
||||
----
|
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer].
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>> and <<spring-cloud-circuitbreaker,below for details of Spring Cloud CircuitBreaker support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer].
|
||||
You can also specify a URL using the `url` attribute
|
||||
(absolute value or just a hostname). The name of the bean in the
|
||||
application context is the fully qualified name of the interface.
|
||||
@@ -119,6 +119,7 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT
|
||||
* `Logger` feignLogger: `Slf4jLogger`
|
||||
* `Contract` feignContract: `SpringMvcContract`
|
||||
* `Feign.Builder` feignBuilder: `HystrixFeign.Builder`
|
||||
* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder`
|
||||
* `Client` feignClient: if Ribbon is in the classpath and is enabled it is a `LoadBalancerFeignClient`, otherwise if Spring Cloud LoadBalancer is in the classpath, `FeignBlockingLoadBalancerClient` is used.
|
||||
If none of them is in the classpath, the default feign client is used.
|
||||
|
||||
@@ -430,6 +431,44 @@ static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClie
|
||||
|
||||
WARNING: There is a limitation with the implementation of fallbacks in Feign and how Hystrix fallbacks work. Fallbacks are currently not supported for methods that return `com.netflix.hystrix.HystrixCommand` and `rx.Observable`.
|
||||
|
||||
[[spring-cloud-feign-circuitbreaker]]
|
||||
=== Feign Spring Cloud CircuitBreaker Support
|
||||
|
||||
If Spring Cloud CircuitBreaker is on the classpath and `feign.circuitbreaker.enabled=true`, Feign will wrap all methods with a circuit breaker.
|
||||
|
||||
To disable Spring Cloud CircuitBreaker support on a per-client basis create a vanilla `Feign.Builder` with the "prototype" scope, e.g.:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class FooConfiguration {
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
public Feign.Builder feignBuilder() {
|
||||
return Feign.builder();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The circuit breaker name follows this pattern `<feignClientName>_<calledMethod>`. When calling a `@FeignClient` with name `foo` and the called interface method is `bar` then the circuit breaker name will be `foo_bar`.
|
||||
|
||||
[[spring-cloud-feign-circuitbreaker-fallback]]
|
||||
=== Feign Spring Cloud CircuitBreaker Fallbacks
|
||||
|
||||
Spring Cloud CircuitBreaker supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{core_path}/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CircuitBreakerTests.java[tags=client_with_fallback, indent=0]
|
||||
----
|
||||
|
||||
If one needs access to the cause that made the fallback trigger, one can use the `fallbackFactory` attribute inside `@FeignClient`.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{core_path}/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CircuitBreakerTests.java[tags=client_with_fallback_factory, indent=0]
|
||||
----
|
||||
|
||||
=== Feign and `@Primary`
|
||||
|
||||
When using Feign with Hystrix fallbacks, there are multiple beans in the `ApplicationContext` of the same type. This will cause `@Autowired` to not work because there isn't exactly one bean, or one marked as primary. To work around this, Spring Cloud OpenFeign marks all Feign instances as `@Primary`, so Spring Framework will know which bean to inject. In some cases, this may not be desirable. To turn off this behavior set the `primary` attribute of `@FeignClient` to false.
|
||||
@@ -665,6 +704,14 @@ To work around this problem you can use an `ObjectProvider` when autowiring your
|
||||
ObjectProvider<TestFeginClient> testFeginClient;
|
||||
----
|
||||
|
||||
=== Spring Data Support
|
||||
You may consider enabling Jackson Modules for the support `org.springframework.data.domain.Page` and `org.springframework.data.domain.Sort` decoding.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
feign.autoconfiguration.jackson.enabled=true
|
||||
----
|
||||
|
||||
== Configuration properties
|
||||
|
||||
To see the list of all Sleuth related configuration properties please check link:appendix.html[the Appendix page].
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.BUILD-SNAPSHOT</version>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud OpenFeign</name>
|
||||
<description>Spring Cloud OpenFeign</description>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>2.3.1.RELEASE</version>
|
||||
<version>2.3.2.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,8 +26,8 @@
|
||||
<properties>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<jackson.version>2.11.3</jackson.version>
|
||||
<spring-cloud-commons.version>2.2.7.BUILD-SNAPSHOT</spring-cloud-commons.version>
|
||||
<spring-cloud-netflix.version>2.2.7.BUILD-SNAPSHOT</spring-cloud-netflix.version>
|
||||
<spring-cloud-commons.version>2.2.7.RELEASE</spring-cloud-commons.version>
|
||||
<spring-cloud-netflix.version>2.2.7.RELEASE</spring-cloud-netflix.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.BUILD-SNAPSHOT</version>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import static feign.Util.checkNotNull;
|
||||
|
||||
/**
|
||||
* Used to control the fallback given its cause.
|
||||
*
|
||||
* Ex.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* // This instance will be invoked if there are errors of any kind.
|
||||
* FallbackFactory<GitHub> fallbackFactory = cause -> (owner, repo) -> {
|
||||
* if (cause instanceof FeignException && ((FeignException) cause).status() == 403) {
|
||||
* return Collections.emptyList();
|
||||
* } else {
|
||||
* return Arrays.asList("yogi");
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* GitHub github = FeignCircuitBreaker.builder()
|
||||
* ...
|
||||
* .target(GitHub.class, "https://api.github.com", fallbackFactory);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the feign interface type
|
||||
*/
|
||||
public interface FallbackFactory<T> {
|
||||
|
||||
/**
|
||||
* Returns an instance of the fallback appropriate for the given cause.
|
||||
* @param cause cause of an exception.
|
||||
* @return fallback
|
||||
*/
|
||||
T create(Throwable cause);
|
||||
|
||||
final class Default<T> implements FallbackFactory<T> {
|
||||
|
||||
final Log logger;
|
||||
|
||||
final T constant;
|
||||
|
||||
public Default(T constant) {
|
||||
this(constant, LogFactory.getLog(Default.class));
|
||||
}
|
||||
|
||||
Default(T constant, Log logger) {
|
||||
this.constant = checkNotNull(constant, "fallback");
|
||||
this.logger = checkNotNull(logger, "logger");
|
||||
}
|
||||
|
||||
@Override
|
||||
public T create(Throwable cause) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("fallback due to: " + cause.getMessage(), cause);
|
||||
}
|
||||
return constant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return constant.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+56
-4
@@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
@@ -39,25 +40,34 @@ import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
import org.springframework.cloud.openfeign.support.DefaultGzipDecoderConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.cloud.openfeign.support.PageJacksonModule;
|
||||
import org.springframework.cloud.openfeign.support.SortJacksonModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Julien Roy
|
||||
* @author Grzegorz Poznachowski
|
||||
* @author Nikita Konev
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@@ -83,6 +93,39 @@ public class FeignAutoConfiguration {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Module.class, Page.class, Sort.class })
|
||||
@ConditionalOnProperty(value = "feign.autoconfiguration.jackson.enabled",
|
||||
havingValue = "true")
|
||||
protected static class FeignJacksonConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PageJacksonModule.class)
|
||||
public PageJacksonModule pageJacksonModule() {
|
||||
return new PageJacksonModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SortJacksonModule.class)
|
||||
public SortJacksonModule sortModule() {
|
||||
return new SortJacksonModule();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingClass({ "feign.hystrix.HystrixFeign",
|
||||
"org.springframework.cloud.client.circuitbreaker.CircuitBreaker" })
|
||||
protected static class DefaultFeignTargeterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Targeter feignTargeter() {
|
||||
return new DefaultTargeter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
|
||||
protected static class HystrixFeignTargeterConfiguration {
|
||||
@@ -96,13 +139,22 @@ public class FeignAutoConfiguration {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
|
||||
protected static class DefaultFeignTargeterConfiguration {
|
||||
@ConditionalOnClass(CircuitBreaker.class)
|
||||
@ConditionalOnProperty("feign.circuitbreaker.enabled")
|
||||
protected static class CircuitBreakerPresentFeignTargeterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
|
||||
public Targeter defaultFeignTargeter() {
|
||||
return new DefaultTargeter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Targeter feignTargeter() {
|
||||
return new DefaultTargeter();
|
||||
@ConditionalOnBean(CircuitBreakerFactory.class)
|
||||
public Targeter circuitBreakerFeignTargeter(
|
||||
CircuitBreakerFactory circuitBreakerFactory) {
|
||||
return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
|
||||
/**
|
||||
* Allows Feign interfaces to work with {@link CircuitBreaker}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public final class FeignCircuitBreaker {
|
||||
|
||||
private FeignCircuitBreaker() {
|
||||
throw new IllegalStateException("Don't instantiate a utility class");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return builder for Feign CircuitBreaker integration
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for Feign CircuitBreaker integration.
|
||||
*/
|
||||
public static final class Builder extends Feign.Builder {
|
||||
|
||||
private CircuitBreakerFactory circuitBreakerFactory;
|
||||
|
||||
private String feignClientName;
|
||||
|
||||
Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) {
|
||||
this.circuitBreakerFactory = circuitBreakerFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder feignClientName(String feignClientName) {
|
||||
this.feignClientName = feignClientName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> T target(Target<T> target, T fallback) {
|
||||
return build(
|
||||
fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
|
||||
.newInstance(target);
|
||||
}
|
||||
|
||||
public <T> T target(Target<T> target,
|
||||
FallbackFactory<? extends T> fallbackFactory) {
|
||||
return build(fallbackFactory).newInstance(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T target(Target<T> target) {
|
||||
return build(null).newInstance(target);
|
||||
}
|
||||
|
||||
public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
|
||||
super.invocationHandlerFactory(
|
||||
(target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
|
||||
circuitBreakerFactory, feignClientName, target, dispatch,
|
||||
nullableFallbackFactory));
|
||||
return super.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
class FeignCircuitBreakerDisabledConditions extends AnyNestedCondition {
|
||||
|
||||
FeignCircuitBreakerDisabledConditions() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingClass("org.springframework.cloud.client.circuitbreaker.CircuitBreaker")
|
||||
static class CircuitBreakerClassMissing {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "false",
|
||||
matchIfMissing = true)
|
||||
static class CircuitBreakerDisabled {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import feign.InvocationHandlerFactory;
|
||||
import feign.Target;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
|
||||
import static feign.Util.checkNotNull;
|
||||
|
||||
class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final CircuitBreakerFactory factory;
|
||||
|
||||
private final String feignClientName;
|
||||
|
||||
private final Target<?> target;
|
||||
|
||||
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
|
||||
|
||||
private final FallbackFactory<?> nullableFallbackFactory;
|
||||
|
||||
private final Map<Method, Method> fallbackMethodMap;
|
||||
|
||||
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory,
|
||||
String feignClientName, Target<?> target,
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
|
||||
FallbackFactory<?> nullableFallbackFactory) {
|
||||
this.factory = factory;
|
||||
this.feignClientName = feignClientName;
|
||||
this.target = checkNotNull(target, "target");
|
||||
this.dispatch = checkNotNull(dispatch, "dispatch");
|
||||
this.fallbackMethodMap = toFallbackMethod(dispatch);
|
||||
this.nullableFallbackFactory = nullableFallbackFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final Object proxy, final Method method, final Object[] args)
|
||||
throws Throwable {
|
||||
// early exit if the invoked method is from java.lang.Object
|
||||
// code is the same as ReflectiveFeign.FeignInvocationHandler
|
||||
if ("equals".equals(method.getName())) {
|
||||
try {
|
||||
Object otherHandler = args.length > 0 && args[0] != null
|
||||
? Proxy.getInvocationHandler(args[0]) : null;
|
||||
return equals(otherHandler);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ("hashCode".equals(method.getName())) {
|
||||
return hashCode();
|
||||
}
|
||||
else if ("toString".equals(method.getName())) {
|
||||
return toString();
|
||||
}
|
||||
String circuitName = this.feignClientName + "_" + method.getName();
|
||||
CircuitBreaker circuitBreaker = this.factory.create(circuitName);
|
||||
Supplier<Object> supplier = asSupplier(method, args);
|
||||
if (this.nullableFallbackFactory != null) {
|
||||
Function<Throwable, Object> fallbackFunction = throwable -> {
|
||||
Object fallback = this.nullableFallbackFactory.create(throwable);
|
||||
try {
|
||||
return this.fallbackMethodMap.get(method).invoke(fallback, args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
};
|
||||
return circuitBreaker.run(supplier, fallbackFunction);
|
||||
}
|
||||
return circuitBreaker.run(supplier);
|
||||
}
|
||||
|
||||
private Supplier<Object> asSupplier(final Method method, final Object[] args) {
|
||||
return () -> {
|
||||
try {
|
||||
return this.dispatch.get(method).invoke(args);
|
||||
}
|
||||
catch (RuntimeException throwable) {
|
||||
throw throwable;
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If the method param of InvocationHandler.invoke is not accessible, i.e in a
|
||||
* package-private interface, the fallback call will cause of access restrictions. But
|
||||
* methods in dispatch are copied methods. So setting access to dispatch method
|
||||
* doesn't take effect to the method in InvocationHandler.invoke. Use map to store a
|
||||
* copy of method to invoke the fallback to bypass this and reducing the count of
|
||||
* reflection calls.
|
||||
* @return cached methods map for fallback invoking
|
||||
*/
|
||||
static Map<Method, Method> toFallbackMethod(
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
|
||||
Map<Method, Method> result = new LinkedHashMap<Method, Method>();
|
||||
for (Method method : dispatch.keySet()) {
|
||||
method.setAccessible(true);
|
||||
result.put(method, method);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof FeignCircuitBreakerInvocationHandler) {
|
||||
FeignCircuitBreakerInvocationHandler other = (FeignCircuitBreakerInvocationHandler) obj;
|
||||
return this.target.equals(other.target);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.target.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.target.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
class FeignCircuitBreakerTargeter implements Targeter {
|
||||
|
||||
private final CircuitBreakerFactory circuitBreakerFactory;
|
||||
|
||||
FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory) {
|
||||
this.circuitBreakerFactory = circuitBreakerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target) {
|
||||
if (!(feign instanceof FeignCircuitBreaker.Builder)) {
|
||||
return feign.target(target);
|
||||
}
|
||||
FeignCircuitBreaker.Builder builder = (FeignCircuitBreaker.Builder) feign;
|
||||
String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName()
|
||||
: factory.getContextId();
|
||||
Class<?> fallback = factory.getFallback();
|
||||
if (fallback != void.class) {
|
||||
return targetWithFallback(name, context, target, builder, fallback);
|
||||
}
|
||||
Class<?> fallbackFactory = factory.getFallbackFactory();
|
||||
if (fallbackFactory != void.class) {
|
||||
return targetWithFallbackFactory(name, context, target, builder,
|
||||
fallbackFactory);
|
||||
}
|
||||
return builder(name, builder).target(target);
|
||||
}
|
||||
|
||||
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder,
|
||||
Class<?> fallbackFactoryClass) {
|
||||
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext(
|
||||
"fallbackFactory", feignClientName, context, fallbackFactoryClass,
|
||||
FallbackFactory.class);
|
||||
return builder(feignClientName, builder).target(target, fallbackFactory);
|
||||
}
|
||||
|
||||
private <T> T targetWithFallback(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder,
|
||||
Class<?> fallback) {
|
||||
T fallbackInstance = getFromContext("fallback", feignClientName, context,
|
||||
fallback, target.type());
|
||||
return builder(feignClientName, builder).target(target, fallbackInstance);
|
||||
}
|
||||
|
||||
private <T> T getFromContext(String fallbackMechanism, String feignClientName,
|
||||
FeignContext context, Class<?> beanType, Class<T> targetType) {
|
||||
Object fallbackInstance = context.getInstance(feignClientName, beanType);
|
||||
if (fallbackInstance == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"No " + fallbackMechanism
|
||||
+ " instance of type %s found for feign client %s",
|
||||
beanType, feignClientName));
|
||||
}
|
||||
|
||||
if (!targetType.isAssignableFrom(beanType)) {
|
||||
throw new IllegalStateException(String.format("Incompatible "
|
||||
+ fallbackMechanism
|
||||
+ " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
|
||||
beanType, targetType, feignClientName));
|
||||
}
|
||||
return (T) fallbackInstance;
|
||||
}
|
||||
|
||||
private FeignCircuitBreaker.Builder builder(String feignClientName,
|
||||
FeignCircuitBreaker.Builder builder) {
|
||||
return builder.circuitBreakerFactory(this.circuitBreakerFactory)
|
||||
.feignClientName(feignClientName);
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -109,6 +109,7 @@ public @interface FeignClient {
|
||||
* annotated by {@link FeignClient}. The fallback factory must be a valid spring bean.
|
||||
*
|
||||
* @see feign.hystrix.FallbackFactory for details.
|
||||
* @see FallbackFactory for details.
|
||||
* @return fallback factory for the specified Feign client interface
|
||||
*/
|
||||
Class<?> fallbackFactory() default void.class;
|
||||
|
||||
+27
-10
@@ -38,6 +38,8 @@ import feign.codec.ErrorDecoder;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
@@ -58,9 +60,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Matt King
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Ilia Ilinykh
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class FeignClientFactoryBean
|
||||
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
|
||||
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
ApplicationContextAware, BeanFactoryAware {
|
||||
|
||||
/***********************************
|
||||
* WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
|
||||
@@ -83,6 +86,8 @@ class FeignClientFactoryBean
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private Class<?> fallback = void.class;
|
||||
|
||||
private Class<?> fallbackFactory = void.class;
|
||||
@@ -129,8 +134,9 @@ class FeignClientFactoryBean
|
||||
}
|
||||
|
||||
protected void configureFeign(FeignContext context, Feign.Builder builder) {
|
||||
FeignClientProperties properties = applicationContext
|
||||
.getBean(FeignClientProperties.class);
|
||||
FeignClientProperties properties = beanFactory != null
|
||||
? beanFactory.getBean(FeignClientProperties.class)
|
||||
: applicationContext.getBean(FeignClientProperties.class);
|
||||
|
||||
FeignClientConfigurer feignClientConfigurer = getOptional(context,
|
||||
FeignClientConfigurer.class);
|
||||
@@ -283,7 +289,8 @@ class FeignClientFactoryBean
|
||||
|
||||
private <T> T getOrInstantiate(Class<T> tClass) {
|
||||
try {
|
||||
return applicationContext.getBean(tClass);
|
||||
return beanFactory != null ? beanFactory.getBean(tClass)
|
||||
: applicationContext.getBean(tClass);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException e) {
|
||||
return BeanUtils.instantiateClass(tClass);
|
||||
@@ -336,7 +343,7 @@ class FeignClientFactoryBean
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getObject() throws Exception {
|
||||
public Object getObject() {
|
||||
return getTarget();
|
||||
}
|
||||
|
||||
@@ -346,7 +353,9 @@ class FeignClientFactoryBean
|
||||
* information
|
||||
*/
|
||||
<T> T getTarget() {
|
||||
FeignContext context = applicationContext.getBean(FeignContext.class);
|
||||
FeignContext context = beanFactory != null
|
||||
? beanFactory.getBean(FeignContext.class)
|
||||
: applicationContext.getBean(FeignContext.class);
|
||||
Feign.Builder builder = feign(context);
|
||||
|
||||
if (!StringUtils.hasText(url)) {
|
||||
@@ -468,7 +477,8 @@ class FeignClientFactoryBean
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
this.applicationContext = context;
|
||||
applicationContext = context;
|
||||
beanFactory = context;
|
||||
}
|
||||
|
||||
public Class<?> getFallback() {
|
||||
@@ -497,6 +507,7 @@ class FeignClientFactoryBean
|
||||
}
|
||||
FeignClientFactoryBean that = (FeignClientFactoryBean) o;
|
||||
return Objects.equals(applicationContext, that.applicationContext)
|
||||
&& Objects.equals(beanFactory, that.beanFactory)
|
||||
&& decode404 == that.decode404
|
||||
&& inheritParentContext == that.inheritParentContext
|
||||
&& Objects.equals(fallback, that.fallback)
|
||||
@@ -507,8 +518,8 @@ class FeignClientFactoryBean
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(applicationContext, decode404, inheritParentContext, fallback,
|
||||
fallbackFactory, name, path, type, url);
|
||||
return Objects.hash(applicationContext, beanFactory, decode404,
|
||||
inheritParentContext, fallback, fallbackFactory, name, path, type, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -519,9 +530,15 @@ class FeignClientFactoryBean
|
||||
.append("decode404=").append(decode404).append(", ")
|
||||
.append("inheritParentContext=").append(inheritParentContext).append(", ")
|
||||
.append("applicationContext=").append(applicationContext).append(", ")
|
||||
.append("beanFactory=").append(beanFactory).append(", ")
|
||||
.append("fallback=").append(fallback).append(", ")
|
||||
.append("fallbackFactory=").append(fallbackFactory).append("}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+25
-15
@@ -19,7 +19,6 @@ package org.springframework.cloud.openfeign;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
@@ -35,18 +34,19 @@ import feign.optionals.OptionalDecoder;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
|
||||
import org.springframework.cloud.openfeign.support.AbstractFormWriter;
|
||||
import org.springframework.cloud.openfeign.support.PageJacksonModule;
|
||||
import org.springframework.cloud.openfeign.support.PageableSpringEncoder;
|
||||
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SortJacksonModule;
|
||||
import org.springframework.cloud.openfeign.support.SpringDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
@@ -155,18 +155,6 @@ public class FeignClientsConfiguration {
|
||||
return new DefaultFeignLoggerFactory(this.logger);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
|
||||
public Module pageJacksonModule() {
|
||||
return new PageJacksonModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
|
||||
public Module sortModule() {
|
||||
return new SortJacksonModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(FeignClientConfigurer.class)
|
||||
public FeignClientConfigurer feignClientConfigurer() {
|
||||
@@ -212,4 +200,26 @@ public class FeignClientsConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(CircuitBreaker.class)
|
||||
@ConditionalOnProperty("feign.circuitbreaker.enabled")
|
||||
protected static class CircuitBreakerPresentFeignBuilderConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean({ Feign.Builder.class, CircuitBreakerFactory.class })
|
||||
public Feign.Builder defaultFeignBuilder(Retryer retryer) {
|
||||
return Feign.builder().retryer(retryer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(CircuitBreakerFactory.class)
|
||||
public Feign.Builder circuitBreakerFeignBuilder() {
|
||||
return FeignCircuitBreaker.builder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+62
-23
@@ -30,6 +30,9 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.BeanExpressionContext;
|
||||
import org.springframework.beans.factory.config.BeanExpressionResolver;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
@@ -53,6 +56,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Venil Noronha
|
||||
* @author Gang Li
|
||||
* @author Michal Domagala
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class FeignClientsRegistrar
|
||||
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
|
||||
@@ -164,8 +168,6 @@ class FeignClientsRegistrar
|
||||
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
|
||||
Map<String, Object> attrs = metadata
|
||||
.getAnnotationAttributes(EnableFeignClients.class.getName());
|
||||
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
|
||||
FeignClient.class);
|
||||
final Class<?>[] clients = attrs == null ? null
|
||||
: (Class<?>[]) attrs.get("clients");
|
||||
if (clients == null || clients.length == 0) {
|
||||
@@ -206,24 +208,45 @@ class FeignClientsRegistrar
|
||||
private void registerFeignClient(BeanDefinitionRegistry registry,
|
||||
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
|
||||
String className = annotationMetadata.getClassName();
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(FeignClientFactoryBean.class);
|
||||
validate(attributes);
|
||||
definition.addPropertyValue("url", getUrl(attributes));
|
||||
definition.addPropertyValue("path", getPath(attributes));
|
||||
Class clazz = ClassUtils.resolveClassName(className, null);
|
||||
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
|
||||
? (ConfigurableBeanFactory) registry : null;
|
||||
String contextId = getContextId(beanFactory, attributes);
|
||||
String name = getName(attributes);
|
||||
definition.addPropertyValue("name", name);
|
||||
String contextId = getContextId(attributes);
|
||||
definition.addPropertyValue("contextId", contextId);
|
||||
definition.addPropertyValue("type", className);
|
||||
definition.addPropertyValue("decode404", attributes.get("decode404"));
|
||||
definition.addPropertyValue("fallback", attributes.get("fallback"));
|
||||
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
|
||||
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
|
||||
factoryBean.setBeanFactory(beanFactory);
|
||||
factoryBean.setName(name);
|
||||
factoryBean.setContextId(contextId);
|
||||
factoryBean.setType(clazz);
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(clazz, () -> {
|
||||
factoryBean.setUrl(getUrl(beanFactory, attributes));
|
||||
factoryBean.setPath(getPath(beanFactory, attributes));
|
||||
factoryBean.setDecode404(Boolean
|
||||
.parseBoolean(String.valueOf(attributes.get("decode404"))));
|
||||
Object fallback = attributes.get("fallback");
|
||||
if (fallback != null) {
|
||||
factoryBean.setFallback(fallback instanceof Class
|
||||
? (Class<?>) fallback
|
||||
: ClassUtils.resolveClassName(fallback.toString(), null));
|
||||
}
|
||||
Object fallbackFactory = attributes.get("fallbackFactory");
|
||||
if (fallbackFactory != null) {
|
||||
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
|
||||
? (Class<?>) fallbackFactory
|
||||
: ClassUtils.resolveClassName(fallbackFactory.toString(),
|
||||
null));
|
||||
}
|
||||
return factoryBean.getObject();
|
||||
});
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
||||
definition.setLazyInit(true);
|
||||
validate(attributes);
|
||||
|
||||
String alias = contextId + "FeignClient";
|
||||
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
|
||||
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
|
||||
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
|
||||
|
||||
// has a default, won't be null
|
||||
boolean primary = (Boolean) attributes.get("primary");
|
||||
@@ -249,6 +272,10 @@ class FeignClientsRegistrar
|
||||
}
|
||||
|
||||
/* for testing */ String getName(Map<String, Object> attributes) {
|
||||
return getName(null, attributes);
|
||||
}
|
||||
|
||||
String getName(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
|
||||
String name = (String) attributes.get("serviceId");
|
||||
if (!StringUtils.hasText(name)) {
|
||||
name = (String) attributes.get("name");
|
||||
@@ -256,34 +283,46 @@ class FeignClientsRegistrar
|
||||
if (!StringUtils.hasText(name)) {
|
||||
name = (String) attributes.get("value");
|
||||
}
|
||||
name = resolve(name);
|
||||
name = resolve(beanFactory, name);
|
||||
return getName(name);
|
||||
}
|
||||
|
||||
private String getContextId(Map<String, Object> attributes) {
|
||||
private String getContextId(ConfigurableBeanFactory beanFactory,
|
||||
Map<String, Object> attributes) {
|
||||
String contextId = (String) attributes.get("contextId");
|
||||
if (!StringUtils.hasText(contextId)) {
|
||||
return getName(attributes);
|
||||
}
|
||||
|
||||
contextId = resolve(contextId);
|
||||
contextId = resolve(beanFactory, contextId);
|
||||
return getName(contextId);
|
||||
}
|
||||
|
||||
private String resolve(String value) {
|
||||
private String resolve(ConfigurableBeanFactory beanFactory, String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
return this.environment.resolvePlaceholders(value);
|
||||
if (beanFactory == null) {
|
||||
return this.environment.resolvePlaceholders(value);
|
||||
}
|
||||
BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
|
||||
String resolved = beanFactory.resolveEmbeddedValue(value);
|
||||
if (resolver == null) {
|
||||
return resolved;
|
||||
}
|
||||
return String.valueOf(resolver.evaluate(resolved,
|
||||
new BeanExpressionContext(beanFactory, null)));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String getUrl(Map<String, Object> attributes) {
|
||||
String url = resolve((String) attributes.get("url"));
|
||||
private String getUrl(ConfigurableBeanFactory beanFactory,
|
||||
Map<String, Object> attributes) {
|
||||
String url = resolve(beanFactory, (String) attributes.get("url"));
|
||||
return getUrl(url);
|
||||
}
|
||||
|
||||
private String getPath(Map<String, Object> attributes) {
|
||||
String path = resolve((String) attributes.get("path"));
|
||||
private String getPath(ConfigurableBeanFactory beanFactory,
|
||||
Map<String, Object> attributes) {
|
||||
String path = resolve(beanFactory, (String) attributes.get("path"));
|
||||
return getPath(path);
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -20,6 +20,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
@@ -36,6 +37,7 @@ import org.springframework.data.domain.Sort;
|
||||
* This Jackson module provides support to deserialize Spring {@link Page} objects.
|
||||
*
|
||||
* @author Pascal Büttiker
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class PageJacksonModule extends Module {
|
||||
|
||||
@@ -65,7 +67,9 @@ public class PageJacksonModule extends Module {
|
||||
|
||||
SimplePageImpl(@JsonProperty("content") List<T> content,
|
||||
@JsonProperty("number") int number, @JsonProperty("size") int size,
|
||||
@JsonProperty("totalElements") long totalElements,
|
||||
@JsonProperty("totalElements") @JsonAlias({ "total-elements",
|
||||
"total_elements", "totalelements",
|
||||
"TotalElements" }) long totalElements,
|
||||
@JsonProperty("sort") Sort sort) {
|
||||
PageRequest pageRequest;
|
||||
if (sort != null) {
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ public class PageableSpringEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
for (Sort.Order order : sort) {
|
||||
sortQueries.add(order.getProperty() + "," + order.getDirection());
|
||||
sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
|
||||
}
|
||||
if (!sortQueries.isEmpty()) {
|
||||
template.query(sortParameter, sortQueries);
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,16 +26,16 @@ import feign.Response;
|
||||
import feign.codec.Decoder;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Decoder adds compatibility for Spring MVC's ResponseEntity to any other decoder via
|
||||
* composition.
|
||||
*
|
||||
* @author chad jaros
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class ResponseEntityDecoder implements Decoder {
|
||||
|
||||
@@ -81,7 +81,7 @@ public class ResponseEntityDecoder implements Decoder {
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> ResponseEntity<T> createResponse(Object instance, Response response) {
|
||||
|
||||
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
for (String key : response.headers().keySet()) {
|
||||
headers.put(key, new LinkedList<>(response.headers().get(key)));
|
||||
}
|
||||
|
||||
+12
@@ -8,6 +8,18 @@
|
||||
"description": "If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.autoconfiguration.jackson.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.circuitbreaker.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.httpclient.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
+12
-9
@@ -26,10 +26,10 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
@@ -49,18 +49,19 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
"feign.client.config.default.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor" })
|
||||
public class FeignClientUsingConfigurerTest {
|
||||
|
||||
private static final String BEAN_NAME_PREFIX = "&org.springframework.cloud.openfeign.FeignClientUsingConfigurerTest$";
|
||||
private static final String BEAN_NAME_PREFIX = "org.springframework.cloud.openfeign.FeignClientUsingConfigurerTest$";
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
@Autowired
|
||||
private FeignContext context;
|
||||
|
||||
@Test
|
||||
public void testFeignClient() {
|
||||
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
|
||||
.getBean(BEAN_NAME_PREFIX + "TestFeignClient");
|
||||
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) beanFactory
|
||||
.getBeanDefinition(BEAN_NAME_PREFIX + "TestFeignClient")
|
||||
.getAttribute("feignClientsRegistrarFactoryBean");
|
||||
Feign.Builder builder = factoryBean.feign(context);
|
||||
|
||||
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder,
|
||||
@@ -79,8 +80,9 @@ public class FeignClientUsingConfigurerTest {
|
||||
|
||||
@Test
|
||||
public void testNoInheritFeignClient() {
|
||||
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
|
||||
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
|
||||
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) beanFactory
|
||||
.getBeanDefinition(BEAN_NAME_PREFIX + "NoInheritFeignClient")
|
||||
.getAttribute("feignClientsRegistrarFactoryBean");
|
||||
Feign.Builder builder = factoryBean.feign(context);
|
||||
|
||||
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder,
|
||||
@@ -93,8 +95,9 @@ public class FeignClientUsingConfigurerTest {
|
||||
|
||||
@Test
|
||||
public void testNoInheritFeignClient_ignoreProperties() {
|
||||
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
|
||||
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
|
||||
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) beanFactory
|
||||
.getBeanDefinition(BEAN_NAME_PREFIX + "NoInheritFeignClient")
|
||||
.getAttribute("feignClientsRegistrarFactoryBean");
|
||||
Feign.Builder builder = factoryBean.feign(context);
|
||||
|
||||
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set")
|
||||
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,7 +32,9 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -45,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = SpringDecoderTests.Application.class,
|
||||
@@ -152,6 +155,14 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
assertThat(response.getBody()).as("response body was not null").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
// Issue: https://github.com/spring-cloud/spring-cloud-openfeign/issues/456
|
||||
public void testResponseEntityHeaders() {
|
||||
ResponseEntity<String> response = testClient().getContentType();
|
||||
assertThat(response.getHeaders().getContentType())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/helloresponse")
|
||||
@@ -175,6 +186,9 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
@GetMapping("/helloWildcard")
|
||||
ResponseEntity<?> getWildcard();
|
||||
|
||||
@GetMapping(path = "/contentType", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<String> getContentType();
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
@@ -262,6 +276,13 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
return ResponseEntity.ok(new Hello("wildcard"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<String> getContentType() {
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.body("test");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.circuitbreaker;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.client.circuitbreaker.ConfigBuilder;
|
||||
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = CircuitBreakerTests.Application.class,
|
||||
webEnvironment = WebEnvironment.DEFINED_PORT,
|
||||
value = { "spring.application.name=springcircuittest", "spring.jmx.enabled=false",
|
||||
"feign.circuitbreaker.enabled=true" })
|
||||
@DirtiesContext
|
||||
public class CircuitBreakerTests {
|
||||
|
||||
@Autowired
|
||||
MyCircuitBreaker myCircuitBreaker;
|
||||
|
||||
@Autowired
|
||||
TestClient testClient;
|
||||
|
||||
@Autowired
|
||||
TestClientWithFactory testClientWithFactory;
|
||||
|
||||
@LocalServerPort
|
||||
private int port = 0;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
System.setProperty("server.port",
|
||||
String.valueOf(SocketUtils.findAvailableTcpPort()));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClass() {
|
||||
System.clearProperty("server.port");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.myCircuitBreaker.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTypeWithFallback() {
|
||||
Hello hello = testClient.getHello();
|
||||
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test404WithFallback() {
|
||||
assertThat(testClient.getException()).isEqualTo("Fixed response");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTypeWithFallbackFactory() {
|
||||
Hello hello = testClientWithFactory.getHello();
|
||||
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test404WithFallbackFactory() {
|
||||
assertThat(testClientWithFactory.getException()).isEqualTo("Fixed response");
|
||||
}
|
||||
|
||||
// tag::client_with_fallback[]
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/",
|
||||
fallback = Fallback.class)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
static class Fallback implements TestClient {
|
||||
|
||||
@Override
|
||||
public Hello getHello() {
|
||||
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getException() {
|
||||
return "Fixed response";
|
||||
}
|
||||
|
||||
}
|
||||
// end::client_with_fallback[]
|
||||
|
||||
// tag::client_with_fallback_factory[]
|
||||
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
|
||||
fallbackFactory = TestFallbackFactory.class)
|
||||
protected interface TestClientWithFactory {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {
|
||||
|
||||
@Override
|
||||
public FallbackWithFactory create(Throwable cause) {
|
||||
return new FallbackWithFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class FallbackWithFactory implements TestClientWithFactory {
|
||||
|
||||
@Override
|
||||
public Hello getHello() {
|
||||
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getException() {
|
||||
return "Fixed response";
|
||||
}
|
||||
|
||||
}
|
||||
// end::client_with_fallback_factory[]
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients(clients = { TestClient.class, TestClientWithFactory.class })
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application implements TestClient {
|
||||
|
||||
static final Log log = LogFactory.getLog(Application.class);
|
||||
|
||||
@Bean
|
||||
MyCircuitBreaker myCircuitBreaker() {
|
||||
return new MyCircuitBreaker();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CircuitBreakerFactory circuitBreakerFactory(MyCircuitBreaker myCircuitBreaker) {
|
||||
return new CircuitBreakerFactory() {
|
||||
@Override
|
||||
public CircuitBreaker create(String id) {
|
||||
log.info("Creating a circuit breaker with id [" + id + "]");
|
||||
return myCircuitBreaker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigBuilder configBuilder(String id) {
|
||||
return Object::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureDefault(Function defaultConfiguration) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getException() {
|
||||
throw new IllegalStateException("BOOM!");
|
||||
}
|
||||
|
||||
@Bean
|
||||
Fallback fallback() {
|
||||
return new Fallback();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TestFallbackFactory testFallbackFactory() {
|
||||
return new TestFallbackFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.circuitbreaker;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.client.circuitbreaker.ConfigBuilder;
|
||||
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for Feign calls with CircuitBreaker, without fallbacks.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SpringBootTest(classes = CircuitBreakerWithNoFallbackTests.Application.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
|
||||
value = { "spring.application.name=springcircuittest", "spring.jmx.enabled=false",
|
||||
"feign.circuitbreaker.enabled=true" })
|
||||
@DirtiesContext
|
||||
public class CircuitBreakerWithNoFallbackTests {
|
||||
|
||||
@Autowired
|
||||
MyCircuitBreaker myCircuitBreaker;
|
||||
|
||||
@Autowired
|
||||
CircuitBreakerTestClient testClient;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
System.setProperty("server.port",
|
||||
String.valueOf(SocketUtils.findAvailableTcpPort()));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClass() {
|
||||
System.clearProperty("server.port");
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.myCircuitBreaker.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleTypeWithFallback() {
|
||||
Hello hello = testClient.getHello();
|
||||
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test404WithoutFallback() {
|
||||
assertThatThrownBy(() -> testClient.getException())
|
||||
.isInstanceOf(NoFallbackAvailableException.class);
|
||||
}
|
||||
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/")
|
||||
protected interface CircuitBreakerTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients(clients = { CircuitBreakerTestClient.class })
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application implements CircuitBreakerTestClient {
|
||||
|
||||
static final Log log = LogFactory.getLog(Application.class);
|
||||
|
||||
@Bean
|
||||
MyCircuitBreaker myCircuitBreaker() {
|
||||
return new MyCircuitBreaker();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CircuitBreakerFactory circuitBreakerFactory(MyCircuitBreaker myCircuitBreaker) {
|
||||
return new CircuitBreakerFactory() {
|
||||
@Override
|
||||
public CircuitBreaker create(String id) {
|
||||
log.info("Creating a circuit breaker with id [" + id + "]");
|
||||
return myCircuitBreaker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigBuilder configBuilder(String id) {
|
||||
return Object::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureDefault(Function defaultConfiguration) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getException() {
|
||||
throw new IllegalStateException("BOOM!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.circuitbreaker;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class Hello {
|
||||
|
||||
private String message;
|
||||
|
||||
public Hello() {
|
||||
}
|
||||
|
||||
public Hello(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Hello that = (Hello) o;
|
||||
return Objects.equals(this.message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.message);
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.circuitbreaker;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
class MyCircuitBreaker implements CircuitBreaker {
|
||||
|
||||
AtomicBoolean runWasCalled = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public <T> T run(Supplier<T> toRun) {
|
||||
try {
|
||||
this.runWasCalled.set(true);
|
||||
return toRun.get();
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
throw new NoFallbackAvailableException("No fallback available.", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
|
||||
try {
|
||||
return run(toRun);
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
return fallback.apply(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.runWasCalled.set(false);
|
||||
}
|
||||
|
||||
}
|
||||
+64
-2
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.openfeign.encoding;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
@@ -32,7 +33,6 @@ import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoCon
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
|
||||
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
|
||||
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
@@ -55,10 +55,12 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
* Tests the pagination encoding.
|
||||
*
|
||||
* @author Charlie Mordant.
|
||||
* @author Hyeonmin Park
|
||||
*/
|
||||
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class,
|
||||
webEnvironment = RANDOM_PORT,
|
||||
value = { "feign.compression.request.enabled=true",
|
||||
"feign.autoconfiguration.jackson.enabled=true",
|
||||
"hystrix.command.default.execution.isolation.strategy=SEMAPHORE",
|
||||
"ribbon.OkToRetryOnAllOperations=false" })
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -93,13 +95,73 @@ public class FeignPageableEncodingTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPageableWithDescDirection() {
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "sortProperty");
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPaged(pageable);
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
|
||||
Sort sort = response.getBody().getPageable().getSort();
|
||||
assertThat(sort).hasSize(1);
|
||||
assertThat(sort.get()).hasSize(1);
|
||||
|
||||
Optional<Sort.Order> optionalOrder = sort.get().findFirst();
|
||||
assertThat(optionalOrder.isPresent()).isTrue();
|
||||
|
||||
Sort.Order order = optionalOrder.get();
|
||||
assertThat(order.getDirection()).isEqualTo(Sort.Direction.DESC);
|
||||
assertThat(order.getProperty()).isEqualTo("sortProperty");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPageableWithMultipleSort() {
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort
|
||||
.by(Sort.Order.desc("sortProperty1"), Sort.Order.asc("sortProperty2")));
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPaged(pageable);
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
|
||||
Sort sort = response.getBody().getPageable().getSort();
|
||||
assertThat(sort).hasSize(2);
|
||||
|
||||
List<Sort.Order> orderList = sort.toList();
|
||||
assertThat(orderList).hasSize(2);
|
||||
|
||||
Sort.Order firstOrder = orderList.get(0);
|
||||
assertThat(firstOrder.getDirection()).isEqualTo(Sort.Direction.DESC);
|
||||
assertThat(firstOrder.getProperty()).isEqualTo("sortProperty1");
|
||||
|
||||
Sort.Order secondOrder = orderList.get(1);
|
||||
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
|
||||
assertThat(secondOrder.getProperty()).isEqualTo("sortProperty2");
|
||||
|
||||
}
|
||||
|
||||
@EnableFeignClients(clients = InvoiceClient.class)
|
||||
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",
|
||||
exclude = { RepositoryRestMvcAutoConfiguration.class })
|
||||
@EnableSpringDataWebSupport
|
||||
@Import({ NoSecurityConfiguration.class, FeignClientsConfiguration.class })
|
||||
@Import({ NoSecurityConfiguration.class })
|
||||
public static class Application {
|
||||
|
||||
}
|
||||
|
||||
+11
-4
@@ -19,14 +19,18 @@ package org.springframework.cloud.openfeign.support;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for {@link PageJacksonModule}.
|
||||
*
|
||||
* @author Ruben Vervaeke
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class PageJacksonModuleTests {
|
||||
|
||||
@@ -38,10 +42,13 @@ public class PageJacksonModuleTests {
|
||||
objectMapper.registerModule(new PageJacksonModule());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializePage() throws JsonProcessingException {
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "totalElements", "total-elements", "total_elements",
|
||||
"totalelements", "TotalElements" })
|
||||
public void deserializePage(String totalElements) throws JsonProcessingException {
|
||||
// Given
|
||||
String pageJson = "{\"content\":[\"A name\"], \"number\":1, \"size\":2, \"totalElements\": 3}";
|
||||
String pageJson = "{\"content\":[\"A name\"], \"number\":1, \"size\":2, \""
|
||||
+ totalElements + "\": 3}";
|
||||
// When
|
||||
Page<?> result = objectMapper.readValue(pageJson, Page.class);
|
||||
// Then
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.3.2.BUILD-SNAPSHOT</version>
|
||||
<version>2.3.2.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
|
||||
<version>2.2.7.BUILD-SNAPSHOT</version>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-openfeign-dependencies</name>
|
||||
<description>Spring Cloud OpenFeign Dependencies</description>
|
||||
@@ -32,74 +32,16 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<artifactId>feign-bom</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign.form</groupId>
|
||||
<artifactId>feign-form-spring</artifactId>
|
||||
<version>${feign-form.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-slf4j</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-gson</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jackson-jaxb</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jackson</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jaxb</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jaxrs</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-ribbon</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-sax</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-mock</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<profiles>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.BUILD-SNAPSHOT</version>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
|
||||
"https://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
|
||||
<suppressions>
|
||||
<suppress files=".*FeignClientTests.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*FeignClientsRegistrarTests.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*FeignRibbonHttpClientConfigurationTests.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*FeignRibbonOkHttpClientConfigurationTests.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*InvoiceClient.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*InvoiceResource.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*IterableParameterTests.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*LoadBalancerFeignClientOverrideTests.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*ProtobufSpringEncoderTest.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*ProtobufTest.*" checks="LineLengthCheck" />
|
||||
<suppress files=".*FeignClientTests.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*FeignClientsRegistrarTests.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*FeignRibbonHttpClientConfigurationTests.*"
|
||||
checks="LineLengthCheck"/>
|
||||
<suppress files=".*FeignRibbonOkHttpClientConfigurationTests.*"
|
||||
checks="LineLengthCheck"/>
|
||||
<suppress files=".*InvoiceClient.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*InvoiceResource.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*IterableParameterTests.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*LoadBalancerFeignClientOverrideTests.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*ProtobufSpringEncoderTest.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*ProtobufTest.*" checks="LineLengthCheck"/>
|
||||
<suppress files=".*Hello.*" checks="RedundantModifier"/>
|
||||
<!-- Important -->
|
||||
<suppress files=".*HttpEncoding.*" checks="InterfaceIsTypeCheck" />
|
||||
<suppress files=".*HttpEncoding.*" checks="InterfaceIsTypeCheck"/>
|
||||
</suppressions>
|
||||
|
||||
Reference in New Issue
Block a user