diff --git a/README.adoc b/README.adoc index d4e5b749..5110b8b4 100644 --- a/README.adoc +++ b/README.adoc @@ -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. diff --git a/docs/src/main/asciidoc/_attributes.adoc b/docs/src/main/asciidoc/_attributes.adoc index 882edab8..6efc1231 100644 --- a/docs/src/main/asciidoc/_attributes.adoc +++ b/docs/src/main/asciidoc/_attributes.adoc @@ -14,3 +14,5 @@ :sc-ext: java :project-full-name: Spring Cloud OpenFeign :all: {asterisk}{asterisk} + +:core_path: {project-root}/spring-cloud-openfeign-core diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index ab087e78..1845fd4b 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -1,6 +1,7 @@ |=== |Name | Default | Description +|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` | @@ -19,7 +20,6 @@ |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. |=== \ No newline at end of file diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 751279f7..e1c14214 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -114,7 +114,7 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT * `Encoder` feignEncoder: `SpringEncoder` * `Logger` feignLogger: `Slf4jLogger` * `Contract` feignContract: `SpringMvcContract` -* `Feign.Builder` feignBuilder: `HystrixFeign.Builder` +* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder` * `Client` feignClient: 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. @@ -206,26 +206,6 @@ If we create both `@Configuration` bean and configuration properties, configurat It will override `@Configuration` values. But if you want to change the priority to `@Configuration`, you can change `feign.client.default-to-properties` to `false`. -NOTE: If you need to use `ThreadLocal` bound variables in your `RequestInterceptor`s you will need to either set the -thread isolation strategy for Hystrix to `SEMAPHORE` or disable Hystrix in Feign. - -application.yml -[source,yaml] ----- -# To disable Hystrix in Feign -feign: - hystrix: - enabled: false - -# To set thread isolation to SEMAPHORE -hystrix: - command: - default: - execution: - isolation: - strategy: SEMAPHORE ----- - If we want to create multiple feign clients with the same name or url so that they would point to the same server but each with a different custom configuration then we have to use `contextId` attribute of the `@FeignClient` in order to avoid name @@ -330,12 +310,12 @@ the default Feign native annotations. You can also use the `Builder`to configure FeignClient not to inherit beans from the parent context. You can do this by overriding calling `inheritParentContext(false)` on the `Builder`. -[[spring-cloud-feign-hystrix]] -=== Feign Hystrix Support +[[spring-cloud-feign-circuitbreaker]] +=== Feign Spring Cloud CircuitBreaker Support -If Hystrix is on the classpath and `feign.hystrix.enabled=true`, Feign will wrap all methods with a circuit breaker. Returning a `com.netflix.hystrix.HystrixCommand` is also available. This lets you use reactive patterns (with a call to `.toObservable()` or `.observe()` or asynchronous use (with a call to `.queue()`). +If Spring Cloud CircuitBreaker is on the classpath and `feign.circuitbreaker.enabled=true`, Feign will wrap all methods with a circuit breaker. -To disable Hystrix support on a per-client basis create a vanilla `Feign.Builder` with the "prototype" scope, e.g.: +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] ---- @@ -349,60 +329,28 @@ public class FooConfiguration { } ---- -WARNING: Prior to the Spring Cloud Dalston release, if Hystrix was on the classpath Feign would have wrapped -all methods in a circuit breaker by default. This default behavior was changed in Spring Cloud Dalston in -favor for an opt-in approach. +The circuit breaker name follows this pattern `_`. 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-hystrix-fallback]] -=== Feign Hystrix Fallbacks +[[spring-cloud-feign-circuitbreaker-fallback]] +=== Feign Spring Cloud CircuitBreaker Fallbacks -Hystrix 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. +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] ---- -@FeignClient(name = "hello", fallback = HystrixClientFallback.class) -protected interface HystrixClient { - @RequestMapping(method = RequestMethod.GET, value = "/hello") - Hello iFailSometimes(); -} - -static class HystrixClientFallback implements HystrixClient { - @Override - public Hello iFailSometimes() { - return new Hello("fallback"); - } -} +include::{core_path}/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CirciutBreakerTests.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] ---- -@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class) -protected interface HystrixClient { - @RequestMapping(method = RequestMethod.GET, value = "/hello") - Hello iFailSometimes(); -} - -@Component -static class HystrixClientFallbackFactory implements FallbackFactory { - @Override - public HystrixClient create(Throwable cause) { - return new HystrixClient() { - @Override - public Hello iFailSometimes() { - return new Hello("fallback; reason was: " + cause.getMessage()); - } - }; - } -} +include::{core_path}/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CirciutBreakerTests.java[tags=client_with_fallback_factory, indent=0] ---- -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`. - === 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. +When using Feign with Spring Cloud CircuitBreaker 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. [source,java,indent=0] ---- diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FallbackFactory.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FallbackFactory.java new file mode 100644 index 00000000..e47cbc90 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FallbackFactory.java @@ -0,0 +1,86 @@ +/* + * 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. + * + *
+ * {@code
+ * // This instance will be invoked if there are errors of any kind.
+ * FallbackFactory 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);
+ * }
+ * 
+ * + * @param the feign interface type + */ +public interface FallbackFactory { + + /** + * 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 implements FallbackFactory { + 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(); + } + + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java index f48ecee5..f45d9335 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java @@ -39,11 +39,14 @@ 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.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; @@ -51,6 +54,7 @@ import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.openfeign.support.DefaultGzipDecoderConfiguration; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -82,6 +86,7 @@ public class FeignAutoConfiguration { } @Configuration(proxyBeanMethods = false) + @Conditional(FeignCircuitBreakerDisabledConditions.class) protected static class DefaultFeignTargeterConfiguration { @Bean @@ -92,6 +97,26 @@ public class FeignAutoConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(CircuitBreaker.class) + @ConditionalOnProperty("feign.circuitbreaker.enabled") + protected static class CircuitBreakerPresentFeignTargeterConfiguration { + + @Bean + @ConditionalOnMissingBean(CircuitBreakerFactory.class) + public Targeter defaultFeignTargeter() { + return new DefaultTargeter(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(CircuitBreakerFactory.class) + public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) { + return new FeignCircuitBreakerTargeter(circuitBreakerFactory); + } + + } + // the following configuration is for alternate feign clients if // SC loadbalancer is not on the class path. // see corresponding configurations in FeignRibbonClientAutoConfiguration diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java new file mode 100644 index 00000000..392e8746 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java @@ -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 java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; + +import feign.Feign; +import feign.InvocationHandlerFactory; +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 target(Target target, T fallback) { + return build(fallback != null ? new FallbackFactory.Default(fallback) : null).newInstance(target); + } + + public T target(Target target, FallbackFactory fallbackFactory) { + return build(fallbackFactory).newInstance(target); + } + + public Feign build(final FallbackFactory nullableFallbackFactory) { + super.invocationHandlerFactory(new InvocationHandlerFactory() { + @Override + public InvocationHandler create(Target target, Map dispatch) { + return new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory, feignClientName, target, + dispatch, nullableFallbackFactory); + } + }); + return super.build(); + } + + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerDisabledConditions.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerDisabledConditions.java new file mode 100644 index 00000000..157f96b7 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerDisabledConditions.java @@ -0,0 +1,39 @@ +/* + * 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 { + + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java new file mode 100644 index 00000000..d9911a9d --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java @@ -0,0 +1,147 @@ +/* + * 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 dispatch; + + private final FallbackFactory nullableFallbackFactory; + + private final Map fallbackMethodMap; + + FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target target, + Map 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 supplier = asSupplier(method, args); + if (this.nullableFallbackFactory != null) { + Function 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 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 toFallbackMethod(Map dispatch) { + Map result = new LinkedHashMap(); + 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(); + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java new file mode 100644 index 00000000..f6048e95 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java @@ -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 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 target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, + Target.HardCodedTarget 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 targetWithFallbackFactory(String feignClientName, FeignContext context, + Target.HardCodedTarget target, FeignCircuitBreaker.Builder builder, Class fallbackFactoryClass) { + FallbackFactory fallbackFactory = (FallbackFactory) getFromContext("fallbackFactory", + feignClientName, context, fallbackFactoryClass, FallbackFactory.class); + return builder(feignClientName, builder).target(target, fallbackFactory); + } + + private T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget target, + FeignCircuitBreaker.Builder builder, Class fallback) { + T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type()); + return builder(feignClientName, builder).target(target, fallbackInstance); + } + + private T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context, + Class beanType, Class 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); + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClient.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClient.java index ad3265e6..dee564cf 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClient.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClient.java @@ -100,7 +100,7 @@ public @interface FeignClient { * factory must produce instances of fallback classes that implement the interface * 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; diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java index f6566a45..9b75aefa 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java @@ -33,11 +33,15 @@ 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; @@ -48,6 +52,7 @@ import org.springframework.cloud.openfeign.support.SpringDecoder; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.cloud.openfeign.support.SpringMvcContract; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.core.convert.ConversionService; @@ -131,13 +136,6 @@ public class FeignClientsConfiguration { return Retryer.NEVER_RETRY; } - @Bean - @Scope("prototype") - @ConditionalOnMissingBean - public Feign.Builder feignBuilder(Retryer retryer) { - return Feign.builder().retryer(retryer); - } - @Bean @ConditionalOnMissingBean(FeignLoggerFactory.class) public FeignLoggerFactory feignLoggerFactory() { @@ -185,4 +183,39 @@ public class FeignClientsConfiguration { } + @Configuration(proxyBeanMethods = false) + @Conditional(FeignCircuitBreakerDisabledConditions.class) + protected static class DefaultFeignBuilderConfiguration { + + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + public Feign.Builder feignBuilder(Retryer retryer) { + return Feign.builder().retryer(retryer); + } + + } + + @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(); + } + + } + } diff --git a/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 23a732c1..577a75b1 100644 --- a/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -3,9 +3,9 @@ ], "properties": [ { - "name": "feign.hystrix.enabled", + "name": "feign.circuitbreaker.enabled", "type": "java.lang.Boolean", - "description": "If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.", + "description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.", "defaultValue": "false" }, { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java index c4c5c008..6dee1cd0 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java @@ -48,7 +48,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Spencer Gibb */ @SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class, webEnvironment = DEFINED_PORT, - value = { "spring.application.name=feignclienturltest", "feign.hystrix.enabled=false", + value = { "spring.application.name=feignclienturltest", "feign.circuitbreaker.enabled=false", "feign.okhttp.enabled=false", "spring.cloud.loadbalancer.retry.enabled=false" }) @DirtiesContext class FeignHttpClientUrlTests { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CirciutBreakerTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CirciutBreakerTests.java new file mode 100644 index 00000000..7e7b478e --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CirciutBreakerTests.java @@ -0,0 +1,309 @@ +/* + * 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.circuitbreaker; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.function.Supplier; + +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 = CirciutBreakerTests.Application.class, webEnvironment = WebEnvironment.DEFINED_PORT, value = { + "spring.application.name=springcircuittest", "spring.jmx.enabled=false", "feign.circuitbreaker.enabled=true" }) +@DirtiesContext +public class CirciutBreakerTests { + + @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 { + + @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[] + + public static 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); + } + + } + + @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(); + } + + } + + static class MyCircuitBreaker implements CircuitBreaker { + + AtomicBoolean runWasCalled = new AtomicBoolean(); + + @Override + public T run(Supplier toRun) { + this.runWasCalled.set(true); + return toRun.get(); + } + + @Override + public T run(Supplier toRun, Function fallback) { + try { + return run(toRun); + } + catch (Throwable throwable) { + return fallback.apply(throwable); + } + } + + public void clear() { + this.runWasCalled.set(false); + } + + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java index 4d441da3..3225f001 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignContentEncodingTests.java @@ -49,9 +49,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Jakub Narloch */ @SpringBootTest(classes = FeignContentEncodingTests.Application.class, webEnvironment = RANDOM_PORT, - value = { "feign.compression.request.enabled=true", - "hystrix.command.default.execution.isolation.strategy=SEMAPHORE", - "ribbon.OkToRetryOnAllOperations=false" }) + value = { "feign.compression.request.enabled=true", "ribbon.OkToRetryOnAllOperations=false" }) @RunWith(SpringJUnit4ClassRunner.class) public class FeignContentEncodingTests { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java index 93399343..e2951520 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java @@ -56,9 +56,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Charlie Mordant. */ @SpringBootTest(classes = FeignPageableEncodingTests.Application.class, webEnvironment = RANDOM_PORT, - value = { "feign.compression.request.enabled=true", - "hystrix.command.default.execution.isolation.strategy=SEMAPHORE", - "ribbon.OkToRetryOnAllOperations=false" }) + value = { "feign.compression.request.enabled=true", "ribbon.OkToRetryOnAllOperations=false" }) @RunWith(SpringJUnit4ClassRunner.class) public class FeignPageableEncodingTests { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignHttpClientTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignHttpClientTests.java index bc4fdd3d..82d25aa6 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignHttpClientTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignHttpClientTests.java @@ -55,7 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Olga Maciaszek-Sharma */ @SpringBootTest(classes = FeignHttpClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, - value = { "spring.application.name=feignclienttest", "feign.hystrix.enabled=false", + value = { "spring.application.name=feignclienttest", "feign.circuitbreaker.enabled=false", "feign.okhttp.enabled=false", "spring.cloud.loadbalancer.retry.enabled=false" }) @DirtiesContext class FeignHttpClientTests { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignOkHttpTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignOkHttpTests.java index e1e22d55..19b459f9 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignOkHttpTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignOkHttpTests.java @@ -54,7 +54,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Olga Maciaszek-Sharma */ @SpringBootTest(classes = FeignOkHttpTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, - value = { "spring.application.name=feignclienttest", "feign.hystrix.enabled=false", + value = { "spring.application.name=feignclienttest", "feign.circuitbreaker.enabled=false", "feign.httpclient.enabled=false", "feign.okhttp.enabled=true", "spring.cloud.httpclientfactories.ok.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false" }) @DirtiesContext diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/IterableParameterTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/IterableParameterTests.java index dccb3487..2e963a97 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/IterableParameterTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/IterableParameterTests.java @@ -51,7 +51,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = IterableParameterTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = { "spring.application.name=iterableparametertest", "logging.level.org.springframework.cloud.openfeign.valid=DEBUG", "feign.httpclient.enabled=false", - "feign.okhttp.enabled=false", "feign.hystrix.enabled=false" }) + "feign.okhttp.enabled=false", "feign.circuitbreaker.enabled=false" }) @DirtiesContext public class IterableParameterTests { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java index 43f44065..83b97771 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java @@ -97,7 +97,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @SpringBootTest(classes = ValidFeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = { "spring.application.name=feignclienttest", "logging.level.org.springframework.cloud.openfeign.valid=DEBUG", "feign.httpclient.enabled=false", - "feign.okhttp.enabled=false", "feign.hystrix.enabled=true", + "feign.okhttp.enabled=false", "feign.circuitbreaker.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false" }) @DirtiesContext class ValidFeignClientTests { diff --git a/spring-cloud-openfeign-core/src/test/resources/application.yml b/spring-cloud-openfeign-core/src/test/resources/application.yml index 60815792..4facc6ed 100644 --- a/spring-cloud-openfeign-core/src/test/resources/application.yml +++ b/spring-cloud-openfeign-core/src/test/resources/application.yml @@ -15,7 +15,6 @@ eureka: fetchRegistry: false #error: # path: /myerror -hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3001 ReadTimeout: 60001 @@ -30,8 +29,6 @@ badClients: endpoints: health: sensitive: false -hystrix: - shareSecurityContext: true feignClient: localappName: localapp methodLevelRequestMappingPath: /hello2 diff --git a/spring-cloud-openfeign-dependencies/pom.xml b/spring-cloud-openfeign-dependencies/pom.xml index aa0ddac1..f548481c 100644 --- a/spring-cloud-openfeign-dependencies/pom.xml +++ b/spring-cloud-openfeign-dependencies/pom.xml @@ -50,11 +50,6 @@ feign-httpclient ${feign.version} - - io.github.openfeign - feign-hystrix - ${feign.version} - io.github.openfeign feign-okhttp