Compare commits

...

116 Commits

Author SHA1 Message Date
Olga MaciaszekSharma 8222ce4bbb Fix doc intro title. 1 year ago
buildmaster b634184f50 Going back to snapshots 1 year ago
buildmaster 3d6b89ffd8 Update SNAPSHOT to 4.1.0-M2 1 year ago
Olga MaciaszekSharma 89d30ad32f Merge 4.0.x. 1 year ago
Olga MaciaszekSharma a738a87dda Merge remote-tracking branch 'origin/3.1.x' into 4.0.x 1 year ago
Alexey Elin 7ee6246991 document requirements for decoders (+ others) specified via class in the config 1 year ago
dependabot[bot] 1926023ea1
Bump actions/setup-java from 2 to 3 (#898) 1 year ago
dependabot[bot] a130f31761
Bump codecov/codecov-action from 1 to 3 (#899) 1 year ago
dependabot[bot] 28a701bbd5
Bump actions/checkout from 2 to 4 (#901) 1 year ago
dependabot[bot] 7f6d13c2bc
Bump actions/checkout from 2 to 4 (#904) 1 year ago
dependabot[bot] 0dbc8f287a
Bump actions/setup-java from 2 to 3 (#906) 1 year ago
dependabot[bot] a5b5d203e6
Bump com.fasterxml.jackson.dataformat:jackson-dataformat-smile (#903) 1 year ago
dependabot[bot] 0d85c6d548
Bump io.github.openfeign:feign-bom from 12.4 to 12.5 (#907) 1 year ago
dependabot[bot] f39ae8e67e
Bump codecov/codecov-action from 1 to 3 (#908) 1 year ago
dependabot[bot] 294d964f71
Bump com.fasterxml.jackson.dataformat:jackson-dataformat-smile (#905) 1 year ago
dependabot[bot] 283c73c21c
Bump commons-io:commons-io from 2.11.0 to 2.13.0 (#911) 1 year ago
spencergibb 1d0b70251e
Adds antora extension and update ui version 1 year ago
spencergibb 2a5e815b6b
Removes setting FactoryBean.OBJECT_TYPE_ATTRIBUTE 1 year ago
Olga MaciaszekSharma 3a353b13ec Upgrade to antora-ui-spring 0.3.7. 1 year ago
buildmaster 52ec6def53 Bumping versions 1 year ago
Olga MaciaszekSharma 8bc5decb8e Add dependabot.yml. 1 year ago
Olga MaciaszekSharma 0e9bbb55b1 Merge remote-tracking branch 'origin/4.0.x' 1 year ago
Olga MaciaszekSharma cbb711c08d Merge remote-tracking branch 'origin/3.1.x' into 4.0.x 1 year ago
Olga MaciaszekSharma 835b9b5746 Fix checkstyle. 1 year ago
Olga MaciaszekSharma b80680e17d Remove Codacy badge. 1 year ago
Olga MaciaszekSharma 9876860dcb Update readme. 1 year ago
Olga MaciaszekSharma 31201de020 Fix GH actions setup and readme. 1 year ago
Olga MaciaszekSharma a0dafafc3f Fix readme links. 1 year ago
Olga MaciaszekSharma a8b5494451 Fix formatting in configprops entry. 1 year ago
Olga Maciaszek-Sharma 17a2b8cada
Update docs structure. (#894) 1 year ago
Olga MaciaszekSharma 352760c2f0 Update gitignore. 1 year ago
buildmaster 6e93dafa2a Bumping versions 1 year ago
Olga Maciaszek-Sharma fc6fd70abd
Antora (#892) 1 year ago
Olga MaciaszekSharma 6fc80939d6 Edit maven.config. 1 year ago
Olga MaciaszekSharma 0c1bafc2fa Add back maven.config. 1 year ago
Olga MaciaszekSharma 802defdd9f Remove maven.config. 1 year ago
Olga MaciaszekSharma 86c74a4636 Merge remote-tracking branch 'origin/main' 1 year ago
Olga MaciaszekSharma fd16ec8593 Upgrade Maven wrapper. 1 year ago
Olga MaciaszekSharma 22edbeca2b Upgrade Maven wrapper. 1 year ago
spencergibb 3fc733f7ad
Removes setting FactoryBean.OBJECT_TYPE_ATTRIBUTE 1 year ago
spencergibb 7798edef69
Change attribute from String to Class 1 year ago
buildmaster 63a2465503 Going back to snapshots 1 year ago
buildmaster bba14dff62 Update SNAPSHOT to 4.1.0-M1 1 year ago
buildmaster d04022602d Bumping versions to 4.0.5-SNAPSHOT after release 1 year ago
buildmaster 24c8990fe6 Going back to snapshots 1 year ago
buildmaster 3bdf70948a Update SNAPSHOT to 4.0.4 1 year ago
Olga MaciaszekSharma 9761a18a9a Merge remote-tracking branch 'origin/4.0.x' 1 year ago
Olga MaciaszekSharma d59bc46ebe Upgrade to Feign 12.4. Fixes gh-878. 1 year ago
spencergibb 413f361f87
Test updates to work around new behavior. 1 year ago
buildmaster 45d28abb49 Bumping versions to 3.1.9-SNAPSHOT after release 1 year ago
buildmaster a0ca64a90f Going back to snapshots 1 year ago
buildmaster 92e5d92e52 Update SNAPSHOT to 3.1.8 1 year ago
小魏,小魏,我们要去哪里呀 8020b8840e
Support java 11 http2 client gh-689 (#869) 1 year ago
spencergibb cca510cbb2
Updates to version 4.1.0-SNAPSHOT 1 year ago
Olga MaciaszekSharma 235eb6d207 Merge remote-tracking branch 'origin/3.1.x' 1 year ago
Olga MaciaszekSharma 4b3c0d0039 Remove vulnerable commons-fileupload dependency. Fixes gh-853. 1 year ago
buildmaster 11686a1966 Bumping versions 1 year ago
buildmaster c5b9a04ab0 Bumping versions 1 year ago
buildmaster 491e4bbe39 Bumping versions to 4.0.4-SNAPSHOT after release 1 year ago
buildmaster 61b7e94e6f Going back to snapshots 1 year ago
buildmaster 23e5a08650 Update SNAPSHOT to 4.0.3 1 year ago
Olga MaciaszekSharma 07ded06f73 Set root logging level for tests to debug. 2 years ago
Olga MaciaszekSharma fd0958e190 Upgrade Feign to 12.3. Fixes gh-862. 2 years ago
buildmaster 04da648959 Bumping versions 2 years ago
buildmaster 35f28bdaed Bumping versions to 3.1.8-SNAPSHOT after release 2 years ago
buildmaster 5c021abb64 Going back to snapshots 2 years ago
buildmaster 5ed7831723 Update SNAPSHOT to 3.1.7 2 years ago
Olga MaciaszekSharma d411f57b7c Fix sample yaml. 2 years ago
Olga MaciaszekSharma 033e6a85d0 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga MaciaszekSharma 5bc8856903 Fix typo in docs. 2 years ago
buildmaster 0828fff93f Bumping versions to 4.0.3-SNAPSHOT after release 2 years ago
buildmaster 974631b9c2 Going back to snapshots 2 years ago
buildmaster 6ff470d528 Update SNAPSHOT to 4.0.2 2 years ago
Olga Maciaszek-Sharma 06192a575c
Register method params for reflection (#849) 2 years ago
Olga Maciaszek-Sharma 85c41513f9 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma 6e48199c42 Fix scm urls in flattened module poms. Fixes gh-761. 2 years ago
Olga Maciaszek-Sharma b7de95e0df Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma e70b66c187 Clarify example in docs. Fixes gh-805. 2 years ago
Olga Maciaszek-Sharma e09bb5f23f Fix metadata for oauth2. Fixes gh-811. 2 years ago
Olga Maciaszek-Sharma 21c2b829ee merge 3.1.x 2 years ago
Olga Maciaszek-Sharma 5e7eab89a5 Do not manage spring-security-oauth2-client. Fixes gh-786. 2 years ago
Olga Maciaszek-Sharma 8e9b11ffdf Fix pageable processing when querymap present. (#843) 2 years ago
Olga Maciaszek-Sharma f61ca17ad5
Fix pageable processing when querymap present. (#843) 2 years ago
Olga Maciaszek-Sharma 471c5c5cb1 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
apikozh 23ca17df2c
Reuse serviceInstance for retries Fixes gh-840 (#841) 2 years ago
MangKyu 874ff0d383
Docs: Update enable circuitbreaker yaml examples (#831) 2 years ago
buildmaster 647b0b3d1a Bumping versions to 3.1.7-SNAPSHOT after release 2 years ago
buildmaster 6be252163f Going back to snapshots 2 years ago
buildmaster 01ef53091e Update SNAPSHOT to 3.1.6 2 years ago
Olga Maciaszek-Sharma 5568778f6d Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma bc3f12a247 Handle Pageable POST params as query maps. Update docs. Fixes gh-753. 2 years ago
Olga Maciaszek-Sharma 701c4c0c61 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma 68c4513ed9
Fix condition. (#834) 2 years ago
Olga Maciaszek-Sharma 54a6a56b36
Property based url configurable at runtime in AOT/ native (#827) 2 years ago
Olga Maciaszek-Sharma da159123d4
Allow setting Ok HTTP client protocols (#825) 2 years ago
min1854 b0d9438208
fallback support FacotryBean (#823) 2 years ago
buildmaster 41565b73a3 Bumping versions to 4.0.2-SNAPSHOT after release 2 years ago
buildmaster 280283b1c5 Going back to snapshots 2 years ago
buildmaster 2c946e5924 Update SNAPSHOT to 4.0.1 2 years ago
Ryan Baxter c10b06ec07 Adding junit-platform dependency 2 years ago
buildmaster ec245b03f7 Bumping versions 2 years ago
Olga Maciaszek-Sharma 7ad582dbe9 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
小魏,小魏,我们要去哪里呀 10b55a0a13
connectionRequestTimeout for httpClient Fixes gh-799 (#817) 2 years ago
buildmaster 80c80556b4 Bumping versions 2 years ago
buildmaster 9a7957e419 Bumping versions 2 years ago
buildmaster b6c2c821fc Bumping versions to 4.0.1-SNAPSHOT after release 2 years ago
buildmaster 96383d53f9 Going back to snapshots 2 years ago
buildmaster bb09a2e3aa Update SNAPSHOT to 4.0.0 2 years ago
Olga Maciaszek-Sharma 8365c90ce5 Update javadocs. 2 years ago
Olga Maciaszek-Sharma bcb9b74889 merge 3.1.x 2 years ago
Dominique Villard fead135d92
adds support for response interceptor configuration. Fixes gh-802 (#804) 2 years ago
小魏,小魏,我们要去哪里呀 ba07f19c50
Remove LoadBalancerFeignRequestTransformer (#784) 2 years ago
Jared Rufer 8a1f888aac
Disable hostname verification when ssl validation is disabled. (#798) 2 years ago
buildmaster 935405407a Going back to snapshots 2 years ago
buildmaster 9cf0d2c584 Update SNAPSHOT to 4.0.0-RC3 2 years ago
Olga Maciaszek-Sharma 7f66cf3829
Add AOT and native image support (#794) 2 years ago
  1. 49
      .github/dependabot.yml
  2. 32
      .github/workflows/deploy-docs.yml
  3. 6
      .github/workflows/maven.yml
  4. 16
      .gitignore
  5. 1
      .java-version
  6. 3
      .mvn/maven.config
  7. BIN
      .mvn/wrapper/maven-wrapper.jar
  8. 19
      .mvn/wrapper/maven-wrapper.properties
  9. 292
      README.adoc
  10. 39
      docs/antora-playbook.yml
  11. 12
      docs/antora.yml
  12. 4
      docs/modules/ROOT/nav.adoc
  13. 2
      docs/modules/ROOT/pages/_attributes.adoc
  14. 7
      docs/modules/ROOT/pages/appendix.adoc
  15. 6
      docs/modules/ROOT/pages/configprops.adoc
  16. 1
      docs/modules/ROOT/pages/index.adoc
  17. 3
      docs/modules/ROOT/pages/intro.adoc
  18. 89
      docs/modules/ROOT/pages/spring-cloud-openfeign.adoc
  19. 3
      docs/modules/ROOT/partials/_attributes.adoc
  20. 113
      docs/modules/ROOT/partials/_configprops.adoc
  21. 6
      docs/modules/ROOT/partials/_conventions.adoc
  22. 6
      docs/modules/ROOT/partials/_metrics.adoc
  23. 6
      docs/modules/ROOT/partials/_spans.adoc
  24. 65
      docs/pom.xml
  25. 20
      docs/src/main/antora/resources/antora-resources/antora.yml
  26. 15
      docs/src/main/asciidoc/README.adoc
  27. 37
      docs/src/main/asciidoc/_configprops.adoc
  28. 1
      docs/src/main/asciidoc/index.adoc
  29. 303
      mvnw
  30. 96
      mvnw.cmd
  31. 10
      pom.xml
  32. 30
      spring-cloud-openfeign-core/pom.xml
  33. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/DefaultTargeter.java
  34. 69
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java
  35. 14
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java
  36. 39
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java
  37. 26
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java
  38. 69
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
  39. 18
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java
  40. 30
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientSpecification.java
  41. 70
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java
  42. 49
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/PropertyBasedTarget.java
  43. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/Targeter.java
  44. 126
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java
  45. 215
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java
  46. 45
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/Http2ClientFeignConfiguration.java
  47. 9
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClient5FeignConfiguration.java
  48. 9
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java
  49. 9
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingAutoConfiguration.java
  50. 51
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/OkHttpFeignClientBeanMissingCondition.java
  51. 4
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfiguration.java
  52. 76
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/Http2ClientFeignLoadBalancerConfiguration.java
  53. 7
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java
  54. 91
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignHttpClientProperties.java
  55. 36
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java
  56. 14
      spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  57. 2
      spring-cloud-openfeign-core/src/main/resources/META-INF/spring/aot.factories
  58. 169
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EagerInitFeignClientUsingConfigurerTests.java
  59. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java
  60. 8
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java
  61. 3
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java
  62. 1
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientCacheTests.java
  63. 5
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java
  64. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java
  65. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java
  66. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java
  67. 38
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java
  68. 19
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTests.java
  69. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java
  70. 43
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java
  71. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientWithRefreshableOptionsTest.java
  72. 46
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignCompressionTests.java
  73. 70
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttp2ClientConfigurationTests.java
  74. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java
  75. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlWithRetryableLoadBalancerTests.java
  76. 12
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignOkHttpConfigurationTests.java
  77. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/GzipDecodingTests.java
  78. 10
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/LazyInitFeignClientUsingConfigurerTests.java
  79. 5
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/NonRefreshableFeignClientUrlTests.java
  80. 2
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java
  81. 171
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/aot/FeignAotTests.java
  82. 197
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessorTests.java
  83. 3
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/AsyncCircuitBreakerTests.java
  84. 134
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/FallbackSupportFactoryBeanTests.java
  85. 36
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java
  86. 44
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java
  87. 79
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/protocol/FeignOkHttpProtocolsTests.java
  88. 37
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/FeignHttpClientPropertiesTests.java
  89. 5
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageJacksonModuleTests.java
  90. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java
  91. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoderTests.java
  92. 4
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java
  93. 44
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java
  94. 81
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/test/Http2ClientConfigurationTests.java
  95. 256
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignHttp2ClientTests.java
  96. 1
      spring-cloud-openfeign-core/src/test/resources/feign-properties.properties
  97. 11
      spring-cloud-openfeign-core/src/test/resources/logback-test.xml
  98. 17
      spring-cloud-openfeign-dependencies/pom.xml
  99. 5
      spring-cloud-starter-openfeign/pom.xml

49
.github/dependabot.yml

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
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

32
.github/workflows/deploy-docs.yml

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
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)

6
.github/workflows/maven.yml

@ -19,15 +19,15 @@ jobs: @@ -19,15 +19,15 @@ jobs:
java: ["17"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
cache: 'maven'
- name: Build with Maven
run: ./mvnw clean install -B -U -P sonar
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true

16
.gitignore vendored

@ -1,3 +1,6 @@ @@ -1,3 +1,6 @@
/application.yml
/application.properties
asciidoctor.css
*~
#*
*#
@ -5,6 +8,7 @@ @@ -5,6 +8,7 @@
.classpath
.project
.settings/
.settings
.springBeans
target/
bin/
@ -18,6 +22,16 @@ _site/ @@ -18,6 +22,16 @@ _site/
.shelf
*.swp
*.swo
/spring-cloud-release-tools*.jar
antrun
.vscode/
.flattened-pom.xml
node
node_modules
build
_configprops.adoc
_spans.adoc
_metrics.adoc
_conventions.adoc
package.json
package-lock.json

1
.java-version

@ -0,0 +1 @@ @@ -0,0 +1 @@
17

3
.mvn/maven.config

@ -1 +1,2 @@ @@ -1 +1,2 @@
-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

BIN
.mvn/wrapper/maven-wrapper.jar vendored

Binary file not shown.

19
.mvn/wrapper/maven-wrapper.properties vendored

@ -1 +1,18 @@ @@ -1 +1,18 @@
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
# 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

292
README.adoc

@ -7,43 +7,21 @@ Edit the files in the src/main/asciidoc/ directory instead. @@ -7,43 +7,21 @@ Edit the files in the src/main/asciidoc/ directory instead.
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://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"]
:doctype: book
:idprefix:
:idseparator: -
:toc: left
:toclevels: 4
:tabsize: 4
:numbered:
:sectanchors:
:sectnums:
:icons: font
:hide-uri-scheme:
:docinfo: shared,private
:sc-ext: java
:project-full-name: Spring Cloud OpenFeign
:all: {asterisk}{asterisk}
:core_path: {project-root}/spring-cloud-openfeign-core
This project provides OpenFeign integrations for Spring Boot apps through autoconfiguration
and binding to the Spring Environment and other Spring programming model idioms.
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"]
[[features]]
== Features
* Declarative REST Client: Feign creates a dynamic implementation of an interface decorated with JAX-RS or Spring MVC annotations
[[building]]
== Building
:jdkversion: 17
=== Basic Compile and Test
[[basic-compile-and-test]]
== Basic Compile and Test
To build the source you will need to install JDK {jdkversion}.
@ -70,31 +48,36 @@ source control. @@ -70,31 +48,36 @@ source control.
The projects that require middleware (i.e. Redis) for testing generally
require that a local instance of [Docker](https://www.docker.com/get-started) is installed and running.
=== Documentation
[[documentation]]
== Documentation
The spring-cloud-build module has a "docs" profile, and if you switch
that on it will try to build asciidoc sources from
`src/main/asciidoc`. As part of that process it will look for a
`README.adoc` and process it by loading all the includes, but not
that on it will try to build asciidoc sources using https://docs.antora.org/antora/latest/[Antora] from
`modules/ROOT/`.
As part of that process it will look for a
`docs/src/main/asciidoc/README.adoc` and process it by loading all the includes, but not
parsing or rendering it, just copying it to `${main.basedir}`
(defaults to `${basedir}`, i.e. the root of the project). If there are
(defaults to `$\{basedir}`, i.e. the root of the project). If there are
any changes in the README it will then show up after a Maven build as
a modified file in the correct place. Just commit it and push the change.
=== Working with the code
[[working-with-the-code]]
== Working with the code
If you don't have an IDE preference we would recommend that you use
https://www.springsource.com/developer/sts[Spring Tools Suite] or
https://eclipse.org[Eclipse] when working with the code. We use the
https://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools
should also work without issue as long as they use Maven 3.3.3 or better.
==== Activate the Spring Maven profile
[[activate-the-spring-maven-profile]]
=== Activate the Spring Maven profile
Spring Cloud projects require the 'spring' Maven profile to be activated to resolve
the spring milestone and snapshot repositories. Use your preferred IDE to set this
profile to be active, or you may experience build errors.
==== Importing into eclipse with m2eclipse
[[importing-into-eclipse-with-m2eclipse]]
=== Importing into eclipse with m2eclipse
We recommend the https://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with
eclipse. If you don't already have m2eclipse installed it is available from the "eclipse
marketplace".
@ -108,7 +91,8 @@ add the "spring" profile to your `settings.xml`. Alternatively you can @@ -108,7 +91,8 @@ add the "spring" profile to your `settings.xml`. Alternatively you can
copy the repository settings from the "spring" profile of the parent
pom into your `settings.xml`.
==== Importing into eclipse without m2eclipse
[[importing-into-eclipse-without-m2eclipse]]
=== Importing into eclipse without m2eclipse
If you prefer not to use m2eclipse you can generate eclipse project metadata using the
following command:
@ -121,240 +105,12 @@ The generated eclipse projects can be imported by selecting `import existing pro @@ -121,240 +105,12 @@ The generated eclipse projects can be imported by selecting `import existing pro
from the `file` menu.
[[contributing]]
== Contributing
:spring-cloud-build-branch: master
Spring Cloud is released under the non-restrictive Apache 2.0 license,
and follows a very standard Github development process, using Github
tracker for issues and merging pull requests into master. If you want
to contribute even something trivial please do not hesitate, but
follow the guidelines below.
=== Sign the Contributor License Agreement
Before we accept a non-trivial patch or pull request we will need you to sign the
https://cla.pivotal.io/sign/spring[Contributor License Agreement].
Signing the contributor's agreement does not grant anyone commit rights to the main
repository, but it does mean that we can accept your contributions, and you will get an
author credit if we do. Active contributors might be asked to join the core team, and
given the ability to merge pull requests.
=== Code of Conduct
This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/master/docs/src/main/asciidoc/code-of-conduct.adoc[code of
conduct]. By participating, you are expected to uphold this code. Please report
unacceptable behavior to spring-code-of-conduct@pivotal.io.
=== Code Conventions and Housekeeping
None of these is essential for a pull request, but they will all help. They can also be
added after the original pull request but before a merge.
* Use the Spring Framework code format conventions. If you use Eclipse
you can import formatter settings using the
`eclipse-code-formatter.xml` file from the
https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring
Cloud Build] project. If using IntelliJ, you can use the
https://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter
Plugin] to import the same file.
* Make sure all new `.java` files to have a simple Javadoc class comment with at least an
`@author` tag identifying you, and preferably at least a paragraph on what the class is
for.
* Add the ASF license header comment to all new `.java` files (copy from existing files
in the project)
* Add yourself as an `@author` to the .java files that you modify substantially (more
than cosmetic changes).
* Add some Javadocs and, if you change the namespace, some XSD doc elements.
* A few unit tests would help a lot as well -- someone has to do it.
* If no-one else is using your branch, please rebase it against the current master (or
other target branch in the main project).
* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions],
if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit
message (where XXXX is the issue number).
=== Checkstyle
Spring Cloud Build comes with a set of checkstyle rules. You can find them in the `spring-cloud-build-tools` module. The most notable files under the module are:
.spring-cloud-build-tools/
----
└── src
   ├── checkstyle
     └── checkstyle-suppressions.xml <3>
   └── main
   └── resources
   ├── checkstyle-header.txt <2>
   └── checkstyle.xml <1>
----
<1> Default Checkstyle rules
<2> File header setup
<3> Default suppression rules
==== Checkstyle configuration
Checkstyle rules are *disabled by default*. To add checkstyle to your project just define the following properties and plugins.
.pom.xml
----
<properties>
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError> <1>
<maven-checkstyle-plugin.failsOnViolation>true
</maven-checkstyle-plugin.failsOnViolation> <2>
<maven-checkstyle-plugin.includeTestSourceDirectory>true
</maven-checkstyle-plugin.includeTestSourceDirectory> <3>
</properties>
<build>
<plugins>
<plugin> <4>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
</plugin>
<plugin> <5>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
</plugins>
<reporting>
<plugins>
<plugin> <5>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</build>
----
<1> Fails the build upon Checkstyle errors
<2> Fails the build upon Checkstyle violations
<3> Checkstyle analyzes also the test sources
<4> Add the Spring Java Format plugin that will reformat your code to pass most of the Checkstyle formatting rules
<5> Add checkstyle plugin to your build and reporting phases
If you need to suppress some rules (e.g. line length needs to be longer), then it's enough for you to define a file under `${project.root}/src/checkstyle/checkstyle-suppressions.xml` with your suppressions. Example:
.projectRoot/src/checkstyle/checkstyle-suppresions.xml
----
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
"https://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress files=".*ConfigServerApplication\.java" checks="HideUtilityClassConstructor"/>
<suppress files=".*ConfigClientWatch\.java" checks="LineLengthCheck"/>
</suppressions>
----
It's advisable to copy the `${spring-cloud-build.rootFolder}/.editorconfig` and `${spring-cloud-build.rootFolder}/.springformat` to your project. That way, some default formatting rules will be applied. You can do so by running this script:
```bash
$ curl https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/.editorconfig -o .editorconfig
$ touch .springformat
```
=== IDE setup
==== Intellij IDEA
In order to setup Intellij you should import our coding conventions, inspection profiles and set up the checkstyle plugin.
The following files can be found in the https://github.com/spring-cloud/spring-cloud-build/tree/master/spring-cloud-build-tools[Spring Cloud Build] project.
.spring-cloud-build-tools/
----
└── src
   ├── checkstyle
     └── checkstyle-suppressions.xml <3>
   └── main
   └── resources
   ├── checkstyle-header.txt <2>
   ├── checkstyle.xml <1>
   └── intellij
      ├── Intellij_Project_Defaults.xml <4>
      └── Intellij_Spring_Boot_Java_Conventions.xml <5>
----
<1> Default Checkstyle rules
<2> File header setup
<3> Default suppression rules
<4> Project defaults for Intellij that apply most of Checkstyle rules
<5> Project style conventions for Intellij that apply most of Checkstyle rules
.Code style
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-code-style.png[Code style]
Go to `File` -> `Settings` -> `Editor` -> `Code style`. There click on the icon next to the `Scheme` section. There, click on the `Import Scheme` value and pick the `Intellij IDEA code style XML` option. Import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Spring_Boot_Java_Conventions.xml` file.
.Inspection profiles
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-inspections.png[Code style]
Go to `File` -> `Settings` -> `Editor` -> `Inspections`. There click on the icon next to the `Profile` section. There, click on the `Import Profile` and import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Project_Defaults.xml` file.
.Checkstyle
To have Intellij work with Checkstyle, you have to install the `Checkstyle` plugin. It's advisable to also install the `Assertions2Assertj` to automatically convert the JUnit assertions
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-checkstyle.png[Checkstyle]
Go to `File` -> `Settings` -> `Other settings` -> `Checkstyle`. There click on the `+` icon in the `Configuration file` section. There, you'll have to define where the checkstyle rules should be picked from. In the image above, we've picked the rules from the cloned Spring Cloud Build repository. However, you can point to the Spring Cloud Build's GitHub repository (e.g. for the `checkstyle.xml` : `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/main/resources/checkstyle.xml`). We need to provide the following variables:
- `checkstyle.header.file` - please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/main/resources/checkstyle-header.txt` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/main/resources/checkstyle-header.txt` URL.
- `checkstyle.suppressions.file` - default suppressions. Please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` URL.
- `checkstyle.additional.suppressions.file` - this variable corresponds to suppressions in your local project. E.g. you're working on `spring-cloud-contract`. Then point to the `project-root/src/checkstyle/checkstyle-suppressions.xml` folder. Example for `spring-cloud-contract` would be: `/home/username/spring-cloud-contract/src/checkstyle/checkstyle-suppressions.xml`.
IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources.
=== Duplicate Finder
Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, that enables flagging duplicate and conflicting classes and resources on the java classpath.
==== Duplicate Finder configuration
Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`.
.pom.xml
[source,xml]
----
<build>
<plugins>
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
----
For other properties, we have set defaults as listed in the https://github.com/basepom/duplicate-finder-maven-plugin/wiki[plugin documentation].
You can easily override them but setting the value of the selected property prefixed with `duplicate-finder-maven-plugin`. For example, set `duplicate-finder-maven-plugin.skip` to `true` in order to skip duplicates check in your build.
If you need to add `ignoredClassPatterns` or `ignoredResourcePatterns` to your setup, make sure to add them in the plugin configuration section of your project:
[source,xml]
----
<build>
<plugins>
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
<configuration>
<ignoredClassPatterns>
<ignoredClassPattern>org.joda.time.base.BaseDateTime</ignoredClassPattern>
<ignoredClassPattern>.*module-info</ignoredClassPattern>
</ignoredClassPatterns>
<ignoredResourcePatterns>
<ignoredResourcePattern>changelog.txt</ignoredResourcePattern>
</ignoredResourcePatterns>
</configuration>
</plugin>
</plugins>
</build>
----
NOTE: Spring Cloud is released under the non-restrictive Apache 2.0 license. If you would like to contribute to this section of the documentation or if you find an error, please find the source code and issue trackers in the project at {github-project}[github].
[[license]]
== License
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/main/LICENSE.txt[here].

39
docs/antora-playbook.yml

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
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

12
docs/antora.yml

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
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/

4
docs/modules/ROOT/nav.adoc

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
* xref:index.adoc[Introduction]
* xref:spring-cloud-openfeign.adoc[]
* xref:appendix.adoc[]
** xref:configprops.adoc[]

2
docs/src/main/asciidoc/_attributes.adoc → docs/modules/ROOT/pages/_attributes.adoc

@ -1,8 +1,6 @@ @@ -1,8 +1,6 @@
:doctype: book
:idprefix:
:idseparator: -
:toc: left
:toclevels: 4
:tabsize: 4
:numbered:
:sectanchors:

7
docs/src/main/asciidoc/appendix.adoc → docs/modules/ROOT/pages/appendix.adoc

@ -1,14 +1,13 @@ @@ -1,14 +1,13 @@
:numbered!:
[appendix]
[[common-application-properties]]
== Common application properties
= Common application properties
:page-section-summary-toc: 1
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 {project-full-name} properties and references to the underlying classes that consume them.
This appendix provides a list of common Spring Cloud OpenFeign 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[]

6
docs/modules/ROOT/pages/configprops.adoc

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
[[configuration-properties]]
= Configuration Properties
Below you can find a list of configuration properties.
include::partial$_configprops.adoc[]

1
docs/modules/ROOT/pages/index.adoc

@ -0,0 +1 @@ @@ -0,0 +1 @@
include::intro.adoc[]

3
docs/src/main/asciidoc/intro.adoc → docs/modules/ROOT/pages/intro.adoc

@ -1,3 +1,6 @@ @@ -1,3 +1,6 @@
[[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.

89
docs/src/main/asciidoc/spring-cloud-openfeign.adoc → docs/modules/ROOT/pages/spring-cloud-openfeign.adoc

@ -1,10 +1,5 @@ @@ -1,10 +1,5 @@
= Spring Cloud OpenFeign
include::_attributes.adoc[]
*{spring-cloud-version}*
include::intro.adoc[]
[[features]]
= Spring Cloud OpenFeign Features
[[spring-cloud-feign]]
== Declarative REST Client: Feign
@ -78,6 +73,15 @@ TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-class @@ -78,6 +73,15 @@ TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-class
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
@ -129,12 +133,12 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT @@ -129,12 +133,12 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT
* `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 been added to your project if you want to use it.
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 and Apache HttpClient 5 Feign clients can be used by setting `spring.cloud.openfeign.okhttp.enabled` or `spring.cloud.openfeign.httpclient.hc5.enabled` to `true`, respectively, and having them on the classpath.
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 and the ones prefixed with `httpclient.okhttp` to OkHttpClient. You can find a full list of properties you can customise in the appendix.
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.
@ -197,6 +201,7 @@ spring: @@ -197,6 +201,7 @@ spring:
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
responseInterceptor: com.example.BazResponseInterceptor
dismiss404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
@ -207,6 +212,8 @@ spring: @@ -207,6 +212,8 @@ spring:
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.
@ -278,6 +285,7 @@ public FeignClientConfigurer feignClientConfigurer() { @@ -278,6 +285,7 @@ public FeignClientConfigurer feignClientConfigurer() {
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.
@ -294,6 +302,7 @@ We can configure timeouts on both the default and the named client. OpenFeign wo @@ -294,6 +302,7 @@ We can configure timeouts on both the default and the named client. OpenFeign wo
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
@ -403,11 +412,13 @@ You could configure it using configuration properties by doing the following @@ -403,11 +412,13 @@ You could configure it using configuration properties by doing the following
[source,yaml,indent=0]
----
feign:
circuitbreaker:
enabled: true
alphanumeric-ids:
enabled: true
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
alphanumeric-ids:
enabled: true
resilience4j:
circuitbreaker:
instances:
@ -497,6 +508,7 @@ If one needs access to the cause that made the fallback trigger, one can use the @@ -497,6 +508,7 @@ If one needs access to the cause that made the fallback trigger, one can use the
}
----
[[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.
@ -547,6 +559,7 @@ public interface UserClient extends UserService { @@ -547,6 +559,7 @@ 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
@ -569,9 +582,12 @@ spring.cloud.openfeign.compression.request.min-request-size=2048 @@ -569,9 +582,12 @@ 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.
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
@ -600,10 +616,11 @@ public class FooConfiguration { @@ -600,10 +616,11 @@ public class FooConfiguration {
}
----
[[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 <<micrometer-support>>.
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.
@ -618,6 +635,7 @@ public class FooConfiguration { @@ -618,6 +635,7 @@ public class FooConfiguration {
}
----
[[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:
@ -666,6 +684,7 @@ public class FooConfiguration { @@ -666,6 +684,7 @@ public class FooConfiguration {
}
----
[[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:
@ -682,6 +701,7 @@ public interface DemoClient { @@ -682,6 +701,7 @@ public interface DemoClient {
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
@ -714,6 +734,7 @@ public interface DemoTemplate { @@ -714,6 +734,7 @@ public interface DemoTemplate {
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].
@ -734,6 +755,7 @@ public interface DemoTemplate { @@ -734,6 +755,7 @@ public interface DemoTemplate {
}
----
[[spring-matrixvariable-support]]
=== Spring @MatrixVariable Support
Spring Cloud OpenFeign provides support for the Spring `@MatrixVariable` annotation.
@ -764,6 +786,7 @@ public interface DemoTemplate { @@ -764,6 +786,7 @@ public interface DemoTemplate {
}
----
[[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.
@ -773,22 +796,22 @@ In the following example, the `CSV` format is used instead of the default `EXPLO @@ -773,22 +796,22 @@ In the following example, the `CSV` format is used instead of the default `EXPLO
[source,java,indent=0]
----
@FeignClient(name = "demo")
protected interface PageableFeignClient {
protected interface DemoFeignClient {
@CollectionFormat(feign.CollectionFormat.CSV)
@GetMapping(path = "/page")
ResponseEntity performRequest(Pageable page);
@GetMapping(path = "/test")
ResponseEntity performRequest(String test);
}
----
TIP: Set the `CSV` format while sending `Pageable` as a query parameter in order for it to be encoded correctly.
[[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.
@ -800,6 +823,7 @@ To work around this problem you can use an `ObjectProvider` when autowiring your @@ -800,6 +823,7 @@ To work around this problem you can use an `ObjectProvider` when autowiring your
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.
@ -812,12 +836,13 @@ spring.cloud.openfeign.autoconfiguration.jackson.enabled=false @@ -812,12 +836,13 @@ 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.
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`.
@ -828,6 +853,7 @@ spring.cloud.openfeign.client.refresh-enabled=true @@ -828,6 +853,7 @@ 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:
----
@ -840,6 +866,7 @@ TIP:: Using the `serviceId` as OAuth2 client registrationId is convenient for lo @@ -840,6 +866,7 @@ TIP:: Using the `serviceId` as OAuth2 client registrationId is convenient for lo
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.
@ -867,6 +894,7 @@ For `Request`, you need to implement and define `LoadBalancerFeignRequestTransfo @@ -867,6 +894,7 @@ For `Request`, you need to implement and define `LoadBalancerFeignRequestTransfo
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:
@ -876,6 +904,7 @@ Alternatively, you can use `LoadBalancerFeignRequestTransformer.DEFAULT_ORDER` t @@ -876,6 +904,7 @@ Alternatively, you can use `LoadBalancerFeignRequestTransformer.DEFAULT_ORDER` t
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:
@ -905,6 +934,20 @@ The URL provided in the configuration properties remains unused. @@ -905,6 +934,20 @@ The URL provided in the configuration properties remains unused.
|===
[[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].

3
docs/modules/ROOT/partials/_attributes.adoc

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
:sc-ext: java
:project-full-name: Spring Cloud OpenFeign
:all: {asterisk}{asterisk}

113
docs/modules/ROOT/partials/_configprops.adoc

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
|===
|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.
|===

6
docs/modules/ROOT/partials/_conventions.adoc

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
[[observability-conventions]]
=== Observability - Conventions
Below you can find a list of all `GlobalObservationConvention` and `ObservationConvention` declared by this project.

6
docs/modules/ROOT/partials/_metrics.adoc

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
[[observability-metrics]]
=== Observability - Metrics
Below you can find a list of all metrics declared by this project.

6
docs/modules/ROOT/partials/_spans.adoc

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
[[observability-spans]]
=== Observability - Spans
Below you can find a list of all spans declared by this project.

65
docs/pom.xml

@ -3,22 +3,32 @@ @@ -3,22 +3,32 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-docs</artifactId>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-openfeign-docs</artifactId>
<scm>
<url>https://github.com/spring-cloud/spring-cloud-openfeign</url>
</scm>
<packaging>jar</packaging>
<name>Spring Cloud OpenFeign Docs</name>
<description>Spring Cloud Docs</description>
<description>Spring Cloud OpenFeign Docs</description>
<properties>
<docs.main>spring-cloud-openfeign</docs.main>
<main.basedir>${basedir}/..</main.basedir>
<configprops.inclusionPattern>feign.*</configprops.inclusionPattern>
<upload-docs-zip.phase>deploy</upload-docs-zip.phase>
<configprops.inclusionPattern>spring.cloud.*</configprops.inclusionPattern>
<!-- Don't upload docs jar to central / repo.spring.io -->
<maven-deploy-plugin-default.phase>none</maven-deploy-plugin-default.phase>
<!-- Observability -->
<micrometer-docs-generator.version>1.0.2</micrometer-docs-generator.version>
<micrometer-docs-generator.inputPath>${maven.multiModuleProjectDirectory}/</micrometer-docs-generator.inputPath>
<micrometer-docs-generator.inclusionPattern>.*</micrometer-docs-generator.inclusionPattern>
<micrometer-docs-generator.outputPath>${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/</micrometer-docs-generator.outputPath>
</properties>
<dependencies>
<dependency>
@ -33,6 +43,12 @@ @@ -33,6 +43,12 @@
<profile>
<id>docs</id>
<build>
<resources>
<resource>
<directory>src/main/antora/resources/antora-resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>pl.project13.maven</groupId>
@ -42,17 +58,44 @@ @@ -42,17 +58,44 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<!-- TODO: Remove this execution if you have no observability -->
<executions>
<execution>
<id>generate-observability-docs</id>
<phase>${generate-docs.phase}</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>io.micrometer.docs.DocsGeneratorCommand</mainClass>
<includePluginDependencies>true</includePluginDependencies>
<arguments>
<argument>${micrometer-docs-generator.inputPath}</argument>
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
<argument>${micrometer-docs-generator.outputPath}</argument>
</arguments>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-docs-generator</artifactId>
<version>${micrometer-docs-generator.version}</version>
<type>jar</type>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>io.spring.maven.antora</groupId>
<artifactId>antora-component-version-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<groupId>io.spring.maven.antora</groupId>
<artifactId>antora-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

20
docs/src/main/antora/resources/antora-resources/antora.yml

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
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@

15
docs/src/main/asciidoc/README.adoc

@ -1,25 +1,24 @@ @@ -1,25 +1,24 @@
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://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://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://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/src/main/asciidoc/building-jdk8.adoc[]
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[]
[[contributing]]
== Contributing
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/src/main/asciidoc/contributing.adoc[]
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/contributing-docs.adoc[]
[[license]]
== License
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/main/LICENSE.txt[here].

37
docs/src/main/asciidoc/_configprops.adoc

@ -1,37 +0,0 @@ @@ -1,37 +0,0 @@
|===
|Name | Default | Description
|spring.cloud.openfeign.autoconfiguration.jackson.enabled | `false` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.
|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.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.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.max-connections | `200` |
|spring.cloud.openfeign.httpclient.max-connections-per-route | `50` |
|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.micrometer.enabled | `true` | Enables Micrometer capabilities for Feign.
|spring.cloud.openfeign.oauth2.enabled | `false` | Enables feign interceptor for managing oauth2 access token.
|spring.cloud.openfeign.oauth2.load-balanced | `false` | Enables load balancing for oauth2 access token provider.
|spring.cloud.openfeign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign.
|===

1
docs/src/main/asciidoc/index.adoc

@ -1 +0,0 @@ @@ -1 +0,0 @@
include::spring-cloud-openfeign.adoc[]

303
mvnw vendored

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
# "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
# 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
@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
@ -27,7 +27,6 @@ @@ -27,7 +27,6 @@
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@ -36,6 +35,10 @@ @@ -36,6 +35,10 @@
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
@ -50,109 +53,56 @@ fi @@ -50,109 +53,56 @@ fi
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
#
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
# for the new JDKs provided by Oracle.
#
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
#
# Oracle JDKs
#
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
#
# Apple JDKs
#
export JAVA_HOME=`/usr/libexec/java_home`
fi
;;
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
JAVA_HOME=$(java-config --jre-home)
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Migwn, ensure paths are in UNIX format before anything is touched
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
javaHome="$(dirname "\"$javaExecutable\"")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
@ -168,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then @@ -168,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
@ -182,72 +132,177 @@ if [ -z "$JAVA_HOME" ] ; then @@ -182,72 +132,177 @@ if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
local basedir=$(pwd)
local wdir=$(pwd)
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
wdir=$(cd "$wdir/.."; pwd)
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
echo "${basedir}"
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
fi
}
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
echo "Running version check"
VERSION=$( sed '\!<parent!,\!</parent!d' `dirname $0`/pom.xml | grep '<version' | head -1 | sed -e 's/.*<version>//' -e 's!</version>.*$!!' )
echo "The found version is [${VERSION}]"
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
if echo $VERSION | egrep -q 'M|RC'; then
echo Activating \"milestone\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
echo Deactivating \"milestone\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//')
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget > /dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
if echo $VERSION | egrep -q 'RELEASE'; then
echo Activating \"central\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pcentral"
else
echo Deactivating \"central\" profile for version=\"$VERSION\"
echo $MAVEN_ARGS | grep -q central && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pcentral//')
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@"
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

96
mvnw.cmd vendored

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@ -18,15 +18,14 @@ @@ -18,15 +18,14 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@ -35,7 +34,9 @@ @@ -35,7 +34,9 @@
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@ -44,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @@ -44,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
@ -80,8 +81,6 @@ goto error @@ -80,8 +81,6 @@ goto error
:init
set MAVEN_CMD_LINE_ARGS=%*
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
@ -117,11 +116,72 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s @@ -117,11 +116,72 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar""
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
@ -131,15 +191,15 @@ set ERROR_CODE=1 @@ -131,15 +191,15 @@ set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

10
pom.xml

@ -4,14 +4,14 @@ @@ -4,14 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<scm>
@ -25,8 +25,8 @@ @@ -25,8 +25,8 @@
</scm>
<properties>
<main.basedir>${basedir}</main.basedir>
<jackson.version>2.11.3</jackson.version>
<spring-cloud-commons.version>4.0.0-SNAPSHOT</spring-cloud-commons.version>
<jackson.version>2.15.2</jackson.version>
<spring-cloud-commons.version>4.1.0-SNAPSHOT</spring-cloud-commons.version>
<!-- Plugin versions -->
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
@ -228,7 +228,7 @@ @@ -228,7 +228,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Sets the VM argument line used when unit tests are run. -->
<argLine>${surefireArgLine}</argLine>
<argLine>${surefireArgLine} --add-opens=java.base/java.net=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>

30
spring-cloud-openfeign-core/pom.xml

@ -6,9 +6,12 @@ @@ -6,9 +6,12 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<scm>
<url>https://github.com/spring-cloud/spring-cloud-openfeign</url>
</scm>
<artifactId>spring-cloud-openfeign-core</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud OpenFeign Core</name>
@ -100,8 +103,16 @@ @@ -100,8 +103,16 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<exclusion>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
@ -122,6 +133,11 @@ @@ -122,6 +133,11 @@
<artifactId>feign-okhttp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-java11</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
@ -188,7 +204,17 @@ @@ -188,7 +204,17 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<version>2.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>

2
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/DefaultTargeter.java

@ -25,7 +25,7 @@ import feign.Target; @@ -25,7 +25,7 @@ import feign.Target;
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}

69
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.cloud.openfeign;
import java.lang.reflect.Method;
import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
@ -24,6 +25,7 @@ import java.time.Duration; @@ -24,6 +25,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@ -38,12 +40,18 @@ import feign.Client; @@ -38,12 +40,18 @@ import feign.Client;
import feign.Feign;
import feign.Target;
import feign.hc5.ApacheHttp5Client;
import feign.http2client.Http2Client;
import feign.okhttp.OkHttpClient;
import jakarta.annotation.PreDestroy;
import okhttp3.ConnectionPool;
import okhttp3.Protocol;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -55,6 +63,8 @@ import org.springframework.cache.interceptor.CacheInterceptor; @@ -55,6 +63,8 @@ import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.aot.FeignChildContextInitializer;
import org.springframework.cloud.openfeign.aot.FeignClientBeanFactoryInitializationAotProcessor;
import org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor;
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
@ -64,12 +74,14 @@ import org.springframework.context.annotation.Bean; @@ -64,12 +74,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.util.ClassUtils;
/**
* @author Spencer Gibb
@ -84,6 +96,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio @@ -84,6 +96,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
* @author Sam Kruglov
* @author Wojciech Mąka
* @author Dangzhicairang(小水牛)
* @author changjin wei(魏昌进)
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@ -102,12 +115,24 @@ public class FeignAutoConfiguration { @@ -102,12 +115,24 @@ public class FeignAutoConfiguration {
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
public FeignClientFactory feignContext() {
FeignClientFactory context = new FeignClientFactory();
context.setConfigurations(this.configurations);
return context;
}
@Bean
static FeignChildContextInitializer feignChildContextInitializer(GenericApplicationContext parentContext,
FeignClientFactory feignClientFactory) {
return new FeignChildContextInitializer(parentContext, feignClientFactory);
}
@Bean
static FeignClientBeanFactoryInitializationAotProcessor feignClientBeanFactoryInitializationCodeGenerator(
GenericApplicationContext applicationContext, FeignClientFactory feignClientFactory) {
return new FeignClientBeanFactoryInitializationAotProcessor(applicationContext, feignClientFactory);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.openfeign.cache.enabled", matchIfMissing = true)
@ConditionalOnBean(CacheInterceptor.class)
@ -239,11 +264,14 @@ public class FeignAutoConfiguration { @@ -239,11 +264,14 @@ public class FeignAutoConfiguration {
int connectTimeout = httpClientProperties.getConnectionTimeout();
boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
Duration readTimeout = httpClientProperties.getOkHttp().getReadTimeout();
List<Protocol> protocols = httpClientProperties.getOkHttp().getProtocols().stream().map(Protocol::valueOf)
.collect(Collectors.toList());
if (disableSslValidation) {
disableSsl(builder);
}
this.okHttpClient = builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.followRedirects(followRedirects).readTimeout(readTimeout).connectionPool(connectionPool).build();
.followRedirects(followRedirects).readTimeout(readTimeout).connectionPool(connectionPool)
.protocols(protocols).build();
return this.okHttpClient;
}
@ -357,4 +385,37 @@ public class FeignAutoConfiguration { @@ -357,4 +385,37 @@ public class FeignAutoConfiguration {
}
// the following configuration is for alternate feign clients if
// SC loadbalancer is not on the class path.
// see corresponding configurations in FeignLoadBalancerAutoConfiguration
// for load-balanced clients.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Http2Client.class, HttpClient.class })
@ConditionalOnMissingBean(HttpClient.class)
@ConditionalOnProperty("spring.cloud.openfeign.http2client.enabled")
@Import(org.springframework.cloud.openfeign.clientconfig.Http2ClientFeignConfiguration.class)
protected static class Http2ClientFeignConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new Http2Client(httpClient);
}
}
}
class FeignHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (!ClassUtils.isPresent("feign.Feign", classLoader)) {
return;
}
hints.reflection().registerType(TypeReference.of(FeignClientFactoryBean.class),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
}
}

14
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -154,12 +154,12 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { @@ -154,12 +154,12 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
}
/**
* If the method param of InvocationHandler.invoke is not accessible, i.e in a
* package-private interface, the fallback call will cause of access restrictions. But
* methods in dispatch are copied methods. So setting access to dispatch method
* doesn't take effect to the method in InvocationHandler.invoke. Use map to store a
* copy of method to invoke the fallback to bypass this and reducing the count of
* reflection calls.
* If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])}
* is not accessible, i.e in a package-private interface, the fallback call will cause
* of access restrictions. But methods in dispatch are copied methods. So setting
* access to dispatch method doesn't take effect to the method in
* InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback
* to bypass this and reducing the count of reflection calls.
* @return cached methods map for fallback invoking
*/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {

39
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java

@ -19,9 +19,13 @@ package org.springframework.cloud.openfeign; @@ -19,9 +19,13 @@ package org.springframework.cloud.openfeign;
import feign.Feign;
import feign.Target;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.util.StringUtils;
/**
* @author 黄学敏huangxuemin)
*/
@SuppressWarnings("unchecked")
class FeignCircuitBreakerTargeter implements Targeter {
@ -39,7 +43,7 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -39,7 +43,7 @@ class FeignCircuitBreakerTargeter implements Targeter {
}
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof FeignCircuitBreaker.Builder builder)) {
return feign.target(target);
@ -56,20 +60,20 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -56,20 +60,20 @@ class FeignCircuitBreakerTargeter implements Targeter {
return builder(name, builder).target(target);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
private <T> T targetWithFallbackFactory(String feignClientName, FeignClientFactory context,
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder, Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory",
feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
return builder(feignClientName, builder).target(target, fallbackFactory);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target,
FeignCircuitBreaker.Builder builder, Class<?> fallback) {
private <T> T targetWithFallback(String feignClientName, FeignClientFactory context,
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder(feignClientName, builder).target(target, fallbackInstance);
}
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignClientFactory context,
Class<?> beanType, Class<T> targetType) {
Object fallbackInstance = context.getInstance(feignClientName, beanType);
if (fallbackInstance == null) {
@ -78,10 +82,27 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -78,10 +82,27 @@ class FeignCircuitBreakerTargeter implements Targeter {
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));
if (fallbackInstance instanceof FactoryBean<?> factoryBean) {
try {
fallbackInstance = factoryBean.getObject();
}
catch (Exception e) {
throw new IllegalStateException(fallbackMechanism + " create fail", e);
}
if (!targetType.isAssignableFrom(fallbackInstance.getClass())) {
throw new IllegalStateException(String.format("Incompatible " + fallbackMechanism
+ " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
fallbackInstance.getClass(), targetType, feignClientName));
}
}
else {
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;
}

26
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignContext.java → spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java

@ -16,11 +16,14 @@ @@ -16,11 +16,14 @@
package org.springframework.cloud.openfeign;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.cloud.context.named.NamedContextFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.lang.Nullable;
/**
@ -31,11 +34,18 @@ import org.springframework.lang.Nullable; @@ -31,11 +34,18 @@ import org.springframework.lang.Nullable;
* @author Dave Syer
* @author Matt King
* @author Jasbir Singh
* @author Olga Maciaszek-Sharma
*/
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public class FeignClientFactory extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "spring.cloud.openfeign", "spring.cloud.openfeign.client.name");
public FeignClientFactory() {
this(new HashMap<>());
}
public FeignClientFactory(
Map<String, ApplicationContextInitializer<GenericApplicationContext>> applicationContextInitializers) {
super(FeignClientsConfiguration.class, "spring.cloud.openfeign", "spring.cloud.openfeign.client.name",
applicationContextInitializers);
}
@Nullable
@ -57,4 +67,14 @@ public class FeignContext extends NamedContextFactory<FeignClientSpecification> @@ -57,4 +67,14 @@ public class FeignContext extends NamedContextFactory<FeignClientSpecification>
return getContext(contextName).getBean(beanName, type);
}
@SuppressWarnings("unchecked")
public FeignClientFactory withApplicationContextInitializers(Map<String, Object> applicationContextInitializers) {
Map<String, ApplicationContextInitializer<GenericApplicationContext>> convertedInitializers = new HashMap<>();
applicationContextInitializers.keySet()
.forEach(contextId -> convertedInitializers.put(contextId,
(ApplicationContextInitializer<GenericApplicationContext>) applicationContextInitializers
.get(contextId)));
return new FeignClientFactory(convertedInitializers);
}
}

69
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -32,6 +32,7 @@ import feign.Logger; @@ -32,6 +32,7 @@ import feign.Logger;
import feign.QueryMapEncoder;
import feign.Request;
import feign.RequestInterceptor;
import feign.ResponseInterceptor;
import feign.Retryer;
import feign.Target.HardCodedTarget;
import feign.codec.Decoder;
@ -70,6 +71,7 @@ import org.springframework.util.StringUtils; @@ -70,6 +71,7 @@ import org.springframework.util.StringUtils;
* @author Jasbir Singh
* @author Hyeonmin Park
* @author Felix Dittrich
* @author Dominique Villard
*/
public class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
@ -113,13 +115,22 @@ public class FeignClientFactoryBean @@ -113,13 +115,22 @@ public class FeignClientFactoryBean
private final List<FeignBuilderCustomizer> additionalCustomizers = new ArrayList<>();
private String[] qualifiers = new String[] {};
// For AOT testing
public FeignClientFactoryBean() {
if (LOG.isDebugEnabled()) {
LOG.debug("Creating a FeignClientFactoryBean.");
}
}
@Override
public void afterPropertiesSet() {
Assert.hasText(contextId, "Context id must be set");
Assert.hasText(name, "Name must be set");
}
protected Feign.Builder feign(FeignContext context) {
protected Feign.Builder feign(FeignClientFactory context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
@ -137,7 +148,7 @@ public class FeignClientFactoryBean @@ -137,7 +148,7 @@ public class FeignClientFactoryBean
return builder;
}
private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) {
private void applyBuildCustomizers(FeignClientFactory context, Feign.Builder builder) {
Map<String, FeignBuilderCustomizer> customizerMap = context.getInstances(contextId,
FeignBuilderCustomizer.class);
@ -148,7 +159,7 @@ public class FeignClientFactoryBean @@ -148,7 +159,7 @@ public class FeignClientFactoryBean
additionalCustomizers.forEach(customizer -> customizer.customize(builder));
}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
protected void configureFeign(FeignClientFactory context, Feign.Builder builder) {
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
: applicationContext.getBean(FeignClientProperties.class);
@ -172,7 +183,7 @@ public class FeignClientFactoryBean @@ -172,7 +183,7 @@ public class FeignClientFactoryBean
}
}
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
protected void configureUsingConfiguration(FeignClientFactory context, Feign.Builder builder) {
Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
@ -210,6 +221,10 @@ public class FeignClientFactoryBean @@ -210,6 +221,10 @@ public class FeignClientFactoryBean
AnnotationAwareOrderComparator.sort(interceptors);
builder.requestInterceptors(interceptors);
}
ResponseInterceptor responseInterceptor = getInheritedAwareOptional(context, ResponseInterceptor.class);
if (responseInterceptor != null) {
builder.responseInterceptor(responseInterceptor);
}
QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context, QueryMapEncoder.class);
if (queryMapEncoder != null) {
builder.queryMapEncoder(queryMapEncoder);
@ -268,6 +283,10 @@ public class FeignClientFactoryBean @@ -268,6 +283,10 @@ public class FeignClientFactoryBean
}
}
if (config.getResponseInterceptor() != null) {
builder.responseInterceptor(getOrInstantiate(config.getResponseInterceptor()));
}
if (config.getDismiss404() != null) {
if (config.getDismiss404()) {
builder.dismiss404();
@ -340,7 +359,7 @@ public class FeignClientFactoryBean @@ -340,7 +359,7 @@ public class FeignClientFactoryBean
}
}
protected <T> T get(FeignContext context, Class<T> type) {
protected <T> T get(FeignClientFactory context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + contextId);
@ -348,11 +367,11 @@ public class FeignClientFactoryBean @@ -348,11 +367,11 @@ public class FeignClientFactoryBean
return instance;
}
protected <T> T getOptional(FeignContext context, Class<T> type) {
protected <T> T getOptional(FeignClientFactory context, Class<T> type) {
return context.getInstance(contextId, type);
}
protected <T> T getInheritedAwareOptional(FeignContext context, Class<T> type) {
protected <T> T getInheritedAwareOptional(FeignClientFactory context, Class<T> type) {
if (inheritParentContext) {
return getOptional(context, type);
}
@ -361,7 +380,7 @@ public class FeignClientFactoryBean @@ -361,7 +380,7 @@ public class FeignClientFactoryBean
}
}
protected <T> Map<String, T> getInheritedAwareInstances(FeignContext context, Class<T> type) {
protected <T> Map<String, T> getInheritedAwareInstances(FeignClientFactory context, Class<T> type) {
if (inheritParentContext) {
return context.getInstances(contextId, type);
}
@ -370,7 +389,7 @@ public class FeignClientFactoryBean @@ -370,7 +389,7 @@ public class FeignClientFactoryBean
}
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
protected <T> T loadBalance(Feign.Builder builder, FeignClientFactory context, HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
@ -389,7 +408,7 @@ public class FeignClientFactoryBean @@ -389,7 +408,7 @@ public class FeignClientFactoryBean
* @param contextId name of feign client
* @return returns Options found in context
*/
protected Request.Options getOptionsByName(FeignContext context, String contextId) {
protected Request.Options getOptionsByName(FeignClientFactory context, String contextId) {
if (refreshableClient) {
return context.getInstance(contextId, Request.Options.class.getCanonicalName() + "-" + contextId,
Request.Options.class);
@ -409,9 +428,9 @@ public class FeignClientFactoryBean @@ -409,9 +428,9 @@ public class FeignClientFactoryBean
*/
@SuppressWarnings("unchecked")
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class)
: applicationContext.getBean(FeignClientFactory.class);
Feign.Builder builder = feign(feignClientFactory);
if (!StringUtils.hasText(url) && !isUrlAvailableInConfig(contextId)) {
if (LOG.isInfoEnabled()) {
@ -424,13 +443,13 @@ public class FeignClientFactoryBean @@ -424,13 +443,13 @@ public class FeignClientFactoryBean
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
return (T) loadBalance(builder, feignClientFactory, new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
Client client = getOptional(feignClientFactory, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
@ -445,10 +464,10 @@ public class FeignClientFactoryBean @@ -445,10 +464,10 @@ public class FeignClientFactoryBean
builder.client(client);
}
applyBuildCustomizers(context, builder);
applyBuildCustomizers(feignClientFactory, builder);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, resolveTarget(context, contextId, url));
Targeter targeter = get(feignClientFactory, Targeter.class);
return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));
}
private String cleanPath() {
@ -468,7 +487,7 @@ public class FeignClientFactoryBean @@ -468,7 +487,7 @@ public class FeignClientFactoryBean
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> HardCodedTarget<T> resolveTarget(FeignContext context, String contextId, String url) {
private <T> HardCodedTarget<T> resolveTarget(FeignClientFactory context, String contextId, String url) {
if (StringUtils.hasText(url)) {
return new HardCodedTarget(type, name, url);
}
@ -486,7 +505,7 @@ public class FeignClientFactoryBean @@ -486,7 +505,7 @@ public class FeignClientFactoryBean
"Provide Feign client URL either in @FeignClient() or in config properties.");
}
return new HardCodedTarget(type, name, FeignClientsRegistrar.getUrl(config.getUrl()));
return new PropertyBasedTarget(type, name, config);
}
private boolean isUrlAvailableInConfig(String contextId) {
@ -600,6 +619,14 @@ public class FeignClientFactoryBean @@ -600,6 +619,14 @@ public class FeignClientFactoryBean
this.refreshableClient = refreshableClient;
}
public String[] getQualifiers() {
return qualifiers;
}
public void setQualifiers(String[] qualifiers) {
this.qualifiers = qualifiers;
}
@Override
public boolean equals(Object o) {
if (this == o) {

18
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java

@ -28,6 +28,7 @@ import feign.ExceptionPropagationPolicy; @@ -28,6 +28,7 @@ import feign.ExceptionPropagationPolicy;
import feign.Logger;
import feign.QueryMapEncoder;
import feign.RequestInterceptor;
import feign.ResponseInterceptor;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
@ -43,6 +44,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @@ -43,6 +44,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Olga Maciaszek-Sharma
* @author Hyeonmin Park
* @author Jasbir Singh
* @author Dominique Villard
*/
@ConfigurationProperties("spring.cloud.openfeign.client")
public class FeignClientProperties {
@ -126,6 +128,8 @@ public class FeignClientProperties { @@ -126,6 +128,8 @@ public class FeignClientProperties {
private List<Class<RequestInterceptor>> requestInterceptors;
private Class<ResponseInterceptor> responseInterceptor;
private Map<String, Collection<String>> defaultRequestHeaders;
private Map<String, Collection<String>> defaultQueryParameters;
@ -202,6 +206,14 @@ public class FeignClientProperties { @@ -202,6 +206,14 @@ public class FeignClientProperties {
this.requestInterceptors = requestInterceptors;
}
public Class<ResponseInterceptor> getResponseInterceptor() {
return responseInterceptor;
}
public void setResponseInterceptor(Class<ResponseInterceptor> responseInterceptor) {
this.responseInterceptor = responseInterceptor;
}
public Map<String, Collection<String>> getDefaultRequestHeaders() {
return defaultRequestHeaders;
}
@ -311,6 +323,7 @@ public class FeignClientProperties { @@ -311,6 +323,7 @@ public class FeignClientProperties {
&& Objects.equals(readTimeout, that.readTimeout) && Objects.equals(retryer, that.retryer)
&& Objects.equals(errorDecoder, that.errorDecoder)
&& Objects.equals(requestInterceptors, that.requestInterceptors)
&& Objects.equals(responseInterceptor, that.responseInterceptor)
&& Objects.equals(dismiss404, that.dismiss404) && Objects.equals(encoder, that.encoder)
&& Objects.equals(decoder, that.decoder) && Objects.equals(contract, that.contract)
&& Objects.equals(exceptionPropagationPolicy, that.exceptionPropagationPolicy)
@ -325,8 +338,9 @@ public class FeignClientProperties { @@ -325,8 +338,9 @@ public class FeignClientProperties {
@Override
public int hashCode() {
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors,
dismiss404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters,
defaultRequestHeaders, capabilities, queryMapEncoder, micrometer, followRedirects, url);
responseInterceptor, dismiss404, encoder, decoder, contract, exceptionPropagationPolicy,
defaultQueryParameters, defaultRequestHeaders, capabilities, queryMapEncoder, micrometer,
followRedirects, url);
}
}

30
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientSpecification.java

@ -24,18 +24,22 @@ import org.springframework.cloud.context.named.NamedContextFactory; @@ -24,18 +24,22 @@ import org.springframework.cloud.context.named.NamedContextFactory;
/**
* @author Dave Syer
* @author Gregor Zurowski
* @author Olga Maciaszek-Sharma
*/
public class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private String className;
private Class<?>[] configuration;
FeignClientSpecification() {
public FeignClientSpecification() {
}
public FeignClientSpecification(String name, Class<?>[] configuration) {
public FeignClientSpecification(String name, String className, Class<?>[] configuration) {
this.name = name;
this.className = className;
this.configuration = configuration;
}
@ -47,6 +51,14 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati @@ -47,6 +51,14 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public Class<?>[] getConfiguration() {
return this.configuration;
}
@ -60,22 +72,24 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati @@ -60,22 +72,24 @@ public class FeignClientSpecification implements NamedContextFactory.Specificati
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (!(o instanceof FeignClientSpecification that)) {
return false;
}
FeignClientSpecification that = (FeignClientSpecification) o;
return Objects.equals(name, that.name) && Arrays.equals(configuration, that.configuration);
return Objects.equals(name, that.name) && Objects.equals(className, that.className)
&& Arrays.equals(configuration, that.configuration);
}
@Override
public int hashCode() {
return Objects.hash(name, Arrays.hashCode(configuration));
int result = Objects.hash(name, className);
result = 31 * result + Arrays.hashCode(configuration);
return result;
}
@Override
public String toString() {
return new StringBuilder("FeignClientSpecification{").append("name='").append(name).append("', ")
.append("configuration=").append(Arrays.toString(configuration)).append("}").toString();
return "FeignClientSpecification{" + "name='" + name + "', " + "className='" + className + "', "
+ "configuration=" + Arrays.toString(configuration) + "}";
}
}

70
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java

@ -32,7 +32,6 @@ import java.util.Set; @@ -32,7 +32,6 @@ import java.util.Set;
import feign.Request;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
@ -71,7 +70,7 @@ import org.springframework.util.StringUtils; @@ -71,7 +70,7 @@ import org.springframework.util.StringUtils;
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
// and RibbonClientsConfigurationRegistrar
private ResourceLoader resourceLoader;
@ -162,12 +161,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -162,12 +161,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
registerClientConfiguration(registry, name, "default", defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
@ -196,20 +194,73 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -196,20 +194,73 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
String className = annotationMetadata.getClassName();
registerClientConfiguration(registry, name, className, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
@SuppressWarnings("unchecked")
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
if (String.valueOf(false).equals(
environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
else {
lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
}
}
private void eagerlyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes,
BeanDefinitionRegistry registry) {
validate(attributes);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(null, attributes));
definition.addPropertyValue("path", getPath(null, attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(null, attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("dismiss404", Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
definition.addPropertyValue("fallback",
(fallback instanceof Class ? fallback : ClassUtils.resolveClassName(fallback.toString(), null)));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
definition.addPropertyValue("fallbackFactory", fallbackFactory instanceof Class ? fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.addPropertyValue("refreshableClient", isClientRefreshEnabled());
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
// This is done so that there's a way to retrieve qualifiers while generating AOT
// code
definition.addPropertyValue("qualifiers", qualifiers);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
}
private void lazilyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes,
BeanDefinitionRegistry registry) {
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
Class clazz = ClassUtils.resolveClassName(className, null);
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
@ -239,7 +290,6 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -239,7 +290,6 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
@ -407,9 +457,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -407,9 +457,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(className);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());

49
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/PropertyBasedTarget.java

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
/*
* 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;
}
}

2
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/Targeter.java

@ -24,7 +24,7 @@ import feign.Target; @@ -24,7 +24,7 @@ import feign.Target;
*/
public interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target);
}

126
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java

@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
/*
* 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());
}
}
}

215
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java

@ -0,0 +1,215 @@ @@ -0,0 +1,215 @@
/*
* 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;
}
}
}

45
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/Http2ClientFeignConfiguration.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* 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();
}
}

9
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClient5FeignConfiguration.java

@ -35,6 +35,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -35,6 +35,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.ssl.TLS;
@ -54,6 +55,7 @@ import org.springframework.context.annotation.Configuration; @@ -54,6 +55,7 @@ import org.springframework.context.annotation.Configuration;
* Default configuration for {@link CloseableHttpClient}.
*
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ -89,7 +91,11 @@ public class HttpClient5FeignConfiguration { @@ -89,7 +91,11 @@ public class HttpClient5FeignConfiguration {
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(
Timeout.of(httpClientProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS))
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.setConnectionRequestTimeout(
Timeout.of(httpClientProperties.getHc5().getConnectionRequestTimeout(),
httpClientProperties.getHc5().getConnectionRequestTimeoutUnit()))
.build())
.build();
return httpClient5;
}
@ -110,6 +116,7 @@ public class HttpClient5FeignConfiguration { @@ -110,6 +116,7 @@ public class HttpClient5FeignConfiguration {
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { new DisabledValidationTrustManager() }, new SecureRandom());
sslConnectionSocketFactoryBuilder.setSslContext(sslContext);
sslConnectionSocketFactoryBuilder.setHostnameVerifier(NoopHostnameVerifier.INSTANCE);
}
catch (NoSuchAlgorithmException | KeyManagementException e) {
LOG.warn("Error creating SSLContext", e);

9
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -22,17 +22,18 @@ import feign.Feign; @@ -22,17 +22,18 @@ import feign.Feign;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Configures the Feign response compression.
*
* @author Jakub Narloch
* @author Olga Maciaszek-Sharma
* @see FeignAcceptGzipEncodingInterceptor
*/
@Configuration(proxyBeanMethods = false)
@ -41,8 +42,8 @@ import org.springframework.context.annotation.Configuration; @@ -41,8 +42,8 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnBean(Client.class)
@ConditionalOnProperty("spring.cloud.openfeign.compression.response.enabled")
// The OK HTTP client uses "transparent" compression.
// If the accept-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(type = "okhttp3.OkHttpClient")
// If the accept-encoding header is present, it disables transparent compression.
@Conditional(OkHttpFeignClientBeanMissingCondition.class)
@AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignAcceptGzipEncodingAutoConfiguration {

9
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingAutoConfiguration.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -20,25 +20,26 @@ import feign.Feign; @@ -20,25 +20,26 @@ import feign.Feign;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Configures the Feign request compression.
*
* @author Jakub Narloch
* @author Olga Maciaszek-Sharma
* @see FeignContentGzipEncodingInterceptor
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(FeignClientEncodingProperties.class)
@ConditionalOnClass(Feign.class)
// The OK HTTP client uses "transparent" compression.
// If the content-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(type = "okhttp3.OkHttpClient")
// If the content-encoding header is present, it disables transparent compression.
@Conditional(OkHttpFeignClientBeanMissingCondition.class)
@ConditionalOnProperty("spring.cloud.openfeign.compression.request.enabled")
@AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignContentGzipEncodingAutoConfiguration {

51
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/OkHttpFeignClientBeanMissingCondition.java

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
/*
* 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 {
}
}

4
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfiguration.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -54,7 +54,7 @@ import org.springframework.context.annotation.Import; @@ -54,7 +54,7 @@ import org.springframework.context.annotation.Import;
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
Http2ClientFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
@Bean

76
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/Http2ClientFeignLoadBalancerConfiguration.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* 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.loadbalancer;
import java.net.http.HttpClient;
import java.util.List;
import feign.Client;
import feign.http2client.Http2Client;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
* that uses {@link Http2Client} under the hood.
*
* @author changjin wei(魏昌进)
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Http2Client.class, HttpClient.class })
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty("spring.cloud.openfeign.http2client.enabled")
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
class Http2ClientFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new Http2Client(httpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnBean(LoadBalancedRetryFactory.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new Http2Client(httpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory, transformers);
}
}

7
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -68,6 +68,7 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils @@ -68,6 +68,7 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @author Wonsik Cheung
* @author Andriy Pikozh
* @since 2.2.6
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@ -145,8 +146,8 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -145,8 +146,8 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
// On retries the policy will choose the server and set it in the context
// and extract the server and update the request being made
if (context instanceof LoadBalancedRetryContext lbContext) {
ServiceInstance serviceInstance = lbContext.getServiceInstance();
if (serviceInstance == null) {
retrievedServiceInstance = lbContext.getServiceInstance();
if (retrievedServiceInstance == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. "
+ "Reattempting service instance selection");

91
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignHttpClientProperties.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -16,10 +16,14 @@ @@ -16,10 +16,14 @@
package org.springframework.cloud.openfeign.support;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import feign.http2client.Http2Client;
import feign.okhttp.OkHttpClient;
import okhttp3.Protocol;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -27,6 +31,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @@ -27,6 +31,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Ryan Baxter
* @author Nguyen Ky Thanh
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
*/
@ConfigurationProperties(prefix = "spring.cloud.openfeign.httpclient")
public class FeignHttpClientProperties {
@ -97,6 +102,11 @@ public class FeignHttpClientProperties { @@ -97,6 +102,11 @@ public class FeignHttpClientProperties {
*/
private OkHttp okHttp = new OkHttp();
/**
* Additional {@link Http2Client}-specific properties.
*/
private Http2Properties http2 = new Http2Properties();
public int getConnectionTimerRepeat() {
return connectionTimerRepeat;
}
@ -177,6 +187,14 @@ public class FeignHttpClientProperties { @@ -177,6 +187,14 @@ public class FeignHttpClientProperties {
this.okHttp = okHttp;
}
public Http2Properties getHttp2() {
return http2;
}
public void setHttp2(Http2Properties http2) {
this.http2 = http2;
}
public static class Hc5Properties {
/**
@ -199,6 +217,16 @@ public class FeignHttpClientProperties { @@ -199,6 +217,16 @@ public class FeignHttpClientProperties {
*/
public static final TimeUnit DEFAULT_SOCKET_TIMEOUT_UNIT = TimeUnit.SECONDS;
/**
* Default value for connection request timeout.
*/
public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = 3;
/**
* Default value for connection request timeout unit.
*/
public static final TimeUnit DEFAULT_CONNECTION_REQUEST_TIMEOUT_UNIT = TimeUnit.MINUTES;
/**
* Pool concurrency policies.
*/
@ -219,6 +247,16 @@ public class FeignHttpClientProperties { @@ -219,6 +247,16 @@ public class FeignHttpClientProperties {
*/
private TimeUnit socketTimeoutUnit = DEFAULT_SOCKET_TIMEOUT_UNIT;
/**
* Default value for connection request timeout.
*/
private int connectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
/**
* Default value for connection request timeout unit.
*/
private TimeUnit connectionRequestTimeoutUnit = DEFAULT_CONNECTION_REQUEST_TIMEOUT_UNIT;
public PoolConcurrencyPolicy getPoolConcurrencyPolicy() {
return poolConcurrencyPolicy;
}
@ -251,6 +289,22 @@ public class FeignHttpClientProperties { @@ -251,6 +289,22 @@ public class FeignHttpClientProperties {
this.socketTimeout = socketTimeout;
}
public int getConnectionRequestTimeout() {
return connectionRequestTimeout;
}
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
this.connectionRequestTimeout = connectionRequestTimeout;
}
public TimeUnit getConnectionRequestTimeoutUnit() {
return connectionRequestTimeoutUnit;
}
public void setConnectionRequestTimeoutUnit(TimeUnit connectionRequestTimeoutUnit) {
this.connectionRequestTimeoutUnit = connectionRequestTimeoutUnit;
}
/**
* Enumeration of pool concurrency policies.
*/
@ -299,6 +353,12 @@ public class FeignHttpClientProperties { @@ -299,6 +353,12 @@ public class FeignHttpClientProperties {
*/
private Duration readTimeout = Duration.ofSeconds(60);
/**
* Configure the protocols used by this client to communicate with remote servers.
* Uses {@link String} values of {@link Protocol}.
*/
private List<String> protocols = List.of("HTTP_2", "HTTP_1_1");
public Duration getReadTimeout() {
return readTimeout;
}
@ -307,6 +367,35 @@ public class FeignHttpClientProperties { @@ -307,6 +367,35 @@ public class FeignHttpClientProperties {
this.readTimeout = readTimeout;
}
public List<String> getProtocols() {
return protocols;
}
public void setProtocols(List<String> protocols) {
this.protocols = protocols;
}
}
/**
* {@link Http2Client}-specific properties.
*/
public static class Http2Properties {
/**
* Configure the protocols used by this client to communicate with remote servers.
* Uses {@link String} value of {@link HttpClient.Version}.
*/
private String version = "HTTP_2";
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
}

36
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -34,12 +34,14 @@ import feign.Contract; @@ -34,12 +34,14 @@ import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import feign.Param;
import feign.QueryMap;
import feign.Request;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.CollectionFormat;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.cloud.openfeign.annotation.CookieValueParameterProcessor;
import org.springframework.cloud.openfeign.annotation.MatrixVariableParameterProcessor;
import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor;
@ -60,12 +62,14 @@ import org.springframework.core.convert.TypeDescriptor; @@ -60,12 +62,14 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.domain.Pageable;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import static feign.Util.checkState;
import static feign.Util.emptyToNull;
@ -157,7 +161,7 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource @@ -157,7 +161,7 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
private static TypeDescriptor getElementTypeDescriptor(TypeDescriptor typeDescriptor) {
TypeDescriptor elementTypeDescriptor = typeDescriptor.getElementTypeDescriptor();
// that means it's not a collection but it is iterable, gh-135
// that means it's not a collection, but it is iterable, gh-135
if (elementTypeDescriptor == null && Iterable.class.isAssignableFrom(typeDescriptor.getType())) {
ResolvableType type = typeDescriptor.getResolvableType().as(Iterable.class).getGeneric(0);
if (type.resolve() == null) {
@ -265,6 +269,20 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource @@ -265,6 +269,20 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean isHttpAnnotation = false;
try {
if (Pageable.class.isAssignableFrom(data.method().getParameterTypes()[paramIndex])) {
// do not set a Pageable as QueryMap if there's an actual QueryMap param
// present
if (!queryMapParamPresent(data)) {
data.queryMapIndex(paramIndex);
return false;
}
}
}
catch (NoClassDefFoundError ignored) {
// Do nothing; added to avoid exceptions if optional dependency not present
}
AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data,
paramIndex);
Method method = processedMethods.get(data.configKey());
@ -293,6 +311,20 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource @@ -293,6 +311,20 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
return isHttpAnnotation;
}
private boolean queryMapParamPresent(MethodMetadata data) {
Annotation[][] paramsAnnotations = data.method().getParameterAnnotations();
for (int i = 0; i < paramsAnnotations.length; i++) {
Annotation[] paramAnnotations = paramsAnnotations[i];
Class<?> parameterType = data.method().getParameterTypes()[i];
if (Arrays.stream(paramAnnotations).anyMatch(
annotation -> Map.class.isAssignableFrom(parameterType) && annotation instanceof RequestParam
|| annotation instanceof SpringQueryMap || annotation instanceof QueryMap)) {
return true;
}
}
return false;
}
private void parseProduces(MethodMetadata md, Method method, RequestMapping annotation) {
String[] serverProduces = annotation.produces();
String clientAccepts = serverProduces.length == 0 ? null : emptyToNull(serverProduces[0]);

14
spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -44,6 +44,12 @@ @@ -44,6 +44,12 @@
"description": "Enables the use of the OK HTTP Client by Feign.",
"defaultValue": "false"
},
{
"name": "spring.cloud.openfeign.http2client.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the Java11 HTTP 2 Client by Feign.",
"defaultValue": "false"
},
{
"name": "spring.cloud.openfeign.compression.response.enabled",
"type": "java.lang.Boolean",
@ -75,10 +81,16 @@ @@ -75,10 +81,16 @@
"defaultValue": "false"
},
{
"name": "spring.cloud.openfeign.oauth2.registrationClientId",
"name": "spring.cloud.openfeign.oauth2.clientRegistrationId",
"type": "java.lang.String",
"description": "Provides a clientId to be used with OAuth2.",
"defaultValue": ""
},
{
"name": "spring.cloud.openfeign.lazy-attributes-resolution",
"type": "java.lang.Boolean",
"description": "Switches @FeignClient attributes resolution mode to lazy.",
"defaultValue": "false"
}
]
}

2
spring-cloud-openfeign-core/src/main/resources/META-INF/spring/aot.factories

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.cloud.openfeign.FeignHints

169
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EagerInitFeignClientUsingConfigurerTests.java

@ -0,0 +1,169 @@ @@ -0,0 +1,169 @@
/*
* 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.Field;
import java.util.List;
import feign.Capability;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.micrometer.MicrometerObservationCapability;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author matt king
* @author Jonatan Ivanov
* @author Olga Maciaszek-Sharma
*/
@DirtiesContext
@SpringBootTest(classes = EagerInitFeignClientUsingConfigurerTests.Application.class, value = {
"spring.cloud.openfeign.client.config.default.loggerLevel=full",
"spring.cloud.openfeign.client.config.default.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor",
"spring.cloud.openfeign.client.config.default.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor" })
class EagerInitFeignClientUsingConfigurerTests {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private FeignClientFactory context;
private static final String BEAN_NAME_PREFIX = "&org.springframework.cloud.openfeign.EagerInitFeignClientUsingConfigurerTests$";
@Test
public void testFeignClient() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "TestFeignClient");
Feign.Builder builder = factoryBean.feign(context);
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder, "requestInterceptors");
assertThat(interceptors.size()).as("interceptors not set").isEqualTo(3);
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.FULL);
List<Capability> capabilities = (List) getBuilderValue(builder, "capabilities");
assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class)
.hasAtLeastOneElementOfType(MicrometerObservationCapability.class);
}
private Object getBuilderValue(Feign.Builder builder, String member) {
Field builderField = ReflectionUtils.findField(Feign.Builder.class, member);
ReflectionUtils.makeAccessible(builderField);
return ReflectionUtils.getField(builderField, builder);
}
@Test
public void testNoInheritFeignClient() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
Feign.Builder builder = factoryBean.feign(context);
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder, "requestInterceptors");
assertThat(interceptors).as("interceptors not set").isEmpty();
assertThat(factoryBean.isInheritParentContext()).as("is inheriting from parent configuration").isFalse();
List<Capability> capabilities = (List) getBuilderValue(builder, "capabilities");
assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class)
.hasAtLeastOneElementOfType(MicrometerObservationCapability.class);
}
@Test
public void testNoInheritFeignClient_ignoreProperties() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
Feign.Builder builder = factoryBean.feign(context);
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.HEADERS);
List<Capability> capabilities = (List) getBuilderValue(builder, "capabilities");
assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class)
.hasAtLeastOneElementOfType(MicrometerObservationCapability.class);
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = { TestFeignClient.class, NoInheritFeignClient.class })
protected static class Application {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
};
}
@Bean
public NoOpCapability noOpCapability() {
return new NoOpCapability();
}
}
public static class NoInheritConfiguration {
@Bean
public Logger.Level logLevel() {
return Logger.Level.HEADERS;
}
@Bean
public NoOpCapability noOpCapability() {
return new NoOpCapability();
}
@Bean
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {
@Override
public boolean inheritParentConfiguration() {
return false;
}
};
}
}
@FeignClient("testFeignClient")
interface TestFeignClient {
}
@FeignClient(name = "noInheritFeignClient", configuration = NoInheritConfiguration.class)
interface NoInheritFeignClient {
}
private static class NoOpCapability implements Capability {
}
}

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EnableFeignClientsSpringDataTests.java

@ -34,11 +34,11 @@ import org.springframework.test.annotation.DirtiesContext; @@ -34,11 +34,11 @@ import org.springframework.test.annotation.DirtiesContext;
class EnableFeignClientsSpringDataTests {
@Autowired
private FeignContext feignContext;
private FeignClientFactory feignClientFactory;
@Test
void encoderDefaultCorrect() {
PageableSpringEncoder.class.cast(this.feignContext.getInstance("foo", Encoder.class));
PageableSpringEncoder.class.cast(this.feignClientFactory.getInstance("foo", Encoder.class));
}
@Configuration(proxyBeanMethods = false)

8
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java

@ -162,8 +162,8 @@ class FeignBuilderCustomizerTests { @@ -162,8 +162,8 @@ class FeignBuilderCustomizerTests {
protected static class SampleConfiguration2 {
@Bean
FeignContext feignContext() {
return new FeignContext();
FeignClientFactory feignContext() {
return new FeignClientFactory();
}
@Bean
@ -198,8 +198,8 @@ class FeignBuilderCustomizerTests { @@ -198,8 +198,8 @@ class FeignBuilderCustomizerTests {
protected static class SampleConfiguration3 {
@Bean
FeignContext feignContext() {
return new FeignContext();
FeignClientFactory feignContext() {
return new FeignClientFactory();
}
@Bean

3
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java

@ -158,7 +158,8 @@ class FeignClientBuilderTests { @@ -158,7 +158,8 @@ class FeignClientBuilderTests {
@Test
void forType_build() {
// given:
Mockito.when(this.applicationContext.getBean(FeignContext.class)).thenThrow(new ClosedFileSystemException()); // throw
Mockito.when(this.applicationContext.getBean(FeignClientFactory.class))
.thenThrow(new ClosedFileSystemException()); // throw
// an
// unusual
// exception

1
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientCacheTests.java

@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Sam Kruglov
* @author Dominique Villard
*/
@SpringBootTest(classes = FeignClientCacheTests.TestConfiguration.class)
@DirtiesContext

5
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java

@ -27,6 +27,7 @@ import feign.ExceptionPropagationPolicy; @@ -27,6 +27,7 @@ import feign.ExceptionPropagationPolicy;
import feign.Logger;
import feign.QueryMapEncoder;
import feign.RequestInterceptor;
import feign.ResponseInterceptor;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
@ -60,6 +61,7 @@ class FeignClientConfigurationTests { @@ -60,6 +61,7 @@ class FeignClientConfigurationTests {
assertThat(config.getRetryer()).isNull();
assertThat(config.getErrorDecoder()).isNull();
assertThat(config.getRequestInterceptors()).isNull();
assertThat(config.getResponseInterceptor()).isNull();
assertThat(config.getDefaultRequestHeaders()).isNull();
assertThat(config.getDefaultQueryParameters()).isNull();
assertThat(config.getDismiss404()).isNull();
@ -82,6 +84,8 @@ class FeignClientConfigurationTests { @@ -82,6 +84,8 @@ class FeignClientConfigurationTests {
config.setErrorDecoder(ErrorDecoder.class);
List<Class<RequestInterceptor>> requestInterceptors = Lists.list(RequestInterceptor.class);
config.setRequestInterceptors(requestInterceptors);
Class<ResponseInterceptor> responseInterceptor = ResponseInterceptor.class;
config.setResponseInterceptor(responseInterceptor);
Map<String, Collection<String>> defaultRequestHeaders = Maps.newHashMap("default", Collections.emptyList());
config.setDefaultRequestHeaders(defaultRequestHeaders);
Map<String, Collection<String>> defaultQueryParameters = Maps.newHashMap("default", Collections.emptyList());
@ -103,6 +107,7 @@ class FeignClientConfigurationTests { @@ -103,6 +107,7 @@ class FeignClientConfigurationTests {
assertThat(config.getRetryer()).isSameAs(Retryer.class);
assertThat(config.getErrorDecoder()).isSameAs(ErrorDecoder.class);
assertThat(config.getRequestInterceptors()).isSameAs(requestInterceptors);
assertThat(config.getResponseInterceptor()).isSameAs(responseInterceptor);
assertThat(config.getDefaultRequestHeaders()).isSameAs(defaultRequestHeaders);
assertThat(config.getDefaultQueryParameters()).isSameAs(defaultQueryParameters);
assertThat(config.getDismiss404()).isTrue();

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java

@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class FeignClientDisabledClientLevelFeaturesTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java

@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class FeignClientDisabledFeaturesTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java

@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class FeignClientErrorDecoderTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

38
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignContextTest.java → spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java

@ -30,24 +30,24 @@ import org.springframework.context.annotation.Import; @@ -30,24 +30,24 @@ import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat;
class FeignContextTest {
class FeignClientFactoryTest {
@Test
void getInstanceWithoutAncestors_verifyNullForMissing() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("empty", null, EmptyConfiguration.class)));
Logger.Level level = feignContext.getInstanceWithoutAncestors("empty", Logger.Level.class);
Logger.Level level = feignClientFactory.getInstanceWithoutAncestors("empty", Logger.Level.class);
assertThat(level).as("Logger was not null").isNull();
}
private FeignClientSpecification getSpec(String name, Class<?> configClass) {
return new FeignClientSpecification(name, new Class[] { configClass });
private FeignClientSpecification getSpec(String name, String className, Class<?> configClass) {
return new FeignClientSpecification(name, className, new Class[] { configClass });
}
@Test
@ -55,11 +55,11 @@ class FeignContextTest { @@ -55,11 +55,11 @@ class FeignContextTest {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("empty", null, EmptyConfiguration.class)));
Collection<RequestInterceptor> interceptors = feignContext
Collection<RequestInterceptor> interceptors = feignClientFactory
.getInstancesWithoutAncestors("empty", RequestInterceptor.class).values();
assertThat(interceptors).as("Interceptors is not empty").isEmpty();
@ -70,11 +70,11 @@ class FeignContextTest { @@ -70,11 +70,11 @@ class FeignContextTest {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("demo", null, DemoConfiguration.class)));
Logger.Level level = feignContext.getInstanceWithoutAncestors("demo", Logger.Level.class);
Logger.Level level = feignClientFactory.getInstanceWithoutAncestors("demo", Logger.Level.class);
assertThat(level).isEqualTo(Logger.Level.FULL);
}
@ -84,11 +84,11 @@ class FeignContextTest { @@ -84,11 +84,11 @@ class FeignContextTest {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setApplicationContext(parent);
feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("demo", null, DemoConfiguration.class)));
Collection<RequestInterceptor> interceptors = feignContext
Collection<RequestInterceptor> interceptors = feignClientFactory
.getInstancesWithoutAncestors("demo", RequestInterceptor.class).values();
assertThat(interceptors.size()).isEqualTo(1);

19
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTests.java

@ -52,9 +52,10 @@ public class FeignClientFactoryTests { @@ -52,9 +52,10 @@ public class FeignClientFactoryTests {
public void testChildContexts() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext context = new FeignContext();
FeignClientFactory context = new FeignClientFactory();
context.setApplicationContext(parent);
context.setConfigurations(Arrays.asList(getSpec("foo", FooConfig.class), getSpec("bar", BarConfig.class)));
context.setConfigurations(
Arrays.asList(getSpec("foo", null, FooConfig.class), getSpec("bar", null, BarConfig.class)));
Foo foo = context.getInstance("foo", Foo.class);
assertThat(foo).as("foo was null").isNotNull();
@ -83,8 +84,8 @@ public class FeignClientFactoryTests { @@ -83,8 +84,8 @@ public class FeignClientFactoryTests {
assertThat(client).isInstanceOf(Client.Default.class);
}
private FeignClientSpecification getSpec(String name, Class<?> configClass) {
return new FeignClientSpecification(name, new Class[] { configClass });
private FeignClientSpecification getSpec(String name, String className, Class<?> configClass) {
return new FeignClientSpecification(name, className, new Class[] { configClass });
}
interface TestType {
@ -103,11 +104,11 @@ public class FeignClientFactoryTests { @@ -103,11 +104,11 @@ public class FeignClientFactoryTests {
}
@Bean
FeignContext feignContext() {
FeignContext feignContext = new FeignContext();
feignContext.setConfigurations(Collections.singletonList(
new FeignClientSpecification("test", new Class[] { LoadBalancerAutoConfiguration.class })));
return feignContext;
FeignClientFactory feignContext() {
FeignClientFactory feignClientFactory = new FeignClientFactory();
feignClientFactory.setConfigurations(Collections.singletonList(
new FeignClientSpecification("test", null, new Class[] { LoadBalancerAutoConfiguration.class })));
return feignClientFactory;
}
@Bean

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java

@ -63,7 +63,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; @@ -63,7 +63,7 @@ import static org.assertj.core.api.Assertions.assertThatCode;
class FeignClientOverrideDefaultsTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
private FooClient foo;

43
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java

@ -34,11 +34,13 @@ import java.util.stream.Stream; @@ -34,11 +34,13 @@ import java.util.stream.Stream;
import feign.Capability;
import feign.Feign;
import feign.InvocationContext;
import feign.InvocationHandlerFactory;
import feign.QueryMapEncoder;
import feign.Request;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.ResponseInterceptor;
import feign.RetryableException;
import feign.Retryer;
import feign.codec.EncodeException;
@ -79,6 +81,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen @@ -79,6 +81,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Ilia Ilinykh
* @author Jonatan Ivanov
* @author Hyeonmin Park
* @author Dominique Villard
*/
@SuppressWarnings("FieldMayBeFinal")
@SpringBootTest(classes = FeignClientUsingPropertiesTests.Application.class, webEnvironment = RANDOM_PORT)
@ -87,7 +90,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen @@ -87,7 +90,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
public class FeignClientUsingPropertiesTests {
@Autowired
FeignContext context;
FeignClientFactory context;
@Autowired
private ApplicationContext applicationContext;
@ -99,6 +102,8 @@ public class FeignClientUsingPropertiesTests { @@ -99,6 +102,8 @@ public class FeignClientUsingPropertiesTests {
private FeignClientFactoryBean barFactoryBean;
private FeignClientFactoryBean bazFactoryBean;
private FeignClientFactoryBean unwrapFactoryBean;
private FeignClientFactoryBean formFactoryBean;
@ -116,6 +121,10 @@ public class FeignClientUsingPropertiesTests { @@ -116,6 +121,10 @@ public class FeignClientUsingPropertiesTests {
barFactoryBean.setContextId("bar");
barFactoryBean.setType(FeignClientFactoryBean.class);
bazFactoryBean = new FeignClientFactoryBean();
bazFactoryBean.setContextId("baz");
bazFactoryBean.setType(FeignClientFactoryBean.class);
unwrapFactoryBean = new FeignClientFactoryBean();
unwrapFactoryBean.setContextId("unwrap");
unwrapFactoryBean.setType(FeignClientFactoryBean.class);
@ -143,6 +152,11 @@ public class FeignClientUsingPropertiesTests { @@ -143,6 +152,11 @@ public class FeignClientUsingPropertiesTests {
return barFactoryBean.feign(context).target(BarClient.class, "http://localhost:" + port);
}
public PingClient pingClient() {
bazFactoryBean.setApplicationContext(applicationContext);
return bazFactoryBean.feign(context).target(PingClient.class, "http://localhost:" + port);
}
public UnwrapClient unwrapClient() {
unwrapFactoryBean.setApplicationContext(applicationContext);
return unwrapFactoryBean.feign(context).target(UnwrapClient.class, "http://localhost:" + port);
@ -164,6 +178,12 @@ public class FeignClientUsingPropertiesTests { @@ -164,6 +178,12 @@ public class FeignClientUsingPropertiesTests {
assertThatThrownBy(() -> barClient().bar()).isInstanceOf(RetryableException.class);
}
@Test
public void testBaz() {
String response = pingClient().ping();
assertThat(response).isEqualTo("baz");
}
@Test
public void testUnwrap() throws Exception {
assertThatThrownBy(() -> unwrapClient().unwrap()).isInstanceOf(SocketTimeoutException.class);
@ -298,6 +318,13 @@ public class FeignClientUsingPropertiesTests { @@ -298,6 +318,13 @@ public class FeignClientUsingPropertiesTests {
}
protected interface PingClient {
@GetMapping(path = "/ping")
String ping();
}
protected interface UnwrapClient {
@GetMapping(path = "/bar") // intentionally /bar
@ -355,6 +382,11 @@ public class FeignClientUsingPropertiesTests { @@ -355,6 +382,11 @@ public class FeignClientUsingPropertiesTests {
return "OK";
}
@GetMapping(path = "/ping")
public String ping() {
return "pong";
}
@PostMapping(path = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String form(HttpServletRequest request) {
return request.getParameter("form");
@ -394,6 +426,15 @@ public class FeignClientUsingPropertiesTests { @@ -394,6 +426,15 @@ public class FeignClientUsingPropertiesTests {
}
public static class BazResponseInterceptor implements ResponseInterceptor {
@Override
public Object aroundDecode(InvocationContext invocationContext) {
return "baz";
}
}
public static class NoRetryer implements Retryer {
@Override

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientWithRefreshableOptionsTest.java

@ -67,7 +67,7 @@ public class FeignClientWithRefreshableOptionsTest { @@ -67,7 +67,7 @@ public class FeignClientWithRefreshableOptionsTest {
private FeignClientProperties clientProperties;
@Test
public void overridedOptionsBeanShouldBePresentInsteadOfRefreshable() {
public void overriddenOptionsBeanShouldBePresentInsteadOfRefreshable() {
OptionsTestClient.OptionsResponseForTests options = overrideOptionsClient.override();
assertConnectionAndReadTimeout(options, 1, 1);
}

46
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignCompressionTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -14,19 +14,20 @@ @@ -14,19 +14,20 @@
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
package org.springframework.cloud.openfeign.encoding;
import java.util.Map;
import feign.RequestInterceptor;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration;
import org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingInterceptor;
import org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration;
import org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingInterceptor;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -38,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -38,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class FeignCompressionTests {
@Test
void testInterceptors() {
void shouldAddCompressionInterceptors() {
new ApplicationContextRunner()
.withPropertyValues("spring.cloud.openfeign.compression.response.enabled=true",
"spring.cloud.openfeign.compression.request.enabled=true",
@ -47,8 +48,8 @@ class FeignCompressionTests { @@ -47,8 +48,8 @@ class FeignCompressionTests {
FeignContentGzipEncodingAutoConfiguration.class,
FeignAcceptGzipEncodingAutoConfiguration.class))
.run(context -> {
FeignContext feignContext = context.getBean(FeignContext.class);
Map<String, RequestInterceptor> interceptors = feignContext.getInstances("foo",
FeignClientFactory feignClientFactory = context.getBean(FeignClientFactory.class);
Map<String, RequestInterceptor> interceptors = feignClientFactory.getInstances("foo",
RequestInterceptor.class);
assertThat(interceptors.size()).isEqualTo(2);
assertThat(interceptors.get("feignAcceptGzipEncodingInterceptor"))
@ -58,4 +59,31 @@ class FeignCompressionTests { @@ -58,4 +59,31 @@ class FeignCompressionTests {
});
}
@Test
void shouldNotAddInterceptorsIfFeignOkHttpClientPresent() {
new ApplicationContextRunner()
.withPropertyValues("spring.cloud.openfeign.compression.response.enabled=true",
"spring.cloud.openfeign.compression.request.enabled=true",
"spring.cloud.openfeign.okhttp.enabled=true", "spring.cloud.openfeign.httpclient.hc5.enabled")
.withConfiguration(AutoConfigurations.of(FeignAutoConfiguration.class,
FeignContentGzipEncodingAutoConfiguration.class,
FeignAcceptGzipEncodingAutoConfiguration.class))
.run(context -> {
FeignClientFactory feignClientFactory = context.getBean(FeignClientFactory.class);
Map<String, RequestInterceptor> interceptors = feignClientFactory.getInstances("foo",
RequestInterceptor.class);
assertThat(interceptors).isEmpty();
});
}
@Configuration
static class OkHttpClientConfiguration {
@Bean
OkHttpClient okHttpClient() {
return new OkHttpClient();
}
}
}

70
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttp2ClientConfigurationTests.java

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
/*
* 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 java.net.http.HttpClient;
import java.time.Duration;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author changjin wei(魏昌进)
*/
class FeignHttp2ClientConfigurationTests {
private ConfigurableApplicationContext context;
@BeforeEach
void setUp() {
context = new SpringApplicationBuilder()
.properties("debug=true", "spring.cloud.openfeign.http2client.enabled=true",
"spring.cloud.openfeign.httpclient.http2.version=HTTP_1_1",
"spring.cloud.openfeign.httpclient.connectionTimeout=15")
.web(WebApplicationType.NONE).sources(FeignAutoConfiguration.class).run();
}
@AfterEach
void tearDown() {
if (context != null) {
context.close();
}
}
@Test
void shouldConfigureConnectTimeout() {
HttpClient httpClient = context.getBean(HttpClient.class);
assertThat(httpClient.connectTimeout()).isEqualTo(Optional.ofNullable(Duration.ofMillis(15)));
}
@Test
void shouldResolveVersionFromProperties() {
HttpClient httpClient = context.getBean(HttpClient.class);
assertThat(httpClient.version()).isEqualTo(HttpClient.Version.HTTP_1_1);
}
}

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlTests.java

@ -153,7 +153,7 @@ class FeignHttpClientUrlTests { @@ -153,7 +153,7 @@ class FeignHttpClientUrlTests {
public Targeter feignTargeter() {
return new Targeter() {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
Field field = ReflectionUtils.findField(Feign.Builder.class, "client");
ReflectionUtils.makeAccessible(field);

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClientUrlWithRetryableLoadBalancerTests.java

@ -154,7 +154,7 @@ class FeignHttpClientUrlWithRetryableLoadBalancerTests { @@ -154,7 +154,7 @@ class FeignHttpClientUrlWithRetryableLoadBalancerTests {
public Targeter feignTargeter() {
return new Targeter() {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,
Target.HardCodedTarget<T> target) {
Field field = ReflectionUtils.findField(Feign.Builder.class, "client");
ReflectionUtils.makeAccessible(field);

12
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignOkHttpConfigurationTests.java

@ -21,6 +21,7 @@ import java.lang.reflect.Field; @@ -21,6 +21,7 @@ import java.lang.reflect.Field;
import javax.net.ssl.HostnameVerifier;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
* @author changjin wei(魏昌进)
*/
class FeignOkHttpConfigurationTests {
@ -45,7 +47,8 @@ class FeignOkHttpConfigurationTests { @@ -45,7 +47,8 @@ class FeignOkHttpConfigurationTests {
.properties("debug=true", "spring.cloud.openfeign.httpclient.disableSslValidation=true",
"spring.cloud.openfeign.okhttp.enabled=true",
"spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.httpclient.okhttp.read-timeout=9s")
"spring.cloud.openfeign.httpclient.okhttp.read-timeout=9s",
"spring.cloud.openfeign.httpclient.okhttp.protocols=H2_PRIOR_KNOWLEDGE")
.web(WebApplicationType.NONE).sources(FeignAutoConfiguration.class).run();
}
@ -71,6 +74,13 @@ class FeignOkHttpConfigurationTests { @@ -71,6 +74,13 @@ class FeignOkHttpConfigurationTests {
assertThat(httpClient.readTimeoutMillis()).isEqualTo(9000);
}
@Test
void shouldResolveProtocolFromProperties() {
OkHttpClient httpClient = context.getBean(OkHttpClient.class);
assertThat(httpClient.protocols()).containsExactly(Protocol.H2_PRIOR_KNOWLEDGE);
}
protected Object getField(Object target, String name) {
Field field = ReflectionUtils.findField(target.getClass(), name);
ReflectionUtils.makeAccessible(field);

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/GzipDecodingTests.java

@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class GzipDecodingTests extends FeignClientFactoryBean {
@Autowired
FeignContext context;
FeignClientFactory context;
@Value("${local.server.port}")
private int port = 0;

10
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java → spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/LazyInitFeignClientUsingConfigurerTests.java

@ -41,21 +41,23 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -41,21 +41,23 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author matt king
* @author Jonatan Ivanov
* @author Olga Maciaszek-Sharma
*/
@DirtiesContext
@SpringBootTest(classes = FeignClientUsingConfigurerTest.Application.class, value = {
@SpringBootTest(classes = LazyInitFeignClientUsingConfigurerTests.Application.class, value = {
"spring.cloud.openfeign.lazy-attributes-resolution=true",
"spring.cloud.openfeign.client.config.default.loggerLevel=full",
"spring.cloud.openfeign.client.config.default.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor",
"spring.cloud.openfeign.client.config.default.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor" })
class FeignClientUsingConfigurerTest {
class LazyInitFeignClientUsingConfigurerTests {
private static final String BEAN_NAME_PREFIX = "org.springframework.cloud.openfeign.FeignClientUsingConfigurerTest$";
private static final String BEAN_NAME_PREFIX = "org.springframework.cloud.openfeign.LazyInitFeignClientUsingConfigurerTests$";
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Autowired
private FeignContext context;
private FeignClientFactory context;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test

5
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/NonRefreshableFeignClientUrlTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jasbir Singh
* @author Olga Maciaszek-Sharma
*/
@SpringBootTest
@TestPropertySource("classpath:feign-properties.properties")
@ -65,7 +66,7 @@ class NonRefreshableFeignClientUrlTests { @@ -65,7 +66,7 @@ class NonRefreshableFeignClientUrlTests {
public void shouldInstantiateFeignClientWhenUrlFromProperties() {
UrlTestClient.UrlResponseForTests response = configBasedClient.test();
assertThat(response.getUrl()).isEqualTo("http://localhost:9999/test");
assertThat(response.getTargetType()).isEqualTo(Target.HardCodedTarget.class);
assertThat(response.getTargetType()).isEqualTo(PropertyBasedTarget.class);
}
@Test

2
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java

@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class SpringDecoderTests extends FeignClientFactoryBean {
@Autowired
FeignContext context;
FeignClientFactory context;
@LocalServerPort
private int port = 0;

171
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/aot/FeignAotTests.java

@ -0,0 +1,171 @@ @@ -0,0 +1,171 @@
/*
* 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.net.URL;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.aot.AotDetector;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.javapoet.ClassName;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
/**
* AOT processing tests.
*
* @author Olga Maciaszek-Sharma
*/
@ExtendWith(OutputCaptureExtension.class)
public class FeignAotTests {
private static final Log LOG = LogFactory.getLog(FeignAotTests.class);
@BeforeEach
@AfterEach
void reset() {
ReflectionTestUtils.setField(TomcatURLStreamHandlerFactory.class, "instance", null);
ReflectionTestUtils.setField(URL.class, "factory", null);
}
@Test
@CompileWithForkedClassLoader
@SuppressWarnings("unchecked")
void shouldStartFeignChildContextsFromAotContributions(CapturedOutput output) {
WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(
AnnotationConfigServletWebApplicationContext::new)
.withConfiguration(AutoConfigurations.of(ServletWebServerFactoryAutoConfiguration.class,
FeignAutoConfiguration.class))
.withConfiguration(UserConfigurations.of(TestFeignConfiguration.class))
.withPropertyValues("logging.level.org.springframework.cloud=DEBUG");
contextRunner.prepare(context -> {
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
ClassName className = new ApplicationContextAotGenerator().processAheadOfTime(
(GenericApplicationContext) context.getSourceApplicationContext(), generationContext);
generationContext.writeGeneratedContent();
TestCompiler compiler = TestCompiler.forSystem();
compiler.with(generationContext).compile(compiled -> {
ServletWebServerApplicationContext freshApplicationContext = new ServletWebServerApplicationContext();
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
.getInstance(ApplicationContextInitializer.class, className.toString());
initializer.initialize(freshApplicationContext);
assertThat(output).contains("Creating a FeignClientFactoryBean.");
assertThat(output).contains("Refreshing FeignClientFactory-test-with-config",
"Refreshing FeignClientFactory-test");
assertThat(output).doesNotContain("Instantiating bean from Test custom config",
"Instantiating bean from default custom config");
TestPropertyValues.of(AotDetector.AOT_ENABLED + "=true")
.applyToSystemProperties(freshApplicationContext::refresh);
assertThat(output).contains("Instantiating bean from Test custom config",
"Instantiating bean from default custom config");
assertThat(freshApplicationContext.getBean(TestFeignClient.class)).isNotNull();
assertThat(freshApplicationContext.getBean(TestFeignClientWithConfig.class)).isNotNull();
});
});
}
static class TestTarget {
}
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = { TestFeignClient.class, TestFeignClientWithConfig.class },
defaultConfiguration = DefaultConfiguration.class)
public static class TestFeignConfiguration {
@Autowired
TestFeignClient testFeignClient;
@Autowired
TestFeignClientWithConfig testFeignClientWithConfig;
}
public static class TestConfiguration {
@Bean
TestBean testBean() {
if (LOG.isDebugEnabled()) {
LOG.debug("Instantiating bean from Test custom config");
}
return new TestBean();
}
}
public static class DefaultConfiguration {
@Bean
TestBean defaultTestBean() {
if (LOG.isDebugEnabled()) {
LOG.debug("Instantiating bean from default custom config");
}
return new TestBean();
}
}
public static class TestBean {
}
@FeignClient(value = "test", dismiss404 = true, url = "http://example.com")
interface TestFeignClient {
@GetMapping
void test();
}
@FeignClient(value = "test-with-config", configuration = TestConfiguration.class)
interface TestFeignClientWithConfig {
@GetMapping
void test();
}
}

197
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessorTests.java

@ -0,0 +1,197 @@ @@ -0,0 +1,197 @@
/*
* 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.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.cloud.openfeign.FeignClientSpecification;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.javapoet.TypeSpec;
import org.springframework.web.bind.annotation.PostMapping;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.proxies;
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
/**
* Tests for {@link FeignClientBeanFactoryInitializationAotProcessor}.
*
* @author Olga Maciaszek-Sharma
*/
class FeignClientBeanFactoryInitializationAotProcessorTests {
private static final String BEAN_DEFINITION_CLASS_NAME = "org.springframework.cloud.openfeign.aot.FeignClientBeanFactoryInitializationAotProcessorTests$TestClient";
private final TestGenerationContext generationContext = new TestGenerationContext();
private final FeignClientFactory feignClientFactory = mock(FeignClientFactory.class);
private final BeanFactoryInitializationCode beanFactoryInitializationCode = new MockBeanFactoryInitializationCode(
generationContext);
@BeforeEach
void setUp() {
Map<String, FeignClientSpecification> configurations = Map.of("test",
new FeignClientSpecification("test", TestClient.class.getCanonicalName(), new Class<?>[] {}));
when(feignClientFactory.getConfigurations()).thenReturn(configurations);
}
@Test
void shouldGenerateBeanInitializationCodeAndRegisterHints() {
DefaultListableBeanFactory beanFactory = beanFactory();
GenericApplicationContext context = new GenericApplicationContext(beanFactory);
FeignClientBeanFactoryInitializationAotProcessor processor = new FeignClientBeanFactoryInitializationAotProcessor(
context, feignClientFactory);
BeanFactoryInitializationAotContribution contribution = processor.processAheadOfTime(beanFactory);
assertThat(contribution).isNotNull();
verifyContribution((FeignClientBeanFactoryInitializationAotProcessor.AotContribution) contribution);
contribution.applyTo(generationContext, beanFactoryInitializationCode);
RuntimeHints hints = generationContext.getRuntimeHints();
assertThat(reflection().onType(TestReturnType.class)).accepts(hints);
assertThat(reflection().onType(TestArgType.class)).accepts(hints);
assertThat(proxies().forInterfaces(TestClient.class)).accepts(hints);
}
private static void verifyContribution(
FeignClientBeanFactoryInitializationAotProcessor.AotContribution contribution) {
BeanDefinition contributionBeanDefinition = contribution.getFeignClientBeanDefinitions()
.get(TestClient.class.getCanonicalName());
assertThat(contributionBeanDefinition.getBeanClassName()).isEqualTo(BEAN_DEFINITION_CLASS_NAME);
PropertyValues propertyValues = contributionBeanDefinition.getPropertyValues();
assertThat(propertyValues).isNotEmpty();
assertThat(((String[]) propertyValues.getPropertyValue("qualifiers").getValue())).containsExactly("test");
assertThat(propertyValues.getPropertyValue("type").getValue()).isEqualTo(TestClient.class.getCanonicalName());
assertThat(propertyValues.getPropertyValue("fallback").getValue()).isEqualTo(String.class);
}
private DefaultListableBeanFactory beanFactory() {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition(new RootBeanDefinition(TestClient.class,
new ConstructorArgumentValues(),
new MutablePropertyValues(List.of(new PropertyValue("type", TestClient.class.getCanonicalName()),
new PropertyValue("qualifiers", new String[] { "test" }),
new PropertyValue("fallback", String.class),
new PropertyValue("fallbackFactory", Void.class)))));
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition(TestClient.class.getCanonicalName(), beanDefinition);
return beanFactory;
}
@FeignClient
interface TestClient {
@PostMapping("/test")
TestReturnType test(TestArgType arg);
}
@SuppressWarnings("NullableProblems")
static class MockBeanFactoryInitializationCode implements BeanFactoryInitializationCode {
private static final Consumer<TypeSpec.Builder> emptyTypeCustomizer = type -> {
};
private final GeneratedClass generatedClass;
MockBeanFactoryInitializationCode(GenerationContext generationContext) {
generatedClass = generationContext.getGeneratedClasses().addForFeature("Test", emptyTypeCustomizer);
}
@Override
public GeneratedMethods getMethods() {
return generatedClass.getMethods();
}
@Override
public void addInitializer(MethodReference methodReference) {
new ArrayList<>();
}
}
static class TestReturnType {
private String test;
TestReturnType(String test) {
this.test = test;
}
TestReturnType() {
}
String getTest() {
return test;
}
void setTest(String test) {
this.test = test;
}
}
static class TestArgType {
private String test;
TestArgType(String test) {
this.test = test;
}
TestArgType() {
}
String getTest() {
return test;
}
void setTest(String test) {
this.test = test;
}
}
}

3
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/AsyncCircuitBreakerTests.java

@ -65,7 +65,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -65,7 +65,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author John Niang
*/
@SpringBootTest(classes = AsyncCircuitBreakerTests.Application.class, webEnvironment = RANDOM_PORT,
properties = "spring.cloud.openfeign.circuitbreaker.enabled=true")
properties = { "spring.cloud.openfeign.circuitbreaker.enabled=true",
"spring.cloud.openfeign.lazy-attributes-resolution=true" })
@AutoConfigureMockMvc
class AsyncCircuitBreakerTests {

134
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/circuitbreaker/FallbackSupportFactoryBeanTests.java

@ -0,0 +1,134 @@ @@ -0,0 +1,134 @@
/*
* 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.circuitbreaker;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.ConfigBuilder;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author 黄学敏huangxuemin)
*/
class FallbackSupportFactoryBeanTests {
private static final String FACTORY_BEAN_FALLBACK_MESSAGE = "factoryBean fallback message";
private static final String ORIGINAL_FALLBACK_MESSAGE = "OriginalFeign fallback message";
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withBean(FactoryBeanFallbackFeignFallback.class).withBean(OriginalFeignFallback.class)
.withConfiguration(AutoConfigurations.of(TestConfiguration.class, FeignAutoConfiguration.class))
.withBean(CircuitBreakerFactory.class, () -> new CircuitBreakerFactory() {
@Override
public CircuitBreaker create(String id) {
return new CircuitBreaker() {
@Override
public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
try {
return toRun.get();
}
catch (Throwable t) {
return fallback.apply(t);
}
}
};
}
@Override
protected ConfigBuilder configBuilder(String id) {
return null;
}
@Override
public void configureDefault(Function defaultConfiguration) {
}
}).withPropertyValues("spring.cloud.openfeign.circuitbreaker.enabled=true");
@Test
void shouldRunFallbackFromBeanOrFactoryBean() {
runner.run(ctx -> {
assertThat(ctx.getBean(OriginalFeign.class).get().equals(ORIGINAL_FALLBACK_MESSAGE)).isTrue();
assertThat(ctx.getBean(FactoryBeanFallbackFeign.class).get().equals(FACTORY_BEAN_FALLBACK_MESSAGE))
.isTrue();
});
}
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = { FallbackSupportFactoryBeanTests.OriginalFeign.class,
FallbackSupportFactoryBeanTests.FactoryBeanFallbackFeign.class })
@EnableAutoConfiguration
private static class TestConfiguration {
}
@FeignClient(name = "original", url = "https://original", fallback = OriginalFeignFallback.class)
interface OriginalFeign {
@GetMapping("/")
String get();
}
@FeignClient(name = "factoryBean", url = "https://factoryBean", fallback = FactoryBeanFallbackFeignFallback.class)
interface FactoryBeanFallbackFeign {
@GetMapping("/")
String get();
}
private static class FactoryBeanFallbackFeignFallback implements FactoryBean<FactoryBeanFallbackFeign> {
@Override
public FactoryBeanFallbackFeign getObject() {
return () -> FACTORY_BEAN_FALLBACK_MESSAGE;
}
@Override
public Class<?> getObjectType() {
return FactoryBeanFallbackFeign.class;
}
}
private static class OriginalFeignFallback implements OriginalFeign {
@Override
public String get() {
return ORIGINAL_FALLBACK_MESSAGE;
}
}
}

36
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -16,10 +16,12 @@ @@ -16,10 +16,12 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.net.http.HttpClient;
import java.util.Map;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import feign.http2client.Http2Client;
import feign.okhttp.OkHttpClient;
import org.junit.jupiter.api.Test;
@ -37,6 +39,7 @@ import static org.springframework.test.util.ReflectionTestUtils.getField; @@ -37,6 +39,7 @@ import static org.springframework.test.util.ReflectionTestUtils.getField;
/**
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
*/
class FeignLoadBalancerAutoConfigurationTests {
@ -65,6 +68,21 @@ class FeignLoadBalancerAutoConfigurationTests { @@ -65,6 +68,21 @@ class FeignLoadBalancerAutoConfigurationTests {
}
@Test
void shouldInstantiateHttp2ClientFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.http2client.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
Map<String, FeignBlockingLoadBalancerClient> beans = context
.getBeansOfType(FeignBlockingLoadBalancerClient.class);
assertThat(beans).as("Missing bean of type %s", Http2Client.class).hasSize(1);
Client client = beans.get("feignClient").getDelegate();
assertThat(client).isInstanceOf(Http2Client.class);
Http2Client http2Client = (Http2Client) client;
HttpClient httpClient = (HttpClient) getField(http2Client, "client");
assertThat(httpClient).isInstanceOf(HttpClient.class);
}
@Test
void shouldInstantiateHttpFeignClient5WhenAvailableAndOkHttpDisabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.okhttp.enabled=false",
@ -73,6 +91,14 @@ class FeignLoadBalancerAutoConfigurationTests { @@ -73,6 +91,14 @@ class FeignLoadBalancerAutoConfigurationTests {
assertLoadBalanced(context, ApacheHttp5Client.class);
}
@Test
void shouldInstantiateHttpFeignClient5WhenAvailableAndHttp2ClientDisabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.http2client.enabled=false",
"spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, ApacheHttp5Client.class);
}
@Test
void shouldInstantiateRetryableDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false");
@ -88,6 +114,14 @@ class FeignLoadBalancerAutoConfigurationTests { @@ -88,6 +114,14 @@ class FeignLoadBalancerAutoConfigurationTests {
assertLoadBalancedWithRetries(context, OkHttpClient.class);
}
@Test
void shouldInstantiateRetryableHttp2ClientFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.http2client.enabled=true");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, Http2Client.class);
}
@Test
void shouldInstantiateRetryableHttpFeignClient5WhenEnabled() {
ConfigurableApplicationContext context = initContext();

44
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -75,6 +75,7 @@ import static org.mockito.Mockito.when; @@ -75,6 +75,7 @@ import static org.mockito.Mockito.when;
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @author Wonsik Cheung
* @author Andriy Pikozh
* @see <a href=
* "https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java">BlockingLoadBalancerClientTests</a>
*/
@ -164,10 +165,51 @@ class RetryableFeignBlockingLoadBalancerClientTests { @@ -164,10 +165,51 @@ class RetryableFeignBlockingLoadBalancerClientTests {
feignBlockingLoadBalancerClient.execute(request, new Request.Options());
verify(loadBalancerClient, times(2)).choose(eq("test"), any());
verify(loadBalancerClient, times(2)).reconstructURI(serviceInstance, URI.create("http://test/path"));
verify(delegate, times(2)).execute(any(), any());
}
@Test
void shouldReuseServerInstanceOnSameInstanceRetry() throws IOException {
properties.getRetry().setMaxRetriesOnSameServiceInstance(1);
properties.getRetry().setMaxRetriesOnNextServiceInstance(0);
properties.getRetry().getRetryableStatusCodes().add(503);
Request request = testRequest();
Response response = testResponse(503);
when(delegate.execute(any(), any())).thenReturn(response);
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
.thenReturn(URI.create("http://testhost:80/path"));
feignBlockingLoadBalancerClient.execute(request, new Request.Options());
verify(loadBalancerClient, times(1)).choose(eq("test"), any());
verify(loadBalancerClient, times(2)).reconstructURI(serviceInstance, URI.create("http://test/path"));
verify(delegate, times(2)).execute(any(), any());
}
@Test
void shouldReuseServerInstanceOnSameInstanceRetryWithBothSameAndNextRetries() throws IOException {
properties.getRetry().setMaxRetriesOnSameServiceInstance(1);
properties.getRetry().setMaxRetriesOnNextServiceInstance(1);
properties.getRetry().getRetryableStatusCodes().add(503);
Request request = testRequest();
Response response = testResponse(503);
when(delegate.execute(any(), any())).thenReturn(response);
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
.thenReturn(URI.create("http://testhost:80/path"));
feignBlockingLoadBalancerClient.execute(request, new Request.Options());
verify(loadBalancerClient, times(2)).choose(eq("test"), any());
verify(loadBalancerClient, times(4)).reconstructURI(serviceInstance, URI.create("http://test/path"));
verify(delegate, times(4)).execute(any(), any());
}
@Test
void shouldNotRetryOnDisabled() throws IOException {
properties.getRetry().setEnabled(false);

79
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/protocol/FeignOkHttpProtocolsTests.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
/*
* 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.protocol;
import java.lang.reflect.Field;
import feign.Client;
import okhttp3.Protocol;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author changjin wei(魏昌进)
*/
@SpringBootTest(classes = FeignOkHttpProtocolsTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.circuitbreaker.enabled=false",
"spring.cloud.openfeign.httpclient.hc5.enabled=false", "spring.cloud.openfeign.okhttp.enabled=true",
"spring.cloud.httpclientfactories.ok.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false",
"server.http2.enabled=true", "spring.cloud.openfeign.httpclient.okhttp.protocols=H2_PRIOR_KNOWLEDGE" })
@DirtiesContext
class FeignOkHttpProtocolsTests {
@Autowired
private Client feignClient;
@Test
void shouldCreateCorrectFeignClientBeanWithProtocolFromProperties() {
assertThat(feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) feignClient;
Client delegate = client.getDelegate();
assertThat(delegate).isInstanceOf(feign.okhttp.OkHttpClient.class);
okhttp3.OkHttpClient OkHttpClient = (okhttp3.OkHttpClient) getField(delegate, "delegate");
assertThat(OkHttpClient.protocols()).containsExactly(Protocol.H2_PRIOR_KNOWLEDGE);
}
protected Object getField(Object target, String name) {
Field field = ReflectionUtils.findField(target.getClass(), name);
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, target);
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@RestController
@LoadBalancerClients
@Import(NoSecurityConfiguration.class)
protected static class Application {
}
}

37
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/FeignHttpClientPropertiesTests.java

@ -32,12 +32,15 @@ import org.springframework.context.annotation.Configuration; @@ -32,12 +32,15 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_CONNECTION_REQUEST_TIMEOUT;
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_CONNECTION_REQUEST_TIMEOUT_UNIT;
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_SOCKET_TIMEOUT;
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_SOCKET_TIMEOUT_UNIT;
/**
* @author Ryan Baxter
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
*/
@DirtiesContext
class FeignHttpClientPropertiesTests {
@ -67,22 +70,30 @@ class FeignHttpClientPropertiesTests { @@ -67,22 +70,30 @@ class FeignHttpClientPropertiesTests {
assertThat(getProperties().getHc5().getPoolReusePolicy()).isEqualTo(PoolReusePolicy.FIFO);
assertThat(getProperties().getHc5().getSocketTimeout()).isEqualTo(DEFAULT_SOCKET_TIMEOUT);
assertThat(getProperties().getHc5().getSocketTimeoutUnit()).isEqualTo(DEFAULT_SOCKET_TIMEOUT_UNIT);
assertThat(getProperties().getHc5().getConnectionRequestTimeout())
.isEqualTo(DEFAULT_CONNECTION_REQUEST_TIMEOUT);
assertThat(getProperties().getHc5().getConnectionRequestTimeoutUnit())
.isEqualTo(DEFAULT_CONNECTION_REQUEST_TIMEOUT_UNIT);
}
@Test
void testCustomization() {
TestPropertyValues.of("spring.cloud.openfeign.httpclient.maxConnections=2",
"spring.cloud.openfeign.httpclient.connectionTimeout=2",
"spring.cloud.openfeign.httpclient.maxConnectionsPerRoute=2",
"spring.cloud.openfeign.httpclient.timeToLive=2",
"spring.cloud.openfeign.httpclient.disableSslValidation=true",
"spring.cloud.openfeign.httpclient.followRedirects=false",
"spring.cloud.openfeign.httpclient.disableSslValidation=true",
"spring.cloud.openfeign.httpclient.followRedirects=false",
"spring.cloud.openfeign.httpclient.hc5.poolConcurrencyPolicy=lax",
"spring.cloud.openfeign.httpclient.hc5.poolReusePolicy=lifo",
"spring.cloud.openfeign.httpclient.hc5.socketTimeout=200",
"spring.cloud.openfeign.httpclient.hc5.socketTimeoutUnit=milliseconds").applyTo(this.context);
TestPropertyValues
.of("spring.cloud.openfeign.httpclient.maxConnections=2",
"spring.cloud.openfeign.httpclient.connectionTimeout=2",
"spring.cloud.openfeign.httpclient.maxConnectionsPerRoute=2",
"spring.cloud.openfeign.httpclient.timeToLive=2",
"spring.cloud.openfeign.httpclient.disableSslValidation=true",
"spring.cloud.openfeign.httpclient.followRedirects=false",
"spring.cloud.openfeign.httpclient.disableSslValidation=true",
"spring.cloud.openfeign.httpclient.followRedirects=false",
"spring.cloud.openfeign.httpclient.hc5.poolConcurrencyPolicy=lax",
"spring.cloud.openfeign.httpclient.hc5.poolReusePolicy=lifo",
"spring.cloud.openfeign.httpclient.hc5.socketTimeout=200",
"spring.cloud.openfeign.httpclient.hc5.socketTimeoutUnit=milliseconds",
"spring.cloud.openfeign.httpclient.hc5.connectionRequestTimeout=200",
"spring.cloud.openfeign.httpclient.hc5.connectionRequestTimeoutUnit=milliseconds")
.applyTo(this.context);
setupContext();
assertThat(getProperties().getMaxConnections()).isEqualTo(2);
assertThat(getProperties().getConnectionTimeout()).isEqualTo(2);
@ -94,6 +105,8 @@ class FeignHttpClientPropertiesTests { @@ -94,6 +105,8 @@ class FeignHttpClientPropertiesTests {
assertThat(getProperties().getHc5().getPoolReusePolicy()).isEqualTo(PoolReusePolicy.LIFO);
assertThat(getProperties().getHc5().getSocketTimeout()).isEqualTo(200);
assertThat(getProperties().getHc5().getSocketTimeoutUnit()).isEqualTo(TimeUnit.MILLISECONDS);
assertThat(getProperties().getHc5().getConnectionRequestTimeout()).isEqualTo(200);
assertThat(getProperties().getHc5().getConnectionRequestTimeoutUnit()).isEqualTo(TimeUnit.MILLISECONDS);
}
private void setupContext() {

5
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageJacksonModuleTests.java

@ -28,6 +28,7 @@ import org.junit.jupiter.params.provider.ValueSource; @@ -28,6 +28,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import static org.assertj.core.api.Assertions.assertThat;
@ -69,7 +70,7 @@ public class PageJacksonModuleTests { @@ -69,7 +70,7 @@ public class PageJacksonModuleTests {
@Test
public void serializeAndDeserializeEmpty() throws JsonProcessingException {
// Given
PageImpl<Object> objects = new PageImpl<>(new ArrayList<>());
PageImpl<Object> objects = new PageImpl<>(new ArrayList<>(), Pageable.ofSize(1), 0);
String pageJson = objectMapper.writeValueAsString(objects);
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
@ -105,7 +106,7 @@ public class PageJacksonModuleTests { @@ -105,7 +106,7 @@ public class PageJacksonModuleTests {
@Test
public void serializeAndDeserializeEmptyCascade() throws JsonProcessingException {
// Given
PageImpl<Object> objects = new PageImpl<>(new ArrayList<>());
PageImpl<Object> objects = new PageImpl<>(new ArrayList<>(), Pageable.ofSize(1), 0);
String pageJson = objectMapper.writeValueAsString(objects);
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableEncoderTests.java

@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@ -51,7 +51,7 @@ class PageableEncoderTests { @@ -51,7 +51,7 @@ class PageableEncoderTests {
public static final String SORT_1 = "sort1";
@Autowired
private FeignContext context;
private FeignClientFactory context;
protected String getPageParameter() {
return "page";

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoderTests.java

@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@ -52,7 +52,7 @@ class PageableSpringQueryMapEncoderTests { @@ -52,7 +52,7 @@ class PageableSpringQueryMapEncoderTests {
public static final String SORT_1 = "sort1";
@Autowired
private FeignContext context;
private FeignClientFactory context;
protected String getPageParameter() {
return "page";

4
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java

@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Qualifier; @@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@ -77,7 +77,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; @@ -77,7 +77,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
class SpringEncoderTests {
@Autowired
private FeignContext context;
private FeignClientFactory context;
@Autowired
@Qualifier("myHttpMessageConverter")

44
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* 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.
@ -38,6 +38,8 @@ import org.junit.jupiter.api.Test; @@ -38,6 +38,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.cloud.openfeign.CollectionFormat;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.number.NumberStyleFormatter;
@ -626,6 +628,26 @@ class SpringMvcContractTests { @@ -626,6 +628,26 @@ class SpringMvcContractTests {
.isEqualTo("cookie1={cookie1}; cookie2={cookie2}");
}
@Test
void shouldNotFailWhenBothPageableAndRequestBodyParamsInPostRequest() {
List<MethodMetadata> data = contract.parseAndValidateMetadata(TestTemplate_PageablePost.class);
assertThat(data.get(0).queryMapIndex()).isEqualTo(0);
assertThat(data.get(0).bodyIndex()).isEqualTo(1);
assertThat(data.get(1).queryMapIndex()).isEqualTo(1);
assertThat(data.get(1).bodyIndex()).isEqualTo(0);
}
@Test
void shouldSetPageableAsBodyWhenQueryMapParamPresent() {
List<MethodMetadata> data = contract.parseAndValidateMetadata(TestTemplate_PageablePostWithQueryMap.class);
assertThat(data.get(0).queryMapIndex()).isEqualTo(0);
assertThat(data.get(0).bodyIndex()).isEqualTo(1);
assertThat(data.get(1).queryMapIndex()).isEqualTo(1);
assertThat(data.get(1).bodyIndex()).isEqualTo(0);
}
private ConversionService getConversionService() {
FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean();
conversionServiceFactoryBean.afterPropertiesSet();
@ -827,6 +849,26 @@ class SpringMvcContractTests { @@ -827,6 +849,26 @@ class SpringMvcContractTests {
}
interface TestTemplate_PageablePost {
@PostMapping
Page<String> getPage(Pageable pageable, @RequestBody String body);
@PostMapping
Page<String> getPage(@RequestBody String body, Pageable pageable);
}
interface TestTemplate_PageablePostWithQueryMap {
@PostMapping
Page<String> getPage(@SpringQueryMap TestObject pojo, Pageable pageable);
@PostMapping
Page<String> getPage(Pageable pageable, @SpringQueryMap TestObject pojo);
}
@JsonAutoDetect(fieldVisibility = ANY, getterVisibility = NONE, setterVisibility = NONE)
public static class TestObject {

81
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/test/Http2ClientConfigurationTests.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
/*
* 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.test;
import java.net.http.HttpClient;
import feign.Client;
import feign.http2client.Http2Client;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author changjin wei(魏昌进)
*/
@SpringBootTest(properties = { "spring.cloud.openfeign.http2client.enabled= true",
"spring.cloud.openfeign.httpclient.hc5.enabled= false", "spring.cloud.loadbalancer.retry.enabled= false" })
@DirtiesContext
class Http2ClientConfigurationTests {
@Autowired
FeignBlockingLoadBalancerClient feignClient;
private static final HttpClient defaultHttpClient = HttpClient.newHttpClient();
@Test
void shouldInstantiateFeignHttp2Client() {
Client delegate = feignClient.getDelegate();
assertThat(delegate instanceof Http2Client).isTrue();
Http2Client http2Client = (Http2Client) delegate;
HttpClient httpClient = getField(http2Client, "client");
assertThat(httpClient).isEqualTo(defaultHttpClient);
}
@SuppressWarnings("unchecked")
protected <T> T getField(Object target, String name) {
Object value = ReflectionTestUtils.getField(target, target.getClass(), name);
return (T) value;
}
@FeignClient(name = "foo")
interface FooClient {
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class TestConfig {
@Bean
public HttpClient client() {
return defaultHttpClient;
}
}
}

256
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignHttp2ClientTests.java

@ -0,0 +1,256 @@ @@ -0,0 +1,256 @@
/*
* 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.valid;
import java.util.Objects;
import feign.Client;
import feign.http2client.Http2Client;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author changjin wei(魏昌进)
*/
@SpringBootTest(classes = FeignHttp2ClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.circuitbreaker.enabled=false",
"spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.http2client.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
class FeignHttp2ClientTests {
@Autowired
private TestClient testClient;
@Autowired
private Client feignClient;
@Autowired
private UserClient userClient;
@Test
void testSimpleType() {
Hello hello = testClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
}
@Test
void testPatch() {
ResponseEntity<Void> response = testClient.patchHello(new Hello("foo"));
assertThat(response).isNotNull();
String header = response.getHeaders().getFirst("x-hello");
assertThat(header).isEqualTo("hello world patch");
}
@Test
void testFeignClientType() {
assertThat(feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) feignClient;
Client delegate = client.getDelegate();
assertThat(delegate).isInstanceOf(Http2Client.class);
}
@Test
void testFeignInheritanceSupport() {
assertThat(userClient).as("UserClient was null").isNotNull();
final User user = userClient.getUser(1);
assertThat(user).as("Returned user was null").isNotNull();
assertThat(new User("John Smith")).as("Users were different").isEqualTo(user);
}
@FeignClient("localapp")
protected interface TestClient extends BaseTestClient {
}
protected interface BaseTestClient {
@GetMapping("/hello")
Hello getHello();
@PatchMapping(value = "/hellop", consumes = "application/json")
ResponseEntity<Void> patchHello(Hello hello);
}
protected interface UserService {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") long id);
}
@FeignClient("localapp1")
protected interface UserClient extends UserService {
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@RestController
@EnableFeignClients(clients = { TestClient.class, UserClient.class })
@LoadBalancerClients({
@LoadBalancerClient(name = "localapp", configuration = FeignHttpClientTests.LocalClientConfiguration.class),
@LoadBalancerClient(name = "localapp1",
configuration = FeignHttpClientTests.LocalClientConfiguration.class) })
@Import(NoSecurityConfiguration.class)
protected static class Application implements UserService {
@GetMapping("/hello")
public Hello getHello() {
return new Hello("hello world 1");
}
@PatchMapping("/hellop")
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
@RequestHeader("Content-Length") int contentLength) {
if (contentLength <= 0) {
throw new IllegalArgumentException("Invalid Content-Length " + contentLength);
}
if (!hello.getMessage().equals("foo")) {
throw new IllegalArgumentException("Invalid Hello: " + hello.getMessage());
}
return ResponseEntity.ok().header("X-Hello", "hello world patch").build();
}
@Override
public User getUser(@PathVariable("id") long id) {
return new User("John Smith");
}
}
public static class Hello {
private String message;
Hello() {
}
Hello(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Hello that = (Hello) o;
return Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(message);
}
}
public static class User {
private String name;
User() {
}
User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User that = (User) o;
return Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
// Load balancer with fixed server list for "local" pointing to localhost
@Configuration(proxyBeanMethods = false)
static class LocalClientConfiguration {
@LocalServerPort
private int port = 0;
@Bean
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
return ServiceInstanceListSuppliers.from("local",
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
}
}
}

1
spring-cloud-openfeign-core/src/test/resources/feign-properties.properties

@ -12,6 +12,7 @@ spring.cloud.openfeign.client.config.default.capabilities=org.springframework.cl @@ -12,6 +12,7 @@ spring.cloud.openfeign.client.config.default.capabilities=org.springframework.cl
spring.cloud.openfeign.client.config.default.queryMapEncoder=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.NoOpQueryMapEncoder
spring.cloud.openfeign.client.config.foo.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor
spring.cloud.openfeign.client.config.foo.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor
spring.cloud.openfeign.client.config.baz.responseInterceptor=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BazResponseInterceptor
spring.cloud.openfeign.client.config.singleValue.defaultRequestHeaders[singleValueHeaders]=header
spring.cloud.openfeign.client.config.singleValue.defaultQueryParameters[singleValueParameters]=parameter
spring.cloud.openfeign.client.config.multipleValue.defaultRequestHeaders[multipleValueHeaders]=header1,header2

11
spring-cloud-openfeign-core/src/test/resources/logback-test.xml

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>

17
spring-cloud-openfeign-dependencies/pom.xml

@ -6,16 +6,16 @@ @@ -6,16 +6,16 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-cloud-openfeign-dependencies</name>
<description>Spring Cloud OpenFeign Dependencies</description>
<properties>
<feign.version>12.1</feign.version>
<feign.version>12.5</feign.version>
<feign-form.version>3.8.0</feign-form.version>
</properties>
<dependencyManagement>
@ -41,6 +41,17 @@ @@ -41,6 +41,17 @@
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>${feign-form.version}</version>
<exclusions>
<exclusion>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
</dependencies>
</dependencyManagement>

5
spring-cloud-starter-openfeign/pom.xml

@ -5,9 +5,12 @@ @@ -5,9 +5,12 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<scm>
<url>https://github.com/spring-cloud/spring-cloud-openfeign</url>
</scm>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<name>Spring Cloud Starter OpenFeign</name>
<description>Spring Cloud Starter OpenFeign</description>

Loading…
Cancel
Save