Compare commits

...
Author SHA1 Message Date
Olga MaciaszekSharma 3bba08931d Upgarde protobuf-java (security fix) and vavr. 2022-01-11 13:59:21 +01:00
buildmaster 0853ccc1e5 Bumping versions to 3.0.7-SNAPSHOT after release 2021-12-15 17:02:34 +00:00
buildmaster 13f91320b1 Going back to snapshots 2021-12-15 17:02:33 +00:00
buildmaster 7af0efd569 Update SNAPSHOT to 3.0.6 2021-12-15 17:01:03 +00:00
Been24 70a9002002 Update RetryableFeignBlockingLoadBalancerClient.java
fix log  class
2021-11-29 16:45:40 +01:00
Nikita Konev ad892a23f0 Support size = 0 in PageJacksonModule 2021-11-29 16:30:20 +01:00
Hyeonmin Park c0f314a5f0 GitHub workflows: Use Java version in matrix, cache functionality of actions/setup-java@v2 (#614)
* Fix GitHub workflows not using Java version in matrix

* Use cache functionality of actions/setup-java@v2
2021-10-28 18:15:02 +02:00
buildmaster 56dc4e07e4 Bumping versions to 3.0.6-SNAPSHOT after release 2021-10-21 11:48:48 +00:00
buildmaster 9167752bfb Going back to snapshots 2021-10-21 11:48:47 +00:00
buildmaster 127c866616 Update SNAPSHOT to 3.0.5 2021-10-21 11:46:52 +00:00
Olga MaciaszekSharma 0e6b33f480 Merge remote-tracking branch 'origin/2.2.x' into 3.0.x 2021-10-21 13:04:57 +02:00
Olga MaciaszekSharma 17d0161faf Reformat. 2021-10-21 12:40:20 +02:00
Olga MaciaszekSharma d6783a6f1e Block clas-level request mapping on Feign clients. 2021-10-21 12:14:19 +02:00
Olga MaciaszekSharma 548e4d55ef Revert default circuit breaker name prefix to target.type(). Fixes gh-606. 2021-10-11 18:40:21 +02:00
buildmaster 55ceafae4d Bumping versions to 3.0.5-SNAPSHOT after release 2021-09-21 14:39:31 +00:00
buildmaster 0c87527ea5 Going back to snapshots 2021-09-21 14:39:31 +00:00
buildmaster a2c473db2a Update SNAPSHOT to 3.0.4 2021-09-21 14:37:38 +00:00
Can Bezmen ae33802a4b FIX gh-601 2021-09-20 15:35:28 +02:00
Olga MaciaszekSharma c761919bc9 Only add default query params and default headers if not already in template. Fixes gh-590. 2021-09-09 17:59:08 +02:00
Olga MaciaszekSharma 998baed0d3 Merge remote-tracking branch 'origin/3.0.x' into 3.0.x 2021-09-08 18:52:18 +02:00
Olga MaciaszekSharma 4fc80e6b01 Update docs. Fixes gh-585. 2021-09-08 18:52:11 +02:00
Kwangyong Kim c101fd2c6e Add CircuitBreakerNameResolver (#575) 2021-07-27 10:59:32 +02:00
Olga MaciaszekSharma 71930fd9f0 Do not reset RequestAttributes. Fixes gh-572. 2021-07-26 17:03:33 +02:00
Olga MaciaszekSharma b5283e2f88 Fix Github workflow setup. 2021-07-21 13:21:08 +02:00
ManasviGoyal 6aeb1ff676 Added Tip (#580) 2021-07-21 12:21:21 +02:00
spencergibb 44c63798b0 rollback to 3.0.x versions 2021-07-09 14:47:28 -04:00
Jonathan SchneiderandModerne 769b38c5c1 Refactor: replace @RequestMapping annotations
Co-authored-by: Moderne <team@moderne.io>
2021-07-09 12:16:27 +02:00
Can Bezmen 3c402201ec Codecov for main branch (#554) 2021-07-09 11:51:57 +02:00
buildmaster deb150ed88 Bumping versions 2021-07-09 00:24:22 +00:00
Olga MaciaszekSharma 1275875ddb Reformat. 2021-07-07 18:58:48 +02:00
Olga MaciaszekSharma c4b9492a9b Bumping versions to 2.2.10.BUILD-SNAPSHOT after release
This reverts commit f7e490c466.
2021-07-07 18:46:53 +02:00
Olga MaciaszekSharma 3acf5f9e92 Going back to snapshots
This reverts commit 6479db0a54.
2021-07-07 18:46:20 +02:00
buildmaster e9e38d0274 Bumping versions 2021-07-07 14:07:20 +00:00
Olga MaciaszekSharma 28b50401c5 Revert "Update SNAPSHOT to 2.2.9.RELEASE"
This reverts commit 1005749eb5.
2021-07-07 14:38:12 +02:00
Olga MaciaszekSharma 6479db0a54 Revert "Going back to snapshots"
This reverts commit 190f5a9083.
2021-07-07 14:37:39 +02:00
Olga MaciaszekSharma f7e490c466 Revert "Bumping versions to 2.2.10.BUILD-SNAPSHOT after release"
This reverts commit d60cda824d.
2021-07-07 14:37:03 +02:00
buildmaster d60cda824d Bumping versions to 2.2.10.BUILD-SNAPSHOT after release 2021-07-06 14:53:53 +00:00
buildmaster 190f5a9083 Going back to snapshots 2021-07-06 14:53:53 +00:00
buildmaster 1005749eb5 Update SNAPSHOT to 2.2.9.RELEASE 2021-07-06 14:52:01 +00:00
Olga MaciaszekSharma 6de9fdd3e1 Merge remote-tracking branch 'origin/2.2.x' 2021-07-05 13:19:39 +02:00
Michal Domagala df1f982046 Fix for RetryableFeignBlockingLoadBalancerClient closes stream (#569) 2021-07-05 13:03:06 +02:00
Michal Domagala baf7cf95f2 Simplify Log.info logic (#556) 2021-07-05 12:43:59 +02:00
Olga MaciaszekSharma 45861dd657 Merge remote-tracking branch 'origin/2.2.x'. Refactor. 2021-07-05 12:09:23 +02:00
Olga MaciaszekSharma 00ea88c67f Fix typo. Refactor. 2021-07-05 11:55:41 +02:00
Olga MaciaszekSharma d897b18e16 Add missing author tags. Refactor. 2021-07-05 11:26:06 +02:00
Can Bezmen 8c08204c78 Issue #549
* Fixed: Form URL Encoded bug fixed.

Fixes gh-549.
2021-07-05 11:19:29 +02:00
Olga MaciaszekSharma 1f5bdd4224 Merge remote-tracking branch 'origin/2.2.x' 2021-06-10 16:08:39 +02:00
Olga MaciaszekSharma 2f43715f6f Change log level. Fixes gh-552. 2021-06-10 16:04:17 +02:00
buildmaster 280291ec93 Bumping versions 2021-05-28 00:22:29 +00:00
buildmaster 0de9dd614d Going back to snapshots 2021-05-27 21:20:38 +00:00
49 changed files with 610 additions and 413 deletions
+11 -13
View File
@@ -5,9 +5,9 @@ name: Build
on: on:
push: push:
branches: [ main ] branches: [ 3.0.x ]
pull_request: pull_request:
branches: [ main ] branches: [ 3.0.x ]
jobs: jobs:
build: build:
@@ -20,16 +20,14 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK 1.8 - name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: 1.8 distribution: 'zulu'
- name: Cache local Maven repository java-version: ${{ matrix.java }}
uses: actions/cache@v2 cache: 'maven'
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Build with Maven - name: Build with Maven
run: ./mvnw clean install -B -U run: ./mvnw clean install -B -U -P sonar
- uses: codecov/codecov-action@v1
with:
fail_ci_if_error: true
+2 -16
View File
@@ -66,23 +66,9 @@ the `.mvn` configuration, so if you find you have to do it to make a
build succeed, please raise a ticket to get the settings added to build succeed, please raise a ticket to get the settings added to
source control. source control.
For hints on how to build the project look in `.travis.yml` if there The projects that require middleware (i.e. Redis) for testing generally
is one. There should be a "script" and maybe "install" command. Also require that a local instance of [Docker](https://www.docker.com/get-started) is installed and running.
look at the "services" section to see if any services need to be
running locally (e.g. mongo or rabbit). Ignore the git-related bits
that you might find in "before_install" since they're related to setting git
credentials and you already have those.
The projects that require middleware generally include a
`docker-compose.yml`, so consider using
https://docs.docker.com/compose/[Docker Compose] to run the middeware servers
in Docker containers. See the README in the
https://github.com/spring-cloud-samples/scripts[scripts demo
repository] for specific instructions about the common cases of mongo,
rabbit and redis.
NOTE: If all else fails, build with the command from `.travis.yml` (usually
`./mvnw install`).
=== Documentation === Documentation
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.3</version> <version>3.0.7-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-cloud-openfeign-docs</artifactId> <artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -66,10 +66,15 @@ The load-balancer client above will want to discover the physical addresses
for the "stores" service. If your application is a Eureka client then for the "stores" service. If your application is a Eureka client then
it will resolve the service in the Eureka service registry. If you it will resolve the service in the Eureka service registry. If you
don't want to use Eureka, you can configure a list of servers don't want to use Eureka, you can configure a list of servers
in your external configuration using https://cloud.spring.io/spring-cloud-static/spring-cloud-commons/current/reference/html/#simplediscoveryclient[`SimpleDiscoveryClient`]. in your external configuration using https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#simplediscoveryclient[`SimpleDiscoveryClient`].
Spring Cloud OpenFeign supports all the features available for the blocking mode of Spring Cloud LoadBalancer. You can read more about them in the https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer[project documentation]. Spring Cloud OpenFeign supports all the features available for the blocking mode of Spring Cloud LoadBalancer. You can read more about them in the https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer[project documentation].
TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-classes, make sure to specify where the clients are located, for example:
`@EnableFeignClients(basePackages = "com.example.clients")`
or list them explicitly:
`@EnableFeignClients(clients = InventoryServiceFeignClient.class)`
[[spring-cloud-feign-overriding-defaults]] [[spring-cloud-feign-overriding-defaults]]
=== Overriding Feign Defaults === Overriding Feign Defaults
@@ -343,7 +348,22 @@ public class FooConfiguration {
} }
---- ----
The circuit breaker name follows this pattern `<feignClientName>#<calledMethod>`. When calling a `@FeignClient` with name `foo` and the called interface method is `bar` then the circuit breaker name will be `foo_bar`. The circuit breaker name follows this pattern `<feignClientClassName>#<calledMethod>(<parameterTypes>)`. When calling a `@FeignClient` with `FooClient` interface and the called interface method that has no parameters is `bar` then the circuit breaker name will be `FooClient#bar()`.
NOTE: As of 2020.0.2, the circuit breaker name pattern has changed from `<feignClientName>_<calledMethod>`.
Using `CircuitBreakerNameResolver` introduced in 2020.0.4, circuit breaker names can retain the old pattern.
Providing a bean of `CircuitBreakerNameResolver`, you can change the circuit breaker name pattern.
[source,java,indent=0]
----
@Configuration
public class FooConfiguration {
@Bean
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
}
}
----
To enable Spring Cloud CircuitBreaker group set the `feign.circuitbreaker.group.enabled` property to `true` (by default `false`). To enable Spring Cloud CircuitBreaker group set the `feign.circuitbreaker.group.enabled` property to `true` (by default `false`).
@@ -471,10 +491,7 @@ public interface UserClient extends UserService {
} }
---- ----
NOTE: It is generally not advisable to share an interface between a WARNING: `@FeignClient` interfaces should not be shared between server and client and annotating `@FeignClient` interfaces with `@RequestMapping` on class level is no longer supported.
server and a client. It introduces tight coupling, and also actually
doesn't work with Spring MVC in its current form (method parameter
mapping is not inherited).
=== Feign request/response compression === Feign request/response compression
+3 -3
View File
@@ -4,14 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 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> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.3</version> <version>3.0.7-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name> <name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description> <description>Spring Cloud OpenFeign</description>
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId> <artifactId>spring-cloud-build</artifactId>
<version>3.0.3</version> <version>3.0.5</version>
<relativePath/> <relativePath/>
</parent> </parent>
<scm> <scm>
@@ -26,7 +26,7 @@
<properties> <properties>
<main.basedir>${basedir}</main.basedir> <main.basedir>${basedir}</main.basedir>
<jackson.version>2.11.3</jackson.version> <jackson.version>2.11.3</jackson.version>
<spring-cloud-commons.version>3.0.3</spring-cloud-commons.version> <spring-cloud-commons.version>3.0.6-SNAPSHOT</spring-cloud-commons.version>
<!-- Plugin versions --> <!-- Plugin versions -->
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version> <maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
+3 -3
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.3</version> <version>3.0.7-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository --> <relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent> </parent>
<artifactId>spring-cloud-openfeign-core</artifactId> <artifactId>spring-cloud-openfeign-core</artifactId>
@@ -183,13 +183,13 @@
<dependency> <dependency>
<groupId>com.google.protobuf</groupId> <groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId> <artifactId>protobuf-java</artifactId>
<version>3.14.0</version> <version>3.19.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.vavr</groupId> <groupId>io.vavr</groupId>
<artifactId>vavr</artifactId> <artifactId>vavr</artifactId>
<version>0.10.3</version> <version>0.10.4</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -0,0 +1,34 @@
/*
* 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.Method;
import feign.Target;
/**
* Used to resolve a circuitbreaker name which will be used in
* {@link org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory}.
*
* @author Kwangyong Kim
* @since 2020.0.4
*/
public interface CircuitBreakerNameResolver {
String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method);
}
@@ -17,6 +17,7 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Timer; import java.util.Timer;
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.databind.Module;
import feign.Client; import feign.Client;
import feign.Feign; import feign.Feign;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.Target;
import feign.hc5.ApacheHttp5Client; import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient; import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient; import feign.okhttp.OkHttpClient;
@@ -79,6 +81,7 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResour
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh * @author Nguyen Ky Thanh
* @author Andrii Bohutskyi * @author Andrii Bohutskyi
* @author Kwangyong Kim
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class) @ConditionalOnClass(Feign.class)
@@ -146,12 +149,29 @@ public class FeignAutoConfiguration {
return new DefaultTargeter(); return new DefaultTargeter();
} }
@Bean
@ConditionalOnMissingBean(CircuitBreakerNameResolver.class)
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return new DefaultCircuitBreakerNameResolver();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnBean(CircuitBreakerFactory.class) @ConditionalOnBean(CircuitBreakerFactory.class)
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory, public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,
@Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled) { @Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,
return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled); CircuitBreakerNameResolver circuitBreakerNameResolver) {
return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,
circuitBreakerNameResolver);
}
static class DefaultCircuitBreakerNameResolver implements CircuitBreakerNameResolver {
@Override
public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
return Feign.configKey(target.type(), method);
}
} }
} }
@@ -27,6 +27,7 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
* *
* @author Marcin Grzejszczak * @author Marcin Grzejszczak
* @author Andrii Bohutskyi * @author Andrii Bohutskyi
* @author Kwangyong Kim
* @since 3.0.0 * @since 3.0.0
*/ */
public final class FeignCircuitBreaker { public final class FeignCircuitBreaker {
@@ -53,6 +54,8 @@ public final class FeignCircuitBreaker {
private boolean circuitBreakerGroupEnabled; private boolean circuitBreakerGroupEnabled;
private CircuitBreakerNameResolver circuitBreakerNameResolver;
Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) { Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) {
this.circuitBreakerFactory = circuitBreakerFactory; this.circuitBreakerFactory = circuitBreakerFactory;
return this; return this;
@@ -68,6 +71,11 @@ public final class FeignCircuitBreaker {
return this; return this;
} }
Builder circuitBreakerNameResolver(CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
return this;
}
public <T> T target(Target<T> target, T fallback) { public <T> T target(Target<T> target, T fallback) {
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target); return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
} }
@@ -82,9 +90,9 @@ public final class FeignCircuitBreaker {
} }
public Feign build(final FallbackFactory<?> nullableFallbackFactory) { public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory( super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
(target, dispatch) -> new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory, circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory,
feignClientName, target, dispatch, nullableFallbackFactory, circuitBreakerGroupEnabled)); circuitBreakerGroupEnabled, circuitBreakerNameResolver));
return super.build(); return super.build();
} }
@@ -24,7 +24,6 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import feign.Feign;
import feign.InvocationHandlerFactory; import feign.InvocationHandlerFactory;
import feign.Target; import feign.Target;
@@ -51,9 +50,11 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
private final boolean circuitBreakerGroupEnabled; private final boolean circuitBreakerGroupEnabled;
private final CircuitBreakerNameResolver circuitBreakerNameResolver;
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target, FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target,
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory,
boolean circuitBreakerGroupEnabled) { boolean circuitBreakerGroupEnabled, CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.factory = factory; this.factory = factory;
this.feignClientName = feignClientName; this.feignClientName = feignClientName;
this.target = checkNotNull(target, "target"); this.target = checkNotNull(target, "target");
@@ -61,6 +62,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
this.fallbackMethodMap = toFallbackMethod(dispatch); this.fallbackMethodMap = toFallbackMethod(dispatch);
this.nullableFallbackFactory = nullableFallbackFactory; this.nullableFallbackFactory = nullableFallbackFactory;
this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled; this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
} }
@Override @Override
@@ -82,7 +84,8 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
else if ("toString".equals(method.getName())) { else if ("toString".equals(method.getName())) {
return toString(); return toString();
} }
String circuitName = Feign.configKey(target.type(), method);
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName) CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)
: factory.create(circuitName); : factory.create(circuitName);
Supplier<Object> supplier = asSupplier(method, args); Supplier<Object> supplier = asSupplier(method, args);
@@ -106,7 +109,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
return () -> { return () -> {
try { try {
RequestContextHolder.setRequestAttributes(requestAttributes); RequestContextHolder.setRequestAttributes(requestAttributes);
return this.dispatch.get(method).invoke(args); return dispatch.get(method).invoke(args);
} }
catch (RuntimeException throwable) { catch (RuntimeException throwable) {
throw throwable; throw throwable;
@@ -114,9 +117,6 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
catch (Throwable throwable) { catch (Throwable throwable) {
throw new RuntimeException(throwable); throw new RuntimeException(throwable);
} }
finally {
RequestContextHolder.resetRequestAttributes();
}
}; };
} }
@@ -29,9 +29,13 @@ class FeignCircuitBreakerTargeter implements Targeter {
private final boolean circuitBreakerGroupEnabled; private final boolean circuitBreakerGroupEnabled;
FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled) { private final CircuitBreakerNameResolver circuitBreakerNameResolver;
FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled,
CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerFactory = circuitBreakerFactory; this.circuitBreakerFactory = circuitBreakerFactory;
this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled; this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
} }
@Override @Override
@@ -85,7 +89,8 @@ class FeignCircuitBreakerTargeter implements Targeter {
private FeignCircuitBreaker.Builder builder(String feignClientName, FeignCircuitBreaker.Builder builder) { private FeignCircuitBreaker.Builder builder(String feignClientName, FeignCircuitBreaker.Builder builder) {
return builder.circuitBreakerFactory(circuitBreakerFactory).feignClientName(feignClientName) return builder.circuitBreakerFactory(circuitBreakerFactory).feignClientName(feignClientName)
.circuitBreakerGroupEnabled(circuitBreakerGroupEnabled); .circuitBreakerGroupEnabled(circuitBreakerGroupEnabled)
.circuitBreakerNameResolver(circuitBreakerNameResolver);
} }
} }
@@ -17,6 +17,7 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -276,13 +277,8 @@ public class FeignClientFactoryBean
builder.encoder(getOrInstantiate(config.getEncoder())); builder.encoder(getOrInstantiate(config.getEncoder()));
} }
if (Objects.nonNull(config.getDefaultRequestHeaders())) { addDefaultRequestHeaders(config, builder);
builder.requestInterceptor(requestTemplate -> requestTemplate.headers(config.getDefaultRequestHeaders())); addDefaultQueryParams(config, builder);
}
if (Objects.nonNull(config.getDefaultQueryParameters())) {
builder.requestInterceptor(requestTemplate -> requestTemplate.queries(config.getDefaultQueryParameters()));
}
if (Objects.nonNull(config.getDecoder())) { if (Objects.nonNull(config.getDecoder())) {
builder.decoder(getOrInstantiate(config.getDecoder())); builder.decoder(getOrInstantiate(config.getDecoder()));
@@ -301,6 +297,35 @@ public class FeignClientFactoryBean
} }
} }
private void addDefaultQueryParams(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
Map<String, Collection<String>> defaultQueryParameters = config.getDefaultQueryParameters();
if (Objects.nonNull(defaultQueryParameters)) {
builder.requestInterceptor(requestTemplate -> {
Map<String, Collection<String>> queries = requestTemplate.queries();
defaultQueryParameters.keySet().forEach(key -> {
if (!queries.containsKey(key)) {
requestTemplate.query(key, defaultQueryParameters.get(key));
}
});
});
}
}
private void addDefaultRequestHeaders(FeignClientProperties.FeignClientConfiguration config,
Feign.Builder builder) {
Map<String, Collection<String>> defaultRequestHeaders = config.getDefaultRequestHeaders();
if (Objects.nonNull(defaultRequestHeaders)) {
builder.requestInterceptor(requestTemplate -> {
Map<String, Collection<String>> headers = requestTemplate.headers();
defaultRequestHeaders.keySet().forEach(key -> {
if (!headers.containsKey(key)) {
requestTemplate.header(key, defaultRequestHeaders.get(key));
}
});
});
}
}
private <T> T getOrInstantiate(Class<T> tClass) { private <T> T getOrInstantiate(Class<T> tClass) {
try { try {
return beanFactory != null ? beanFactory.getBean(tClass) : applicationContext.getBean(tClass); return beanFactory != null ? beanFactory.getBean(tClass) : applicationContext.getBean(tClass);
@@ -382,11 +407,9 @@ public class FeignClientFactoryBean
Feign.Builder builder = feign(context); Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) { if (!StringUtils.hasText(url)) {
if (url != null && LOG.isWarnEnabled()) {
LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing."); if (LOG.isInfoEnabled()) {
} LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
else if (LOG.isDebugEnabled()) {
LOG.debug("URL not provided. Will use LoadBalancer.");
} }
if (!name.startsWith("http")) { if (!name.startsWith("http")) {
url = "http://" + name; url = "http://" + name;
@@ -0,0 +1,46 @@
/*
* 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.loadbalancer;
import java.io.ByteArrayInputStream;
import java.net.URI;
import feign.Response;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
/**
* A {@link RetryableStatusCodeException} for {@link Response}s.
*
* @author Ryan Baxter
*/
public class LoadBalancerResponseStatusCodeException extends RetryableStatusCodeException {
private final Response response;
public LoadBalancerResponseStatusCodeException(String serviceId, Response response, byte[] body, URI uri) {
super(serviceId, response.status(), response, uri);
this.response = Response.builder().body(new ByteArrayInputStream(body), body.length).headers(response.headers())
.reason(response.reason()).status(response.status()).request(response.request()).build();
}
@Override
public Response getResponse() {
return this.response;
}
}
@@ -46,7 +46,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValida
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.ResponseData; import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext; import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -57,6 +56,7 @@ import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData; import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
@@ -70,7 +70,7 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
public class RetryableFeignBlockingLoadBalancerClient implements Client { public class RetryableFeignBlockingLoadBalancerClient implements Client {
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class); private static final Log LOG = LogFactory.getLog(RetryableFeignBlockingLoadBalancerClient.class);
private final Client delegate; private final Client delegate;
@@ -161,8 +161,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Retrying on status code: %d", responseStatus)); LOG.debug(String.format("Retrying on status code: %d", responseStatus));
} }
byte[] byteArray = response.body() == null ? new byte[] {}
: StreamUtils.copyToByteArray(response.body().asInputStream());
response.close(); response.close();
throw new RetryableStatusCodeException(serviceId, responseStatus, response, URI.create(request.url())); throw new LoadBalancerResponseStatusCodeException(serviceId, response, byteArray,
URI.create(request.url()));
} }
return response; return response;
}, new LoadBalancedRecoveryCallback<Response, Response>() { }, new LoadBalancedRecoveryCallback<Response, Response>() {
@@ -38,6 +38,8 @@ import org.springframework.data.domain.Sort;
* *
* @author Pascal Büttiker * @author Pascal Büttiker
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Pedro Mendes
* @author Nikita Konev
*/ */
public class PageJacksonModule extends Module { public class PageJacksonModule extends Module {
@@ -69,18 +71,22 @@ public class PageJacksonModule extends Module {
@JsonProperty("size") int size, @JsonProperty("totalElements") @JsonAlias({ "total-elements", @JsonProperty("size") int size, @JsonProperty("totalElements") @JsonAlias({ "total-elements",
"total_elements", "totalelements", "TotalElements" }) long totalElements, "total_elements", "totalelements", "TotalElements" }) long totalElements,
@JsonProperty("sort") Sort sort) { @JsonProperty("sort") Sort sort) {
PageRequest pageRequest; if (size > 0) {
if (sort != null) { PageRequest pageRequest;
pageRequest = PageRequest.of(number, size, sort); if (sort != null) {
pageRequest = PageRequest.of(number, size, sort);
}
else {
pageRequest = PageRequest.of(number, size);
}
delegate = new PageImpl<>(content, pageRequest, totalElements);
} }
else { else {
pageRequest = PageRequest.of(number, size); delegate = new PageImpl<>(content);
} }
delegate = new PageImpl<>(content, pageRequest, totalElements);
} }
@JsonProperty @JsonIgnore
@Override @Override
public int getTotalPages() { public int getTotalPages() {
return delegate.getTotalPages(); return delegate.getTotalPages();
@@ -104,7 +110,7 @@ public class PageJacksonModule extends Module {
return delegate.getSize(); return delegate.getSize();
} }
@JsonProperty @JsonIgnore
@Override @Override
public int getNumberOfElements() { public int getNumberOfElements() {
return delegate.getNumberOfElements(); return delegate.getNumberOfElements();
@@ -128,13 +134,13 @@ public class PageJacksonModule extends Module {
return delegate.getSort(); return delegate.getSort();
} }
@JsonProperty @JsonIgnore
@Override @Override
public boolean isFirst() { public boolean isFirst() {
return delegate.isFirst(); return delegate.isFirst();
} }
@JsonProperty @JsonIgnore
@Override @Override
public boolean isLast() { public boolean isLast() {
return delegate.isLast(); return delegate.isLast();
@@ -176,6 +182,18 @@ public class PageJacksonModule extends Module {
return delegate.iterator(); return delegate.iterator();
} }
@JsonIgnore
@Override
public Pageable getPageable() {
return delegate.getPageable();
}
@JsonIgnore
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
} }
} }
@@ -24,6 +24,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
import feign.RequestTemplate; import feign.RequestTemplate;
@@ -48,6 +49,10 @@ import org.springframework.web.multipart.MultipartFile;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders; import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
import static org.springframework.http.MediaType.MULTIPART_MIXED;
import static org.springframework.http.MediaType.MULTIPART_RELATED;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -56,6 +61,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
* @author Aaron Whiteside * @author Aaron Whiteside
* @author Darren Foong * @author Darren Foong
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Can Bezmen
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class SpringEncoder implements Encoder { public class SpringEncoder implements Encoder {
@@ -95,8 +101,8 @@ public class SpringEncoder implements Encoder {
requestContentType = MediaType.valueOf(type); requestContentType = MediaType.valueOf(type);
} }
if (isMultipartType(requestContentType)) { if (isFormRelatedContentType(requestContentType)) {
this.springFormEncoder.encode(requestBody, bodyType, request); springFormEncoder.encode(requestBody, bodyType, request);
return; return;
} }
else { else {
@@ -207,9 +213,16 @@ public class SpringEncoder implements Encoder {
} }
} }
private boolean isFormRelatedContentType(MediaType requestContentType) {
return isMultipartType(requestContentType) || isFormUrlEncoded(requestContentType);
}
private boolean isMultipartType(MediaType requestContentType) { private boolean isMultipartType(MediaType requestContentType) {
return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, MediaType.MULTIPART_RELATED) return Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED).contains(requestContentType);
.contains(requestContentType); }
private boolean isFormUrlEncoded(MediaType requestContentType) {
return Objects.equals(APPLICATION_FORM_URLENCODED, requestContentType);
} }
private boolean binaryContentType(FeignOutputMessage outputMessage) { private boolean binaryContentType(FeignOutputMessage outputMessage) {
@@ -227,21 +240,21 @@ public class SpringEncoder implements Encoder {
private final HttpHeaders httpHeaders; private final HttpHeaders httpHeaders;
private FeignOutputMessage(RequestTemplate request) { private FeignOutputMessage(RequestTemplate request) {
this.httpHeaders = getHttpHeaders(request.headers()); httpHeaders = getHttpHeaders(request.headers());
} }
@Override @Override
public OutputStream getBody() throws IOException { public OutputStream getBody() throws IOException {
return this.outputStream; return outputStream;
} }
@Override @Override
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
return this.httpHeaders; return httpHeaders;
} }
public ByteArrayOutputStream getOutputStream() { public ByteArrayOutputStream getOutputStream() {
return this.outputStream; return outputStream;
} }
} }
@@ -35,6 +35,8 @@ import feign.Feign;
import feign.MethodMetadata; import feign.MethodMetadata;
import feign.Param; import feign.Param;
import feign.Request; 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.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.CollectionFormat; import org.springframework.cloud.openfeign.CollectionFormat;
@@ -82,6 +84,8 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
*/ */
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware { 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 ACCEPT = "Accept";
private static final String CONTENT_TYPE = "Content-Type"; private static final String CONTENT_TYPE = "Content-Type";
@@ -169,47 +173,18 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
@Override @Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) { protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
if (clz.getInterfaces().length == 0) { RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class); if (classAnnotation != null) {
if (classAnnotation != null) { LOG.error("Cannot process class: " + clz.getName()
// Prepend path from class annotation if specified + ". @RequestMapping annotation is not allowed on @FeignClient interfaces.");
if (classAnnotation.value().length > 0) { throw new IllegalArgumentException("@RequestMapping annotation not allowed on @FeignClient interfaces");
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);
}
}
}
} }
} }
@Override @Override
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
processedMethods.put(Feign.configKey(targetType, method), method); processedMethods.put(Feign.configKey(targetType, method), method);
MethodMetadata md = super.parseAndValidateMetadata(targetType, method); return 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;
} }
@Override @Override
@@ -16,12 +16,16 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.lang.reflect.Method;
import feign.Target;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.FeignAutoConfiguration.CircuitBreakerPresentFeignTargeterConfiguration.DefaultCircuitBreakerNameResolver;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -31,6 +35,7 @@ import static org.mockito.Mockito.mock;
* @author Tim Peeters * @author Tim Peeters
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Andrii Bohutskyi * @author Andrii Bohutskyi
* @author Kwangyong Kim
*/ */
class FeignAutoConfigurationTests { class FeignAutoConfigurationTests {
@@ -50,6 +55,8 @@ class FeignAutoConfigurationTests {
.withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> { .withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> {
assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class); assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class);
assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, false); assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, false);
assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx,
DefaultCircuitBreakerNameResolver.class);
}); });
} }
@@ -63,6 +70,17 @@ class FeignAutoConfigurationTests {
}); });
} }
@Test
void shouldInstantiateFeignCircuitBreakerTargeterWhenEnabledWithCustomCircuitBreakerNameResolver() {
runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class))
.withBean(CircuitBreakerNameResolver.class, CustomCircuitBreakerNameResolver::new)
.withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> {
assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class);
assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx,
CustomCircuitBreakerNameResolver.class);
});
}
private void assertOnlyOneTargeterPresent(ConfigurableApplicationContext ctx, Class<?> beanClass) { private void assertOnlyOneTargeterPresent(ConfigurableApplicationContext ctx, Class<?> beanClass) {
assertThat(ctx.getBeansOfType(Targeter.class)).hasSize(1).hasValueSatisfying(new Condition<>( assertThat(ctx.getBeansOfType(Targeter.class)).hasSize(1).hasValueSatisfying(new Condition<>(
beanClass::isInstance, String.format("Targeter should be an instance of %s", beanClass))); beanClass::isInstance, String.format("Targeter should be an instance of %s", beanClass)));
@@ -75,4 +93,19 @@ class FeignAutoConfigurationTests {
assertThat(bean).hasFieldOrPropertyWithValue("circuitBreakerGroupEnabled", expectedValue); assertThat(bean).hasFieldOrPropertyWithValue("circuitBreakerGroupEnabled", expectedValue);
} }
private void assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(
ConfigurableApplicationContext ctx, Class<?> beanClass) {
final CircuitBreakerNameResolver bean = ctx.getBean(CircuitBreakerNameResolver.class);
assertThat(bean).isExactlyInstanceOf(beanClass);
}
static class CustomCircuitBreakerNameResolver implements CircuitBreakerNameResolver {
@Override
public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
return feignClientName + "_" + method.getName();
}
}
} }
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -83,7 +82,7 @@ class FeignClientDisabledClientLevelFeaturesTests {
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class) @FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
interface BarClient { interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET) @GetMapping("/")
String get(); String get();
} }
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -82,7 +81,7 @@ class FeignClientDisabledFeaturesTests {
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class) @FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
interface BarClient { interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET) @GetMapping("/")
String get(); String get();
} }
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -133,7 +132,7 @@ public class FeignClientErrorDecoderTests {
@FeignClient(name = "bar", url = "http://bar", configuration = BarConfiguration.class) @FeignClient(name = "bar", url = "http://bar", configuration = BarConfiguration.class)
interface BarClient { interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET) @GetMapping("/")
String get(); String get();
} }
@@ -39,10 +39,9 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -90,7 +89,7 @@ public class FeignClientFactoryTests {
interface TestType { interface TestType {
@RequestMapping(value = "/", method = GET) @GetMapping("/")
String hello(); String hello();
} }
@@ -66,8 +66,6 @@ import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -295,8 +293,7 @@ public class FeignClientUsingPropertiesTests {
protected interface FormClient { protected interface FormClient {
@RequestMapping(value = "/form", method = RequestMethod.POST, @PostMapping(value = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
String form(Map<String, String> form); String form(Map<String, String> form);
} }
@@ -328,7 +325,7 @@ public class FeignClientUsingPropertiesTests {
@Import(NoSecurityConfiguration.class) @Import(NoSecurityConfiguration.class)
protected static class Application { protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/foo") @GetMapping("/foo")
public String foo(HttpServletRequest request) throws IllegalAccessException { public String foo(HttpServletRequest request) throws IllegalAccessException {
if ("Foo".equals(request.getHeader("Foo")) && "Bar".equals(request.getHeader("Bar"))) { if ("Foo".equals(request.getHeader("Foo")) && "Bar".equals(request.getHeader("Bar"))) {
return "OK"; return "OK";
@@ -25,8 +25,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatCode;
@@ -105,7 +104,7 @@ public class FeignClientsRegistrarTests {
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class) @FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class)
protected interface FallbackClient { protected interface FallbackClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
String fallbackTest(); String fallbackTest();
} }
@@ -114,7 +113,7 @@ public class FeignClientsRegistrarTests {
fallbackFactory = FallbackFactoryClient.class) fallbackFactory = FallbackFactoryClient.class)
protected interface FallbackFactoryClient { protected interface FallbackFactoryClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
String fallbackFactoryTest(); String fallbackFactoryTest();
} }
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.SocketUtils; import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -102,7 +101,7 @@ class FeignHttpClientUrlTests {
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/") @FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
protected interface UrlClient { protected interface UrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -110,7 +109,7 @@ class FeignHttpClientUrlTests {
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path") @FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
protected interface BeanUrlClient { protected interface BeanUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -118,7 +117,7 @@ class FeignHttpClientUrlTests {
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path") @FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
protected interface BeanUrlClientNoProtocol { protected interface BeanUrlClientNoProtocol {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -130,12 +129,12 @@ class FeignHttpClientUrlTests {
@Import(NoSecurityConfiguration.class) @Import(NoSecurityConfiguration.class)
protected static class TestConfig { protected static class TestConfig {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
public Hello getHello() { public Hello getHello() {
return new Hello("hello world 1"); return new Hello("hello world 1");
} }
@RequestMapping(method = RequestMethod.GET, value = "/path/hello") @GetMapping("/path/hello")
public Hello getHelloWithPath() { public Hello getHelloWithPath() {
return getHello(); return getHello();
} }
@@ -38,8 +38,6 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.SocketUtils; import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -104,7 +102,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/") @FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
protected interface UrlClient { protected interface UrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -112,7 +110,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path") @FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
protected interface BeanUrlClient { protected interface BeanUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -120,7 +118,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path") @FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
protected interface BeanUrlClientNoProtocol { protected interface BeanUrlClientNoProtocol {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -39,8 +39,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -154,22 +152,22 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
protected interface TestClient { protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/helloresponse") @GetMapping("/helloresponse")
ResponseEntity<Hello> getHelloResponse(); ResponseEntity<Hello> getHelloResponse();
@RequestMapping(method = RequestMethod.GET, value = "/hellovoid") @GetMapping("/hellovoid")
ResponseEntity<Void> getHelloVoid(); ResponseEntity<Void> getHelloVoid();
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellos") @GetMapping("/hellos")
List<Hello> getHellos(); List<Hello> getHellos();
@RequestMapping(method = RequestMethod.GET, value = "/hellostrings") @GetMapping("/hellostrings")
List<String> getHelloStrings(); List<String> getHelloStrings();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound") @GetMapping("/hellonotfound")
ResponseEntity<String> getNotFound(); ResponseEntity<String> getNotFound();
@GetMapping("/helloWildcard") @GetMapping("/helloWildcard")
@@ -39,8 +39,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -122,7 +121,7 @@ public class BeansFeignClientTests {
return feignClientBuilder.forType(TestClient.class, "builderapp").build(); return feignClientBuilder.forType(TestClient.class, "builderapp").build();
} }
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
public Hello getHello() { public Hello getHello() {
return new Hello("hello world 1"); return new Hello("hello world 1");
} }
@@ -19,14 +19,13 @@ package org.springframework.cloud.openfeign.beans;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello; import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Primary @Primary
@FeignClient("localapp") @FeignClient("localapp")
public interface TestClient { public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -18,13 +18,12 @@ package org.springframework.cloud.openfeign.beans.extra;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello; import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "otherapp", qualifier = "uniquequalifier") @FeignClient(value = "otherapp", qualifier = "uniquequalifier")
public interface TestClient { public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -45,8 +45,7 @@ import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.SocketUtils; import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -115,10 +114,10 @@ public class CircuitBreakerTests {
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class) @FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient { protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound") @GetMapping("/hellonotfound")
String getException(); String getException();
} }
@@ -142,10 +141,10 @@ public class CircuitBreakerTests {
fallbackFactory = TestFallbackFactory.class) fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory { protected interface TestClientWithFactory {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound") @GetMapping("/hellonotfound")
String getException(); String getException();
} }
@@ -40,8 +40,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.SocketUtils; import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -97,10 +96,10 @@ public class CircuitBreakerWithNoFallbackTests {
@FeignClient(name = "test", url = "http://localhost:${server.port}/") @FeignClient(name = "test", url = "http://localhost:${server.port}/")
protected interface CircuitBreakerTestClient { protected interface CircuitBreakerTestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound") @GetMapping("/hellonotfound")
String getException(); String getException();
} }
@@ -24,9 +24,9 @@ import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/** /**
* Simple Feign client for retrieving the invoice list. * Simple Feign client for retrieving the invoice list.
@@ -37,23 +37,23 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("local") @FeignClient("local")
public interface InvoiceClient { public interface InvoiceClient {
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable); ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable);
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST, @PostMapping(value = "invoicesPagedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody( ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(
@SpringQueryMap org.springframework.data.domain.Pageable pageable, @RequestBody String titlePrefix); @SpringQueryMap org.springframework.data.domain.Pageable pageable, @RequestBody String titlePrefix);
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST, @PostMapping(value = "invoicesSortedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(@SpringQueryMap org.springframework.data.domain.Sort sort, ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(@SpringQueryMap org.springframework.data.domain.Sort sort,
@RequestBody String titlePrefix); @RequestBody String titlePrefix);
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> getInvoices(); ResponseEntity<List<Invoice>> getInvoices();
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, @PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> saveInvoices(List<Invoice> invoices); ResponseEntity<List<Invoice>> saveInvoices(List<Invoice> invoices);
@@ -28,9 +28,9 @@ import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
@@ -42,28 +42,28 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class InvoiceResource { public class InvoiceResource {
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Invoice>> getInvoices() { public ResponseEntity<List<Invoice>> getInvoices() {
return ResponseEntity.ok(createInvoiceList(null, 100, null)); return ResponseEntity.ok(createInvoiceList(null, 100, null));
} }
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, @PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> saveInvoices(@RequestBody List<Invoice> invoices) { ResponseEntity<List<Invoice>> saveInvoices(@RequestBody List<Invoice> invoices) {
return ResponseEntity.ok(invoices); return ResponseEntity.ok(invoices);
} }
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable) { public ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(null, pageable.getPageSize(), pageable.getSort()), Page<Invoice> page = new PageImpl<>(createInvoiceList(null, pageable.getPageSize(), pageable.getSort()),
pageable, 100); pageable, 100);
return ResponseEntity.ok(page); return ResponseEntity.ok(page);
} }
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST, @PostMapping(value = "invoicesPagedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(org.springframework.data.domain.Pageable pageable, public ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(org.springframework.data.domain.Pageable pageable,
@RequestBody String titlePrefix) { @RequestBody String titlePrefix) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, pageable.getPageSize(), pageable.getSort()), Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, pageable.getPageSize(), pageable.getSort()),
@@ -71,8 +71,8 @@ public class InvoiceResource {
return ResponseEntity.ok(page); return ResponseEntity.ok(page);
} }
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST, @PostMapping(value = "invoicesSortedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(org.springframework.data.domain.Sort sort, public ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(org.springframework.data.domain.Sort sort,
@RequestBody String titlePrefix) { @RequestBody String titlePrefix) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, 100, sort), PageRequest.of(0, 100, sort), Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, 100, sort), PageRequest.of(0, 100, sort),
@@ -28,8 +28,7 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -74,7 +73,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "bar") @FeignClient(name = "bar")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @GetMapping("/")
String get(); String get();
} }
@@ -90,7 +89,7 @@ public class FeignClientValidationTests {
@FeignClient(contextId = "foo", name = "bar") @FeignClient(contextId = "foo", name = "bar")
interface FooClient { interface FooClient {
@RequestMapping(method = RequestMethod.GET, value = "/") @GetMapping("/")
String get(); String get();
} }
@@ -98,7 +97,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "bar") @FeignClient(name = "bar")
interface BarClient { interface BarClient {
@RequestMapping(method = RequestMethod.GET, value = "/") @GetMapping("/")
String get(); String get();
} }
@@ -113,7 +112,7 @@ public class FeignClientValidationTests {
@FeignClient("foo_bar") @FeignClient("foo_bar")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @GetMapping("/")
String get(); String get();
} }
@@ -16,7 +16,10 @@
package org.springframework.cloud.openfeign.loadbalancer; package org.springframework.cloud.openfeign.loadbalancer;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
@@ -29,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
import feign.Client; import feign.Client;
import feign.Request; import feign.Request;
import feign.Response; import feign.Response;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -117,6 +121,13 @@ class RetryableFeignBlockingLoadBalancerClientTests {
return Response.builder().request(testRequest()).status(status).build(); return Response.builder().request(testRequest()).status(status).build();
} }
private Response testResponse(int status, String body) {
// ByteArrayInputStream ignores close() and must be wrapped
InputStream reallyCloseable = new BufferedInputStream(
new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));
return Response.builder().request(testRequest()).status(status).body(reallyCloseable, null).build();
}
@Test @Test
void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException { void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException {
Request request = testRequest(); Request request = testRequest();
@@ -148,6 +159,22 @@ class RetryableFeignBlockingLoadBalancerClientTests {
verify(delegate, times(2)).execute(any(), any()); verify(delegate, times(2)).execute(any(), any());
} }
@Test
void shouldExposeResponseBodyOnRetry() throws IOException {
properties.getRetry().getRetryableStatusCodes().add(503);
Request request = testRequest();
when(delegate.execute(any(), any())).thenReturn(testResponse(503, "foo"), testResponse(503, "foo"));
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
.thenReturn(URI.create("http://testhost:80/path"));
Response response = feignBlockingLoadBalancerClient.execute(request, new Request.Options());
String bodyContent = IOUtils.toString(response.body().asReader(StandardCharsets.UTF_8));
assertThat(bodyContent).isEqualTo("foo");
}
@Test @Test
void shouldPassCorrectRequestToDelegate() throws IOException { void shouldPassCorrectRequestToDelegate() throws IOException {
Request request = testRequest(); Request request = testRequest();
@@ -16,13 +16,18 @@
package org.springframework.cloud.openfeign.support; package org.springframework.cloud.openfeign.support;
import java.util.ArrayList;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -31,6 +36,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Ruben Vervaeke * @author Ruben Vervaeke
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Pedro Mendes
* @author Nikita Konev
*/ */
public class PageJacksonModuleTests { public class PageJacksonModuleTests {
@@ -40,6 +47,7 @@ public class PageJacksonModuleTests {
public static void initialize() { public static void initialize() {
objectMapper = new ObjectMapper(); objectMapper = new ObjectMapper();
objectMapper.registerModule(new PageJacksonModule()); objectMapper.registerModule(new PageJacksonModule());
objectMapper.registerModule(new SortJacksonModule());
} }
@ParameterizedTest @ParameterizedTest
@@ -58,4 +66,40 @@ public class PageJacksonModuleTests {
assertThat(result.getPageable().getPageNumber()).isEqualTo(1); assertThat(result.getPageable().getPageNumber()).isEqualTo(1);
} }
@Test
public void serializeAndDeserializeEmpty() throws JsonProcessingException {
// Given
PageImpl<Object> objects = new PageImpl<>(new ArrayList<>());
String pageJson = objectMapper.writeValueAsString(objects);
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(0);
assertThat(result.getContent()).hasSize(0);
}
@Test
public void serializeAndDeserializeFilledMultiple() throws JsonProcessingException {
// Given
ArrayList<Object> strings0 = new ArrayList<>();
strings0.add("first element");
strings0.add("second element");
PageImpl<Object> objects = new PageImpl<>(strings0, PageRequest.of(6, 2), 100);
assertThat(objects.getContent()).hasSize(2);
assertThat(objects.getPageable().getPageSize()).isEqualTo(2);
String pageJson = objectMapper.writeValueAsString(objects);
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(100);
assertThat(result.getContent()).hasSize(2);
assertThat(result.getContent().get(0)).isEqualTo("first element");
assertThat(result.getContent().get(1)).isEqualTo("second element");
assertThat(result.getPageable().getPageSize()).isEqualTo(2);
assertThat(result.getPageable().getPageNumber()).isEqualTo(6);
}
} }
@@ -60,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.HttpHeaders.ACCEPT; import static org.springframework.http.HttpHeaders.ACCEPT;
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH; import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
@@ -68,6 +69,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
* @author Spencer Gibb * @author Spencer Gibb
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Ahmad Mozafarnia * @author Ahmad Mozafarnia
* @author Can Bezmen
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, @SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
@@ -179,6 +181,17 @@ public class SpringEncoderTests {
assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains("hi"); assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains("hi");
} }
@Test
public void testFromURLEncodedValue() {
Encoder encoder = context.getInstance("formUrlEncoded", Encoder.class);
assertThat(encoder).isNotNull();
RequestTemplate request = new RequestTemplate();
request.header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE);
String body = "test";
encoder.encode(body, String.class, request);
assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains(body);
}
@Test @Test
public void testNoCharsetForBinaryFiles() { public void testNoCharsetForBinaryFiles() {
Encoder encoder = context.getInstance("test", Encoder.class); Encoder encoder = context.getInstance("test", Encoder.class);
@@ -50,6 +50,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.MatrixVariable; import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -62,6 +63,7 @@ import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static feign.CollectionFormat.SSV; import static feign.CollectionFormat.SSV;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
/** /**
@@ -168,16 +170,12 @@ public class SpringMvcContractTests {
} }
@Test @Test
public void testProcessAnnotations_Class_AnnotationsGetSpecificTest() throws Exception { public void testProcessAnnotations_Class_Annotations_RequestMapping() {
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getSpecificTest", String.class, assertThatIllegalArgumentException().isThrownBy(() -> {
String.class); Method method = TestTemplate_Class_RequestMapping.class.getDeclaredMethod("getSpecificTest", String.class,
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); String.class);
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");
} }
@Test @Test
@@ -185,25 +183,13 @@ public class SpringMvcContractTests {
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getAllTests", String.class); Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getAllTests", String.class);
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); 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.template().method()).isEqualTo("GET");
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId"); assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
assertThat(data.template().decodeSlash()).isTrue(); 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 @Test
public void testProcessAnnotations_ExtendedInterface() throws Exception { public void testProcessAnnotations_ExtendedInterface() throws Exception {
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests", String.class); Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests", String.class);
@@ -220,22 +206,6 @@ public class SpringMvcContractTests {
assertThat(data.template().decodeSlash()).isTrue(); 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 @Test
public void testProcessAnnotations_SimplePost() throws Exception { public void testProcessAnnotations_SimplePost() throws Exception {
Method method = TestTemplate_Simple.class.getDeclaredMethod("postTest", TestObject.class); Method method = TestTemplate_Simple.class.getDeclaredMethod("postTest", TestObject.class);
@@ -266,7 +236,7 @@ public class SpringMvcContractTests {
Integer.class); Integer.class);
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); 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().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -296,7 +266,7 @@ public class SpringMvcContractTests {
Integer.class); Integer.class);
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); 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().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -315,7 +285,7 @@ public class SpringMvcContractTests {
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest2", String.class, Integer.class); Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest2", String.class, Integer.class);
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); 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().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -367,7 +337,7 @@ public class SpringMvcContractTests {
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest"); Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest");
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); 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().method()).isEqualTo("GET");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -459,7 +429,7 @@ public class SpringMvcContractTests {
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); 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().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -587,13 +557,13 @@ public class SpringMvcContractTests {
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(value = "/test/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTest(@PathVariable("id") String id); ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
TestObject getTest(); TestObject getTest();
@GetMapping(value = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getMappingTest(@PathVariable("id") String id); ResponseEntity<TestObject> getMappingTest(@PathVariable("id") String id);
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
TestObject postTest(@RequestBody TestObject object); TestObject postTest(@RequestBody TestObject object);
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@@ -602,11 +572,18 @@ public class SpringMvcContractTests {
} }
@RequestMapping("/prepend/{classId}") @RequestMapping("/prepend/{classId}")
public interface TestTemplate_Class_Annotations { public interface TestTemplate_Class_RequestMapping {
@RequestMapping(value = "/test/{testId}", method = RequestMethod.GET) @RequestMapping(value = "/test/{testId}", method = RequestMethod.GET)
TestObject getSpecificTest(@PathVariable("classId") String classId, @PathVariable("testId") String testId); TestObject getSpecificTest(@PathVariable("classId") String classId, @PathVariable("testId") String testId);
}
public interface TestTemplate_Class_Annotations {
@GetMapping("/test/{testId}")
TestObject getSpecificTest(@PathVariable("classId") String classId, @PathVariable("testId") String testId);
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
TestObject getAllTests(@PathVariable("classId") String classId); TestObject getAllTests(@PathVariable("classId") String classId);
@@ -618,47 +595,46 @@ public class SpringMvcContractTests {
public interface TestTemplate_Headers { public interface TestTemplate_Headers {
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, headers = "X-Foo=bar") @GetMapping(value = "/test/{id}", headers = "X-Foo=bar")
ResponseEntity<TestObject> getTest(@PathVariable("id") String id); ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
} }
public interface TestTemplate_HeadersWithoutValues { public interface TestTemplate_HeadersWithoutValues {
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, @GetMapping(value = "/test/{id}", headers = { "X-Foo", "!X-Bar", "X-Baz!=fooBar" })
headers = { "X-Foo", "!X-Bar", "X-Baz!=fooBar" })
ResponseEntity<TestObject> getTest(@PathVariable("id") String id); ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
} }
public interface TestTemplate_ListParams { public interface TestTemplate_ListParams {
@RequestMapping(value = "/test", method = RequestMethod.GET) @GetMapping("/test")
ResponseEntity<TestObject> getTest(@RequestParam("id") List<String> id); ResponseEntity<TestObject> getTest(@RequestParam("id") List<String> id);
} }
public interface TestTemplate_ListParamsWithoutName { public interface TestTemplate_ListParamsWithoutName {
@RequestMapping(value = "/test", method = RequestMethod.GET) @GetMapping("/test")
ResponseEntity<TestObject> getTest(@RequestParam List<String> id); ResponseEntity<TestObject> getTest(@RequestParam List<String> id);
} }
public interface TestTemplate_MapParams { public interface TestTemplate_MapParams {
@RequestMapping(value = "/test", method = RequestMethod.GET) @GetMapping("/test")
ResponseEntity<TestObject> getTest(@RequestParam Map<String, String> params); ResponseEntity<TestObject> getTest(@RequestParam Map<String, String> params);
} }
public interface TestTemplate_HeaderMap { public interface TestTemplate_HeaderMap {
@RequestMapping(path = "/headerMap") @GetMapping("/headerMap")
String headerMap(@RequestHeader MultiValueMap<String, String> headerMap, String headerMap(@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader(name = "aHeader") String aHeader); @RequestHeader(name = "aHeader") String aHeader);
@RequestMapping(path = "/headerMapMoreThanOnce") @GetMapping("/headerMapMoreThanOnce")
String headerMapMoreThanOnce(@RequestHeader MultiValueMap<String, String> headerMap1, String headerMapMoreThanOnce(@RequestHeader MultiValueMap<String, String> headerMap1,
@RequestHeader MultiValueMap<String, String> headerMap2); @RequestHeader MultiValueMap<String, String> headerMap2);
@@ -666,42 +642,40 @@ public class SpringMvcContractTests {
public interface TestTemplate_QueryMap { public interface TestTemplate_QueryMap {
@RequestMapping(path = "/queryMap") @GetMapping("/queryMap")
String queryMap(@RequestParam MultiValueMap<String, String> queryMap, String queryMap(@RequestParam MultiValueMap<String, String> queryMap,
@RequestParam(name = "aParam") String aParam); @RequestParam(name = "aParam") String aParam);
@RequestMapping(path = "/queryMapMoreThanOnce") @GetMapping("/queryMapMoreThanOnce")
String queryMapMoreThanOnce(@RequestParam MultiValueMap<String, String> queryMap1, String queryMapMoreThanOnce(@RequestParam MultiValueMap<String, String> queryMap1,
@RequestParam MultiValueMap<String, String> queryMap2); @RequestParam MultiValueMap<String, String> queryMap2);
@RequestMapping(path = "/queryMapObject") @GetMapping("/queryMapObject")
String queryMapObject(@SpringQueryMap TestObject queryMap, @RequestParam(name = "aParam") String aParam); String queryMapObject(@SpringQueryMap TestObject queryMap, @RequestParam(name = "aParam") String aParam);
} }
public interface TestTemplate_RequestPart { public interface TestTemplate_RequestPart {
@RequestMapping(path = "/requestPart", method = RequestMethod.POST, @PostMapping(path = "/requestPart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
void requestWithMultipleParts(@RequestPart("file") MultipartFile file, @RequestPart("id") String identifier); void requestWithMultipleParts(@RequestPart("file") MultipartFile file, @RequestPart("id") String identifier);
} }
public interface TestTemplate_MatrixVariable { public interface TestTemplate_MatrixVariable {
@RequestMapping(path = "/matrixVariable/{params}") @GetMapping("/matrixVariable/{params}")
String matrixVariable(@MatrixVariable("params") Map<String, Object> params); String matrixVariable(@MatrixVariable("params") Map<String, Object> params);
@RequestMapping(path = "/matrixVariableObject/{param}") @GetMapping("/matrixVariableObject/{param}")
String matrixVariableObject(@MatrixVariable("param") Object object); String matrixVariableObject(@MatrixVariable("param") Object object);
@RequestMapping(path = "/matrixVariable/{params}") @GetMapping("/matrixVariable/{params}")
String matrixVariableNotNamed(@MatrixVariable Map<String, Object> params); String matrixVariableNotNamed(@MatrixVariable Map<String, Object> params);
} }
@JsonAutoDetect @JsonAutoDetect
@RequestMapping("/advanced")
public interface TestTemplate_Advanced { public interface TestTemplate_Advanced {
@CollectionFormat(SSV) @CollectionFormat(SSV)
@@ -709,21 +683,20 @@ public class SpringMvcContractTests {
ResponseEntity<TestObject> getWithCollectionFormat(); ResponseEntity<TestObject> getWithCollectionFormat();
@ExceptionHandler @ExceptionHandler
@RequestMapping(path = "/test/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) @PutMapping(path = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTest(@RequestHeader("Authorization") String auth, @PathVariable("id") String id, ResponseEntity<TestObject> getTest(@RequestHeader("Authorization") String auth, @PathVariable("id") String id,
@RequestParam("amount") Integer amount); @RequestParam("amount") Integer amount);
@RequestMapping(path = "/test2", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) @PutMapping(path = "/test2", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTest2(@RequestHeader(name = "Authorization") String auth, ResponseEntity<TestObject> getTest2(@RequestHeader(name = "Authorization") String auth,
@RequestParam(name = "amount") Integer amount); @RequestParam(name = "amount") Integer amount);
@ExceptionHandler @ExceptionHandler
@RequestMapping(path = "/testfallback/{id}", method = RequestMethod.PUT, @PutMapping(path = "/testfallback/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTestFallback(@RequestHeader String Authorization, @PathVariable String id, ResponseEntity<TestObject> getTestFallback(@RequestHeader String Authorization, @PathVariable String id,
@RequestParam Integer amount); @RequestParam Integer amount);
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
TestObject getTest(); TestObject getTest();
@GetMapping(produces = "application/json") @GetMapping(produces = "application/json")
@@ -735,7 +708,7 @@ public class SpringMvcContractTests {
String CUSTOM_PATTERN = "dd-MM-yyyy HH:mm"; String CUSTOM_PATTERN = "dd-MM-yyyy HH:mm";
@RequestMapping(method = RequestMethod.GET) @GetMapping
String getTest(@RequestParam(name = "localDateTime") @DateTimeFormat( String getTest(@RequestParam(name = "localDateTime") @DateTimeFormat(
pattern = CUSTOM_PATTERN) LocalDateTime localDateTime); pattern = CUSTOM_PATTERN) LocalDateTime localDateTime);
@@ -745,7 +718,7 @@ public class SpringMvcContractTests {
String CUSTOM_PATTERN = "$###,###.###"; String CUSTOM_PATTERN = "$###,###.###";
@RequestMapping(method = RequestMethod.GET) @GetMapping
String getTest(@RequestParam("amount") @NumberFormat(pattern = CUSTOM_PATTERN) BigDecimal amount); String getTest(@RequestParam("amount") @NumberFormat(pattern = CUSTOM_PATTERN) BigDecimal amount);
} }
@@ -17,8 +17,7 @@
package org.springframework.cloud.openfeign.testclients; package org.springframework.cloud.openfeign.testclients;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/** /**
* @author Ryan Baxter * @author Ryan Baxter
@@ -26,7 +25,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "localapp") @FeignClient(name = "localapp")
public interface TestClient { public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
String getHello(); String getHello();
} }
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -83,7 +82,7 @@ public class FeignClientNotPrimaryTests {
@FeignClient(name = "localapp", primary = false) @FeignClient(name = "localapp", primary = false)
protected interface TestClient { protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, path = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -101,7 +100,7 @@ public class FeignClientNotPrimaryTests {
return new PrimaryTestClient(); return new PrimaryTestClient();
} }
@RequestMapping(method = RequestMethod.GET, path = "/hello") @GetMapping("/hello")
public Hello getHello() { public Hello getHello() {
return new Hello(HELLO_WORLD_1); return new Hello(HELLO_WORLD_1);
} }
@@ -27,8 +27,7 @@ import org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoCon
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -70,7 +69,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "example", url = "https://example.com") @FeignClient(name = "example", url = "https://example.com")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @GetMapping("/")
@Deprecated @Deprecated
String get(); String get();
@@ -86,7 +85,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "example", url = "${feignClient.url:https://example.com}") @FeignClient(name = "example", url = "${feignClient.url:https://example.com}")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @GetMapping("/")
@Deprecated @Deprecated
String get(); String get();
@@ -102,7 +101,7 @@ public class FeignClientValidationTests {
@FeignClient("foo") @FeignClient("foo")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @GetMapping("/")
@Deprecated @Deprecated
String get(); String get();
@@ -41,11 +41,11 @@ import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -107,17 +107,17 @@ class FeignHttpClientTests {
protected interface BaseTestClient { protected interface BaseTestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json") @PatchMapping(value = "/hellop", consumes = "application/json")
ResponseEntity<Void> patchHello(Hello hello); ResponseEntity<Void> patchHello(Hello hello);
} }
protected interface UserService { protected interface UserService {
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
User getUser(@PathVariable("id") long id); User getUser(@PathVariable("id") long id);
} }
@@ -136,12 +136,12 @@ class FeignHttpClientTests {
@Import(NoSecurityConfiguration.class) @Import(NoSecurityConfiguration.class)
protected static class Application implements UserService { protected static class Application implements UserService {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
public Hello getHello() { public Hello getHello() {
return new Hello("hello world 1"); return new Hello("hello world 1");
} }
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop") @PatchMapping("/hellop")
public ResponseEntity<Void> patchHello(@RequestBody Hello hello, public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
@RequestHeader("Content-Length") int contentLength) { @RequestHeader("Content-Length") int contentLength) {
if (contentLength <= 0) { if (contentLength <= 0) {
@@ -40,11 +40,11 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -107,17 +107,17 @@ class FeignOkHttpTests {
protected interface BaseTestClient { protected interface BaseTestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json") @PatchMapping(value = "/hellop", consumes = "application/json")
ResponseEntity<Void> patchHello(Hello hello); ResponseEntity<Void> patchHello(Hello hello);
} }
protected interface UserService { protected interface UserService {
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}") @GetMapping("/users/{id}")
User getUser(@PathVariable("id") long id); User getUser(@PathVariable("id") long id);
} }
@@ -138,12 +138,12 @@ class FeignOkHttpTests {
@Import(NoSecurityConfiguration.class) @Import(NoSecurityConfiguration.class)
protected static class Application implements UserService { protected static class Application implements UserService {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
public Hello getHello() { public Hello getHello() {
return new Hello("hello world 1"); return new Hello("hello world 1");
} }
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop") @PatchMapping("/hellop")
public ResponseEntity<Void> patchHello(@RequestBody Hello hello, public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
@RequestHeader("Content-Length") int contentLength) { @RequestHeader("Content-Length") int contentLength) {
if (contentLength <= 0) { if (contentLength <= 0) {
@@ -38,7 +38,6 @@ import javax.servlet.http.Part;
import feign.Client; import feign.Client;
import feign.Logger; import feign.Logger;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.codec.EncodeException; import feign.codec.EncodeException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -65,7 +64,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.format.Formatter; import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -73,6 +71,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -84,6 +84,9 @@ import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -277,6 +280,13 @@ class ValidFeignClientTests {
assertThat(testClient.getToString(args)).isEqualTo(expectedResult); assertThat(testClient.getToString(args)).isEqualTo(expectedResult);
} }
@Test
void testFormURLEncoded() {
Hello hello = new Hello(HELLO_WORLD_1);
Hello response = testClient.postFormUrlEncoded(hello);
assertThat(response).isEqualTo(hello);
}
@Test @Test
void namedFeignClientWorks() { void namedFeignClientWorks() {
assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull(); assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull();
@@ -382,52 +392,42 @@ class ValidFeignClientTests {
@FeignClient(name = "localapp8") @FeignClient(name = "localapp8")
protected interface MultipartClient { protected interface MultipartClient {
@RequestMapping(method = RequestMethod.POST, path = "/singlePart", @PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String singlePart(@RequestPart("hello") String hello); String singlePart(@RequestPart("hello") String hello);
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart", @PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String singlePojoPart(@RequestPart("hello") Hello hello); String singlePojoPart(@RequestPart("hello") Hello hello);
@RequestMapping(method = RequestMethod.POST, path = "/multipart", @PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world, String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("file") MultipartFile file); @RequestPart("file") MultipartFile file);
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo", @PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world, String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2, @RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
@RequestPart("file") MultipartFile file); @RequestPart("file") MultipartFile file);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames", @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String requestPartListOfMultipartFilesReturnsPartNames(@RequestPart("files") List<MultipartFile> files); String requestPartListOfMultipartFilesReturnsPartNames(@RequestPart("files") List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames", @PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String requestPartListOfMultipartFilesReturnsFileNames(@RequestPart("files") List<MultipartFile> files); String requestPartListOfMultipartFilesReturnsFileNames(@RequestPart("files") List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles", @PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos, String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
@RequestPart("files") List<MultipartFile> files); @RequestPart("files") List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames", @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String requestBodyListOfMultipartFiles(@RequestBody List<MultipartFile> files); String requestBodyListOfMultipartFiles(@RequestBody List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames", @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String requestBodySingleMultipartFile(@RequestBody MultipartFile file); String requestBodySingleMultipartFile(@RequestBody MultipartFile file);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames", @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String requestBodyMap(@RequestBody Map<String, ?> form); String requestBodyMap(@RequestBody Map<String, ?> form);
@RequestMapping(method = RequestMethod.POST, path = "/invalid", @PostMapping(path = "/invalid", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) produces = TEXT_PLAIN_VALUE)
String invalid(@RequestBody MultipartFile file); String invalid(@RequestBody MultipartFile file);
} }
@@ -435,36 +435,36 @@ class ValidFeignClientTests {
@FeignClient(name = "localapp", configuration = TestClientConfig.class) @FeignClient(name = "localapp", configuration = TestClientConfig.class)
protected interface TestClient { protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, path = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.GET, path = "/hello") @GetMapping("/hello")
Optional<Hello> getOptionalHello(); Optional<Hello> getOptionalHello();
@RequestMapping(method = RequestMethod.GET, path = "${feignClient.methodLevelRequestMappingPath}") @GetMapping("${feignClient.methodLevelRequestMappingPath}")
Hello getHelloUsingPropertyPlaceHolder(); Hello getHelloUsingPropertyPlaceHolder();
@RequestMapping(method = RequestMethod.GET, path = "/hellos") @GetMapping("/hellos")
List<Hello> getHellos(); List<Hello> getHellos();
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings") @GetMapping("/hellostrings")
List<String> getHelloStrings(); List<String> getHelloStrings();
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders") @GetMapping("/helloheaders")
List<String> getHelloHeaders(); List<String> getHelloHeaders();
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders", @GetMapping(path = "/helloheadersplaceholders",
headers = "myPlaceholderHeader=${feignClient.myPlaceholderHeader}") headers = "myPlaceholderHeader=${feignClient.myPlaceholderHeader}")
String getHelloHeadersPlaceholders(); String getHelloHeadersPlaceholders();
@RequestMapping(method = RequestMethod.GET, path = "/helloparams") @GetMapping("/helloparams")
List<String> getParams(@RequestParam("params") List<String> params); List<String> getParams(@RequestParam("params") List<String> params);
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams") @GetMapping("/formattedparams")
List<LocalDate> getFormattedParams( List<LocalDate> getFormattedParams(
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params); @RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params);
@RequestMapping(method = RequestMethod.GET, path = "/noContent") @GetMapping("/noContent")
ResponseEntity<Void> noContent(); ResponseEntity<Void> noContent();
@RequestMapping(method = RequestMethod.HEAD, path = "/head") @RequestMapping(method = RequestMethod.HEAD, path = "/head")
@@ -477,21 +477,24 @@ class ValidFeignClientTests {
produces = "application/vnd.io.spring.cloud.test.v1+json", path = "/complex") produces = "application/vnd.io.spring.cloud.test.v1+json", path = "/complex")
String moreComplexContentType(String body); String moreComplexContentType(String body);
@RequestMapping(method = RequestMethod.GET, path = "/tostring") @GetMapping("/tostring")
String getToString(@RequestParam("arg") Arg arg); String getToString(@RequestParam("arg") Arg arg);
@RequestMapping(method = RequestMethod.GET, path = "/tostring2") @GetMapping("/tostring2")
String getToString(@RequestParam("arg") OtherArg arg); String getToString(@RequestParam("arg") OtherArg arg);
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection") @GetMapping("/tostringcollection")
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args); Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args);
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
Hello postFormUrlEncoded(Hello hello);
} }
@FeignClient(name = "localapp1") @FeignClient(name = "localapp1")
protected interface TestClientServiceId { protected interface TestClientServiceId {
@RequestMapping(method = RequestMethod.GET, path = "/hello") @GetMapping("/hello")
Hello getHello(); Hello getHello();
} }
@@ -499,10 +502,10 @@ class ValidFeignClientTests {
@FeignClient(name = "localapp2", decode404 = true) @FeignClient(name = "localapp2", decode404 = true)
protected interface DecodingTestClient { protected interface DecodingTestClient {
@RequestMapping(method = RequestMethod.GET, path = "/notFound") @GetMapping("/notFound")
ResponseEntity<String> notFound(); ResponseEntity<String> notFound();
@RequestMapping(method = RequestMethod.GET, path = "/notFound") @GetMapping("/notFound")
Optional<String> optional(); Optional<String> optional();
} }
@@ -526,22 +529,12 @@ class ValidFeignClientTests {
@Bean @Bean
public RequestInterceptor interceptor1() { public RequestInterceptor interceptor1() {
return new RequestInterceptor() { return template -> template.header(MYHEADER1, "myheader1value");
@Override
public void apply(RequestTemplate template) {
template.header(MYHEADER1, "myheader1value");
}
};
} }
@Bean @Bean
public RequestInterceptor interceptor2() { public RequestInterceptor interceptor2() {
return new RequestInterceptor() { return template -> template.header(MYHEADER2, "myheader2value");
@Override
public void apply(RequestTemplate template) {
template.header(MYHEADER2, "myheader2value");
}
};
} }
} }
@@ -570,27 +563,21 @@ class ValidFeignClientTests {
@Bean @Bean
FeignFormatterRegistrar feignFormatterRegistrar() { FeignFormatterRegistrar feignFormatterRegistrar() {
return new FeignFormatterRegistrar() { return registry -> registry.addFormatter(new Formatter<OtherArg>() {
@Override @Override
public void registerFormatters(FormatterRegistry registry) { public String print(OtherArg object, Locale locale) {
registry.addFormatter(new Formatter<OtherArg>() { if ("foo".equals(object.value)) {
return "bar";
@Override }
public String print(OtherArg object, Locale locale) { return object.value;
if ("foo".equals(object.value)) {
return "bar";
}
return object.value;
}
@Override
public OtherArg parse(String text, Locale locale) throws ParseException {
return new OtherArg(text);
}
});
} }
};
@Override
public OtherArg parse(String text, Locale locale) throws ParseException {
return new OtherArg(text);
}
});
} }
@Bean @Bean
@@ -598,23 +585,23 @@ class ValidFeignClientTests {
return new JsonFormWriter(); return new JsonFormWriter();
} }
@RequestMapping(method = RequestMethod.GET, path = "/hello") @GetMapping("/hello")
public Hello getHello() { public Hello getHello() {
return new Hello(HELLO_WORLD_1); return new Hello(HELLO_WORLD_1);
} }
@RequestMapping(method = RequestMethod.GET, path = "/hello2") @GetMapping("/hello2")
public Hello getHello2() { public Hello getHello2() {
return new Hello(OI_TERRA_2); return new Hello(OI_TERRA_2);
} }
@RequestMapping(method = RequestMethod.GET, path = "/hellos") @GetMapping("/hellos")
public List<Hello> getHellos() { public List<Hello> getHellos() {
ArrayList<Hello> hellos = getHelloList(); ArrayList<Hello> hellos = getHelloList();
return hellos; return hellos;
} }
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings") @GetMapping("/hellostrings")
public List<String> getHelloStrings() { public List<String> getHelloStrings() {
ArrayList<String> hellos = new ArrayList<>(); ArrayList<String> hellos = new ArrayList<>();
hellos.add(HELLO_WORLD_1); hellos.add(HELLO_WORLD_1);
@@ -622,7 +609,7 @@ class ValidFeignClientTests {
return hellos; return hellos;
} }
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders") @GetMapping("/helloheaders")
public List<String> getHelloHeaders(@RequestHeader(MYHEADER1) String myheader1, public List<String> getHelloHeaders(@RequestHeader(MYHEADER1) String myheader1,
@RequestHeader(MYHEADER2) String myheader2) { @RequestHeader(MYHEADER2) String myheader2) {
ArrayList<String> headers = new ArrayList<>(); ArrayList<String> headers = new ArrayList<>();
@@ -631,23 +618,23 @@ class ValidFeignClientTests {
return headers; return headers;
} }
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders") @GetMapping("/helloheadersplaceholders")
public String getHelloHeadersPlaceholders(@RequestHeader("myPlaceholderHeader") String myPlaceholderHeader) { public String getHelloHeadersPlaceholders(@RequestHeader("myPlaceholderHeader") String myPlaceholderHeader) {
return myPlaceholderHeader; return myPlaceholderHeader;
} }
@RequestMapping(method = RequestMethod.GET, path = "/helloparams") @GetMapping("/helloparams")
public List<String> getParams(@RequestParam("params") List<String> params) { public List<String> getParams(@RequestParam("params") List<String> params) {
return params; return params;
} }
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams") @GetMapping("/formattedparams")
public List<LocalDate> getFormattedParams( public List<LocalDate> getFormattedParams(
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params) { @RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params) {
return params; return params;
} }
@RequestMapping(method = RequestMethod.GET, path = "/noContent") @GetMapping("/noContent")
ResponseEntity<Void> noContent() { ResponseEntity<Void> noContent() {
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
} }
@@ -662,7 +649,7 @@ class ValidFeignClientTests {
throw new RuntimeException("always fails"); throw new RuntimeException("always fails");
} }
@RequestMapping(method = RequestMethod.GET, path = "/notFound") @GetMapping("/notFound")
ResponseEntity<String> notFound() { ResponseEntity<String> notFound() {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body((String) null); return ResponseEntity.status(HttpStatus.NOT_FOUND).body((String) null);
} }
@@ -676,17 +663,17 @@ class ValidFeignClientTests {
return body; return body;
} }
@RequestMapping(method = RequestMethod.GET, path = "/tostring") @GetMapping("/tostring")
String getToString(@RequestParam("arg") Arg arg) { String getToString(@RequestParam("arg") Arg arg) {
return arg.toString(); return arg.toString();
} }
@RequestMapping(method = RequestMethod.GET, path = "/tostring2") @GetMapping("/tostring2")
String getToString(@RequestParam("arg") OtherArg arg) { String getToString(@RequestParam("arg") OtherArg arg) {
return arg.value; return arg.value;
} }
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection") @GetMapping("/tostringcollection")
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args) { Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args) {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
for (OtherArg arg : args) { for (OtherArg arg : args) {
@@ -695,47 +682,40 @@ class ValidFeignClientTests {
return result; return result;
} }
@RequestMapping(method = RequestMethod.POST, path = "/singlePart", @PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String singlePart(@RequestPart("hello") String hello) { String singlePart(@RequestPart("hello") String hello) {
return hello; return hello;
} }
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart", @PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String singlePojoPart(@RequestPart("hello") Hello hello) { String singlePojoPart(@RequestPart("hello") Hello hello) {
return hello.getMessage(); return hello.getMessage();
} }
@RequestMapping(method = RequestMethod.POST, path = "/multipart", @PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world, String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("file") MultipartFile file) { @RequestPart("file") MultipartFile file) {
return hello + world + file.getOriginalFilename(); return hello + world + file.getOriginalFilename();
} }
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo", @PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world, String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2, @RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
@RequestPart("file") MultipartFile file) { @RequestPart("file") MultipartFile file) {
return hello + world + pojo1.getMessage() + pojo2.getMessage() + file.getOriginalFilename(); return hello + world + pojo1.getMessage() + pojo2.getMessage() + file.getOriginalFilename();
} }
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames", @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String multipartNames(HttpServletRequest request) throws Exception { String multipartNames(HttpServletRequest request) throws Exception {
return request.getParts().stream().map(Part::getName).collect(Collectors.joining(",")); return request.getParts().stream().map(Part::getName).collect(Collectors.joining(","));
} }
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames", @PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String multipartFilenames(HttpServletRequest request) throws Exception { String multipartFilenames(HttpServletRequest request) throws Exception {
return request.getParts().stream().map(Part::getSubmittedFileName).collect(Collectors.joining(",")); return request.getParts().stream().map(Part::getSubmittedFileName).collect(Collectors.joining(","));
} }
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles", @PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos, String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
@RequestPart("files") List<MultipartFile> files) { @RequestPart("files") List<MultipartFile> files) {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
@@ -751,6 +731,11 @@ class ValidFeignClientTests {
return result.toString(); return result.toString();
} }
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
Hello postFormUrlEncoded(Hello hello) {
return hello;
}
} }
public static class Hello { public static class Hello {
@@ -35,8 +35,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -70,7 +69,7 @@ public class FeignClientEnvVarTests {
@Import(NoSecurityConfiguration.class) @Import(NoSecurityConfiguration.class)
protected static class Application { protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
public String getHello() { public String getHello() {
return "hello world 1"; return "hello world 1";
} }
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -83,7 +82,7 @@ public class FeignClientScanningTests {
@FeignClient("localapp123") @FeignClient("localapp123")
protected interface TestClient { protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
String getHello(); String getHello();
} }
@@ -91,7 +90,7 @@ public class FeignClientScanningTests {
@FeignClient("${feignClient.localappName}") @FeignClient("${feignClient.localappName}")
protected interface TestClientByKey { protected interface TestClientByKey {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
String getHello(); String getHello();
} }
@@ -104,7 +103,7 @@ public class FeignClientScanningTests {
@Import(NoSecurityConfiguration.class) @Import(NoSecurityConfiguration.class)
protected static class Application { protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @GetMapping("/hello")
public String getHello() { public String getHello() {
return "hello world 1"; return "hello world 1";
} }
+2 -2
View File
@@ -6,11 +6,11 @@
<parent> <parent>
<artifactId>spring-cloud-dependencies-parent</artifactId> <artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<version>3.0.3</version> <version>3.0.6-SNAPSHOT</version>
<relativePath/> <relativePath/>
</parent> </parent>
<artifactId>spring-cloud-openfeign-dependencies</artifactId> <artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>3.0.3</version> <version>3.0.7-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>spring-cloud-openfeign-dependencies</name> <name>spring-cloud-openfeign-dependencies</name>
<description>Spring Cloud OpenFeign Dependencies</description> <description>Spring Cloud OpenFeign Dependencies</description>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.3</version> <version>3.0.7-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>spring-cloud-starter-openfeign</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>