Compare commits
34
Commits
v3.1.0-M3
...
v3.1.0-RC1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d11e0d9dd4 | ||
|
|
e5deb642a2 | ||
|
|
ba7346573e | ||
|
|
38ccf1be95 | ||
|
|
5fee522f4c | ||
|
|
ce04947d92 | ||
|
|
61f71e6ac7 | ||
|
|
939f279a38 | ||
|
|
c0f314a5f0 | ||
|
|
0cd57b2ce7 | ||
|
|
1752dd9815 | ||
|
|
b78eacd714 | ||
|
|
56dc4e07e4 | ||
|
|
9167752bfb | ||
|
|
127c866616 | ||
|
|
876778935c | ||
|
|
ae97365b6c | ||
|
|
fd6c1290fe | ||
|
|
0e6b33f480 | ||
|
|
17d0161faf | ||
|
|
d6783a6f1e | ||
|
|
7f03521b0d | ||
|
|
21adacbc43 | ||
|
|
3de4b98b3d | ||
|
|
1275875ddb | ||
|
|
c4b9492a9b | ||
|
|
3acf5f9e92 | ||
|
|
e9e38d0274 | ||
|
|
28b50401c5 | ||
|
|
6479db0a54 | ||
|
|
f7e490c466 | ||
|
|
d60cda824d | ||
|
|
190f5a9083 | ||
|
|
1005749eb5 |
@@ -20,17 +20,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up JDK ${{ matrix.java }}
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
distribution: 'zulu'
|
||||
java-version: ${{ matrix.java }}
|
||||
cache: 'maven'
|
||||
- name: Build with Maven
|
||||
run: ./mvnw clean install -B -U -P sonar
|
||||
- uses: codecov/codecov-action@v1
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.1.0-M3</version>
|
||||
<version>3.1.0-RC1</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-docs</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -122,6 +122,7 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT
|
||||
* `Encoder` feignEncoder: `SpringEncoder`
|
||||
* `Logger` feignLogger: `Slf4jLogger`
|
||||
* `MicrometerCapability` micrometerCapability: If `feign-micrometer` is on the classpath and `MeterRegistry` is available
|
||||
* `CachingCapability` cachingCapability: If `@EnableCaching` annotation is used. Can be disabled via `feign.cache.enabled`.
|
||||
* `Contract` feignContract: `SpringMvcContract`
|
||||
* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder`
|
||||
* `Client` feignClient: If Spring Cloud LoadBalancer is on the classpath, `FeignBlockingLoadBalancerClient` is used.
|
||||
@@ -141,7 +142,7 @@ Spring Cloud OpenFeign _does not_ provide the following beans by default for fei
|
||||
* `Collection<RequestInterceptor>`
|
||||
* `SetterFactory`
|
||||
* `QueryMapEncoder`
|
||||
* `Capability` (`MicrometerCapability` is provided by default)
|
||||
* `Capability` (`MicrometerCapability` and `CachingCapability` are provided by default)
|
||||
|
||||
A bean of `Retryer.NEVER_RETRY` with the type `Retryer` is created by default, which will disable retrying.
|
||||
Notice this retrying behavior is different from the Feign default one, where it will automatically retry IOExceptions,
|
||||
@@ -494,10 +495,7 @@ public interface UserClient extends UserService {
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: It is generally not advisable to share an interface between a
|
||||
server and a client. It introduces tight coupling, and is also not supported by
|
||||
all the maintained Spring MVC versions (method parameter
|
||||
mapping is not inherited in some versions).
|
||||
WARNING: `@FeignClient` interfaces should not be shared between server and client and annotating `@FeignClient` interfaces with `@RequestMapping` on class level is no longer supported.
|
||||
|
||||
=== Feign request/response compression
|
||||
|
||||
@@ -605,6 +603,22 @@ public class FooConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
=== Feign Caching
|
||||
|
||||
If `@EnableCaching` annotation is used, a `CachingCapability` bean is created and registered so that your Feign client recognizes `@Cache*` annotations on its interface:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public interface DemoClient {
|
||||
|
||||
@GetMapping("/demo/{filterParam}")
|
||||
@Cacheable(cacheNames = "demo-cache", key = "#keyParam")
|
||||
String demoEndpoint(String keyParam, @PathVariable String filterParam);
|
||||
}
|
||||
----
|
||||
|
||||
You can also disable the feature via property `feign.cache.enabled=false`.
|
||||
|
||||
=== Feign @QueryMap support
|
||||
|
||||
The OpenFeign `@QueryMap` annotation provides support for POJOs to be used as
|
||||
@@ -692,20 +706,21 @@ public interface DemoTemplate {
|
||||
----
|
||||
|
||||
=== Feign `CollectionFormat` support
|
||||
We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation.You can annotate a Feign client method with it by passing the desired `feign.CollectionFormat` as annotation value.
|
||||
We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation.
|
||||
You can annotate a Feign client method (or the whole class to affect all methods) with it by passing the desired `feign.CollectionFormat` as annotation value.
|
||||
|
||||
In the following example, the `CSV` format is used instead of the default `EXPLODED` to process the method.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@FeignClient(name = "demo")
|
||||
protected interface PageableFeignClient {
|
||||
protected interface PageableFeignClient {
|
||||
|
||||
@CollectionFormat(feign.CollectionFormat.CSV)
|
||||
@GetMapping(path = "/page")
|
||||
ResponseEntity performRequest(Pageable page);
|
||||
@CollectionFormat(feign.CollectionFormat.CSV)
|
||||
@GetMapping(path = "/page")
|
||||
ResponseEntity performRequest(Pageable page);
|
||||
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
TIP: Set the `CSV` format while sending `Pageable` as a query parameter in order for it to be encoded correctly.
|
||||
|
||||
@@ -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>3.1.0-M3</version>
|
||||
<version>3.1.0-RC1</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>3.1.0-M3</version>
|
||||
<version>3.1.0-RC1</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,7 +26,7 @@
|
||||
<properties>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<jackson.version>2.11.3</jackson.version>
|
||||
<spring-cloud-commons.version>3.1.0-M3</spring-cloud-commons.version>
|
||||
<spring-cloud-commons.version>3.1.0-RC1</spring-cloud-commons.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>3.1.0-M3</version>
|
||||
<version>3.1.0-RC1</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import feign.Capability;
|
||||
import feign.InvocationHandlerFactory;
|
||||
|
||||
import org.springframework.cache.interceptor.CacheInterceptor;
|
||||
|
||||
/**
|
||||
* Allows Spring's @Cache* annotations to be declared on the feign client's methods.
|
||||
*
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class CachingCapability implements Capability {
|
||||
|
||||
private final CacheInterceptor cacheInterceptor;
|
||||
|
||||
public CachingCapability(CacheInterceptor cacheInterceptor) {
|
||||
this.cacheInterceptor = cacheInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) {
|
||||
return new FeignCachingInvocationHandlerFactory(invocationHandlerFactory, cacheInterceptor);
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -25,9 +25,10 @@ import java.lang.annotation.Target;
|
||||
* Indicates which collection format should be used while processing the annotated method.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Sam Kruglov
|
||||
* @see feign.CollectionFormat
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CollectionFormat {
|
||||
|
||||
|
||||
+10
@@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import feign.Capability;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.RequestInterceptor;
|
||||
@@ -50,6 +51,7 @@ 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.cache.interceptor.CacheInterceptor;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
@@ -81,6 +83,7 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResour
|
||||
* @author Nguyen Ky Thanh
|
||||
* @author Andrii Bohutskyi
|
||||
* @author Kwangyong Kim
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@@ -105,6 +108,13 @@ public class FeignAutoConfiguration {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "feign.cache.enabled", matchIfMissing = true)
|
||||
@ConditionalOnBean(CacheInterceptor.class)
|
||||
public Capability cachingCapability(CacheInterceptor cacheInterceptor) {
|
||||
return new CachingCapability(cacheInterceptor);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Module.class, Page.class, Sort.class })
|
||||
@ConditionalOnProperty(value = "feign.autoconfiguration.jackson.enabled", havingValue = "true")
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import feign.InvocationHandlerFactory;
|
||||
import feign.Target;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.cache.interceptor.CacheInterceptor;
|
||||
|
||||
/**
|
||||
* Allows Spring's @Cache* annotations to be declared on the feign client's methods.
|
||||
*
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignCachingInvocationHandlerFactory implements InvocationHandlerFactory {
|
||||
|
||||
private final InvocationHandlerFactory delegateFactory;
|
||||
|
||||
private final CacheInterceptor cacheInterceptor;
|
||||
|
||||
public FeignCachingInvocationHandlerFactory(InvocationHandlerFactory delegateFactory,
|
||||
CacheInterceptor cacheInterceptor) {
|
||||
this.delegateFactory = delegateFactory;
|
||||
this.cacheInterceptor = cacheInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
|
||||
final InvocationHandler delegateHandler = delegateFactory.create(target, dispatch);
|
||||
return (proxy, method, argsNullable) -> {
|
||||
Object[] args = Optional.ofNullable(argsNullable).orElseGet(() -> new Object[0]);
|
||||
return cacheInterceptor.invoke(new MethodInvocation() {
|
||||
@Override
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getArguments() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object proceed() throws Throwable {
|
||||
return delegateHandler.invoke(proxy, method, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getThis() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibleObject getStaticPart() {
|
||||
return method;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -25,7 +25,7 @@ import org.springframework.cloud.context.named.NamedContextFactory;
|
||||
* @author Dave Syer
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
class FeignClientSpecification implements NamedContextFactory.Specification {
|
||||
public class FeignClientSpecification implements NamedContextFactory.Specification {
|
||||
|
||||
private String name;
|
||||
|
||||
@@ -34,7 +34,7 @@ class FeignClientSpecification implements NamedContextFactory.Specification {
|
||||
FeignClientSpecification() {
|
||||
}
|
||||
|
||||
FeignClientSpecification(String name, Class<?>[] configuration) {
|
||||
public FeignClientSpecification(String name, Class<?>[] configuration) {
|
||||
this.name = name;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
+6
-7
@@ -25,7 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
@@ -39,15 +39,15 @@ import org.springframework.context.annotation.Configuration;
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
|
||||
class DefaultFeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties,
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, properties,
|
||||
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
|
||||
loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@@ -58,10 +58,9 @@ class DefaultFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
|
||||
loadBalancedRetryFactory, properties, loadBalancerClientFactory);
|
||||
loadBalancedRetryFactory, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-3
@@ -60,15 +60,24 @@ public class FeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private final LoadBalancerClient loadBalancerClient;
|
||||
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory;
|
||||
|
||||
/**
|
||||
* @deprecated in favour of
|
||||
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory)}
|
||||
*/
|
||||
@Deprecated
|
||||
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.properties = properties;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
@@ -116,6 +125,7 @@ public class FeignBlockingLoadBalancerClient implements Client {
|
||||
}
|
||||
|
||||
private String getHint(String serviceId) {
|
||||
LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId);
|
||||
String defaultHint = properties.getHint().getOrDefault("default", "default");
|
||||
String hintPropertyValue = properties.getHint().get(serviceId);
|
||||
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
|
||||
|
||||
+6
-7
@@ -27,7 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -46,16 +46,16 @@ import org.springframework.context.annotation.Import;
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
|
||||
@Import(HttpClient5FeignConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
|
||||
class HttpClient5FeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient5,
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
Client delegate = new ApacheHttp5Client(httpClient5);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -65,11 +65,10 @@ class HttpClient5FeignLoadBalancerConfiguration {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient5,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
Client delegate = new ApacheHttp5Client(httpClient5);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
properties, loadBalancerClientFactory);
|
||||
loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-7
@@ -27,7 +27,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.HttpClient5DisabledConditions;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
|
||||
@@ -50,16 +50,16 @@ import org.springframework.context.annotation.Import;
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
@Conditional(HttpClient5DisabledConditions.class)
|
||||
@Import(HttpClientFeignConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
|
||||
class HttpClientFeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -69,11 +69,10 @@ class HttpClientFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
properties, loadBalancerClientFactory);
|
||||
loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-7
@@ -26,7 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.OkHttpFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -46,16 +46,16 @@ import org.springframework.context.annotation.Import;
|
||||
@ConditionalOnProperty("feign.okhttp.enabled")
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@Import(OkHttpFeignConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
|
||||
class OkHttpFeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -65,11 +65,10 @@ class OkHttpFeignLoadBalancerConfiguration {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
properties, loadBalancerClientFactory);
|
||||
loadBalancerClientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-3
@@ -78,17 +78,27 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private final LoadBalancedRetryFactory loadBalancedRetryFactory;
|
||||
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory;
|
||||
|
||||
/**
|
||||
* @deprecated in favour of
|
||||
* {@link RetryableFeignBlockingLoadBalancerClient#RetryableFeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancedRetryFactory, LoadBalancerClientFactory)}
|
||||
*/
|
||||
@Deprecated
|
||||
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
|
||||
this.properties = properties;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
@@ -232,6 +242,7 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
}
|
||||
|
||||
private String getHint(String serviceId) {
|
||||
LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId);
|
||||
String defaultHint = properties.getHint().getOrDefault("default", "default");
|
||||
String hintPropertyValue = properties.getHint().get(serviceId);
|
||||
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
|
||||
|
||||
+15
-35
@@ -35,6 +35,8 @@ import feign.Feign;
|
||||
import feign.MethodMetadata;
|
||||
import feign.Param;
|
||||
import feign.Request;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.CollectionFormat;
|
||||
@@ -80,9 +82,12 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
|
||||
* @author Artyom Romanenko
|
||||
* @author Darren Foong
|
||||
* @author Ram Anaswara
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(SpringMvcContract.class);
|
||||
|
||||
private static final String ACCEPT = "Accept";
|
||||
|
||||
private static final String CONTENT_TYPE = "Content-Type";
|
||||
@@ -170,47 +175,22 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
|
||||
@Override
|
||||
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
|
||||
if (clz.getInterfaces().length == 0) {
|
||||
RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
|
||||
if (classAnnotation != null) {
|
||||
// Prepend path from class annotation if specified
|
||||
if (classAnnotation.value().length > 0) {
|
||||
String pathValue = emptyToNull(classAnnotation.value()[0]);
|
||||
pathValue = resolve(pathValue);
|
||||
if (!pathValue.startsWith("/")) {
|
||||
pathValue = "/" + pathValue;
|
||||
}
|
||||
data.template().uri(pathValue);
|
||||
if (data.template().decodeSlash() != decodeSlash) {
|
||||
data.template().decodeSlash(decodeSlash);
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
|
||||
if (classAnnotation != null) {
|
||||
LOG.error("Cannot process class: " + clz.getName()
|
||||
+ ". @RequestMapping annotation is not allowed on @FeignClient interfaces.");
|
||||
throw new IllegalArgumentException("@RequestMapping annotation not allowed on @FeignClient interfaces");
|
||||
}
|
||||
CollectionFormat collectionFormat = findMergedAnnotation(clz, CollectionFormat.class);
|
||||
if (collectionFormat != null) {
|
||||
data.template().collectionFormat(collectionFormat.value());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
|
||||
processedMethods.put(Feign.configKey(targetType, method), method);
|
||||
MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
|
||||
|
||||
RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class);
|
||||
if (classAnnotation != null) {
|
||||
// produces - use from class annotation only if method has not specified this
|
||||
if (!md.template().headers().containsKey(ACCEPT)) {
|
||||
parseProduces(md, method, classAnnotation);
|
||||
}
|
||||
|
||||
// consumes -- use from class annotation only if method has not specified this
|
||||
if (!md.template().headers().containsKey(CONTENT_TYPE)) {
|
||||
parseConsumes(md, method, classAnnotation);
|
||||
}
|
||||
|
||||
// headers -- class annotation is inherited to methods, always write these if
|
||||
// present
|
||||
parseHeaders(md, method, classAnnotation);
|
||||
}
|
||||
return md;
|
||||
return super.parseAndValidateMetadata(targetType, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2020-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.net.UnknownHostException;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.RequestLine;
|
||||
import feign.RetryableException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
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.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.interceptor.SimpleKey;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
@SpringBootTest(classes = FeignClientCacheTests.TestConfiguration.class)
|
||||
@DirtiesContext
|
||||
public class FeignClientCacheTests {
|
||||
|
||||
private static final String CACHE_NAME = "foo-cache";
|
||||
|
||||
@Autowired
|
||||
private FooClient foo;
|
||||
|
||||
@Test
|
||||
void cacheExists(@Autowired CacheManager cacheManager) {
|
||||
assertThat(cacheManager.getCache(CACHE_NAME)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void interceptedCallsReal() {
|
||||
assertThatExceptionOfType(RetryableException.class).isThrownBy(foo::getWithCache)
|
||||
.withRootCauseInstanceOf(UnknownHostException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonInterceptedCallsReal() {
|
||||
assertThatExceptionOfType(RetryableException.class).isThrownBy(foo::getWithoutCache)
|
||||
.withRootCauseInstanceOf(UnknownHostException.class);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class givenCached {
|
||||
|
||||
String cachedValue = "cached";
|
||||
|
||||
@BeforeEach
|
||||
void setUp(@Autowired CacheManager cacheManager) {
|
||||
cacheManager.getCache(CACHE_NAME).put(SimpleKey.EMPTY, cachedValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void interceptedReturnsCached() {
|
||||
assertThat(foo.getWithCache()).isSameAs(cachedValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonInterceptedCallsReal() {
|
||||
assertThatExceptionOfType(RetryableException.class).isThrownBy(foo::getWithoutCache)
|
||||
.withRootCauseInstanceOf(UnknownHostException.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableFeignClients(clients = FooClient.class)
|
||||
@EnableAutoConfiguration
|
||||
@EnableCaching
|
||||
protected static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "foo", url = "http://foo", configuration = FooConfiguration.class)
|
||||
interface FooClient {
|
||||
|
||||
@RequestLine("GET /with-cache")
|
||||
@Cacheable(cacheNames = CACHE_NAME)
|
||||
String getWithCache();
|
||||
|
||||
@RequestLine("GET /without-cache")
|
||||
String getWithoutCache();
|
||||
|
||||
}
|
||||
|
||||
public static class FooConfiguration {
|
||||
|
||||
@Bean
|
||||
Contract feignContract() {
|
||||
return new Contract.Default();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+10
-4
@@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -71,16 +72,21 @@ import static org.mockito.Mockito.when;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FeignBlockingLoadBalancerClientTests {
|
||||
|
||||
private Client delegate = mock(Client.class);
|
||||
private final Client delegate = mock(Client.class);
|
||||
|
||||
private BlockingLoadBalancerClient loadBalancerClient = mock(BlockingLoadBalancerClient.class);
|
||||
private final BlockingLoadBalancerClient loadBalancerClient = mock(BlockingLoadBalancerClient.class);
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory = mock(LoadBalancerClientFactory.class);
|
||||
|
||||
private final LoadBalancerProperties loadBalancerProperties = new LoadBalancerProperties();
|
||||
|
||||
private FeignBlockingLoadBalancerClient feignBlockingLoadBalancerClient = new FeignBlockingLoadBalancerClient(
|
||||
delegate, loadBalancerClient, loadBalancerProperties, loadBalancerClientFactory);
|
||||
private final FeignBlockingLoadBalancerClient feignBlockingLoadBalancerClient = new FeignBlockingLoadBalancerClient(
|
||||
delegate, loadBalancerClient, loadBalancerClientFactory);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(loadBalancerClientFactory.getProperties(any(String.class))).thenReturn(loadBalancerProperties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExtractServiceIdFromRequestUrl() throws IOException {
|
||||
|
||||
+7
-6
@@ -76,23 +76,24 @@ import static org.mockito.Mockito.when;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RetryableFeignBlockingLoadBalancerClientTests {
|
||||
|
||||
private Client delegate = mock(Client.class);
|
||||
private final Client delegate = mock(Client.class);
|
||||
|
||||
private LoadBalancedRetryFactory retryFactory = mock(LoadBalancedRetryFactory.class);
|
||||
private final LoadBalancedRetryFactory retryFactory = mock(LoadBalancedRetryFactory.class);
|
||||
|
||||
private BlockingLoadBalancerClient loadBalancerClient = mock(BlockingLoadBalancerClient.class);
|
||||
private final BlockingLoadBalancerClient loadBalancerClient = mock(BlockingLoadBalancerClient.class);
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory = mock(LoadBalancerClientFactory.class);
|
||||
|
||||
private LoadBalancerProperties properties = new LoadBalancerProperties();
|
||||
private final LoadBalancerProperties properties = new LoadBalancerProperties();
|
||||
|
||||
private RetryableFeignBlockingLoadBalancerClient feignBlockingLoadBalancerClient = new RetryableFeignBlockingLoadBalancerClient(
|
||||
private final RetryableFeignBlockingLoadBalancerClient feignBlockingLoadBalancerClient = new RetryableFeignBlockingLoadBalancerClient(
|
||||
delegate, loadBalancerClient, retryFactory, properties, loadBalancerClientFactory);
|
||||
|
||||
private ServiceInstance serviceInstance = new DefaultServiceInstance("test-a", "test", "testhost", 80, false);
|
||||
private final ServiceInstance serviceInstance = new DefaultServiceInstance("test-a", "test", "testhost", 80, false);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(loadBalancerClientFactory.getProperties(any(String.class))).thenReturn(properties);
|
||||
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
|
||||
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
|
||||
when(loadBalancerClient.choose(eq("test"), any())).thenReturn(serviceInstance);
|
||||
|
||||
+35
-45
@@ -62,8 +62,10 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
|
||||
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
|
||||
import static feign.CollectionFormat.CSV;
|
||||
import static feign.CollectionFormat.SSV;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
/**
|
||||
@@ -73,6 +75,7 @@ import static org.junit.Assume.assumeTrue;
|
||||
* @author Aaron Whiteside
|
||||
* @author Artyom Romanenko
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class SpringMvcContractTests {
|
||||
|
||||
@@ -180,16 +183,12 @@ public class SpringMvcContractTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_Class_AnnotationsGetSpecificTest() throws Exception {
|
||||
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getSpecificTest", String.class,
|
||||
String.class);
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/prepend/{classId}/test/{testId}");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
|
||||
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
|
||||
assertThat(data.indexToName().get(1).iterator().next()).isEqualTo("testId");
|
||||
public void testProcessAnnotations_Class_Annotations_RequestMapping() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> {
|
||||
Method method = TestTemplate_Class_RequestMapping.class.getDeclaredMethod("getSpecificTest", String.class,
|
||||
String.class);
|
||||
contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -197,25 +196,13 @@ public class SpringMvcContractTests {
|
||||
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getAllTests", String.class);
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/prepend/{classId}");
|
||||
assertThat(data.template().url()).isEqualTo("/");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
|
||||
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
|
||||
assertThat(data.template().decodeSlash()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_Class_AnnotationsGetAllTests_EncodeSlash() throws Exception {
|
||||
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), false);
|
||||
|
||||
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getAllTests", String.class);
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/prepend/{classId}");
|
||||
|
||||
assertThat(data.template().decodeSlash()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_ExtendedInterface() throws Exception {
|
||||
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests", String.class);
|
||||
@@ -232,22 +219,6 @@ public class SpringMvcContractTests {
|
||||
assertThat(data.template().decodeSlash()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_ExtendedInterface_EncodeSlash() throws Exception {
|
||||
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), false);
|
||||
|
||||
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests", String.class);
|
||||
MethodMetadata extendedData = contract.parseAndValidateMetadata(extendedMethod.getDeclaringClass(),
|
||||
extendedMethod);
|
||||
|
||||
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getAllTests", String.class);
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo(extendedData.template().url());
|
||||
assertThat(data.template().method()).isEqualTo(extendedData.template().method());
|
||||
assertThat(data.template().decodeSlash()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_SimplePost() throws Exception {
|
||||
Method method = TestTemplate_Simple.class.getDeclaredMethod("postTest", TestObject.class);
|
||||
@@ -278,7 +249,7 @@ public class SpringMvcContractTests {
|
||||
Integer.class);
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().url()).isEqualTo("/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -302,13 +273,22 @@ public class SpringMvcContractTests {
|
||||
assertThat(data.template().collectionFormat()).isEqualTo(SSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processAnnotationOnClass_CollectionFormat() throws NoSuchMethodException {
|
||||
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getWithoutCollectionFormat");
|
||||
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().collectionFormat()).isEqualTo(CSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_Advanced() throws Exception {
|
||||
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest", String.class, String.class,
|
||||
Integer.class);
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().url()).isEqualTo("/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -327,7 +307,7 @@ public class SpringMvcContractTests {
|
||||
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest2", String.class, Integer.class);
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/advanced/test2?amount=" + "{amount}");
|
||||
assertThat(data.template().url()).isEqualTo("/test2?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -379,7 +359,7 @@ public class SpringMvcContractTests {
|
||||
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest");
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/advanced");
|
||||
assertThat(data.template().url()).isEqualTo("/");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -471,7 +451,7 @@ public class SpringMvcContractTests {
|
||||
|
||||
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/advanced/testfallback/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().url()).isEqualTo("/testfallback/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -635,6 +615,13 @@ public class SpringMvcContractTests {
|
||||
}
|
||||
|
||||
@RequestMapping("/prepend/{classId}")
|
||||
public interface TestTemplate_Class_RequestMapping {
|
||||
|
||||
@RequestMapping(value = "/test/{testId}", method = RequestMethod.GET)
|
||||
TestObject getSpecificTest(@PathVariable("classId") String classId, @PathVariable("testId") String testId);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_Class_Annotations {
|
||||
|
||||
@GetMapping("/test/{testId}")
|
||||
@@ -743,13 +730,16 @@ public class SpringMvcContractTests {
|
||||
}
|
||||
|
||||
@JsonAutoDetect
|
||||
@RequestMapping("/advanced")
|
||||
@CollectionFormat(CSV)
|
||||
public interface TestTemplate_Advanced {
|
||||
|
||||
@CollectionFormat(SSV)
|
||||
@GetMapping
|
||||
ResponseEntity<TestObject> getWithCollectionFormat();
|
||||
|
||||
@GetMapping
|
||||
ResponseEntity<TestObject> getWithoutCollectionFormat();
|
||||
|
||||
@ExceptionHandler
|
||||
@PutMapping(path = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<TestObject> getTest(@RequestHeader("Authorization") String auth, @PathVariable("id") String id,
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.1.0-M3</version>
|
||||
<version>3.1.0-RC1</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
|
||||
<version>3.1.0-M3</version>
|
||||
<version>3.1.0-RC1</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-openfeign-dependencies</name>
|
||||
<description>Spring Cloud OpenFeign Dependencies</description>
|
||||
<properties>
|
||||
<feign.version>11.6</feign.version>
|
||||
<feign.version>11.7</feign.version>
|
||||
<feign-form.version>3.8.0</feign-form.version>
|
||||
<spring-security-oauth2-autoconfigure.version>2.1.2.RELEASE</spring-security-oauth2-autoconfigure.version>
|
||||
</properties>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.1.0-M3</version>
|
||||
<version>3.1.0-RC1</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user