Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Olga MaciaszekSharma | 161789e33a | 3 years ago |
buildmaster | 650b14ac22 | 3 years ago |
280 changed files with 11238 additions and 12697 deletions
@ -1,49 +0,0 @@
@@ -1,49 +0,0 @@
|
||||
version: 2 |
||||
updates: |
||||
- package-ecosystem: "github-actions" |
||||
directory: "/" |
||||
target-branch: "3.1.x" # oldest OSS supported branch |
||||
schedule: |
||||
interval: "weekly" |
||||
- package-ecosystem: "github-actions" |
||||
directory: "/" |
||||
target-branch: "4.0.x" # oldest OSS supported branch |
||||
schedule: |
||||
interval: "weekly" |
||||
- package-ecosystem: "github-actions" |
||||
directory: "/" |
||||
target-branch: "main" |
||||
schedule: |
||||
interval: "weekly" |
||||
- package-ecosystem: maven |
||||
directory: / |
||||
schedule: |
||||
interval: daily |
||||
target-branch: 3.1.x |
||||
ignore: |
||||
# only upgrade patch versions for maintenance branch |
||||
- dependency-name: "*" |
||||
update-types: |
||||
- version-update:semver-major |
||||
- version-update:semver-minor |
||||
- package-ecosystem: maven |
||||
directory: / |
||||
schedule: |
||||
interval: daily |
||||
target-branch: 4.0.x |
||||
ignore: |
||||
# only upgrade patch versions for maintenance branch |
||||
- dependency-name: "*" |
||||
update-types: |
||||
- version-update:semver-major |
||||
- version-update:semver-minor |
||||
- package-ecosystem: maven |
||||
directory: / |
||||
schedule: |
||||
interval: daily |
||||
target-branch: main |
||||
ignore: |
||||
# only upgrade by minor or patch |
||||
- dependency-name: "*" |
||||
update-types: |
||||
- version-update:semver-major |
@ -1,32 +0,0 @@
@@ -1,32 +0,0 @@
|
||||
name: Deploy Docs |
||||
on: |
||||
push: |
||||
branches-ignore: [ gh-pages ] |
||||
tags: '**' |
||||
repository_dispatch: |
||||
types: request-build-reference # legacy |
||||
#schedule: |
||||
#- cron: '0 10 * * *' # Once per day at 10am UTC |
||||
workflow_dispatch: |
||||
permissions: |
||||
actions: write |
||||
jobs: |
||||
build: |
||||
runs-on: ubuntu-latest |
||||
# if: github.repository_owner == 'spring-cloud' |
||||
steps: |
||||
- name: Checkout |
||||
uses: actions/checkout@v4 |
||||
with: |
||||
ref: docs-build |
||||
fetch-depth: 1 |
||||
- name: Dispatch (partial build) |
||||
if: github.ref_type == 'branch' |
||||
env: |
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }} |
||||
- name: Dispatch (full build) |
||||
if: github.ref_type == 'tag' |
||||
env: |
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) |
@ -1,2 +1 @@
@@ -1,2 +1 @@
|
||||
-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local |
||||
-P spring |
||||
-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring |
||||
|
Binary file not shown.
@ -1,18 +1 @@
@@ -1,18 +1 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you 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 |
||||
# |
||||
# http://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. |
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip |
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar |
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip |
@ -1,3 +0,0 @@
@@ -1,3 +0,0 @@
|
||||
# Enable auto-env through the sdkman_auto_env config |
||||
# Add key=value pairs of SDKs to use below |
||||
java=17.0.1-tem |
@ -1,39 +0,0 @@
@@ -1,39 +0,0 @@
|
||||
antora: |
||||
extensions: |
||||
- '@springio/antora-extensions/partial-build-extension' |
||||
- require: '@springio/antora-extensions/latest-version-extension' |
||||
- require: '@springio/antora-extensions/inject-collector-cache-config-extension' |
||||
- '@antora/collector-extension' |
||||
- '@antora/atlas-extension' |
||||
- require: '@springio/antora-extensions/root-component-extension' |
||||
root_component_name: 'cloud-openfeign' |
||||
- '@springio/antora-extensions/static-page-extension' |
||||
site: |
||||
title: Spring Cloud Openfeign |
||||
url: https://docs.spring.io/spring-cloud-openfeign/reference/ |
||||
content: |
||||
sources: |
||||
- url: ./.. |
||||
branches: HEAD |
||||
start_path: docs |
||||
worktrees: true |
||||
asciidoc: |
||||
attributes: |
||||
page-stackoverflow-url: https://stackoverflow.com/tags/spring-cloud |
||||
page-pagination: '' |
||||
hide-uri-scheme: '@' |
||||
tabs-sync-option: '@' |
||||
chomp: 'all' |
||||
extensions: |
||||
- '@asciidoctor/tabs' |
||||
- '@springio/asciidoctor-extensions' |
||||
sourcemap: true |
||||
urls: |
||||
latest_version_segment: '' |
||||
runtime: |
||||
log: |
||||
failure_level: warn |
||||
format: pretty |
||||
ui: |
||||
bundle: |
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.2/ui-bundle.zip |
@ -1,12 +0,0 @@
@@ -1,12 +0,0 @@
|
||||
name: cloud-openfeign |
||||
version: true |
||||
title: Spring Cloud OpenFeign |
||||
nav: |
||||
- modules/ROOT/nav.adoc |
||||
ext: |
||||
collector: |
||||
run: |
||||
command: ./mvnw --no-transfer-progress -B process-resources -Pdocs -pl docs -Dantora-maven-plugin.phase=none -Dgenerate-docs.phase=none -Dgenerate-readme.phase=none -Dgenerate-cloud-resources.phase=none -Dmaven-dependency-plugin-for-docs.phase=none -Dmaven-dependency-plugin-for-docs-classes.phase=none -DskipTests |
||||
local: true |
||||
scan: |
||||
dir: ./target/classes/antora-resources/ |
@ -1,4 +0,0 @@
@@ -1,4 +0,0 @@
|
||||
* xref:index.adoc[Introduction] |
||||
* xref:spring-cloud-openfeign.adoc[] |
||||
* xref:appendix.adoc[] |
||||
** xref:configprops.adoc[] |
@ -1,6 +0,0 @@
@@ -1,6 +0,0 @@
|
||||
[[configuration-properties]] |
||||
= Configuration Properties |
||||
|
||||
Below you can find a list of configuration properties. |
||||
|
||||
include::partial$_configprops.adoc[] |
@ -1 +0,0 @@
@@ -1 +0,0 @@
|
||||
include::intro.adoc[] |
@ -1,953 +0,0 @@
@@ -1,953 +0,0 @@
|
||||
[[features]] |
||||
= Spring Cloud OpenFeign Features |
||||
|
||||
[[spring-cloud-feign]] |
||||
== Declarative REST Client: Feign |
||||
|
||||
https://github.com/OpenFeign/feign[Feign] is a declarative web service client. |
||||
It makes writing web service clients easier. |
||||
To use Feign create an interface and annotate it. |
||||
It has pluggable annotation support including Feign annotations and JAX-RS annotations. |
||||
Feign also supports pluggable encoders and decoders. |
||||
Spring Cloud adds support for Spring MVC annotations and for using the same `HttpMessageConverters` used by default in Spring Web. |
||||
Spring Cloud integrates Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign. |
||||
|
||||
[[netflix-feign-starter]] |
||||
=== How to Include Feign |
||||
|
||||
To include Feign in your project use the starter with group `org.springframework.cloud` |
||||
and artifact id `spring-cloud-starter-openfeign`. See the https://projects.spring.io/spring-cloud/[Spring Cloud Project page] |
||||
for details on setting up your build system with the current Spring Cloud Release Train. |
||||
|
||||
Example spring boot app |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@SpringBootApplication |
||||
@EnableFeignClients |
||||
public class Application { |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication.run(Application.class, args); |
||||
} |
||||
|
||||
} |
||||
---- |
||||
|
||||
.StoreClient.java |
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("stores") |
||||
public interface StoreClient { |
||||
@RequestMapping(method = RequestMethod.GET, value = "/stores") |
||||
List<Store> getStores(); |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/stores") |
||||
Page<Store> getStores(Pageable pageable); |
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") |
||||
Store update(@PathVariable("storeId") Long storeId, Store store); |
||||
|
||||
@RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}") |
||||
void delete(@PathVariable Long storeId); |
||||
} |
||||
---- |
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create a https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java[Spring Cloud LoadBalancer client]. |
||||
You can also specify a URL using the `url` attribute |
||||
(absolute value or just a hostname). The name of the bean in the |
||||
application context is the fully qualified name of the interface. |
||||
To specify your own alias value you can use the `qualifiers` value |
||||
of the `@FeignClient` annotation. |
||||
|
||||
The load-balancer client above will want to discover the physical addresses |
||||
for the "stores" service. If your application is a Eureka client then |
||||
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 |
||||
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]. |
||||
|
||||
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)` |
||||
|
||||
[[attribute-resolution-mode]] |
||||
==== Attribute resolution mode |
||||
|
||||
While creating `Feign` client beans, we resolve the values passed via the `@FeignClient` annotation. As of `4.x`, the values are being resolved eagerly. This is a good solution for most use-cases, and it also allows for AOT support. |
||||
|
||||
If you need the attributes to be resolved lazily, set the `spring.cloud.openfeign.lazy-attributes-resolution` property value to `true`. |
||||
|
||||
TIP: For Spring Cloud Contract test integration, lazy attribute resolution should be used. |
||||
|
||||
[[spring-cloud-feign-overriding-defaults]] |
||||
=== Overriding Feign Defaults |
||||
|
||||
A central concept in Spring Cloud's Feign support is that of the named client. Each feign client is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer using the `@FeignClient` annotation. Spring Cloud creates a new ensemble as an |
||||
`ApplicationContext` on demand for each named client using `FeignClientsConfiguration`. This contains (amongst other things) an `feign.Decoder`, a `feign.Encoder`, and a `feign.Contract`. |
||||
It is possible to override the name of that ensemble by using the `contextId` |
||||
attribute of the `@FeignClient` annotation. |
||||
|
||||
Spring Cloud lets you take full control of the feign client by declaring additional configuration (on top of the `FeignClientsConfiguration`) using `@FeignClient`. Example: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "stores", configuration = FooConfiguration.class) |
||||
public interface StoreClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
In this case the client is composed from the components already in `FeignClientsConfiguration` together with any in `FooConfiguration` (where the latter will override the former). |
||||
|
||||
NOTE: `FooConfiguration` does not need to be annotated with `@Configuration`. However, if it is, then take care to exclude it from any `@ComponentScan` that would otherwise include this configuration as it will become the default source for `feign.Decoder`, `feign.Encoder`, `feign.Contract`, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any `@ComponentScan` or `@SpringBootApplication`, or it can be explicitly excluded in `@ComponentScan`. |
||||
|
||||
NOTE: Using `contextId` attribute of the `@FeignClient` annotation in addition to changing the name of |
||||
the `ApplicationContext` ensemble, it will override the alias of the client name |
||||
and it will be used as part of the name of the configuration bean created for that client. |
||||
|
||||
WARNING: Previously, using the `url` attribute, did not require the `name` attribute. Using `name` is now required. |
||||
|
||||
Placeholders are supported in the `name` and `url` attributes. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "${feign.name}", url = "${feign.url}") |
||||
public interface StoreClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
Spring Cloud OpenFeign provides the following beans by default for feign (`BeanType` beanName: `ClassName`): |
||||
|
||||
* `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`) |
||||
* `Encoder` feignEncoder: `SpringEncoder` |
||||
* `Logger` feignLogger: `Slf4jLogger` |
||||
* `MicrometerObservationCapability` micrometerObservationCapability: If `feign-micrometer` is on the classpath and `ObservationRegistry` is available |
||||
* `MicrometerCapability` micrometerCapability: If `feign-micrometer` is on the classpath, `MeterRegistry` is available and `ObservationRegistry` is not available |
||||
* `CachingCapability` cachingCapability: If `@EnableCaching` annotation is used. Can be disabled via `spring.cloud.openfeign.cache.enabled`. |
||||
* `Contract` feignContract: `SpringMvcContract` |
||||
* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder` |
||||
* `Client` feignClient: If Spring Cloud LoadBalancer is on the classpath, `FeignBlockingLoadBalancerClient` is used. |
||||
If none of them is on the classpath, the default feign client is used. |
||||
|
||||
NOTE: `spring-cloud-starter-openfeign` supports `spring-cloud-starter-loadbalancer`. However, as is an optional dependency, you need to make sure it has been added to your project if you want to use it. |
||||
|
||||
The OkHttpClient, Apache HttpClient 5 and Http2Client Feign clients can be used by setting `spring.cloud.openfeign.okhttp.enabled` or `spring.cloud.openfeign.httpclient.hc5.enabled` or `spring.cloud.openfeign.http2client.enabled` to `true`, respectively, and having them on the classpath. |
||||
You can customize the HTTP client used by providing a bean of either `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5. |
||||
|
||||
You can further customise http clients by setting values in the `spring.cloud.openfeign.httpclient.xxx` properties. The ones prefixed just with `httpclient` will work for all the clients, the ones prefixed with `httpclient.hc5` to Apache HttpClient 5, the ones prefixed with `httpclient.okhttp` to OkHttpClient and the ones prefixed with `httpclient.http2` to Http2Client. You can find a full list of properties you can customise in the appendix. |
||||
|
||||
TIP: Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead. |
||||
|
||||
Spring Cloud OpenFeign _does not_ provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client: |
||||
|
||||
* `Logger.Level` |
||||
* `Retryer` |
||||
* `ErrorDecoder` |
||||
* `Request.Options` |
||||
* `Collection<RequestInterceptor>` |
||||
* `SetterFactory` |
||||
* `QueryMapEncoder` |
||||
* `Capability` (`MicrometerObservationCapability` and `CachingCapability` are provided by default) |
||||
|
||||
A bean of `Retryer.NEVER_RETRY` with the type `Retryer` is created by default, which will disable retrying. |
||||
Notice this retrying behavior is different from the Feign default one, where it will automatically retry IOExceptions, |
||||
treating them as transient network related exceptions, and any RetryableException thrown from an ErrorDecoder. |
||||
|
||||
Creating a bean of one of those type and placing it in a `@FeignClient` configuration (such as `FooConfiguration` above) allows you to override each one of the beans described. Example: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
public Contract feignContract() { |
||||
return new feign.Contract.Default(); |
||||
} |
||||
|
||||
@Bean |
||||
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { |
||||
return new BasicAuthRequestInterceptor("user", "password"); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
This replaces the `SpringMvcContract` with `feign.Contract.Default` and adds a `RequestInterceptor` to the collection of `RequestInterceptor`. |
||||
|
||||
`@FeignClient` also can be configured using configuration properties. |
||||
|
||||
application.yml |
||||
[source,yaml] |
||||
---- |
||||
spring: |
||||
cloud: |
||||
openfeign: |
||||
client: |
||||
config: |
||||
feignName: |
||||
url: http://remote-service.com |
||||
connectTimeout: 5000 |
||||
readTimeout: 5000 |
||||
loggerLevel: full |
||||
errorDecoder: com.example.SimpleErrorDecoder |
||||
retryer: com.example.SimpleRetryer |
||||
defaultQueryParameters: |
||||
query: queryValue |
||||
defaultRequestHeaders: |
||||
header: headerValue |
||||
requestInterceptors: |
||||
- com.example.FooRequestInterceptor |
||||
- com.example.BarRequestInterceptor |
||||
responseInterceptor: com.example.BazResponseInterceptor |
||||
dismiss404: false |
||||
encoder: com.example.SimpleEncoder |
||||
decoder: com.example.SimpleDecoder |
||||
contract: com.example.SimpleContract |
||||
capabilities: |
||||
- com.example.FooCapability |
||||
- com.example.BarCapability |
||||
queryMapEncoder: com.example.SimpleQueryMapEncoder |
||||
micrometer.enabled: false |
||||
---- |
||||
`feignName` in this example refers to `@FeignClient` `value`, that is also aliased with `@FeignClient` `name` and `@FeignClient` `contextId`. In a load-balanced scenario, it also corresponds to the `serviceId` of the server app that will be used to retrieve the instances. The specified classes for decoders, retryer and other ones must have a bean in the Spring context or have a default constructor. |
||||
|
||||
|
||||
Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients. |
||||
|
||||
If you prefer using configuration properties to configure all `@FeignClient`, you can create configuration properties with `default` feign name. |
||||
|
||||
You can use `spring.cloud.openfeign.client.config.feignName.defaultQueryParameters` and `spring.cloud.openfeign.client.config.feignName.defaultRequestHeaders` to specify query parameters and headers that will be sent with every request of the client named `feignName`. |
||||
|
||||
application.yml |
||||
[source,yaml] |
||||
---- |
||||
spring: |
||||
cloud: |
||||
openfeign: |
||||
client: |
||||
config: |
||||
default: |
||||
connectTimeout: 5000 |
||||
readTimeout: 5000 |
||||
loggerLevel: basic |
||||
---- |
||||
|
||||
If we create both `@Configuration` bean and configuration properties, configuration properties will win. |
||||
It will override `@Configuration` values. But if you want to change the priority to `@Configuration`, |
||||
you can change `spring.cloud.openfeign.client.default-to-properties` to `false`. |
||||
|
||||
If we want to create multiple feign clients with the same name or url |
||||
so that they would point to the same server but each with a different custom configuration then |
||||
we have to use `contextId` attribute of the `@FeignClient` in order to avoid name |
||||
collision of these configuration beans. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class) |
||||
public interface FooClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class) |
||||
public interface BarClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
It is also possible to configure FeignClient not to inherit beans from the parent context. |
||||
You can do this by overriding the `inheritParentConfiguration()` in a `FeignClientConfigurer` |
||||
bean to return `false`: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class CustomConfiguration{ |
||||
|
||||
@Bean |
||||
public FeignClientConfigurer feignClientConfigurer() { |
||||
return new FeignClientConfigurer() { |
||||
|
||||
@Override |
||||
public boolean inheritParentConfiguration() { |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
} |
||||
} |
||||
---- |
||||
|
||||
TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `spring.cloud.openfeign.client.decodeSlash` to `false`. |
||||
|
||||
[[springencoder-configuration]] |
||||
==== `SpringEncoder` configuration |
||||
|
||||
In the `SpringEncoder` that we provide, we set `null` charset for binary content types and `UTF-8` for all the other ones. |
||||
|
||||
You can modify this behaviour to derive the charset from the `Content-Type` header charset instead by setting the value of `spring.cloud.openfeign.encoder.charset-from-content-type` to `true`. |
||||
|
||||
[[timeout-handling]] |
||||
=== Timeout Handling |
||||
|
||||
We can configure timeouts on both the default and the named client. OpenFeign works with two timeout parameters: |
||||
|
||||
- `connectTimeout` prevents blocking the caller due to the long server processing time. |
||||
- `readTimeout` is applied from the time of connection establishment and is triggered when returning the response takes too long. |
||||
|
||||
NOTE: In case the server is not running or available a packet results in _connection refused_. The communication ends either with an error message or in a fallback. This can happen _before_ the `connectTimeout` if it is set very low. The time taken to perform a lookup and to receive such a packet causes a significant part of this delay. It is subject to change based on the remote host that involves a DNS lookup. |
||||
|
||||
[[creating-feign-clients-manually]] |
||||
=== Creating Feign Clients Manually |
||||
|
||||
In some cases it might be necessary to customize your Feign Clients in a way that is not |
||||
possible using the methods above. In this case you can create Clients using the |
||||
https://github.com/OpenFeign/feign/#basics[Feign Builder API]. Below is an example |
||||
which creates two Feign Clients with the same interface but configures each one with |
||||
a separate request interceptor. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Import(FeignClientsConfiguration.class) |
||||
class FooController { |
||||
|
||||
private FooClient fooClient; |
||||
|
||||
private FooClient adminClient; |
||||
|
||||
@Autowired |
||||
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) { |
||||
this.fooClient = Feign.builder().client(client) |
||||
.encoder(encoder) |
||||
.decoder(decoder) |
||||
.contract(contract) |
||||
.addCapability(micrometerObservationCapability) |
||||
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) |
||||
.target(FooClient.class, "https://PROD-SVC"); |
||||
|
||||
this.adminClient = Feign.builder().client(client) |
||||
.encoder(encoder) |
||||
.decoder(decoder) |
||||
.contract(contract) |
||||
.addCapability(micrometerObservationCapability) |
||||
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) |
||||
.target(FooClient.class, "https://PROD-SVC"); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
NOTE: In the above example `FeignClientsConfiguration.class` is the default configuration |
||||
provided by Spring Cloud OpenFeign. |
||||
|
||||
NOTE: `PROD-SVC` is the name of the service the Clients will be making requests to. |
||||
|
||||
NOTE: The Feign `Contract` object defines what annotations and values are valid on interfaces. The |
||||
autowired `Contract` bean provides supports for SpringMVC annotations, instead of |
||||
the default Feign native annotations. |
||||
|
||||
You can also use the `Builder`to configure FeignClient not to inherit beans from the parent context. |
||||
You can do this by overriding calling `inheritParentContext(false)` on the `Builder`. |
||||
|
||||
[[spring-cloud-feign-circuitbreaker]] |
||||
=== Feign Spring Cloud CircuitBreaker Support |
||||
|
||||
If Spring Cloud CircuitBreaker is on the classpath and `spring.cloud.openfeign.circuitbreaker.enabled=true`, Feign will wrap all methods with a circuit breaker. |
||||
|
||||
To disable Spring Cloud CircuitBreaker support on a per-client basis create a vanilla `Feign.Builder` with the "prototype" scope, e.g.: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
@Scope("prototype") |
||||
public Feign.Builder feignBuilder() { |
||||
return Feign.builder(); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
The circuit breaker name follows this pattern `<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 `spring.cloud.openfeign.circuitbreaker.group.enabled` property to `true` (by default `false`). |
||||
|
||||
[[spring-clou-feign-circuitbreaker-configurationproperties]] |
||||
=== Configuring CircuitBreakers With Configuration Properties |
||||
|
||||
You can configure CircuitBreakers via configuration properties. |
||||
|
||||
For example, if you had this Feign client |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(url = "http://localhost:8080") |
||||
public interface DemoClient { |
||||
|
||||
@GetMapping("demo") |
||||
String getDemo(); |
||||
} |
||||
---- |
||||
|
||||
You could configure it using configuration properties by doing the following |
||||
|
||||
[source,yaml,indent=0] |
||||
---- |
||||
spring: |
||||
cloud: |
||||
openfeign: |
||||
circuitbreaker: |
||||
enabled: true |
||||
alphanumeric-ids: |
||||
enabled: true |
||||
resilience4j: |
||||
circuitbreaker: |
||||
instances: |
||||
DemoClientgetDemo: |
||||
minimumNumberOfCalls: 69 |
||||
timelimiter: |
||||
instances: |
||||
DemoClientgetDemo: |
||||
timeoutDuration: 10s |
||||
---- |
||||
|
||||
NOTE: If you want to switch back to the circuit breaker names used prior to Spring Cloud |
||||
2022.0.0 you can set `spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled` to `false`. |
||||
|
||||
[[spring-cloud-feign-circuitbreaker-fallback]] |
||||
=== Feign Spring Cloud CircuitBreaker Fallbacks |
||||
|
||||
Spring Cloud CircuitBreaker supports the notion of a fallback: a default code path that is executed when the circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class) |
||||
protected interface TestClient { |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello") |
||||
Hello getHello(); |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound") |
||||
String getException(); |
||||
|
||||
} |
||||
|
||||
@Component |
||||
static class Fallback implements TestClient { |
||||
|
||||
@Override |
||||
public Hello getHello() { |
||||
throw new NoFallbackAvailableException("Boom!", new RuntimeException()); |
||||
} |
||||
|
||||
@Override |
||||
public String getException() { |
||||
return "Fixed response"; |
||||
} |
||||
|
||||
} |
||||
---- |
||||
|
||||
If one needs access to the cause that made the fallback trigger, one can use the `fallbackFactory` attribute inside `@FeignClient`. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/", |
||||
fallbackFactory = TestFallbackFactory.class) |
||||
protected interface TestClientWithFactory { |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello") |
||||
Hello getHello(); |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound") |
||||
String getException(); |
||||
|
||||
} |
||||
|
||||
@Component |
||||
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> { |
||||
|
||||
@Override |
||||
public FallbackWithFactory create(Throwable cause) { |
||||
return new FallbackWithFactory(); |
||||
} |
||||
|
||||
} |
||||
|
||||
static class FallbackWithFactory implements TestClientWithFactory { |
||||
|
||||
@Override |
||||
public Hello getHello() { |
||||
throw new NoFallbackAvailableException("Boom!", new RuntimeException()); |
||||
} |
||||
|
||||
@Override |
||||
public String getException() { |
||||
return "Fixed response"; |
||||
} |
||||
|
||||
} |
||||
---- |
||||
|
||||
[[feign-and-primary]] |
||||
=== Feign and `@Primary` |
||||
|
||||
When using Feign with Spring Cloud CircuitBreaker fallbacks, there are multiple beans in the `ApplicationContext` of the same type. This will cause `@Autowired` to not work because there isn't exactly one bean, or one marked as primary. To work around this, Spring Cloud OpenFeign marks all Feign instances as `@Primary`, so Spring Framework will know which bean to inject. In some cases, this may not be desirable. To turn off this behavior set the `primary` attribute of `@FeignClient` to false. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "hello", primary = false) |
||||
public interface HelloClient { |
||||
// methods here |
||||
} |
||||
---- |
||||
|
||||
[[spring-cloud-feign-inheritance]] |
||||
=== Feign Inheritance Support |
||||
|
||||
Feign supports boilerplate apis via single-inheritance interfaces. |
||||
This allows grouping common operations into convenient base interfaces. |
||||
|
||||
.UserService.java |
||||
[source,java,indent=0] |
||||
---- |
||||
public interface UserService { |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}") |
||||
User getUser(@PathVariable("id") long id); |
||||
} |
||||
---- |
||||
|
||||
.UserResource.java |
||||
[source,java,indent=0] |
||||
---- |
||||
@RestController |
||||
public class UserResource implements UserService { |
||||
|
||||
} |
||||
---- |
||||
|
||||
.UserClient.java |
||||
[source,java,indent=0] |
||||
---- |
||||
package project.user; |
||||
|
||||
@FeignClient("users") |
||||
public interface UserClient extends UserService { |
||||
|
||||
} |
||||
---- |
||||
|
||||
WARNING: `@FeignClient` interfaces should not be shared between server and client and annotating `@FeignClient` interfaces with `@RequestMapping` on class level is no longer supported. |
||||
|
||||
[[feign-request/response-compression]] |
||||
=== Feign request/response compression |
||||
|
||||
You may consider enabling the request or response GZIP compression for your |
||||
Feign requests. You can do this by enabling one of the properties: |
||||
|
||||
[source,java] |
||||
---- |
||||
spring.cloud.openfeign.compression.request.enabled=true |
||||
spring.cloud.openfeign.compression.response.enabled=true |
||||
---- |
||||
|
||||
Feign request compression gives you settings similar to what you may set for your web server: |
||||
|
||||
[source,java] |
||||
---- |
||||
spring.cloud.openfeign.compression.request.enabled=true |
||||
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json |
||||
spring.cloud.openfeign.compression.request.min-request-size=2048 |
||||
---- |
||||
|
||||
These properties allow you to be selective about the compressed media types and minimum request threshold length. |
||||
|
||||
TIP: Since the OkHttpClient uses "transparent" compression, that is disabled if the `content-encoding` or `accept-encoding` header is present, we do not enable compression when `feign.okhttp.OkHttpClient` is present on the classpath and `spring.cloud.openfeign.okhttp.enabled` is set to `true`. |
||||
|
||||
[[feign-logging]] |
||||
=== Feign logging |
||||
|
||||
A logger is created for each Feign client created. By default, the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the `DEBUG` level. |
||||
|
||||
.application.yml |
||||
|
||||
[source,yaml] |
||||
---- |
||||
logging.level.project.user.UserClient: DEBUG |
||||
---- |
||||
|
||||
The `Logger.Level` object that you may configure per client, tells Feign how much to log. Choices are: |
||||
|
||||
* `NONE`, No logging (*DEFAULT*). |
||||
* `BASIC`, Log only the request method and URL and the response status code and execution time. |
||||
* `HEADERS`, Log the basic information along with request and response headers. |
||||
* `FULL`, Log the headers, body, and metadata for both requests and responses. |
||||
|
||||
For example, the following would set the `Logger.Level` to `FULL`: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
Logger.Level feignLoggerLevel() { |
||||
return Logger.Level.FULL; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
[[feign-capability-support]] |
||||
=== Feign Capability support |
||||
|
||||
The Feign capabilities expose core Feign components so that these components can be modified. For example, the capabilities can take the `Client`, _decorate_ it, and give the decorated instance back to Feign. |
||||
The support for Micrometer is a good real-life example for this. See xref:spring-cloud-openfeign.adoc#micrometer-support[Micrometer Support]. |
||||
|
||||
Creating one or more `Capability` beans and placing them in a `@FeignClient` configuration lets you register them and modify the behavior of the involved client. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
Capability customCapability() { |
||||
return new CustomCapability(); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
[[micrometer-support]] |
||||
=== Micrometer Support |
||||
|
||||
If all of the following conditions are true, a `MicrometerObservationCapability` bean is created and registered so that your Feign client is observable by Micrometer: |
||||
|
||||
* `feign-micrometer` is on the classpath |
||||
* A `ObservationRegistry` bean is available |
||||
* feign micrometer properties are set to `true` (by default) |
||||
- `spring.cloud.openfeign.micrometer.enabled=true` (for all clients) |
||||
- `spring.cloud.openfeign.client.config.feignName.micrometer.enabled=true` (for a single client) |
||||
|
||||
NOTE: If your application already uses Micrometer, enabling this feature is as simple as putting `feign-micrometer` onto your classpath. |
||||
|
||||
You can also disable the feature by either: |
||||
|
||||
* excluding `feign-micrometer` from your classpath |
||||
* setting one of the feign micrometer properties to `false` |
||||
- `spring.cloud.openfeign.micrometer.enabled=false` |
||||
- `spring.cloud.openfeign.client.config.feignName.micrometer.enabled=false` |
||||
|
||||
NOTE: `spring.cloud.openfeign.micrometer.enabled=false` disables Micrometer support for *all* Feign clients regardless of the value of the client-level flags: `spring.cloud.openfeign.client.config.feignName.micrometer.enabled`. |
||||
If you want to enable or disable Micrometer support per client, don't set `spring.cloud.openfeign.micrometer.enabled` and use `spring.cloud.openfeign.client.config.feignName.micrometer.enabled`. |
||||
|
||||
You can also customize the `MicrometerObservationCapability` by registering your own bean: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
public MicrometerObservationCapability micrometerObservationCapability(ObservationRegistry registry) { |
||||
return new MicrometerObservationCapability(registry); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
It is still possible to use `MicrometerCapability` with Feign (metrics-only support), you need to disable Micrometer support (`spring.cloud.openfeign.micrometer.enabled=false`) and create a `MicrometerCapability` bean: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
public MicrometerCapability micrometerCapability(MeterRegistry meterRegistry) { |
||||
return new MicrometerCapability(meterRegistry); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
[[feign-caching]] |
||||
=== Feign Caching |
||||
|
||||
If `@EnableCaching` annotation is used, a `CachingCapability` bean is created and registered so that your Feign client recognizes `@Cache*` annotations on its interface: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
public interface DemoClient { |
||||
|
||||
@GetMapping("/demo/{filterParam}") |
||||
@Cacheable(cacheNames = "demo-cache", key = "#keyParam") |
||||
String demoEndpoint(String keyParam, @PathVariable String filterParam); |
||||
} |
||||
---- |
||||
|
||||
You can also disable the feature via property `spring.cloud.openfeign.cache.enabled=false`. |
||||
|
||||
[[feign-querymap-support]] |
||||
=== Feign @QueryMap support |
||||
|
||||
Spring Cloud OpenFeign provides an equivalent `@SpringQueryMap` annotation, which |
||||
is used to annotate a POJO or Map parameter as a query parameter map. |
||||
|
||||
For example, the `Params` class defines parameters `param1` and `param2`: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
// Params.java |
||||
public class Params { |
||||
private String param1; |
||||
private String param2; |
||||
|
||||
// [Getters and setters omitted for brevity] |
||||
} |
||||
---- |
||||
|
||||
The following feign client uses the `Params` class by using the `@SpringQueryMap` annotation: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("demo") |
||||
public interface DemoTemplate { |
||||
|
||||
@GetMapping(path = "/demo") |
||||
String demoEndpoint(@SpringQueryMap Params params); |
||||
} |
||||
---- |
||||
|
||||
If you need more control over the generated query parameter map, you can implement a custom `QueryMapEncoder` bean. |
||||
|
||||
[[hateoas-support]] |
||||
=== HATEOAS support |
||||
|
||||
Spring provides some APIs to create REST representations that follow the https://en.wikipedia.org/wiki/HATEOAS[HATEOAS] principle, https://spring.io/projects/spring-hateoas[Spring Hateoas] and https://spring.io/projects/spring-data-rest[Spring Data REST]. |
||||
|
||||
If your project use the `org.springframework.boot:spring-boot-starter-hateoas` starter |
||||
or the `org.springframework.boot:spring-boot-starter-data-rest` starter, Feign HATEOAS support is enabled by default. |
||||
|
||||
When HATEOAS support is enabled, Feign clients are allowed to serialize |
||||
and deserialize HATEOAS representation models: https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/EntityModel.html[EntityModel], https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/CollectionModel.html[CollectionModel] and https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/PagedModel.html[PagedModel]. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("demo") |
||||
public interface DemoTemplate { |
||||
|
||||
@GetMapping(path = "/stores") |
||||
CollectionModel<Store> getStores(); |
||||
} |
||||
---- |
||||
|
||||
[[spring-matrixvariable-support]] |
||||
=== Spring @MatrixVariable Support |
||||
|
||||
Spring Cloud OpenFeign provides support for the Spring `@MatrixVariable` annotation. |
||||
|
||||
If a map is passed as the method argument, the `@MatrixVariable` path segment is created by joining key-value pairs from the map with a `=`. |
||||
|
||||
If a different object is passed, either the `name` provided in the `@MatrixVariable` annotation (if defined) or the annotated variable name is |
||||
joined with the provided method argument using `=`. |
||||
|
||||
IMPORTANT:: Even though, on the server side, Spring does not require the users to name the path segment placeholder same as the matrix variable name, since it would be too ambiguous on the client side, Spring Cloud OpenFeign requires that you add a path segment placeholder with a name matching either the `name` provided in the `@MatrixVariable` annotation (if defined) or the annotated variable name. |
||||
|
||||
For example: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@GetMapping("/objects/links/{matrixVars}") |
||||
Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> matrixVars); |
||||
---- |
||||
Note that both variable name and the path segment placeholder are called `matrixVars`. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("demo") |
||||
public interface DemoTemplate { |
||||
|
||||
@GetMapping(path = "/stores") |
||||
CollectionModel<Store> getStores(); |
||||
} |
||||
---- |
||||
|
||||
[[feign-collectionformat-support]] |
||||
=== Feign `CollectionFormat` support |
||||
We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation. |
||||
You can annotate a Feign client method (or the whole class to affect all methods) with it by passing the desired `feign.CollectionFormat` as annotation value. |
||||
|
||||
In the following example, the `CSV` format is used instead of the default `EXPLODED` to process the method. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "demo") |
||||
protected interface DemoFeignClient { |
||||
|
||||
@CollectionFormat(feign.CollectionFormat.CSV) |
||||
@GetMapping(path = "/test") |
||||
ResponseEntity performRequest(String test); |
||||
|
||||
} |
||||
---- |
||||
|
||||
[[reactive-support]] |
||||
=== Reactive Support |
||||
As the https://github.com/OpenFeign/feign[OpenFeign project] does not currently support reactive clients, such as https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html[Spring WebClient], neither does Spring Cloud OpenFeign.We will add support for it here as soon as it becomes available in the core project. |
||||
|
||||
Until that is done, we recommend using https://github.com/Playtika/feign-reactive[feign-reactive] for Spring WebClient support. |
||||
|
||||
[[early-initialization-errors]] |
||||
==== Early Initialization Errors |
||||
|
||||
Depending on how you are using your Feign clients you may see initialization errors when starting your application. |
||||
To work around this problem you can use an `ObjectProvider` when autowiring your client. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Autowired |
||||
ObjectProvider<TestFeignClient> testFeignClient; |
||||
---- |
||||
|
||||
[[spring-data-support]] |
||||
=== Spring Data Support |
||||
|
||||
If Jackson Databind and Spring Data Commons are on the classpath, converters for `org.springframework.data.domain.Page` and `org.springframework.data.domain.Sort` will be added automatically. |
||||
|
||||
To disable this behaviour set |
||||
[source,java] |
||||
---- |
||||
spring.cloud.openfeign.autoconfiguration.jackson.enabled=false |
||||
---- |
||||
|
||||
See `org.springframework.cloud.openfeign.FeignAutoConfiguration.FeignJacksonConfiguration` for details. |
||||
|
||||
[[spring-refreshscope-support]] |
||||
=== Spring `@RefreshScope` Support |
||||
If Feign client refresh is enabled, each Feign client is created with: |
||||
|
||||
* `feign.Request.Options` as a refresh-scoped bean. This means properties such as `connectTimeout` and `readTimeout` can be refreshed against any Feign client instance. |
||||
* A url wrapped under `org.springframework.cloud.openfeign.RefreshableUrl`. This means the URL of Feign client, if defined |
||||
with `spring.cloud.openfeign.client.config.\{feignName}.url` property, can be refreshed against any Feign client instance. |
||||
|
||||
You can refresh these properties through `POST /actuator/refresh`. |
||||
|
||||
By default, refresh behavior in Feign clients is disabled. Use the following property to enable refresh behavior: |
||||
[source,java] |
||||
---- |
||||
spring.cloud.openfeign.client.refresh-enabled=true |
||||
---- |
||||
TIP: DO NOT annotate the `@FeignClient` interface with the `@RefreshScope` annotation. |
||||
|
||||
[[oauth2-support]] |
||||
=== OAuth2 Support |
||||
OAuth2 support can be enabled by setting following flag: |
||||
---- |
||||
spring.cloud.openfeign.oauth2.enabled=true |
||||
---- |
||||
When the flag is set to true, and the oauth2 client context resource details are present, a bean of class `OAuth2AccessTokenInterceptor` is created. Before each request, the interceptor resolves the required access token and includes it as a header. |
||||
`OAuth2AccessTokenInterceptor` uses the `OAuth2AuthorizedClientManager` to get `OAuth2AuthorizedClient` that holds an `OAuth2AccessToken`. If the user has specified an OAuth2 `clientRegistrationId` using the `spring.cloud.openfeign.oauth2.clientRegistrationId` property, it will be used to retrieve the token. If the token is not retrieved or the `clientRegistrationId` has not been specified, the `serviceId` retrieved from the `url` host segment will be used. |
||||
|
||||
TIP:: Using the `serviceId` as OAuth2 client registrationId is convenient for load-balanced Feign clients. For non-load-balanced ones, the property-based `clientRegistrationId` is a suitable approach. |
||||
|
||||
TIP:: If you do not want to use the default setup for the `OAuth2AuthorizedClientManager`, you can just instantiate a bean of this type in your configuration. |
||||
|
||||
[[transform-the-load-balanced-http-request]] |
||||
=== Transform the load-balanced HTTP request |
||||
|
||||
You can use the selected `ServiceInstance` to transform the load-balanced HTTP Request. |
||||
|
||||
For `Request`, you need to implement and define `LoadBalancerFeignRequestTransformer`, as follows: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Bean |
||||
public LoadBalancerFeignRequestTransformer transformer() { |
||||
return new LoadBalancerFeignRequestTransformer() { |
||||
|
||||
@Override |
||||
public Request transformRequest(Request request, ServiceInstance instance) { |
||||
Map<String, Collection<String>> headers = new HashMap<>(request.headers()); |
||||
headers.put("X-ServiceId", Collections.singletonList(instance.getServiceId())); |
||||
headers.put("X-InstanceId", Collections.singletonList(instance.getInstanceId())); |
||||
return Request.create(request.httpMethod(), request.url(), headers, request.body(), request.charset(), |
||||
request.requestTemplate()); |
||||
} |
||||
}; |
||||
} |
||||
---- |
||||
|
||||
If multiple transformers are defined, they are applied in the order in which beans are defined. |
||||
Alternatively, you can use `LoadBalancerFeignRequestTransformer.DEFAULT_ORDER` to specify the order. |
||||
|
||||
[[x-forwarded-headers-support]] |
||||
=== X-Forwarded Headers Support |
||||
|
||||
`X-Forwarded-Host` and `X-Forwarded-Proto` support can be enabled by setting following flag: |
||||
|
||||
[source,properties] |
||||
---- |
||||
spring.cloud.loadbalancer.x-forwarded.enabled=true |
||||
---- |
||||
|
||||
[[supported-ways-to-provide-url-to-a-feign-client]] |
||||
=== Supported Ways To Provide URL To A Feign Client |
||||
You can provide a URL to a Feign client in any of the following ways: |
||||
|
||||
|=== |
||||
|Case |Example |Details |
||||
|
||||
|The URL is provided in the `@FeignClient` annotation. |
||||
|`@FeignClient(name="testClient", url="http://localhost:8081")` |
||||
|The URL is resolved from the `url` attribute of the annotation, without load-balancing. |
||||
|
||||
|The URL is provided in the `@FeignClient` annotation and in the |
||||
configuration properties. |
||||
|`@FeignClient(name="testClient", url="http://localhost:8081")` and the property defined in `application.yml` as |
||||
`spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081` |
||||
|The URL is resolved from the `url` attribute of the annotation, without load-balancing. |
||||
The URL provided in the configuration properties remains unused. |
||||
|
||||
|The URL is not provided in the `@FeignClient` annotation but is provided in configuration properties. |
||||
| `@FeignClient(name="testClient")` and the property defined in `application.yml` as |
||||
`spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081` |
||||
|The URL is resolved from configuration properties, without load-balancing. If |
||||
`spring.cloud.openfeign.client.refresh-enabled=true`, then the URL defined in configuration properties can be refreshed as described in <<Spring `@RefreshScope` Support,Spring RefreshScope Support>>. |
||||
|
||||
|The URL is neither provided in the `@FeignClient` annotation nor in configuration properties. |
||||
|`@FeignClient(name="testClient")` |
||||
|The URL is resolved from `name` attribute of annotation, with load balancing. |
||||
|
||||
|=== |
||||
|
||||
[[aot-and-native-image-support]] |
||||
=== AOT and Native Image Support |
||||
|
||||
Spring Cloud OpenFeign supports Spring AOT transformations and native images, however, only with refresh mode disabled, Feign clients refresh disabled (default setting) and xref:spring-cloud-openfeign.adoc#attribute-resolution-mode[lazy `@FeignClient` attribute resolution] disabled (default setting). |
||||
|
||||
WARNING: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, make sure to set `spring.cloud.refresh.enabled` to `false`. |
||||
|
||||
TIP: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, ensure `spring.cloud.openfeign.client.refresh-enabled` has not been set to `true`. |
||||
|
||||
TIP: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, ensure `spring.cloud.openfeign.lazy-attributes-resolution` has not been set to `true`. |
||||
|
||||
TIP: However, if you set the `url` value via properties, it is possible to override the `@FeignClient` `url` value by running the image with `-Dspring.cloud.openfeign.client.config.[clientId].url=[url]` flag. In order to enable overriding, a `url` value also has to be set via properties and not `@FeignClient` attribute during buildtime. |
||||
|
||||
[[configuration-properties]] |
||||
== Configuration properties |
||||
|
||||
To see the list of all Spring Cloud OpenFeign related configuration properties please check link:appendix.html[the Appendix page]. |
@ -1,3 +0,0 @@
@@ -1,3 +0,0 @@
|
||||
:sc-ext: java |
||||
:project-full-name: Spring Cloud OpenFeign |
||||
:all: {asterisk}{asterisk} |
@ -1,113 +0,0 @@
@@ -1,113 +0,0 @@
|
||||
|=== |
||||
|Name | Default | Description |
||||
|
||||
|spring.cloud.compatibility-verifier.compatible-boot-versions | `+++3.2.x+++` | Default accepted versions for the Spring Boot dependency. You can set {@code x} for the patch version if you don't want to specify a concrete value. Example: {@code 3.4.x} |
||||
|spring.cloud.compatibility-verifier.enabled | `+++false+++` | Enables creation of Spring Cloud compatibility verification. |
||||
|spring.cloud.config.allow-override | `+++true+++` | Flag to indicate that {@link #isOverrideSystemProperties() systemPropertiesOverride} can be used. Set to false to prevent users from changing the default accidentally. Default true. |
||||
|spring.cloud.config.initialize-on-context-refresh | `+++false+++` | Flag to initialize bootstrap configuration on context refresh event. Default false. |
||||
|spring.cloud.config.override-none | `+++false+++` | Flag to indicate that when {@link #setAllowOverride(boolean) allowOverride} is true, external properties should take lowest priority and should not override any existing property sources (including local config files). Default false. This will only have an effect when using config first bootstrap. |
||||
|spring.cloud.config.override-system-properties | `+++true+++` | Flag to indicate that the external properties should override system properties. Default true. |
||||
|spring.cloud.decrypt-environment-post-processor.enabled | `+++true+++` | Enable the DecryptEnvironmentPostProcessor. |
||||
|spring.cloud.discovery.client.composite-indicator.enabled | `+++true+++` | Enables discovery client composite health indicator. |
||||
|spring.cloud.discovery.client.health-indicator.enabled | `+++true+++` | |
||||
|spring.cloud.discovery.client.health-indicator.include-description | `+++false+++` | |
||||
|spring.cloud.discovery.client.health-indicator.use-services-query | `+++true+++` | Whether or not the indicator should use {@link DiscoveryClient#getServices} to check its health. When set to {@code false} the indicator instead uses the lighter {@link DiscoveryClient#probe()}. This can be helpful in large deployments where the number of services returned makes the operation unnecessarily heavy. |
||||
|spring.cloud.discovery.client.simple.instances | | |
||||
|spring.cloud.discovery.client.simple.local.host | | |
||||
|spring.cloud.discovery.client.simple.local.instance-id | | |
||||
|spring.cloud.discovery.client.simple.local.metadata | | |
||||
|spring.cloud.discovery.client.simple.local.port | `+++0+++` | |
||||
|spring.cloud.discovery.client.simple.local.secure | `+++false+++` | |
||||
|spring.cloud.discovery.client.simple.local.service-id | | |
||||
|spring.cloud.discovery.client.simple.local.uri | | |
||||
|spring.cloud.discovery.client.simple.order | | |
||||
|spring.cloud.discovery.enabled | `+++true+++` | Enables discovery client health indicators. |
||||
|spring.cloud.features.enabled | `+++true+++` | Enables the features endpoint. |
||||
|spring.cloud.httpclientfactories.apache.enabled | `+++true+++` | Enables creation of Apache Http Client factory beans. |
||||
|spring.cloud.httpclientfactories.ok.enabled | `+++true+++` | Enables creation of OK Http Client factory beans. |
||||
|spring.cloud.hypermedia.refresh.fixed-delay | `+++5000+++` | |
||||
|spring.cloud.hypermedia.refresh.initial-delay | `+++10000+++` | |
||||
|spring.cloud.inetutils.default-hostname | `+++localhost+++` | The default hostname. Used in case of errors. |
||||
|spring.cloud.inetutils.default-ip-address | `+++127.0.0.1+++` | The default IP address. Used in case of errors. |
||||
|spring.cloud.inetutils.ignored-interfaces | | List of Java regular expressions for network interfaces that will be ignored. |
||||
|spring.cloud.inetutils.preferred-networks | | List of Java regular expressions for network addresses that will be preferred. |
||||
|spring.cloud.inetutils.timeout-seconds | `+++1+++` | Timeout, in seconds, for calculating hostname. |
||||
|spring.cloud.inetutils.use-only-site-local-interfaces | `+++false+++` | Whether to use only interfaces with site local addresses. See {@link InetAddress#isSiteLocalAddress()} for more details. |
||||
|spring.cloud.loadbalancer.call-get-with-request-on-delegates | `+++true+++` | If this flag is set to {@code true}, {@code ServiceInstanceListSupplier#get(Request request)} method will be implemented to call {@code delegate.get(request)} in classes assignable from {@code DelegatingServiceInstanceListSupplier} that don't already implement that method, with the exclusion of {@code CachingServiceInstanceListSupplier} and {@code HealthCheckServiceInstanceListSupplier}, which should be placed in the instance supplier hierarchy directly after the supplier performing instance retrieval over the network, before any request-based filtering is done, {@code true} by default. |
||||
|spring.cloud.loadbalancer.clients | | |
||||
|spring.cloud.loadbalancer.eager-load.clients | | |
||||
|spring.cloud.loadbalancer.health-check.initial-delay | `+++0+++` | Initial delay value for the HealthCheck scheduler. |
||||
|spring.cloud.loadbalancer.health-check.interval | `+++25s+++` | Interval for rerunning the HealthCheck scheduler. |
||||
|spring.cloud.loadbalancer.health-check.interval | `+++25s+++` | Interval for rerunning the HealthCheck scheduler. |
||||
|spring.cloud.loadbalancer.health-check.path | | Path at which the health-check request should be made. Can be set up per `serviceId`. A `default` value can be set up as well. If none is set up, `/actuator/health` will be used. |
||||
|spring.cloud.loadbalancer.health-check.port | | Path at which the health-check request should be made. If none is set, the port under which the requested service is available at the service instance. |
||||
|spring.cloud.loadbalancer.health-check.refetch-instances | `+++false+++` | Indicates whether the instances should be refetched by the `HealthCheckServiceInstanceListSupplier`. This can be used if the instances can be updated and the underlying delegate does not provide an ongoing flux. |
||||
|spring.cloud.loadbalancer.health-check.refetch-instances-interval | `+++25s+++` | Interval for refetching available service instances. |
||||
|spring.cloud.loadbalancer.health-check.repeat-health-check | `+++true+++` | Indicates whether health checks should keep repeating. It might be useful to set it to `false` if periodically refetching the instances, as every refetch will also trigger a healthcheck. |
||||
|spring.cloud.loadbalancer.health-check.update-results-list | `+++true+++` | Indicates whether the {@code healthCheckFlux} should emit on each alive {@link ServiceInstance} that has been retrieved. If set to {@code false}, the entire alive instances sequence is first collected into a list and only then emitted. |
||||
|spring.cloud.loadbalancer.hint | | Allows setting the value of <code>hint</code> that is passed on to the LoadBalancer request and can subsequently be used in {@link ReactiveLoadBalancer} implementations. |
||||
|spring.cloud.loadbalancer.hint-header-name | `+++X-SC-LB-Hint+++` | Allows setting the name of the header used for passing the hint for hint-based service instance filtering. |
||||
|spring.cloud.loadbalancer.retry.backoff.enabled | `+++false+++` | Indicates whether Reactor Retry backoffs should be applied. |
||||
|spring.cloud.loadbalancer.retry.backoff.jitter | `+++0.5+++` | Used to set `RetryBackoffSpec.jitter`. |
||||
|spring.cloud.loadbalancer.retry.backoff.max-backoff | `+++Long.MAX ms+++` | Used to set `RetryBackoffSpec.maxBackoff`. |
||||
|spring.cloud.loadbalancer.retry.backoff.min-backoff | `+++5 ms+++` | Used to set `RetryBackoffSpec#minBackoff`. |
||||
|spring.cloud.loadbalancer.retry.enabled | `+++true+++` | Enables LoadBalancer retries. |
||||
|spring.cloud.loadbalancer.retry.max-retries-on-next-service-instance | `+++1+++` | Number of retries to be executed on the next `ServiceInstance`. A `ServiceInstance` is chosen before each retry call. |
||||
|spring.cloud.loadbalancer.retry.max-retries-on-same-service-instance | `+++0+++` | Number of retries to be executed on the same `ServiceInstance`. |
||||
|spring.cloud.loadbalancer.retry.retry-on-all-exceptions | `+++false+++` | Indicates retries should be attempted for all exceptions, not only those specified in `retryableExceptions`. |
||||
|spring.cloud.loadbalancer.retry.retry-on-all-operations | `+++false+++` | Indicates retries should be attempted on operations other than `HttpMethod.GET`. |
||||
|spring.cloud.loadbalancer.retry.retryable-exceptions | `+++{}+++` | A `Set` of `Throwable` classes that should trigger a retry. |
||||
|spring.cloud.loadbalancer.retry.retryable-status-codes | `+++{}+++` | A `Set` of status codes that should trigger a retry. |
||||
|spring.cloud.loadbalancer.sticky-session.add-service-instance-cookie | `+++false+++` | Indicates whether a cookie with the newly selected instance should be added by LoadBalancer. |
||||
|spring.cloud.loadbalancer.sticky-session.instance-id-cookie-name | `+++sc-lb-instance-id+++` | The name of the cookie holding the preferred instance id. |
||||
|spring.cloud.loadbalancer.x-forwarded.enabled | `+++false+++` | To Enable X-Forwarded Headers. |
||||
|spring.cloud.openfeign.autoconfiguration.jackson.enabled | `+++true+++` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding. |
||||
|spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled | `+++false+++` | If true, Circuit Breaker ids will only contain alphanumeric characters to allow for configuration via configuration properties. |
||||
|spring.cloud.openfeign.circuitbreaker.enabled | `+++false+++` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker. |
||||
|spring.cloud.openfeign.circuitbreaker.group.enabled | `+++false+++` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group. |
||||
|spring.cloud.openfeign.client.config | | |
||||
|spring.cloud.openfeign.client.decode-slash | `+++true+++` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`. |
||||
|spring.cloud.openfeign.client.default-config | `+++default+++` | |
||||
|spring.cloud.openfeign.client.default-to-properties | `+++true+++` | |
||||
|spring.cloud.openfeign.client.refresh-enabled | `+++false+++` | Enables options value refresh capability for Feign. |
||||
|spring.cloud.openfeign.compression.request.enabled | `+++false+++` | Enables the request sent by Feign to be compressed. |
||||
|spring.cloud.openfeign.compression.request.mime-types | `+++[text/xml, application/xml, application/json]+++` | The list of supported mime types. |
||||
|spring.cloud.openfeign.compression.request.min-request-size | `+++2048+++` | The minimum threshold content size. |
||||
|spring.cloud.openfeign.compression.response.enabled | `+++false+++` | Enables the response from Feign to be compressed. |
||||
|spring.cloud.openfeign.encoder.charset-from-content-type | `+++false+++` | Indicates whether the charset should be derived from the {@code Content-Type} header. |
||||
|spring.cloud.openfeign.http2client.enabled | `+++false+++` | Enables the use of the Java11 HTTP 2 Client by Feign. |
||||
|spring.cloud.openfeign.httpclient.connection-timeout | `+++2000+++` | |
||||
|spring.cloud.openfeign.httpclient.connection-timer-repeat | `+++3000+++` | |
||||
|spring.cloud.openfeign.httpclient.disable-ssl-validation | `+++false+++` | |
||||
|spring.cloud.openfeign.httpclient.enabled | `+++true+++` | Enables the use of the Apache HTTP Client by Feign. |
||||
|spring.cloud.openfeign.httpclient.follow-redirects | `+++true+++` | |
||||
|spring.cloud.openfeign.httpclient.hc5.connection-request-timeout | `+++3+++` | Default value for connection request timeout. |
||||
|spring.cloud.openfeign.httpclient.hc5.connection-request-timeout-unit | | Default value for connection request timeout unit. |
||||
|spring.cloud.openfeign.httpclient.hc5.enabled | `+++false+++` | Enables the use of the Apache HTTP Client 5 by Feign. |
||||
|spring.cloud.openfeign.httpclient.hc5.pool-concurrency-policy | | Pool concurrency policies. |
||||
|spring.cloud.openfeign.httpclient.hc5.pool-reuse-policy | | Pool connection re-use policies. |
||||
|spring.cloud.openfeign.httpclient.hc5.socket-timeout | `+++5+++` | Default value for socket timeout. |
||||
|spring.cloud.openfeign.httpclient.hc5.socket-timeout-unit | | Default value for socket timeout unit. |
||||
|spring.cloud.openfeign.httpclient.http2.version | `+++HTTP_2+++` | Configure the protocols used by this client to communicate with remote servers. Uses {@link String} value of {@link HttpClient.Version}. |
||||
|spring.cloud.openfeign.httpclient.max-connections | `+++200+++` | |
||||
|spring.cloud.openfeign.httpclient.max-connections-per-route | `+++50+++` | |
||||
|spring.cloud.openfeign.httpclient.ok-http.protocols | | Configure the protocols used by this client to communicate with remote servers. Uses {@link String} values of {@link Protocol}. |
||||
|spring.cloud.openfeign.httpclient.ok-http.read-timeout | `+++60s+++` | {@link OkHttpClient} read timeout; defaults to 60 seconds. |
||||
|spring.cloud.openfeign.httpclient.time-to-live | `+++900+++` | |
||||
|spring.cloud.openfeign.httpclient.time-to-live-unit | | |
||||
|spring.cloud.openfeign.lazy-attributes-resolution | `+++false+++` | Switches @FeignClient attributes resolution mode to lazy. |
||||
|spring.cloud.openfeign.micrometer.enabled | `+++true+++` | Enables Micrometer capabilities for Feign. |
||||
|spring.cloud.openfeign.oauth2.clientRegistrationId | | Provides a clientId to be used with OAuth2. |
||||
|spring.cloud.openfeign.oauth2.enabled | `+++false+++` | Enables feign interceptor for managing oauth2 access token. |
||||
|spring.cloud.openfeign.okhttp.enabled | `+++false+++` | Enables the use of the OK HTTP Client by Feign. |
||||
|spring.cloud.refresh.additional-property-sources-to-retain | | Additional property sources to retain during a refresh. Typically only system property sources are retained. This property allows property sources, such as property sources created by EnvironmentPostProcessors to be retained as well. |
||||
|spring.cloud.refresh.enabled | `+++true+++` | Enables autoconfiguration for the refresh scope and associated features. |
||||
|spring.cloud.refresh.extra-refreshable | `+++true+++` | Additional class names for beans to post process into refresh scope. |
||||
|spring.cloud.refresh.never-refreshable | `+++true+++` | Comma separated list of class names for beans to never be refreshed or rebound. |
||||
|spring.cloud.refresh.on-restart.enabled | `+++true+++` | Enable refreshing context on start. |
||||
|spring.cloud.service-registry.auto-registration.enabled | `+++true+++` | Whether service auto-registration is enabled. Defaults to true. |
||||
|spring.cloud.service-registry.auto-registration.fail-fast | `+++false+++` | Whether startup fails if there is no AutoServiceRegistration. Defaults to false. |
||||
|spring.cloud.service-registry.auto-registration.register-management | `+++true+++` | Whether to register the management as a service. Defaults to true. |
||||
|spring.cloud.util.enabled | `+++true+++` | Enables creation of Spring Cloud utility beans. |
||||
|
||||
|=== |
@ -1,6 +0,0 @@
@@ -1,6 +0,0 @@
|
||||
[[observability-conventions]] |
||||
=== Observability - Conventions |
||||
|
||||
Below you can find a list of all `GlobalObservationConvention` and `ObservationConvention` declared by this project. |
||||
|
||||
|
@ -1,6 +0,0 @@
@@ -1,6 +0,0 @@
|
||||
[[observability-metrics]] |
||||
=== Observability - Metrics |
||||
|
||||
Below you can find a list of all metrics declared by this project. |
||||
|
||||
|
@ -1,6 +0,0 @@
@@ -1,6 +0,0 @@
|
||||
[[observability-spans]] |
||||
=== Observability - Spans |
||||
|
||||
Below you can find a list of all spans declared by this project. |
||||
|
||||
|
@ -1,20 +0,0 @@
@@ -1,20 +0,0 @@
|
||||
version: @antora-component.version@ |
||||
prerelease: @antora-component.prerelease@ |
||||
|
||||
asciidoc: |
||||
attributes: |
||||
attribute-missing: 'warn' |
||||
chomp: 'all' |
||||
project-root: @maven.multiModuleProjectDirectory@ |
||||
github-repo: @docs.main@ |
||||
github-raw: https://raw.githubusercontent.com/spring-cloud/@docs.main@/@github-tag@ |
||||
github-code: https://github.com/spring-cloud/@docs.main@/tree/@github-tag@ |
||||
github-issues: https://github.com/spring-cloud/@docs.main@/issues/ |
||||
github-wiki: https://github.com/spring-cloud/@docs.main@/wiki |
||||
spring-cloud-version: @project.version@ |
||||
github-tag: @github-tag@ |
||||
version-type: @version-type@ |
||||
docs-url: https://docs.spring.io/@docs.main@/docs/@project.version@ |
||||
raw-docs-url: https://raw.githubusercontent.com/spring-cloud/@docs.main@/@github-tag@ |
||||
project-version: @project.version@ |
||||
project-name: @docs.main@ |
@ -1,24 +1,25 @@
@@ -1,24 +1,25 @@
|
||||
image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=main&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"] |
||||
image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=2.2.x&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"] |
||||
|
||||
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/main/graph/badge.svg["Codecov", link="https://app.codecov.io/gh/spring-cloud/spring-cloud-openfeign/tree/main"] |
||||
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/main/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"] |
||||
|
||||
image:https://api.codacy.com/project/badge/Grade/97b04c4e609c4b4f86b415e4437a6484["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-openfeign?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-openfeign&utm_campaign=Badge_Grade"] |
||||
|
||||
include::_attributes.adoc[] |
||||
|
||||
include::intro.adoc[] |
||||
|
||||
[[features]] |
||||
== Features |
||||
|
||||
* Declarative REST Client: Feign creates a dynamic implementation of an interface decorated with JAX-RS or Spring MVC annotations |
||||
|
||||
[[building]] |
||||
== Building |
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[] |
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/src/main/asciidoc/building-jdk8.adoc[] |
||||
|
||||
[[contributing]] |
||||
== Contributing |
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/contributing-docs.adoc[] |
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/src/main/asciidoc/contributing.adoc[] |
||||
|
||||
[[license]] |
||||
== License |
||||
|
||||
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/main/LICENSE.txt[here]. |
||||
|
@ -1,6 +1,8 @@
@@ -1,6 +1,8 @@
|
||||
:doctype: book |
||||
:idprefix: |
||||
:idseparator: - |
||||
:toc: left |
||||
:toclevels: 4 |
||||
:tabsize: 4 |
||||
:numbered: |
||||
:sectanchors: |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
|=== |
||||
|Name | Default | Description |
||||
|
||||
|feign.autoconfiguration.jackson.enabled | `false` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding. |
||||
|feign.circuitbreaker.enabled | `false` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker. |
||||
|feign.client.config | | |
||||
|feign.client.decode-slash | `true` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`. |
||||
|feign.client.default-config | `default` | |
||||
|feign.client.default-to-properties | `true` | |
||||
|feign.compression.request.enabled | `false` | Enables the request sent by Feign to be compressed. |
||||
|feign.compression.request.mime-types | `[text/xml, application/xml, application/json]` | The list of supported mime types. |
||||
|feign.compression.request.min-request-size | `2048` | The minimum threshold content size. |
||||
|feign.compression.response.enabled | `false` | Enables the response from Feign to be compressed. |
||||
|feign.compression.response.useGzipDecoder | `false` | Enables the default gzip decoder to be used. |
||||
|feign.encoder.charset-from-content-type | `false` | Indicates whether the charset should be derived from the {@code Content-Type} header. |
||||
|feign.httpclient.connection-timeout | `2000` | |
||||
|feign.httpclient.connection-timer-repeat | `3000` | |
||||
|feign.httpclient.disable-ssl-validation | `false` | |
||||
|feign.httpclient.enabled | `true` | Enables the use of the Apache HTTP Client by Feign. |
||||
|feign.httpclient.follow-redirects | `true` | |
||||
|feign.httpclient.hc5.enabled | `false` | Enables the use of the Apache HTTP Client 5 by Feign. |
||||
|feign.httpclient.hc5.pool-concurrency-policy | | Pool concurrency policies. |
||||
|feign.httpclient.hc5.pool-reuse-policy | | Pool connection re-use policies. |
||||
|feign.httpclient.hc5.socket-timeout | `5` | Default value for socket timeout. |
||||
|feign.httpclient.hc5.socket-timeout-unit | | Default value for socket timeout unit. |
||||
|feign.httpclient.max-connections | `200` | |
||||
|feign.httpclient.max-connections-per-route | `50` | |
||||
|feign.httpclient.time-to-live | `900` | |
||||
|feign.httpclient.time-to-live-unit | | |
||||
|feign.hystrix.enabled | `false` | If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker. |
||||
|feign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign. |
||||
|
||||
|=== |
@ -1,13 +1,14 @@
@@ -1,13 +1,14 @@
|
||||
:numbered!: |
||||
[appendix] |
||||
[[common-application-properties]] |
||||
= Common application properties |
||||
:page-section-summary-toc: 1 |
||||
== Common application properties |
||||
|
||||
include::_attributes.adoc[] |
||||
|
||||
Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches. |
||||
This appendix provides a list of common Spring Cloud OpenFeign properties and references to the underlying classes that consume them. |
||||
This appendix provides a list of common {project-full-name} properties and references to the underlying classes that consume them. |
||||
|
||||
NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list. |
||||
Also, you can define your own properties. |
||||
|
||||
include::_configprops.adoc[] |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
spring-cloud-openfeign.adoc |
@ -1,6 +1,3 @@
@@ -1,6 +1,3 @@
|
||||
[[introduction]] |
||||
= Spring Cloud OpenFeign |
||||
|
||||
This project provides OpenFeign integrations for Spring Boot apps through autoconfiguration |
||||
and binding to the Spring Environment and other Spring programming model idioms. |
||||
|
@ -0,0 +1,720 @@
@@ -0,0 +1,720 @@
|
||||
= Spring Cloud OpenFeign |
||||
include::_attributes.adoc[] |
||||
|
||||
*{spring-cloud-version}* |
||||
|
||||
include::intro.adoc[] |
||||
|
||||
|
||||
[[spring-cloud-feign]] |
||||
== Declarative REST Client: Feign |
||||
|
||||
https://github.com/OpenFeign/feign[Feign] is a declarative web service client. |
||||
It makes writing web service clients easier. |
||||
To use Feign create an interface and annotate it. |
||||
It has pluggable annotation support including Feign annotations and JAX-RS annotations. |
||||
Feign also supports pluggable encoders and decoders. |
||||
Spring Cloud adds support for Spring MVC annotations and for using the same `HttpMessageConverters` used by default in Spring Web. |
||||
Spring Cloud integrates Ribbon and Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign. |
||||
|
||||
[[netflix-feign-starter]] |
||||
=== How to Include Feign |
||||
|
||||
To include Feign in your project use the starter with group `org.springframework.cloud` |
||||
and artifact id `spring-cloud-starter-openfeign`. See the https://projects.spring.io/spring-cloud/[Spring Cloud Project page] |
||||
for details on setting up your build system with the current Spring Cloud Release Train. |
||||
|
||||
Example spring boot app |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@SpringBootApplication |
||||
@EnableFeignClients |
||||
public class Application { |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication.run(Application.class, args); |
||||
} |
||||
|
||||
} |
||||
---- |
||||
|
||||
.StoreClient.java |
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("stores") |
||||
public interface StoreClient { |
||||
@RequestMapping(method = RequestMethod.GET, value = "/stores") |
||||
List<Store> getStores(); |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/stores") |
||||
Page<Store> getStores(Pageable pageable); |
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") |
||||
Store update(@PathVariable("storeId") Long storeId, Store store); |
||||
} |
||||
---- |
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>> and <<spring-cloud-circuitbreaker,below for details of Spring Cloud CircuitBreaker support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer]. |
||||
You can also specify a URL using the `url` attribute |
||||
(absolute value or just a hostname). The name of the bean in the |
||||
application context is the fully qualified name of the interface. |
||||
To specify your own alias value you can use the `qualifiers` value |
||||
of the `@FeignClient` annotation. |
||||
|
||||
The load-balancer client above will want to discover the physical addresses |
||||
for the "stores" service. If your application is a Eureka client then |
||||
it will resolve the service in the Eureka service registry. If you |
||||
don't want to use Eureka, you can simply 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`]. |
||||
|
||||
WARNING: In order to maintain backward compatibility, is used as the default load-balancer implementation. |
||||
However, Spring Cloud Netflix Ribbon is now in maintenance mode, so we recommend using Spring Cloud LoadBalancer instead. |
||||
To do this, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false`. |
||||
|
||||
[[spring-cloud-feign-overriding-defaults]] |
||||
=== Overriding Feign Defaults |
||||
|
||||
A central concept in Spring Cloud's Feign support is that of the named client. Each feign client is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer using the `@FeignClient` annotation. Spring Cloud creates a new ensemble as an |
||||
`ApplicationContext` on demand for each named client using `FeignClientsConfiguration`. This contains (amongst other things) an `feign.Decoder`, a `feign.Encoder`, and a `feign.Contract`. |
||||
It is possible to override the name of that ensemble by using the `contextId` |
||||
attribute of the `@FeignClient` annotation. |
||||
|
||||
Spring Cloud lets you take full control of the feign client by declaring additional configuration (on top of the `FeignClientsConfiguration`) using `@FeignClient`. Example: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "stores", configuration = FooConfiguration.class) |
||||
public interface StoreClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
In this case the client is composed from the components already in `FeignClientsConfiguration` together with any in `FooConfiguration` (where the latter will override the former). |
||||
|
||||
NOTE: `FooConfiguration` does not need to be annotated with `@Configuration`. However, if it is, then take care to exclude it from any `@ComponentScan` that would otherwise include this configuration as it will become the default source for `feign.Decoder`, `feign.Encoder`, `feign.Contract`, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any `@ComponentScan` or `@SpringBootApplication`, or it can be explicitly excluded in `@ComponentScan`. |
||||
|
||||
NOTE: The `serviceId` attribute is now deprecated in favor of the `name` attribute. |
||||
|
||||
NOTE: Using `contextId` attribute of the `@FeignClient` annotation in addition to changing the name of |
||||
the `ApplicationContext` ensemble, it will override the alias of the client name |
||||
and it will be used as part of the name of the configuration bean created for that client. |
||||
|
||||
WARNING: Previously, using the `url` attribute, did not require the `name` attribute. Using `name` is now required. |
||||
|
||||
Placeholders are supported in the `name` and `url` attributes. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "${feign.name}", url = "${feign.url}") |
||||
public interface StoreClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
Spring Cloud OpenFeign provides the following beans by default for feign (`BeanType` beanName: `ClassName`): |
||||
|
||||
* `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`) |
||||
* `Encoder` feignEncoder: `SpringEncoder` |
||||
* `Logger` feignLogger: `Slf4jLogger` |
||||
* `Contract` feignContract: `SpringMvcContract` |
||||
* `Feign.Builder` feignBuilder: `HystrixFeign.Builder` |
||||
* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder` |
||||
* `Client` feignClient: if Ribbon is in the classpath and is enabled it is a `LoadBalancerFeignClient`, otherwise if Spring Cloud LoadBalancer is in the classpath, `FeignBlockingLoadBalancerClient` is used. |
||||
If none of them is in the classpath, the default feign client is used. |
||||
|
||||
NOTE: `spring-cloud-starter-openfeign` supports both `spring-cloud-starter-netflix-ribbon` and `spring-cloud-starter-loadbalancer`. However, as they are optional dependencies, you need to make sure the one you want to use has been added to your project. |
||||
|
||||
The OkHttpClient and ApacheHttpClient and ApacheHC5 feign clients can be used by setting `feign.okhttp.enabled` or `feign.httpclient.enabled` or `feign.httpclient.hc5.enabled` to `true`, respectively, and having them on the classpath. |
||||
You can customize the HTTP client used by providing a bean of either `org.apache.http.impl.client.CloseableHttpClient` when using Apache or `okhttp3.OkHttpClient` when using OK HTTP or `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5. |
||||
|
||||
Spring Cloud OpenFeign _does not_ provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client: |
||||
|
||||
* `Logger.Level` |
||||
* `Retryer` |
||||
* `ErrorDecoder` |
||||
* `Request.Options` |
||||
* `Collection<RequestInterceptor>` |
||||
* `SetterFactory` |
||||
* `QueryMapEncoder` |
||||
|
||||
A bean of `Retryer.NEVER_RETRY` with the type `Retryer` is created by default, which will disable retrying. |
||||
Notice this retrying behavior is different from the Feign default one, where it will automatically retry IOExceptions, |
||||
treating them as transient network related exceptions, and any RetryableException thrown from an ErrorDecoder. |
||||
|
||||
Creating a bean of one of those type and placing it in a `@FeignClient` configuration (such as `FooConfiguration` above) allows you to override each one of the beans described. Example: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
public Contract feignContract() { |
||||
return new feign.Contract.Default(); |
||||
} |
||||
|
||||
@Bean |
||||
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { |
||||
return new BasicAuthRequestInterceptor("user", "password"); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
This replaces the `SpringMvcContract` with `feign.Contract.Default` and adds a `RequestInterceptor` to the collection of `RequestInterceptor`. |
||||
|
||||
`@FeignClient` also can be configured using configuration properties. |
||||
|
||||
application.yml |
||||
[source,yaml] |
||||
---- |
||||
feign: |
||||
client: |
||||
config: |
||||
feignName: |
||||
connectTimeout: 5000 |
||||
readTimeout: 5000 |
||||
loggerLevel: full |
||||
errorDecoder: com.example.SimpleErrorDecoder |
||||
retryer: com.example.SimpleRetryer |
||||
defaultQueryParameters: |
||||
query: queryValue |
||||
defaultRequestHeaders: |
||||
header: headerValue |
||||
requestInterceptors: |
||||
- com.example.FooRequestInterceptor |
||||
- com.example.BarRequestInterceptor |
||||
decode404: false |
||||
encoder: com.example.SimpleEncoder |
||||
decoder: com.example.SimpleDecoder |
||||
contract: com.example.SimpleContract |
||||
---- |
||||
|
||||
Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients. |
||||
|
||||
If you prefer using configuration properties to configured all `@FeignClient`, you can create configuration properties with `default` feign name. |
||||
|
||||
You can use `feign.client.config.feignName.defaultQueryParameters` and `feign.client.config.feignName.defaultRequestHeaders` to specify query parameters and headers that will be sent with every request of the client named `feignName`. |
||||
|
||||
application.yml |
||||
[source,yaml] |
||||
---- |
||||
feign: |
||||
client: |
||||
config: |
||||
default: |
||||
connectTimeout: 5000 |
||||
readTimeout: 5000 |
||||
loggerLevel: basic |
||||
---- |
||||
|
||||
If we create both `@Configuration` bean and configuration properties, configuration properties will win. |
||||
It will override `@Configuration` values. But if you want to change the priority to `@Configuration`, |
||||
you can change `feign.client.default-to-properties` to `false`. |
||||
|
||||
NOTE: If you need to use `ThreadLocal` bound variables in your `RequestInterceptor`s you will need to either set the |
||||
thread isolation strategy for Hystrix to `SEMAPHORE` or disable Hystrix in Feign. |
||||
|
||||
application.yml |
||||
[source,yaml] |
||||
---- |
||||
# To disable Hystrix in Feign |
||||
feign: |
||||
hystrix: |
||||
enabled: false |
||||
|
||||
# To set thread isolation to SEMAPHORE |
||||
hystrix: |
||||
command: |
||||
default: |
||||
execution: |
||||
isolation: |
||||
strategy: SEMAPHORE |
||||
---- |
||||
|
||||
If we want to create multiple feign clients with the same name or url |
||||
so that they would point to the same server but each with a different custom configuration then |
||||
we have to use `contextId` attribute of the `@FeignClient` in order to avoid name |
||||
collision of these configuration beans. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class) |
||||
public interface FooClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class) |
||||
public interface BarClient { |
||||
//.. |
||||
} |
||||
---- |
||||
|
||||
It is also possible to configure FeignClient not to inherit beans from the parent context. |
||||
You can do this by overriding the `inheritParentConfiguration()` in a `FeignClientConfigurer` |
||||
bean to return `false`: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class CustomConfiguration{ |
||||
|
||||
@Bean |
||||
public FeignClientConfigurer feignClientConfigurer() { |
||||
return new FeignClientConfigurer() { |
||||
|
||||
@Override |
||||
public boolean inheritParentConfiguration() { |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
} |
||||
} |
||||
---- |
||||
|
||||
TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `feign.client.decodeSlash` to `false`. |
||||
|
||||
==== `SpringEncoder` configuration |
||||
|
||||
In the `SpringEncoder` that we provide, we set `null` charset for binary content types and `UTF-8` for all the other ones. |
||||
|
||||
You can modify this behaviour to derive the charset from the `Content-Type` header charset instead by setting the value of `feign.encoder.charset-from-content-type` to `true`. |
||||
|
||||
[[timeout-handling]] |
||||
=== Timeout Handling |
||||
|
||||
We can configure timeouts on both the default and the named client. OpenFeign works with two timeout parameters: |
||||
|
||||
- `connectTimeout` prevents blocking the caller due to the long server processing time. |
||||
- `readTimeout` is applied from the time of connection establishment and is triggered when returning the response takes too long. |
||||
|
||||
NOTE: In case the server is not running or available a packet results in _connection refused_. The communication ends either with an error message or in a fallback. This can happen _before_ the `connectTimeout` if it is set very low. The time taken to perform a lookup and to receive such a packet causes a significant part of this delay. It is subject to change based on the remote host that involves a DNS lookup. |
||||
|
||||
When Hystrix <<spring-cloud-openfeign#spring-cloud-feign-hystrix,is enabled>>, its timeout configuration link:https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.thread.timeoutInMilliseconds[defaults] to 1000 milliseconds. Hence, it might occur before the client timeout that we configured earlier. Increasing this timeout prevents it from happening. |
||||
|
||||
[source,yaml] |
||||
---- |
||||
feign: |
||||
client: |
||||
config: |
||||
default: |
||||
connectTimeout: 5000 |
||||
readTimeout: 5000 |
||||
hystrix: |
||||
enabled: true |
||||
|
||||
hystrix: |
||||
command: |
||||
default: |
||||
execution: |
||||
timeout: |
||||
enabled: true |
||||
isolation: |
||||
thread: |
||||
timeoutInMilliseconds: 60000 |
||||
---- |
||||
|
||||
NOTE: When the Hystrix timeout is enabled and its timeout is set longer than that of a feign client, `HystrixTimeoutException` wraps a feign exception. Otherwise, the only difference is the cause of the exception. The purpose of `HystrixTimeoutException` is to wrap any runtime exception that occurs first and throw an instance of itself. |
||||
|
||||
=== Creating Feign Clients Manually |
||||
|
||||
In some cases it might be necessary to customize your Feign Clients in a way that is not |
||||
possible using the methods above. In this case you can create Clients using the |
||||
https://github.com/OpenFeign/feign/#basics[Feign Builder API]. Below is an example |
||||
which creates two Feign Clients with the same interface but configures each one with |
||||
a separate request interceptor. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Import(FeignClientsConfiguration.class) |
||||
class FooController { |
||||
|
||||
private FooClient fooClient; |
||||
|
||||
private FooClient adminClient; |
||||
|
||||
@Autowired |
||||
public FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) { |
||||
this.fooClient = Feign.builder().client(client) |
||||
.encoder(encoder) |
||||
.decoder(decoder) |
||||
.contract(contract) |
||||
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) |
||||
.target(FooClient.class, "https://PROD-SVC"); |
||||
|
||||
this.adminClient = Feign.builder().client(client) |
||||
.encoder(encoder) |
||||
.decoder(decoder) |
||||
.contract(contract) |
||||
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) |
||||
.target(FooClient.class, "https://PROD-SVC"); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
NOTE: In the above example `FeignClientsConfiguration.class` is the default configuration |
||||
provided by Spring Cloud OpenFeign. |
||||
|
||||
NOTE: `PROD-SVC` is the name of the service the Clients will be making requests to. |
||||
|
||||
NOTE: The Feign `Contract` object defines what annotations and values are valid on interfaces. The |
||||
autowired `Contract` bean provides supports for SpringMVC annotations, instead of |
||||
the default Feign native annotations. |
||||
|
||||
You can also use the `Builder`to configure FeignClient not to inherit beans from the parent context. |
||||
You can do this by overriding calling `inheritParentContext(false)` on the `Builder`. |
||||
|
||||
[[spring-cloud-feign-hystrix]] |
||||
=== Feign Hystrix Support |
||||
|
||||
If Hystrix is on the classpath and `feign.hystrix.enabled=true`, Feign will wrap all methods with a circuit breaker. Returning a `com.netflix.hystrix.HystrixCommand` is also available. This lets you use reactive patterns (with a call to `.toObservable()` or `.observe()` or asynchronous use (with a call to `.queue()`). |
||||
|
||||
To disable Hystrix support on a per-client basis create a vanilla `Feign.Builder` with the "prototype" scope, e.g.: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
@Scope("prototype") |
||||
public Feign.Builder feignBuilder() { |
||||
return Feign.builder(); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
WARNING: Prior to the Spring Cloud Dalston release, if Hystrix was on the classpath Feign would have wrapped |
||||
all methods in a circuit breaker by default. This default behavior was changed in Spring Cloud Dalston in |
||||
favor for an opt-in approach. |
||||
|
||||
[[spring-cloud-feign-hystrix-fallback]] |
||||
=== Feign Hystrix Fallbacks |
||||
|
||||
Hystrix supports the notion of a fallback: a default code path that is executed when the circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "hello", fallback = HystrixClientFallback.class) |
||||
protected interface HystrixClient { |
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello") |
||||
Hello iFailSometimes(); |
||||
} |
||||
|
||||
static class HystrixClientFallback implements HystrixClient { |
||||
@Override |
||||
public Hello iFailSometimes() { |
||||
return new Hello("fallback"); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
If one needs access to the cause that made the fallback trigger, one can use the `fallbackFactory` attribute inside `@FeignClient`. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class) |
||||
protected interface HystrixClient { |
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello") |
||||
Hello iFailSometimes(); |
||||
} |
||||
|
||||
@Component |
||||
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> { |
||||
@Override |
||||
public HystrixClient create(Throwable cause) { |
||||
return new HystrixClient() { |
||||
@Override |
||||
public Hello iFailSometimes() { |
||||
return new Hello("fallback; reason was: " + cause.getMessage()); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
WARNING: There is a limitation with the implementation of fallbacks in Feign and how Hystrix fallbacks work. Fallbacks are currently not supported for methods that return `com.netflix.hystrix.HystrixCommand` and `rx.Observable`. |
||||
|
||||
[[spring-cloud-feign-circuitbreaker]] |
||||
=== Feign Spring Cloud CircuitBreaker Support |
||||
|
||||
If Spring Cloud CircuitBreaker is on the classpath and `feign.circuitbreaker.enabled=true`, Feign will wrap all methods with a circuit breaker. |
||||
|
||||
To disable Spring Cloud CircuitBreaker support on a per-client basis create a vanilla `Feign.Builder` with the "prototype" scope, e.g.: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
@Scope("prototype") |
||||
public Feign.Builder feignBuilder() { |
||||
return Feign.builder(); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
The circuit breaker name follows this pattern `<feignClientName>#<calledMethod>`. When calling a `@FeignClient` with name `foo` and the called interface method is `bar` then the circuit breaker name will be `foo_bar`. |
||||
|
||||
[[spring-cloud-feign-circuitbreaker-fallback]] |
||||
=== Feign Spring Cloud CircuitBreaker Fallbacks |
||||
|
||||
Spring Cloud CircuitBreaker supports the notion of a fallback: a default code path that is executed when the circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
include::{core_path}/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CircuitBreakerTests.java[tags=client_with_fallback, indent=0] |
||||
---- |
||||
|
||||
If one needs access to the cause that made the fallback trigger, one can use the `fallbackFactory` attribute inside `@FeignClient`. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
include::{core_path}/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/CircuitBreakerTests.java[tags=client_with_fallback_factory, indent=0] |
||||
---- |
||||
|
||||
=== Feign and `@Primary` |
||||
|
||||
When using Feign with Hystrix fallbacks, there are multiple beans in the `ApplicationContext` of the same type. This will cause `@Autowired` to not work because there isn't exactly one bean, or one marked as primary. To work around this, Spring Cloud OpenFeign marks all Feign instances as `@Primary`, so Spring Framework will know which bean to inject. In some cases, this may not be desirable. To turn off this behavior set the `primary` attribute of `@FeignClient` to false. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "hello", primary = false) |
||||
public interface HelloClient { |
||||
// methods here |
||||
} |
||||
---- |
||||
|
||||
[[spring-cloud-feign-inheritance]] |
||||
=== Feign Inheritance Support |
||||
|
||||
Feign supports boilerplate apis via single-inheritance interfaces. |
||||
This allows grouping common operations into convenient base interfaces. |
||||
|
||||
.UserService.java |
||||
[source,java,indent=0] |
||||
---- |
||||
public interface UserService { |
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}") |
||||
User getUser(@PathVariable("id") long id); |
||||
} |
||||
---- |
||||
|
||||
.UserResource.java |
||||
[source,java,indent=0] |
||||
---- |
||||
@RestController |
||||
public class UserResource implements UserService { |
||||
|
||||
} |
||||
---- |
||||
|
||||
.UserClient.java |
||||
[source,java,indent=0] |
||||
---- |
||||
package project.user; |
||||
|
||||
@FeignClient("users") |
||||
public interface UserClient extends UserService { |
||||
|
||||
} |
||||
---- |
||||
|
||||
WARNING: `@FeignClient` interfaces should not be shared between server and client and annotating `@FeignClient` interfaces with `@RequestMapping` on class level is no longer supported. |
||||
|
||||
=== Feign request/response compression |
||||
|
||||
You may consider enabling the request or response GZIP compression for your |
||||
Feign requests. You can do this by enabling one of the properties: |
||||
|
||||
[source,java] |
||||
---- |
||||
feign.compression.request.enabled=true |
||||
feign.compression.response.enabled=true |
||||
---- |
||||
|
||||
Feign request compression gives you settings similar to what you may set for your web server: |
||||
|
||||
[source,java] |
||||
---- |
||||
feign.compression.request.enabled=true |
||||
feign.compression.request.mime-types=text/xml,application/xml,application/json |
||||
feign.compression.request.min-request-size=2048 |
||||
---- |
||||
|
||||
These properties allow you to be selective about the compressed media types and minimum request threshold length. |
||||
|
||||
For http clients except OkHttpClient, default gzip decoder can be enabled to decode gzip response in UTF-8 encoding: |
||||
|
||||
[source,java] |
||||
---- |
||||
feign.compression.response.enabled=true |
||||
feign.compression.response.useGzipDecoder=true |
||||
---- |
||||
|
||||
=== Feign logging |
||||
|
||||
A logger is created for each Feign client created. By default the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the `DEBUG` level. |
||||
|
||||
.application.yml |
||||
|
||||
[source,yaml] |
||||
---- |
||||
logging.level.project.user.UserClient: DEBUG |
||||
---- |
||||
|
||||
The `Logger.Level` object that you may configure per client, tells Feign how much to log. Choices are: |
||||
|
||||
* `NONE`, No logging (*DEFAULT*). |
||||
* `BASIC`, Log only the request method and URL and the response status code and execution time. |
||||
* `HEADERS`, Log the basic information along with request and response headers. |
||||
* `FULL`, Log the headers, body, and metadata for both requests and responses. |
||||
|
||||
For example, the following would set the `Logger.Level` to `FULL`: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Configuration |
||||
public class FooConfiguration { |
||||
@Bean |
||||
Logger.Level feignLoggerLevel() { |
||||
return Logger.Level.FULL; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
=== Feign @QueryMap support |
||||
|
||||
The OpenFeign `@QueryMap` annotation provides support for POJOs to be used as |
||||
GET parameter maps. Unfortunately, the default OpenFeign QueryMap annotation is |
||||
incompatible with Spring because it lacks a `value` property. |
||||
|
||||
Spring Cloud OpenFeign provides an equivalent `@SpringQueryMap` annotation, which |
||||
is used to annotate a POJO or Map parameter as a query parameter map. |
||||
|
||||
For example, the `Params` class defines parameters `param1` and `param2`: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
// Params.java |
||||
public class Params { |
||||
private String param1; |
||||
private String param2; |
||||
|
||||
// [Getters and setters omitted for brevity] |
||||
} |
||||
---- |
||||
|
||||
The following feign client uses the `Params` class by using the `@SpringQueryMap` annotation: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("demo") |
||||
public interface DemoTemplate { |
||||
|
||||
@GetMapping(path = "/demo") |
||||
String demoEndpoint(@SpringQueryMap Params params); |
||||
} |
||||
---- |
||||
|
||||
If you need more control over the generated query parameter map, you can implement a custom `QueryMapEncoder` bean. |
||||
|
||||
=== HATEOAS support |
||||
|
||||
Spring provides some APIs to create REST representations that follow the https://en.wikipedia.org/wiki/HATEOAS[HATEOAS] principle, https://spring.io/projects/spring-hateoas[Spring Hateoas] and https://spring.io/projects/spring-data-rest[Spring Data REST]. |
||||
|
||||
If your project use the `org.springframework.boot:spring-boot-starter-hateoas` starter |
||||
or the `org.springframework.boot:spring-boot-starter-data-rest` starter, Feign HATEOAS support is enabled by default. |
||||
|
||||
When HATEOAS support is enabled, Feign clients are allowed to serialize |
||||
and deserialize HATEOAS representation models: https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/EntityModel.html[EntityModel], https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/CollectionModel.html[CollectionModel] and https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/PagedModel.html[PagedModel]. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("demo") |
||||
public interface DemoTemplate { |
||||
|
||||
@GetMapping(path = "/stores") |
||||
CollectionModel<Store> getStores(); |
||||
} |
||||
---- |
||||
|
||||
=== Spring @MatrixVariable Support |
||||
|
||||
Spring Cloud OpenFeign provides support for the Spring `@MatrixVariable` annotation. |
||||
|
||||
If a map is passed as the method argument, the `@MatrixVariable` path segment is created by joining key-value pairs from the map with a `=`. |
||||
|
||||
If a different object is passed, either the `name` provided in the `@MatrixVariable` annotation (if defined) or the annotated variable name is |
||||
joined with the provided method argument using `=`. |
||||
|
||||
IMPORTANT:: Even though, on the server side, Spring does not require the users to name the path segment placeholder same as the matrix variable name, since it would be too ambiguous on the client side, Spring Cloud OpenFeign requires that you add a path segment placeholder with a name matching either the `name` provided in the `@MatrixVariable` annotation (if defined) or the annotated variable name. |
||||
|
||||
For example: |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@GetMapping("/objects/links/{matrixVars}") |
||||
Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> matrixVars); |
||||
---- |
||||
Note that both variable name and the path segment placeholder are called `matrixVars`. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient("demo") |
||||
public interface DemoTemplate { |
||||
|
||||
@GetMapping(path = "/stores") |
||||
CollectionModel<Store> getStores(); |
||||
} |
||||
---- |
||||
|
||||
=== Feign `CollectionFormat` support |
||||
We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation. You can annotate a Feign client method with it by passing the desired `feign.CollectionFormat` as annotation value. |
||||
|
||||
In the following example, the `CSV` format is used instead of the default `EXPLODED` to process the method. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@FeignClient(name = "demo") |
||||
protected interface PageableFeignClient { |
||||
|
||||
@CollectionFormat(feign.CollectionFormat.CSV) |
||||
@GetMapping(path = "/page") |
||||
ResponseEntity performRequest(Pageable page); |
||||
|
||||
} |
||||
---- |
||||
|
||||
TIP: Set the `CSV` format while sending `Pageable` as a query parameter in order for it to be encoded correctly. |
||||
|
||||
=== Reactive Support |
||||
As the https://github.com/OpenFeign/feign[OpenFeign project] does not currently support reactive clients, such as https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html[Spring WebClient], neither does Spring Cloud OpenFeign. We will add support for it here as soon as it becomes available in the core project. |
||||
|
||||
Until that is done, we recommend using https://github.com/Playtika/feign-reactive[feign-reactive] for Spring WebClient support. |
||||
|
||||
==== Early Initialization Errors |
||||
|
||||
Depending on how you are using your Feign clients you may see initialization errors when starting your application. |
||||
To work around this problem you can use an `ObjectProvider` when autowiring your client. |
||||
|
||||
[source,java,indent=0] |
||||
---- |
||||
@Autowired |
||||
ObjectProvider<TestFeginClient> testFeginClient; |
||||
---- |
||||
|
||||
=== Spring Data Support |
||||
You may consider enabling Jackson Modules for the support `org.springframework.data.domain.Page` and `org.springframework.data.domain.Sort` decoding. |
||||
|
||||
[source,java] |
||||
---- |
||||
feign.autoconfiguration.jackson.enabled=true |
||||
---- |
||||
|
||||
== Configuration properties |
||||
|
||||
To see the list of all Spring Cloud OpenFeign related configuration properties please check link:appendix.html[the Appendix page]. |
@ -1,42 +0,0 @@
@@ -1,42 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.openfeign; |
||||
|
||||
import feign.Capability; |
||||
import feign.InvocationHandlerFactory; |
||||
|
||||
import org.springframework.cache.interceptor.CacheInterceptor; |
||||
|
||||
/** |
||||
* Allows Spring's @Cache* annotations to be declared on the feign client's methods. |
||||
* |
||||
* @author Sam Kruglov |
||||
*/ |
||||
public class CachingCapability implements Capability { |
||||
|
||||
private final CacheInterceptor cacheInterceptor; |
||||
|
||||
public CachingCapability(CacheInterceptor cacheInterceptor) { |
||||
this.cacheInterceptor = cacheInterceptor; |
||||
} |
||||
|
||||
@Override |
||||
public InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) { |
||||
return new FeignCachingInvocationHandlerFactory(invocationHandlerFactory, cacheInterceptor); |
||||
} |
||||
|
||||
} |
@ -1,82 +0,0 @@
@@ -1,82 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.openfeign; |
||||
|
||||
import java.lang.reflect.AccessibleObject; |
||||
import java.lang.reflect.InvocationHandler; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import feign.InvocationHandlerFactory; |
||||
import feign.Target; |
||||
import org.aopalliance.intercept.MethodInvocation; |
||||
|
||||
import org.springframework.cache.interceptor.CacheInterceptor; |
||||
|
||||
/** |
||||
* Allows Spring's @Cache* annotations to be declared on the feign client's methods. |
||||
* |
||||
* @author Sam Kruglov |
||||
*/ |
||||
public class FeignCachingInvocationHandlerFactory implements InvocationHandlerFactory { |
||||
|
||||
private final InvocationHandlerFactory delegateFactory; |
||||
|
||||
private final CacheInterceptor cacheInterceptor; |
||||
|
||||
public FeignCachingInvocationHandlerFactory(InvocationHandlerFactory delegateFactory, |
||||
CacheInterceptor cacheInterceptor) { |
||||
this.delegateFactory = delegateFactory; |
||||
this.cacheInterceptor = cacheInterceptor; |
||||
} |
||||
|
||||
@Override |
||||
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { |
||||
final InvocationHandler delegateHandler = delegateFactory.create(target, dispatch); |
||||
return (proxy, method, argsNullable) -> { |
||||
Object[] args = Optional.ofNullable(argsNullable).orElseGet(() -> new Object[0]); |
||||
return cacheInterceptor.invoke(new MethodInvocation() { |
||||
@Override |
||||
public Method getMethod() { |
||||
return method; |
||||
} |
||||
|
||||
@Override |
||||
public Object[] getArguments() { |
||||
return args; |
||||
} |
||||
|
||||
@Override |
||||
public Object proceed() throws Throwable { |
||||
return delegateHandler.invoke(proxy, method, args); |
||||
} |
||||
|
||||
@Override |
||||
public Object getThis() { |
||||
return target; |
||||
} |
||||
|
||||
@Override |
||||
public AccessibleObject getStaticPart() { |
||||
return method; |
||||
} |
||||
}); |
||||
}; |
||||
} |
||||
|
||||
} |
@ -1,52 +0,0 @@
@@ -1,52 +0,0 @@
|
||||
/* |
||||
* Copyright 2021-2022 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.util.Map; |
||||
|
||||
import org.springframework.context.annotation.Condition; |
||||
import org.springframework.context.annotation.ConditionContext; |
||||
import org.springframework.core.type.AnnotatedTypeMetadata; |
||||
|
||||
/** |
||||
* @author Jonatan Ivanov |
||||
*/ |
||||
class FeignClientMicrometerEnabledCondition implements Condition { |
||||
|
||||
@Override |
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { |
||||
FeignClientProperties feignClientProperties = context.getBeanFactory() |
||||
.getBeanProvider(FeignClientProperties.class).getIfAvailable(); |
||||
if (feignClientProperties != null) { |
||||
Map<String, FeignClientProperties.FeignClientConfiguration> feignClientConfigMap = feignClientProperties |
||||
.getConfig(); |
||||
if (feignClientConfigMap != null) { |
||||
FeignClientProperties.FeignClientConfiguration feignClientConfig = feignClientConfigMap |
||||
.get(context.getEnvironment().getProperty("spring.cloud.openfeign.client.name")); |
||||
if (feignClientConfig != null) { |
||||
FeignClientProperties.MicrometerProperties micrometer = feignClientConfig.getMicrometer(); |
||||
if (micrometer != null && micrometer.getEnabled() != null) { |
||||
return micrometer.getEnabled(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* 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 org.springframework.boot.autoconfigure.condition.AnyNestedCondition; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
||||
/** |
||||
* @author Nguyen Ky Thanh |
||||
*/ |
||||
public class HttpClient5DisabledConditions extends AnyNestedCondition { |
||||
|
||||
public HttpClient5DisabledConditions() { |
||||
super(ConfigurationPhase.PARSE_CONFIGURATION); |
||||
} |
||||
|
||||
@ConditionalOnMissingClass("feign.hc5.ApacheHttp5Client") |
||||
static class ApacheHttp5ClientClassMissing { |
||||
|
||||
} |
||||
|
||||
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "false", |
||||
matchIfMissing = true) |
||||
static class HttpClient5Disabled { |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright 2013-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.openfeign; |
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
||||
/** |
||||
* @author Tim Peeters |
||||
*/ |
||||
class HystrixDisabledConditions extends AnyNestedCondition { |
||||
|
||||
HystrixDisabledConditions() { |
||||
super(ConfigurationPhase.PARSE_CONFIGURATION); |
||||
} |
||||
|
||||
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign") |
||||
static class HystrixFeignClassMissing { |
||||
|
||||
} |
||||
|
||||
@ConditionalOnProperty(value = "feign.hystrix.enabled", havingValue = "false") |
||||
static class HystrixFeignDisabled { |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/* |
||||
* Copyright 2013-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.openfeign; |
||||
|
||||
import feign.Feign; |
||||
import feign.Target; |
||||
import feign.hystrix.FallbackFactory; |
||||
import feign.hystrix.HystrixFeign; |
||||
import feign.hystrix.SetterFactory; |
||||
|
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* @author Spencer Gibb |
||||
* @author Erik Kringen |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
class HystrixTargeter implements Targeter { |
||||
|
||||
@Override |
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, |
||||
FeignContext context, Target.HardCodedTarget<T> target) { |
||||
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { |
||||
return feign.target(target); |
||||
} |
||||
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; |
||||
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName() |
||||
: factory.getContextId(); |
||||
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class); |
||||
if (setterFactory != null) { |
||||
builder.setterFactory(setterFactory); |
||||
} |
||||
Class<?> fallback = factory.getFallback(); |
||||
if (fallback != void.class) { |
||||
return targetWithFallback(name, context, target, builder, fallback); |
||||
} |
||||
Class<?> fallbackFactory = factory.getFallbackFactory(); |
||||
if (fallbackFactory != void.class) { |
||||
return targetWithFallbackFactory(name, context, target, builder, |
||||
fallbackFactory); |
||||
} |
||||
|
||||
return feign.target(target); |
||||
} |
||||
|
||||
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context, |
||||
Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, |
||||
Class<?> fallbackFactoryClass) { |
||||
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext( |
||||
"fallbackFactory", feignClientName, context, fallbackFactoryClass, |
||||
FallbackFactory.class); |
||||
return builder.target(target, fallbackFactory); |
||||
} |
||||
|
||||
private <T> T targetWithFallback(String feignClientName, FeignContext context, |
||||
Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, |
||||
Class<?> fallback) { |
||||
T fallbackInstance = getFromContext("fallback", feignClientName, context, |
||||
fallback, target.type()); |
||||
return builder.target(target, fallbackInstance); |
||||
} |
||||
|
||||
private <T> T getFromContext(String fallbackMechanism, String feignClientName, |
||||
FeignContext context, Class<?> beanType, Class<T> targetType) { |
||||
Object fallbackInstance = context.getInstance(feignClientName, beanType); |
||||
if (fallbackInstance == null) { |
||||
throw new IllegalStateException(String.format( |
||||
"No " + fallbackMechanism |
||||
+ " instance of type %s found for feign client %s", |
||||
beanType, feignClientName)); |
||||
} |
||||
|
||||
if (!targetType.isAssignableFrom(beanType)) { |
||||
throw new IllegalStateException(String.format("Incompatible " |
||||
+ fallbackMechanism |
||||
+ " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", |
||||
beanType, targetType, feignClientName)); |
||||
} |
||||
return (T) fallbackInstance; |
||||
} |
||||
|
||||
private <T> T getOptional(String feignClientName, FeignContext context, |
||||
Class<T> beanType) { |
||||
return context.getInstance(feignClientName, beanType); |
||||
} |
||||
|
||||
} |
@ -1,86 +0,0 @@
@@ -1,86 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2022 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.util.Objects; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import feign.Request; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.FactoryBean; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.ApplicationContextAware; |
||||
|
||||
/** |
||||
* This factory bean is meant to create {@link Request.Options} instance as per the |
||||
* applicable configurations. |
||||
* |
||||
* @author Jasbir Singh |
||||
*/ |
||||
public class OptionsFactoryBean implements FactoryBean<Request.Options>, ApplicationContextAware { |
||||
|
||||
private ApplicationContext applicationContext; |
||||
|
||||
private String contextId; |
||||
|
||||
private Request.Options options; |
||||
|
||||
@Override |
||||
public Class<?> getObjectType() { |
||||
return Request.Options.class; |
||||
} |
||||
|
||||
@Override |
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
||||
this.applicationContext = applicationContext; |
||||
} |
||||
|
||||
@Override |
||||
public Request.Options getObject() { |
||||
if (options != null) { |
||||
return options; |
||||
} |
||||
|
||||
options = new Request.Options(); |
||||
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class); |
||||
options = createOptionsWithApplicableValues(properties.getConfig().get(properties.getDefaultConfig()), options); |
||||
options = createOptionsWithApplicableValues(properties.getConfig().get(contextId), options); |
||||
return options; |
||||
} |
||||
|
||||
public void setContextId(String contextId) { |
||||
this.contextId = contextId; |
||||
} |
||||
|
||||
private Request.Options createOptionsWithApplicableValues( |
||||
FeignClientProperties.FeignClientConfiguration clientConfiguration, Request.Options options) { |
||||
if (Objects.isNull(clientConfiguration)) { |
||||
return options; |
||||
} |
||||
|
||||
int connectTimeoutMillis = Objects.nonNull(clientConfiguration.getConnectTimeout()) |
||||
? clientConfiguration.getConnectTimeout() : options.connectTimeoutMillis(); |
||||
int readTimeoutMillis = Objects.nonNull(clientConfiguration.getReadTimeout()) |
||||
? clientConfiguration.getReadTimeout() : options.readTimeoutMillis(); |
||||
boolean followRedirects = Objects.nonNull(clientConfiguration.isFollowRedirects()) |
||||
? clientConfiguration.isFollowRedirects() : options.isFollowRedirects(); |
||||
return new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis, |
||||
TimeUnit.MILLISECONDS, followRedirects); |
||||
} |
||||
|
||||
} |
@ -1,49 +0,0 @@
@@ -1,49 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.openfeign; |
||||
|
||||
import feign.Target; |
||||
|
||||
/** |
||||
* A {@link HardCodedTarget} implementation that resolves url from properties when the |
||||
* initial call is made. Using it allows specifying the url at runtime in an AOT-packaged |
||||
* application or a native image by setting the value of the |
||||
* `spring.cloud.openfeign.client.config.[clientId].url`. |
||||
* |
||||
* @author Olga Maciaszek-Sharma |
||||
* @see FeignClientProperties.FeignClientConfiguration#getUrl() |
||||
*/ |
||||
public class PropertyBasedTarget<T> extends Target.HardCodedTarget<T> { |
||||
|
||||
private String url; |
||||
|
||||
private final FeignClientProperties.FeignClientConfiguration config; |
||||
|
||||
public PropertyBasedTarget(Class<T> type, String name, FeignClientProperties.FeignClientConfiguration config) { |
||||
super(type, name, config.getUrl()); |
||||
this.config = config; |
||||
} |
||||
|
||||
@Override |
||||
public String url() { |
||||
if (url == null) { |
||||
url = config.getUrl(); |
||||
} |
||||
return url; |
||||
} |
||||
|
||||
} |
@ -1,42 +0,0 @@
@@ -1,42 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.cloud.openfeign; |
||||
|
||||
import feign.Target; |
||||
|
||||
/** |
||||
* This target provides url wrapped under {@link Target}. |
||||
* |
||||
* @author Jasbir Singh |
||||
* @since 4.0.0 |
||||
*/ |
||||
public class RefreshableHardCodedTarget<T> extends Target.HardCodedTarget<T> { |
||||
|
||||
private final RefreshableUrl refreshableUrl; |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public RefreshableHardCodedTarget(Class type, String name, RefreshableUrl refreshableUrl) { |
||||
super(type, name, refreshableUrl.getUrl()); |
||||
this.refreshableUrl = refreshableUrl; |
||||
} |
||||
|
||||
@Override |
||||
public String url() { |
||||
return refreshableUrl.getUrl(); |
||||
} |
||||
|
||||
} |
@ -1,75 +0,0 @@
@@ -1,75 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2022 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.util.Objects; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.FactoryBean; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.ApplicationContextAware; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* This factory bean creates {@link RefreshableUrl} instance as per the applicable |
||||
* configurations. |
||||
* |
||||
* @author Jasbir Singh |
||||
* @since 4.0.0 |
||||
*/ |
||||
public class RefreshableUrlFactoryBean implements FactoryBean<RefreshableUrl>, ApplicationContextAware { |
||||
|
||||
private ApplicationContext applicationContext; |
||||
|
||||
private String contextId; |
||||
|
||||
private RefreshableUrl refreshableUrl; |
||||
|
||||
@Override |
||||
public Class<?> getObjectType() { |
||||
return RefreshableUrl.class; |
||||
} |
||||
|
||||
@Override |
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
||||
this.applicationContext = applicationContext; |
||||
} |
||||
|
||||
@Override |
||||
public RefreshableUrl getObject() { |
||||
if (refreshableUrl != null) { |
||||
return refreshableUrl; |
||||
} |
||||
|
||||
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class); |
||||
if (Objects.isNull(properties.getConfig())) { |
||||
return new RefreshableUrl(null); |
||||
} |
||||
FeignClientProperties.FeignClientConfiguration configuration = properties.getConfig().get(contextId); |
||||
if (Objects.isNull(configuration) || !StringUtils.hasText(configuration.getUrl())) { |
||||
return new RefreshableUrl(null); |
||||
} |
||||
|
||||
refreshableUrl = new RefreshableUrl(FeignClientsRegistrar.getUrl(configuration.getUrl())); |
||||
return refreshableUrl; |
||||
} |
||||
|
||||
public void setContextId(String contextId) { |
||||
this.contextId = contextId; |
||||
} |
||||
|
||||
} |
@ -1,69 +0,0 @@
@@ -1,69 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2022 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.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Collections; |
||||
|
||||
import feign.MethodMetadata; |
||||
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.web.bind.annotation.CookieValue; |
||||
|
||||
import static feign.Util.checkState; |
||||
import static feign.Util.emptyToNull; |
||||
|
||||
/** |
||||
* {@link CookieValue} annotation processor. |
||||
* |
||||
* @author Gong Yi |
||||
* @author Olga Maciaszek-Sharma |
||||
* |
||||
*/ |
||||
public class CookieValueParameterProcessor implements AnnotatedParameterProcessor { |
||||
|
||||
private static final Class<CookieValue> ANNOTATION = CookieValue.class; |
||||
|
||||
@Override |
||||
public Class<? extends Annotation> getAnnotationType() { |
||||
return ANNOTATION; |
||||
} |
||||
|
||||
@Override |
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { |
||||
int parameterIndex = context.getParameterIndex(); |
||||
MethodMetadata data = context.getMethodMetadata(); |
||||
CookieValue cookie = ANNOTATION.cast(annotation); |
||||
String name = cookie.value().trim(); |
||||
checkState(emptyToNull(name) != null, "Cookie.name() was empty on parameter %s", parameterIndex); |
||||
context.setParameterName(name); |
||||
String cookieExpression = data.template().headers() |
||||
.getOrDefault(HttpHeaders.COOKIE, Collections.singletonList("")).stream().findFirst().orElse(""); |
||||
if (cookieExpression.length() == 0) { |
||||
cookieExpression = String.format("%s={%s}", name, name); |
||||
} |
||||
else { |
||||
cookieExpression += String.format("; %s={%s}", name, name); |
||||
} |
||||
data.template().removeHeader(HttpHeaders.COOKIE); |
||||
data.template().header(HttpHeaders.COOKIE, cookieExpression); |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -1,126 +0,0 @@
@@ -1,126 +0,0 @@
|
||||
/* |
||||
* Copyright 2022-2022 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.aot; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.HashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import javax.lang.model.element.Modifier; |
||||
|
||||
import org.springframework.aot.generate.GeneratedMethod; |
||||
import org.springframework.aot.generate.GenerationContext; |
||||
import org.springframework.beans.factory.BeanFactory; |
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; |
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; |
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode; |
||||
import org.springframework.beans.factory.support.RegisteredBean; |
||||
import org.springframework.cloud.openfeign.FeignClientFactory; |
||||
import org.springframework.cloud.openfeign.FeignClientSpecification; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.context.aot.ApplicationContextAotGenerator; |
||||
import org.springframework.context.support.GenericApplicationContext; |
||||
import org.springframework.javapoet.ClassName; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A {@link BeanRegistrationAotProcessor} that creates an |
||||
* {@link BeanRegistrationAotContribution} for Feign child contexts. |
||||
* |
||||
* @author Olga Maciaszek-Sharma |
||||
* @since 4.0.0 |
||||
*/ |
||||
public class FeignChildContextInitializer implements BeanRegistrationAotProcessor { |
||||
|
||||
private final ApplicationContext applicationContext; |
||||
|
||||
private final FeignClientFactory feignClientFactory; |
||||
|
||||
public FeignChildContextInitializer(ApplicationContext applicationContext, FeignClientFactory feignClientFactory) { |
||||
this.applicationContext = applicationContext; |
||||
this.feignClientFactory = feignClientFactory; |
||||
} |
||||
|
||||
@Override |
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { |
||||
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); |
||||
ConfigurableApplicationContext context = ((ConfigurableApplicationContext) applicationContext); |
||||
BeanFactory applicationBeanFactory = context.getBeanFactory(); |
||||
if (!((registeredBean.getBeanClass().equals(FeignClientFactory.class)) |
||||
&& registeredBean.getBeanFactory().equals(applicationBeanFactory))) { |
||||
return null; |
||||
} |
||||
Set<String> contextIds = new HashSet<>(getContextIdsFromConfig()); |
||||
Map<String, GenericApplicationContext> childContextAotContributions = contextIds.stream() |
||||
.map(contextId -> Map.entry(contextId, buildChildContext(contextId))) |
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
||||
return new AotContribution(childContextAotContributions); |
||||
} |
||||
|
||||
private GenericApplicationContext buildChildContext(String contextId) { |
||||
GenericApplicationContext childContext = feignClientFactory.buildContext(contextId); |
||||
feignClientFactory.registerBeans(contextId, childContext); |
||||
return childContext; |
||||
} |
||||
|
||||
private Collection<String> getContextIdsFromConfig() { |
||||
Map<String, FeignClientSpecification> configurations = feignClientFactory.getConfigurations(); |
||||
return configurations.keySet().stream().filter(key -> !key.startsWith("default.")).collect(Collectors.toSet()); |
||||
} |
||||
|
||||
private static class AotContribution implements BeanRegistrationAotContribution { |
||||
|
||||
private final Map<String, GenericApplicationContext> childContexts; |
||||
|
||||
AotContribution(Map<String, GenericApplicationContext> childContexts) { |
||||
this.childContexts = childContexts.entrySet().stream().filter(entry -> entry.getValue() != null) |
||||
.map(entry -> Map.entry(entry.getKey(), entry.getValue())) |
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
||||
} |
||||
|
||||
@Override |
||||
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { |
||||
Map<String, ClassName> generatedInitializerClassNames = childContexts.entrySet().stream().map(entry -> { |
||||
String name = entry.getValue().getDisplayName(); |
||||
name = name.replaceAll("[-]", "_"); |
||||
GenerationContext childGenerationContext = generationContext.withName(name); |
||||
ClassName initializerClassName = new ApplicationContextAotGenerator() |
||||
.processAheadOfTime(entry.getValue(), childGenerationContext); |
||||
return Map.entry(entry.getKey(), initializerClassName); |
||||
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
||||
GeneratedMethod postProcessorMethod = beanRegistrationCode.getMethods() |
||||
.add("addFeignChildContextInitializer", method -> { |
||||
method.addJavadoc("Use AOT child context management initialization") |
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC) |
||||
.addParameter(RegisteredBean.class, "registeredBean") |
||||
.addParameter(FeignClientFactory.class, "instance").returns(FeignClientFactory.class) |
||||
.addStatement("$T<String, Object> initializers = new $T<>()", Map.class, HashMap.class); |
||||
generatedInitializerClassNames.keySet() |
||||
.forEach(contextId -> method.addStatement("initializers.put($S, new $L())", contextId, |
||||
generatedInitializerClassNames.get(contextId))); |
||||
method.addStatement("return instance.withApplicationContextInitializers(initializers)"); |
||||
}); |
||||
beanRegistrationCode.addInstancePostProcessor(postProcessorMethod.toMethodReference()); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -1,215 +0,0 @@
@@ -1,215 +0,0 @@
|
||||
/* |
||||
* Copyright 2022-2023 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.aot; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Parameter; |
||||
import java.util.Map; |
||||
import java.util.Objects; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import javax.lang.model.element.Modifier; |
||||
|
||||
import org.springframework.aot.generate.GenerationContext; |
||||
import org.springframework.aot.generate.MethodReference; |
||||
import org.springframework.aot.hint.BindingReflectionHintsRegistrar; |
||||
import org.springframework.aot.hint.ReflectionHints; |
||||
import org.springframework.aot.hint.RuntimeHints; |
||||
import org.springframework.beans.MutablePropertyValues; |
||||
import org.springframework.beans.factory.BeanFactory; |
||||
import org.springframework.beans.factory.FactoryBean; |
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; |
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; |
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; |
||||
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder; |
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition; |
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; |
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.beans.factory.support.GenericBeanDefinition; |
||||
import org.springframework.beans.factory.support.RegisteredBean; |
||||
import org.springframework.cloud.openfeign.FeignClientFactory; |
||||
import org.springframework.cloud.openfeign.FeignClientFactoryBean; |
||||
import org.springframework.cloud.openfeign.FeignClientSpecification; |
||||
import org.springframework.context.support.GenericApplicationContext; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.javapoet.MethodSpec; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* A {@link BeanFactoryInitializationAotProcessor} that creates an |
||||
* {@link BeanFactoryInitializationAotContribution} that registers bean definitions and |
||||
* proxy and reflection hints for Feign client beans. |
||||
* |
||||
* @author Olga Maciaszek-Sharma |
||||
* @since 4.0.0 |
||||
*/ |
||||
public class FeignClientBeanFactoryInitializationAotProcessor |
||||
implements BeanRegistrationExcludeFilter, BeanFactoryInitializationAotProcessor { |
||||
|
||||
private final GenericApplicationContext context; |
||||
|
||||
private final Map<String, BeanDefinition> feignClientBeanDefinitions; |
||||
|
||||
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); |
||||
|
||||
public FeignClientBeanFactoryInitializationAotProcessor(GenericApplicationContext context, |
||||
FeignClientFactory feignClientFactory) { |
||||
this.context = context; |
||||
this.feignClientBeanDefinitions = getFeignClientBeanDefinitions(feignClientFactory); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { |
||||
return registeredBean.getBeanClass().equals(FeignClientFactoryBean.class) |
||||
|| feignClientBeanDefinitions.containsKey(registeredBean.getBeanClass().getName()); |
||||
} |
||||
|
||||
private Map<String, BeanDefinition> getFeignClientBeanDefinitions(FeignClientFactory feignClientFactory) { |
||||
Map<String, FeignClientSpecification> configurations = feignClientFactory.getConfigurations(); |
||||
return configurations.values().stream().map(FeignClientSpecification::getClassName).filter(Objects::nonNull) |
||||
.filter(className -> !className.equals("default")) |
||||
.map(className -> Map.entry(className, context.getBeanDefinition(className))) |
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
||||
} |
||||
|
||||
@SuppressWarnings("NullableProblems") |
||||
@Override |
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { |
||||
BeanFactory applicationBeanFactory = context.getBeanFactory(); |
||||
if (feignClientBeanDefinitions.isEmpty() || !beanFactory.equals(applicationBeanFactory)) { |
||||
return null; |
||||
} |
||||
return new AotContribution(feignClientBeanDefinitions); |
||||
} |
||||
|
||||
private void registerMethodHints(ReflectionHints hints, Class<?> clazz) { |
||||
for (Method method : clazz.getDeclaredMethods()) { |
||||
registerMethodHints(hints, method); |
||||
} |
||||
} |
||||
|
||||
private void registerMethodHints(ReflectionHints hints, Method method) { |
||||
for (Parameter parameter : method.getParameters()) { |
||||
bindingRegistrar.registerReflectionHints(hints, |
||||
MethodParameter.forParameter(parameter).getGenericParameterType()); |
||||
} |
||||
MethodParameter returnTypeParameter = MethodParameter.forExecutable(method, -1); |
||||
if (!void.class.equals(returnTypeParameter.getParameterType())) { |
||||
bindingRegistrar.registerReflectionHints(hints, returnTypeParameter.getGenericParameterType()); |
||||
} |
||||
|
||||
} |
||||
|
||||
// Visible for tests
|
||||
final class AotContribution implements BeanFactoryInitializationAotContribution { |
||||
|
||||
private final Map<String, BeanDefinition> feignClientBeanDefinitions; |
||||
|
||||
private AotContribution(Map<String, BeanDefinition> feignClientBeanDefinitions) { |
||||
this.feignClientBeanDefinitions = feignClientBeanDefinitions; |
||||
} |
||||
|
||||
@Override |
||||
public void applyTo(GenerationContext generationContext, |
||||
BeanFactoryInitializationCode beanFactoryInitializationCode) { |
||||
RuntimeHints hints = generationContext.getRuntimeHints(); |
||||
Set<String> feignClientRegistrationMethods = feignClientBeanDefinitions.values().stream() |
||||
.map(beanDefinition -> { |
||||
Assert.notNull(beanDefinition, "beanDefinition cannot be null"); |
||||
Assert.isInstanceOf(GenericBeanDefinition.class, beanDefinition); |
||||
GenericBeanDefinition registeredBeanDefinition = (GenericBeanDefinition) beanDefinition; |
||||
MutablePropertyValues feignClientProperties = registeredBeanDefinition.getPropertyValues(); |
||||
String className = (String) feignClientProperties.get("type"); |
||||
Assert.notNull(className, "className cannot be null"); |
||||
Class<?> clazz = ClassUtils.resolveClassName(className, null); |
||||
hints.proxies().registerJdkProxy(clazz); |
||||
registerMethodHints(hints.reflection(), clazz); |
||||
return beanFactoryInitializationCode.getMethods() |
||||
.add(buildMethodName(className), method -> generateFeignClientRegistrationMethod(method, |
||||
feignClientProperties, registeredBeanDefinition)) |
||||
.getName(); |
||||
}).collect(Collectors.toSet()); |
||||
MethodReference initializerMethod = beanFactoryInitializationCode.getMethods() |
||||
.add("initialize", method -> generateInitializerMethod(method, feignClientRegistrationMethods)) |
||||
.toMethodReference(); |
||||
beanFactoryInitializationCode.addInitializer(initializerMethod); |
||||
} |
||||
|
||||
private String buildMethodName(String clientName) { |
||||
return "register" + clientName + "FeignClient"; |
||||
} |
||||
|
||||
private void generateInitializerMethod(MethodSpec.Builder method, Set<String> feignClientRegistrationMethods) { |
||||
method.addModifiers(Modifier.PUBLIC); |
||||
method.addParameter(DefaultListableBeanFactory.class, "registry"); |
||||
feignClientRegistrationMethods.forEach(feignClientRegistrationMethod -> method.addStatement("$N(registry)", |
||||
feignClientRegistrationMethod)); |
||||
} |
||||
|
||||
private void generateFeignClientRegistrationMethod(MethodSpec.Builder method, |
||||
MutablePropertyValues feignClientPropertyValues, GenericBeanDefinition registeredBeanDefinition) { |
||||
Object feignQualifiers = feignClientPropertyValues.get("qualifiers"); |
||||
Assert.notNull(feignQualifiers, "Feign qualifiers cannot be null"); |
||||
String qualifiers = "{\"" + String.join("\",\"", (String[]) feignQualifiers) + "\"}"; |
||||
method.addJavadoc("register Feign Client: $L", feignClientPropertyValues.get("type")) |
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) |
||||
.addParameter(BeanDefinitionRegistry.class, "registry") |
||||
.addStatement("Class clazz = $T.resolveClassName(\"$L\", null)", ClassUtils.class, |
||||
feignClientPropertyValues.get("type")) |
||||
.addStatement("$T definition = $T.genericBeanDefinition($T.class)", BeanDefinitionBuilder.class, |
||||
BeanDefinitionBuilder.class, FeignClientFactoryBean.class) |
||||
.addStatement("definition.addPropertyValue(\"name\",\"$L\")", feignClientPropertyValues.get("name")) |
||||
.addStatement("definition.addPropertyValue(\"contextId\", \"$L\")", |
||||
feignClientPropertyValues.get("contextId")) |
||||
.addStatement("definition.addPropertyValue(\"type\", clazz)") |
||||
.addStatement("definition.addPropertyValue(\"url\", \"$L\")", feignClientPropertyValues.get("url")) |
||||
.addStatement("definition.addPropertyValue(\"path\", \"$L\")", |
||||
feignClientPropertyValues.get("path")) |
||||
.addStatement("definition.addPropertyValue(\"dismiss404\", $L)", |
||||
feignClientPropertyValues.get("dismiss404")) |
||||
.addStatement("definition.addPropertyValue(\"fallback\", $T.class)", |
||||
feignClientPropertyValues.get("fallback")) |
||||
.addStatement("definition.addPropertyValue(\"fallbackFactory\", $T.class)", |
||||
feignClientPropertyValues.get("fallbackFactory")) |
||||
.addStatement("definition.setAutowireMode($L)", registeredBeanDefinition.getAutowireMode()) |
||||
.addStatement("definition.setLazyInit($L)", |
||||
registeredBeanDefinition.getLazyInit() != null ? registeredBeanDefinition.getLazyInit() |
||||
: false) |
||||
.addStatement("$T beanDefinition = definition.getBeanDefinition()", AbstractBeanDefinition.class) |
||||
.addStatement("beanDefinition.setAttribute(\"$L\", clazz)", FactoryBean.OBJECT_TYPE_ATTRIBUTE) |
||||
.addStatement("beanDefinition.setPrimary($L)", registeredBeanDefinition.isPrimary()) |
||||
.addStatement("$T holder = new $T(beanDefinition, \"$L\", new String[]$L)", |
||||
BeanDefinitionHolder.class, BeanDefinitionHolder.class, |
||||
feignClientPropertyValues.get("type"), qualifiers) |
||||
.addStatement("$T.registerBeanDefinition(holder, registry) ", BeanDefinitionReaderUtils.class); |
||||
} |
||||
|
||||
// Visible for tests
|
||||
Map<String, BeanDefinition> getFeignClientBeanDefinitions() { |
||||
return feignClientBeanDefinitions; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -1,45 +0,0 @@
@@ -1,45 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2023 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.clientconfig; |
||||
|
||||
import java.net.http.HttpClient; |
||||
import java.time.Duration; |
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
/** |
||||
* Default configuration for {@link HttpClient}. |
||||
* |
||||
* @author changjin wei(魏昌进) |
||||
*/ |
||||
@Configuration(proxyBeanMethods = false) |
||||
@ConditionalOnMissingBean(HttpClient.class) |
||||
public class Http2ClientFeignConfiguration { |
||||
|
||||
@Bean |
||||
public HttpClient httpClient(FeignHttpClientProperties httpClientProperties) { |
||||
return HttpClient.newBuilder() |
||||
.followRedirects(httpClientProperties.isFollowRedirects() ? HttpClient.Redirect.ALWAYS |
||||
: HttpClient.Redirect.NEVER) |
||||
.version(HttpClient.Version.valueOf(httpClientProperties.getHttp2().getVersion())) |
||||
.connectTimeout(Duration.ofMillis(httpClientProperties.getConnectionTimeout())).build(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,135 @@
@@ -0,0 +1,135 @@
|
||||
/* |
||||
* 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.clientconfig; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Timer; |
||||
import java.util.TimerTask; |
||||
|
||||
import javax.annotation.PreDestroy; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.apache.http.client.config.RequestConfig; |
||||
import org.apache.http.config.RegistryBuilder; |
||||
import org.apache.http.conn.HttpClientConnectionManager; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.impl.client.HttpClientBuilder; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; |
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; |
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
/** |
||||
* Default configuration for {@link CloseableHttpClient}. |
||||
* |
||||
* @author Ryan Baxter |
||||
* @author Marcin Grzejszczak |
||||
* @author Spencer Gibb |
||||
* @author Olga Maciaszek-Sharma |
||||
*/ |
||||
@Configuration(proxyBeanMethods = false) |
||||
@ConditionalOnMissingBean(CloseableHttpClient.class) |
||||
public class HttpClientFeignConfiguration { |
||||
|
||||
private static final Log LOG = LogFactory.getLog(HttpClientFeignConfiguration.class); |
||||
|
||||
private final Timer connectionManagerTimer = new Timer( |
||||
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true); |
||||
|
||||
private CloseableHttpClient httpClient; |
||||
|
||||
@Autowired(required = false) |
||||
private RegistryBuilder registryBuilder; |
||||
|
||||
@Bean |
||||
@ConditionalOnMissingBean(HttpClientConnectionManager.class) |
||||
public HttpClientConnectionManager connectionManager( |
||||
ApacheHttpClientConnectionManagerFactory connectionManagerFactory, |
||||
FeignHttpClientProperties httpClientProperties) { |
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory |
||||
.newConnectionManager(httpClientProperties.isDisableSslValidation(), |
||||
httpClientProperties.getMaxConnections(), |
||||
httpClientProperties.getMaxConnectionsPerRoute(), |
||||
httpClientProperties.getTimeToLive(), |
||||
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder); |
||||
this.connectionManagerTimer.schedule(new TimerTask() { |
||||
@Override |
||||
public void run() { |
||||
connectionManager.closeExpiredConnections(); |
||||
} |
||||
}, 30000, httpClientProperties.getConnectionTimerRepeat()); |
||||
return connectionManager; |
||||
} |
||||
|
||||
@Bean |
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled", |
||||
havingValue = "true") |
||||
public CloseableHttpClient customHttpClient( |
||||
HttpClientConnectionManager httpClientConnectionManager, |
||||
FeignHttpClientProperties httpClientProperties) { |
||||
HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement() |
||||
.useSystemProperties(); |
||||
this.httpClient = createClient(builder, httpClientConnectionManager, |
||||
httpClientProperties); |
||||
return this.httpClient; |
||||
} |
||||
|
||||
@Bean |
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled", |
||||
havingValue = "false", matchIfMissing = true) |
||||
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, |
||||
HttpClientConnectionManager httpClientConnectionManager, |
||||
FeignHttpClientProperties httpClientProperties) { |
||||
this.httpClient = createClient(httpClientFactory.createBuilder(), |
||||
httpClientConnectionManager, httpClientProperties); |
||||
return this.httpClient; |
||||
} |
||||
|
||||
private CloseableHttpClient createClient(HttpClientBuilder builder, |
||||
HttpClientConnectionManager httpClientConnectionManager, |
||||
FeignHttpClientProperties httpClientProperties) { |
||||
RequestConfig defaultRequestConfig = RequestConfig.custom() |
||||
.setConnectTimeout(httpClientProperties.getConnectionTimeout()) |
||||
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build(); |
||||
CloseableHttpClient httpClient = builder |
||||
.setDefaultRequestConfig(defaultRequestConfig) |
||||
.setConnectionManager(httpClientConnectionManager).build(); |
||||
return httpClient; |
||||
} |
||||
|
||||
@PreDestroy |
||||
public void destroy() { |
||||
this.connectionManagerTimer.cancel(); |
||||
if (this.httpClient != null) { |
||||
try { |
||||
this.httpClient.close(); |
||||
} |
||||
catch (IOException e) { |
||||
if (LOG.isErrorEnabled()) { |
||||
LOG.error("Could not correctly close httpClient."); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* 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.clientconfig; |
||||
|
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import javax.annotation.PreDestroy; |
||||
|
||||
import okhttp3.ConnectionPool; |
||||
import okhttp3.OkHttpClient; |
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; |
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; |
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
/** |
||||
* Defualt configuration for {@link OkHttpClient}. |
||||
* |
||||
* @author Ryan Baxter |
||||
* @author Marcin Grzejszczak |
||||
* @author Spencer Gibb |
||||
* @author Olga Maciaszek-Sharma |
||||
*/ |
||||
@Configuration(proxyBeanMethods = false) |
||||
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class) |
||||
public class OkHttpFeignConfiguration { |
||||
|
||||
private okhttp3.OkHttpClient okHttpClient; |
||||
|
||||
@Bean |
||||
@ConditionalOnMissingBean(ConnectionPool.class) |
||||
public ConnectionPool httpClientConnectionPool( |
||||
FeignHttpClientProperties httpClientProperties, |
||||
OkHttpClientConnectionPoolFactory connectionPoolFactory) { |
||||
Integer maxTotalConnections = httpClientProperties.getMaxConnections(); |
||||
Long timeToLive = httpClientProperties.getTimeToLive(); |
||||
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); |
||||
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit); |
||||
} |
||||
|
||||
@Bean |
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, |
||||
ConnectionPool connectionPool, |
||||
FeignHttpClientProperties httpClientProperties) { |
||||
Boolean followRedirects = httpClientProperties.isFollowRedirects(); |
||||
Integer connectTimeout = httpClientProperties.getConnectionTimeout(); |
||||
this.okHttpClient = httpClientFactory |
||||
.createBuilder(httpClientProperties.isDisableSslValidation()) |
||||
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) |
||||
.followRedirects(followRedirects).connectionPool(connectionPool).build(); |
||||
return this.okHttpClient; |
||||
} |
||||
|
||||
@PreDestroy |
||||
public void destroy() { |
||||
if (this.okHttpClient != null) { |
||||
this.okHttpClient.dispatcher().executorService().shutdown(); |
||||
this.okHttpClient.connectionPool().evictAll(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -1,51 +0,0 @@
@@ -1,51 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2023 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.encoding; |
||||
|
||||
import feign.Client; |
||||
import feign.okhttp.OkHttpClient; |
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
import org.springframework.context.annotation.Condition; |
||||
|
||||
/** |
||||
* A {@link Condition} that verifies whether the conditions for creating Feign |
||||
* {@link Client} beans that either are of type {@link OkHttpClient} or have a delegate of |
||||
* type {@link OkHttpClient} are not met. |
||||
* |
||||
* @author Olga Maciaszek-Sharma |
||||
* @since 4.0.2 |
||||
*/ |
||||
public class OkHttpFeignClientBeanMissingCondition extends AnyNestedCondition { |
||||
|
||||
public OkHttpFeignClientBeanMissingCondition() { |
||||
super(ConfigurationPhase.REGISTER_BEAN); |
||||
} |
||||
|
||||
@ConditionalOnMissingClass("feign.okhttp.OkHttpClient") |
||||
static class FeignOkHttpClientPresent { |
||||
|
||||
} |
||||
|
||||
@ConditionalOnProperty(value = "spring.cloud.openfeign.okhttp.enabled", havingValue = "false") |
||||
static class FeignOkHttpClientEnabled { |
||||
|
||||
} |
||||
|
||||
} |
@ -1,41 +0,0 @@
@@ -1,41 +0,0 @@
|
||||
/* |
||||
* Copyright 2016-2022 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.hateoas; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer; |
||||
import org.springframework.hateoas.config.WebConverters; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
|
||||
/** |
||||
* @author Olga Maciaszek-Sharma |
||||
*/ |
||||
public class WebConvertersCustomizer implements HttpMessageConverterCustomizer { |
||||
|
||||
private final WebConverters webConverters; |
||||
|
||||
public WebConvertersCustomizer(WebConverters webConverters) { |
||||
this.webConverters = webConverters; |
||||
} |
||||
|
||||
@Override |
||||
public void accept(List<HttpMessageConverter<?>> httpMessageConverters) { |
||||
webConverters.augmentClient(httpMessageConverters); |
||||
} |
||||
|
||||
} |
@ -1,47 +0,0 @@
@@ -1,47 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-2022 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 feign.Request; |
||||
|
||||
import org.springframework.cloud.client.ServiceInstance; |
||||
import org.springframework.core.annotation.Order; |
||||
|
||||
/** |
||||
* Allows applications to transform the load-balanced {@link Request} given the chosen |
||||
* {@link org.springframework.cloud.client.ServiceInstance}. |
||||
* |
||||
* @author changjin wei(魏昌进) |
||||
*/ |
||||
@Order(LoadBalancerFeignRequestTransformer.DEFAULT_ORDER) |
||||
public interface LoadBalancerFeignRequestTransformer { |
||||
|
||||
/** |
||||
* Order for the {@link LoadBalancerFeignRequestTransformer}. |
||||
*/ |
||||
int DEFAULT_ORDER = 0; |
||||
|
||||
/** |
||||
* Allows transforming load-balanced requests based on the provided |
||||
* {@link ServiceInstance}. |
||||
* @param request Original request. |
||||
* @param instance ServiceInstance returned from LoadBalancer. |
||||
* @return New request or original request |
||||
*/ |
||||
Request transformRequest(Request request, ServiceInstance instance); |
||||
|
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue