Compare commits

...

218 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
spencergibb 62f9d76127
Moves ConditionalOnClass to static class 2 years ago
Olga Maciaszek-Sharma 5fb3790dab Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma cc85cdce8f Raise visibility of FeignOutputMessage. Fixes gh-734. 2 years ago
Jonatan Ivanov 372a631c38
Micrometer Observation API Support (#793) 2 years ago
Jonatan Ivanov 47d8094250
Upgrade to Feign 12.1 (#792) 2 years ago
buildmaster da70160113 Going back to snapshots 2 years ago
buildmaster db7eab8f0e Update SNAPSHOT to 4.0.0-RC2 2 years ago
Ryan Baxter 83c0eff4be
Remove additional Commons HttpClient dependencies. Fixes #788 (#789) 2 years ago
Ryan Baxter 1ec3c6522d
Use OK HttpClient directly (#788) 2 years ago
Olga Maciaszek-Sharma d30540d144
Remove http client 4 support (#785) 2 years ago
Olga Maciaszek-Sharma 6f1fab1ab3 Refactor. 2 years ago
Olga Maciaszek-Sharma a410fd1d10
Upgrade to Feign 12.0. (#782) 2 years ago
buildmaster a86b8cda5e Bumping versions to 3.1.6-SNAPSHOT after release 2 years ago
buildmaster af8db99454 Going back to snapshots 2 years ago
buildmaster cce0de9e89 Update SNAPSHOT to 3.1.5 2 years ago
buildmaster 3de3f8f46a Going back to snapshots 2 years ago
buildmaster 09f342ae6b Update SNAPSHOT to 4.0.0-RC1 2 years ago
Jasbir e33062fac4
Make feign.Target.url refreshable (#766) 2 years ago
Olga Maciaszek-Sharma 3b02c044cb Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma 9328a21d64 Refactor. 2 years ago
Olga Maciaszek-Sharma 9b97262f1f Update javadoc. Refactor. 2 years ago
pandaapo ed40511b19
Handle byte[] arrays correctly.(Fixes gh-741) (#774) 2 years ago
Olga Maciaszek-Sharma 0875ba2e93 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma 172fcbbedf Fix test class name. 2 years ago
vicasong c862311811
Release RequestAttributes when called asynchronously. Fixes gh-716 (#775) 2 years ago
Olga Maciaszek-Sharma 8fa27ddf56 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
정원식 561acf6276
Disable loadbalancer clients retry when clients retry is disabled (fixed) (#773) 2 years ago
小魏,小魏,我们要去哪里呀 e2c2e6c7b0
Enable X-Forwarded Headers. (#748) 2 years ago
Olga Maciaszek-Sharma d7fd71dfb5 Remove deprecated API use. 2 years ago
buildmaster 6ca3ef8fa6 Bumping versions 2 years ago
Nikita Konev e1a9b775ad
enable feign.autoconfiguration.jackson.enabled by default (#476) 2 years ago
Olga Maciaszek-Sharma 28345b2ad9
Remove deprecations and outdated API usage. (#768) 2 years ago
Olga Maciaszek-Sharma 075887eec3 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma 5b0f9f74af
Upgrade Feign to 10.11. Remove some deprecations. Refactor. (#767) 2 years ago
dependabot[bot] b3578fc454 Bump protobuf-java from 3.19.3 to 3.19.6 in /spring-cloud-openfeign-core (#764) 2 years ago
dependabot[bot] 536c53a705
Bump protobuf-java from 3.19.3 to 3.19.6 in /spring-cloud-openfeign-core (#764) 2 years ago
buildmaster d76455cf3d Going back to snapshots 2 years ago
buildmaster e05eba9834 Update SNAPSHOT to 4.0.0-M5 2 years ago
buildmaster a44d6e1a3b Bumping versions 2 years ago
Olga Maciaszek-Sharma 1e31a0d245 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
Olga Maciaszek-Sharma 77072d96e4 Inject OAuth2AuthorizedClientManager via constructor. 2 years ago
dzcr afbe0b232b Adjust based on review. 2 years ago
Olga Maciaszek-Sharma 60fc4a53e7 Refactor. Add docs. Fix dependencies. 2 years ago
dzcr d501cbbd07 step 1 2 years ago
Olga Maciaszek-Sharma 6098d910ac
Merge pull request #759 from spring-cloud/migrate-oauth-support-new 2 years ago
Olga Maciaszek-Sharma 09939b6174 Inject OAuth2AuthorizedClientManager via constructor. 2 years ago
dzcr c35fcffa48 Adjust based on review. 2 years ago
Olga Maciaszek-Sharma 8d3d5c574f Refactor. Add docs. Fix dependencies. 2 years ago
dzcr 66cc168a33 step 1 2 years ago
spencergibb 4dc2852ee0
Disables failing tests 2 years ago
spencergibb 6f583d5bad
Migrates to AutoConfiguration.imports 2 years ago
buildmaster 245b106a76 Bumping versions 2 years ago
buildmaster 11e9c5dab0 Going back to snapshots 2 years ago
Olga Maciaszek-Sharma 666a59df6b Adjust to changes in commons. 2 years ago
Olga Maciaszek-Sharma 5a21e22002 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
小魏,小魏,我们要去哪里呀 5bddccf720
Customize load balanced requests according to the chosen ServiceInstance (#735) 2 years ago
Olga Maciaszek-Sharma 5f40f245af Merge remote-tracking branch 'origin/main' 2 years ago
buildmaster e4bdc13c51 Going back to snapshots 2 years ago
buildmaster f344125527 Update SNAPSHOT to 4.0.0-M4 2 years ago
Olga Maciaszek-Sharma 26e157fd46 Merge remote-tracking branch 'origin/3.1.x' 2 years ago
spencergibb 540ec0410d
Removes libs-*-local repos 2 years ago
Olga Maciaszek-Sharma 1c8e8056e4 Remove vulnerable transitive dependency. Fix checkstyle. 2 years ago
buildmaster 3f55dbecba Going back to snapshots 2 years ago
buildmaster 1a72f2502e Update SNAPSHOT to 4.0.0-M3 2 years ago
Olga Maciaszek-Sharma 8493de9aa6 Fix @LocalServerPort imports. 3 years ago
buildmaster 001b7388a7 Going back to snapshots 3 years ago
buildmaster 0c5c3befbc Update SNAPSHOT to 4.0.0-M2 3 years ago
Olga Maciaszek-Sharma 952a7b0ff0 Fix merge 3.1.x. 3 years ago
Olga Maciaszek-Sharma 2b1ac9caa8 merge 3.1.x 3 years ago
Olga Maciaszek-Sharma 2a008d12c8 Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Ryan Baxter 295b5e4861 Fixing checkstyle and test errors 3 years ago
Ryan Baxter efbb0b8224 Adopt new circuit breaking naming convention by default 3 years ago
Ryan Baxter 4e1859eea6 Merge branch '3.1.x' 3 years ago
spencergibb 0972d4211f
Migrates to TestSocketUtils from spring-cloud-test-support 3 years ago
buildmaster 2f8f114667 Bumping versions 3 years ago
Olga Maciaszek-Sharma 0d0ff50842
Use raw types in ResponseData. (#685) 3 years ago
Olga Maciaszek-Sharma 088221b9fb Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Olga Maciaszek-Sharma 8998eb3423 Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Can Bezmen ddc1b94555
Fix gh-490 (#679) 3 years ago
Olga Maciaszek-Sharma cef268947f Merge remote-tracking branch 'origin/3.1.x' 3 years ago
buildmaster 8d802a3fee Bumping versions 3 years ago
Olga Maciaszek-Sharma e674f488d2 Remove unnecessary dependencies. 3 years ago
Olga Maciaszek-Sharma 2eea81f93b Merge remote-tracking branch 'origin/3.1.x' 3 years ago
buildmaster ee8a42e401 Going back to snapshots 3 years ago
buildmaster 53428758ad Update SNAPSHOT to 4.0.0-M1 3 years ago
buildmaster 59ac5c1b3c Bumping versions 3 years ago
spencergibb 4f7892f34d
formatting 3 years ago
buildmaster 261fc58a5e Bumping versions 3 years ago
Olga MaciaszekSharma 67e8c9072d Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Olga MaciaszekSharma 7b08279aae Merge remote-tracking branch 'origin/3.1.x' 3 years ago
buildmaster f60859120d Bumping versions 3 years ago
Olga MaciaszekSharma c665d2c029 Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Olga MaciaszekSharma 7f425704e4 Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Olga MaciaszekSharma 6c130af613 Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Olga MaciaszekSharma 98b2511d1f Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Olga MaciaszekSharma 10ad8a0c6e Update license entries. 3 years ago
Olga MaciaszekSharma 505894315d Merge remote-tracking branch 'origin/3.1.x' 3 years ago
Olga MaciaszekSharma e2048b3719 Merge remote-tracking branch 'origin/3.1.x' 3 years ago
buildmaster e389726ba2 Bumping versions 3 years ago
Spencer Gibb 2cf8adf482
Update github actions to use java 17 3 years ago
spencergibb 6a1d9fe074
Updates to version 4.0.0-SNAPSHOT 3 years ago
spencergibb b6b16a034a
Updates to version 4.0.0-SNAPSHOT 3 years ago
  1. 49
      .github/dependabot.yml
  2. 32
      .github/workflows/deploy-docs.yml
  3. 8
      .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. 2
      .sdkmanrc
  10. 292
      README.adoc
  11. 39
      docs/antora-playbook.yml
  12. 12
      docs/antora.yml
  13. 4
      docs/modules/ROOT/nav.adoc
  14. 2
      docs/modules/ROOT/pages/_attributes.adoc
  15. 7
      docs/modules/ROOT/pages/appendix.adoc
  16. 6
      docs/modules/ROOT/pages/configprops.adoc
  17. 1
      docs/modules/ROOT/pages/index.adoc
  18. 3
      docs/modules/ROOT/pages/intro.adoc
  19. 341
      docs/modules/ROOT/pages/spring-cloud-openfeign.adoc
  20. 3
      docs/modules/ROOT/partials/_attributes.adoc
  21. 113
      docs/modules/ROOT/partials/_configprops.adoc
  22. 6
      docs/modules/ROOT/partials/_conventions.adoc
  23. 6
      docs/modules/ROOT/partials/_metrics.adoc
  24. 6
      docs/modules/ROOT/partials/_spans.adoc
  25. 65
      docs/pom.xml
  26. 20
      docs/src/main/antora/resources/antora-resources/antora.yml
  27. 15
      docs/src/main/asciidoc/README.adoc
  28. 38
      docs/src/main/asciidoc/_configprops.adoc
  29. 1
      docs/src/main/asciidoc/index.adoc
  30. 303
      mvnw
  31. 96
      mvnw.cmd
  32. 17
      pom.xml
  33. 65
      spring-cloud-openfeign-core/pom.xml
  34. 3
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/DefaultFeignLoggerFactory.java
  35. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/DefaultTargeter.java
  36. 301
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java
  37. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java
  38. 3
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerDisabledConditions.java
  39. 40
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java
  40. 42
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java
  41. 21
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClient.java
  42. 9
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientBuilder.java
  43. 26
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java
  44. 133
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
  45. 10
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientMicrometerEnabledCondition.java
  46. 68
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java
  47. 30
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientSpecification.java
  48. 25
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java
  49. 94
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java
  50. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/OptionsFactoryBean.java
  51. 49
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/PropertyBasedTarget.java
  52. 28
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/RefreshableHardCodedTarget.java
  53. 38
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/RefreshableUrl.java
  54. 75
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/RefreshableUrlFactoryBean.java
  55. 18
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/SpringQueryMap.java
  56. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/Targeter.java
  57. 10
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/CookieValueParameterProcessor.java
  58. 1
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java
  59. 6
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/PathVariableParameterProcessor.java
  60. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/QueryMapParameterProcessor.java
  61. 126
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java
  62. 215
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java
  63. 45
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/Http2ClientFeignConfiguration.java
  64. 21
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClient5FeignConfiguration.java
  65. 125
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClientFeignConfiguration.java
  66. 78
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/OkHttpFeignConfiguration.java
  67. 11
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java
  68. 15
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignClientEncodingProperties.java
  69. 11
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingAutoConfiguration.java
  70. 51
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/OkHttpFeignClientBeanMissingCondition.java
  71. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/hateoas/WebConvertersCustomizer.java
  72. 13
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/DefaultFeignLoadBalancerConfiguration.java
  73. 38
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignBlockingLoadBalancerClient.java
  74. 16
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfiguration.java
  75. 40
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/Http2ClientFeignLoadBalancerConfiguration.java
  76. 18
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/HttpClient5FeignLoadBalancerConfiguration.java
  77. 47
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerFeignRequestTransformer.java
  78. 20
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerUtils.java
  79. 19
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/OkHttpFeignLoadBalancerConfiguration.java
  80. 63
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java
  81. 63
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/XForwardedHeadersTransformer.java
  82. 144
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java
  83. 183
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2FeignRequestInterceptor.java
  84. 81
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2FeignRequestInterceptorBuilder.java
  85. 35
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2FeignRequestInterceptorConfigurer.java
  86. 17
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/AbstractFormWriter.java
  87. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignEncoderProperties.java
  88. 93
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignHttpClientProperties.java
  89. 12
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignUtils.java
  90. 2
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/HttpMessageConverterCustomizer.java
  91. 6
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java
  92. 6
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoder.java
  93. 5
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/ResponseEntityDecoder.java
  94. 6
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringDecoder.java
  95. 29
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java
  96. 42
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java
  97. 48
      spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  98. 2
      spring-cloud-openfeign-core/src/main/resources/META-INF/spring/aot.factories
  99. 9
      spring-cloud-openfeign-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  100. 169
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/EagerInitFeignClientUsingConfigurerTests.java
  101. Some files were not shown because too many files have changed in this diff Show More

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)

8
.github/workflows/maven.yml

@ -16,18 +16,18 @@ jobs: @@ -16,18 +16,18 @@ jobs:
strategy:
matrix:
java: ["8", "11", "16"]
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

2
.sdkmanrc

@ -1,3 +1,3 @@ @@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.292.hs-adpt
java=17.0.1-tem

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.

341
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
@ -121,17 +125,22 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT @@ -121,17 +125,22 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT
* `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`)
* `Encoder` feignEncoder: `SpringEncoder`
* `Logger` feignLogger: `Slf4jLogger`
* `MicrometerCapability` micrometerCapability: If `feign-micrometer` is on the classpath and `MeterRegistry` is available
* `CachingCapability` cachingCapability: If `@EnableCaching` annotation is used. Can be disabled via `feign.cache.enabled`.
* `MicrometerObservationCapability` micrometerObservationCapability: If `feign-micrometer` is on the classpath and `ObservationRegistry` is available
* `MicrometerCapability` micrometerCapability: If `feign-micrometer` is on the classpath, `MeterRegistry` is available and `ObservationRegistry` is not available
* `CachingCapability` cachingCapability: If `@EnableCaching` annotation is used. Can be disabled via `spring.cloud.openfeign.cache.enabled`.
* `Contract` feignContract: `SpringMvcContract`
* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder`
* `Client` feignClient: If Spring Cloud LoadBalancer is on the classpath, `FeignBlockingLoadBalancerClient` is used.
If none of them is on the classpath, the default feign client is used.
NOTE: `spring-cloud-starter-openfeign` supports `spring-cloud-starter-loadbalancer`. However, as is an optional dependency, you need to make sure it 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 ApacheHttpClient and ApacheHC5 feign clients can be used by setting `feign.okhttp.enabled` or `feign.httpclient.enabled` or `feign.httpclient.hc5.enabled` to `true`, respectively, and having them on the classpath.
You can customize the HTTP client used by providing a bean of either `org.apache.http.impl.client.CloseableHttpClient` when using Apache or `okhttp3.OkHttpClient` when using OK HTTP or `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5.
The OkHttpClient, Apache HttpClient 5 and Http2Client Feign clients can be used by setting `spring.cloud.openfeign.okhttp.enabled` or `spring.cloud.openfeign.httpclient.hc5.enabled` or `spring.cloud.openfeign.http2client.enabled` to `true`, respectively, and having them on the classpath.
You can customize the HTTP client used by providing a bean of either `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5.
You can further customise http clients by setting values in the `spring.cloud.openfeign.httpclient.xxx` properties. The ones prefixed just with `httpclient` will work for all the clients, the ones prefixed with `httpclient.hc5` to Apache HttpClient 5, the ones prefixed with `httpclient.okhttp` to OkHttpClient and the ones prefixed with `httpclient.http2` to Http2Client. You can find a full list of properties you can customise in the appendix.
TIP: Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.
Spring Cloud OpenFeign _does not_ provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client:
@ -142,7 +151,7 @@ Spring Cloud OpenFeign _does not_ provide the following beans by default for fei @@ -142,7 +151,7 @@ Spring Cloud OpenFeign _does not_ provide the following beans by default for fei
* `Collection<RequestInterceptor>`
* `SetterFactory`
* `QueryMapEncoder`
* `Capability` (`MicrometerCapability` and `CachingCapability` are provided by default)
* `Capability` (`MicrometerObservationCapability` and `CachingCapability` are provided by default)
A bean of `Retryer.NEVER_RETRY` with the type `Retryer` is created by default, which will disable retrying.
Notice this retrying behavior is different from the Feign default one, where it will automatically retry IOExceptions,
@ -173,54 +182,62 @@ This replaces the `SpringMvcContract` with `feign.Contract.Default` and adds a ` @@ -173,54 +182,62 @@ This replaces the `SpringMvcContract` with `feign.Contract.Default` and adds a `
application.yml
[source,yaml]
----
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
queryMapEncoder: com.example.SimpleQueryMapEncoder
metrics.enabled: false
----
spring:
cloud:
openfeign:
client:
config:
feignName:
url: http://remote-service.com
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
responseInterceptor: com.example.BazResponseInterceptor
dismiss404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
queryMapEncoder: com.example.SimpleQueryMapEncoder
micrometer.enabled: false
----
`feignName` in this example refers to `@FeignClient` `value`, that is also aliased with `@FeignClient` `name` and `@FeignClient` `contextId`. In a load-balanced scenario, it also corresponds to the `serviceId` of the server app that will be used to retrieve the instances. The specified classes for decoders, retryer and other ones must have a bean in the Spring context or have a default constructor.
Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients.
If you prefer using configuration properties to configure all `@FeignClient`, you can create configuration properties with `default` feign name.
You can use `feign.client.config.feignName.defaultQueryParameters` and `feign.client.config.feignName.defaultRequestHeaders` to specify query parameters and headers that will be sent with every request of the client named `feignName`.
You can use `spring.cloud.openfeign.client.config.feignName.defaultQueryParameters` and `spring.cloud.openfeign.client.config.feignName.defaultRequestHeaders` to specify query parameters and headers that will be sent with every request of the client named `feignName`.
application.yml
[source,yaml]
----
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
----
If we create both `@Configuration` bean and configuration properties, configuration properties will win.
It will override `@Configuration` values. But if you want to change the priority to `@Configuration`,
you can change `feign.client.default-to-properties` to `false`.
you can change `spring.cloud.openfeign.client.default-to-properties` to `false`.
If we want to create multiple feign clients with the same name or url
so that they would point to the same server but each with a different custom configuration then
@ -266,13 +283,14 @@ public FeignClientConfigurer feignClientConfigurer() { @@ -266,13 +283,14 @@ public FeignClientConfigurer feignClientConfigurer() {
}
----
TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `feign.client.decodeSlash` to `false`.
TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `spring.cloud.openfeign.client.decodeSlash` to `false`.
[[springencoder-configuration]]
==== `SpringEncoder` configuration
In the `SpringEncoder` that we provide, we set `null` charset for binary content types and `UTF-8` for all the other ones.
You can modify this behaviour to derive the charset from the `Content-Type` header charset instead by setting the value of `feign.encoder.charset-from-content-type` to `true`.
You can modify this behaviour to derive the charset from the `Content-Type` header charset instead by setting the value of `spring.cloud.openfeign.encoder.charset-from-content-type` to `true`.
[[timeout-handling]]
=== Timeout Handling
@ -284,6 +302,7 @@ We can configure timeouts on both the default and the named client. OpenFeign wo @@ -284,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
@ -302,12 +321,12 @@ class FooController { @@ -302,12 +321,12 @@ class FooController {
private FooClient adminClient;
@Autowired
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerCapability micrometerCapability) {
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");
@ -315,7 +334,7 @@ class FooController { @@ -315,7 +334,7 @@ class FooController {
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
@ -337,7 +356,7 @@ You can do this by overriding calling `inheritParentContext(false)` on the `Buil @@ -337,7 +356,7 @@ You can do this by overriding calling `inheritParentContext(false)` on the `Buil
[[spring-cloud-feign-circuitbreaker]]
=== Feign Spring Cloud CircuitBreaker Support
If Spring Cloud CircuitBreaker is on the classpath and `feign.circuitbreaker.enabled=true`, Feign will wrap all methods with a circuit breaker.
If Spring Cloud CircuitBreaker is on the classpath and `spring.cloud.openfeign.circuitbreaker.enabled=true`, Feign will wrap all methods with a circuit breaker.
To disable Spring Cloud CircuitBreaker support on a per-client basis create a vanilla `Feign.Builder` with the "prototype" scope, e.g.:
@ -370,16 +389,12 @@ public class FooConfiguration { @@ -370,16 +389,12 @@ public class FooConfiguration {
}
----
To enable Spring Cloud CircuitBreaker group set the `feign.circuitbreaker.group.enabled` property to `true` (by default `false`).
To enable Spring Cloud CircuitBreaker group set the `spring.cloud.openfeign.circuitbreaker.group.enabled` property to `true` (by default `false`).
[[spring-clou-feign-circuitbreaker-configurationproperties]]
=== Configuring CircuitBreakers With Configuration Properties
You can configure CircuitBreakers via configuration properties. To do set
`feign.circuitbreaker.alphanumeric-ids.enabled` to `true`. Since
you cannot use characters like `#`, `(`, `)` `,` in configuration property names we need to
change the naming convention for the ids of the circuit breakers generated by OpenFeign. The above
property will do this for you.
You can configure CircuitBreakers via configuration properties.
For example, if you had this Feign client
@ -397,11 +412,13 @@ You could configure it using configuration properties by doing the following @@ -397,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:
@ -413,6 +430,8 @@ resilience4j: @@ -413,6 +430,8 @@ resilience4j:
timeoutDuration: 10s
----
NOTE: If you want to switch back to the circuit breaker names used prior to Spring Cloud
2022.0.0 you can set `spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled` to `false`.
[[spring-cloud-feign-circuitbreaker-fallback]]
=== Feign Spring Cloud CircuitBreaker Fallbacks
@ -489,6 +508,7 @@ If one needs access to the cause that made the fallback trigger, one can use the @@ -489,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.
@ -539,6 +559,7 @@ public interface UserClient extends UserService { @@ -539,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
@ -546,24 +567,27 @@ Feign requests. You can do this by enabling one of the properties: @@ -546,24 +567,27 @@ Feign requests. You can do this by enabling one of the properties:
[source,java]
----
feign.compression.request.enabled=true
feign.compression.response.enabled=true
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true
----
Feign request compression gives you settings similar to what you may set for your web server:
[source,java]
----
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json
spring.cloud.openfeign.compression.request.min-request-size=2048
----
These properties allow you to be selective about the compressed media types and minimum request threshold length.
TIP: Since the OkHttpClient uses "transparent" compression, that is disabled if the `content-encoding` or `accept-encoding` header is present, we do not enable compression when `feign.okhttp.OkHttpClient` is present on the classpath and `spring.cloud.openfeign.okhttp.enabled` is set to `true`.
[[feign-logging]]
=== Feign logging
A logger is created for each Feign client created. By default the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the `DEBUG` level.
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
@ -592,10 +616,11 @@ public class FooConfiguration { @@ -592,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 metrics libraries is a good real-life example for this. See <<feign-metrics>>.
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.
@ -610,29 +635,43 @@ public class FooConfiguration { @@ -610,29 +635,43 @@ public class FooConfiguration {
}
----
=== Feign metrics
[[micrometer-support]]
=== Micrometer Support
If all of the following conditions are true, a `MicrometerCapability` bean is created and registered so that your Feign client publishes metrics to Micrometer:
If all of the following conditions are true, a `MicrometerObservationCapability` bean is created and registered so that your Feign client is observable by Micrometer:
* `feign-micrometer` is on the classpath
* A `MeterRegistry` bean is available
* feign metrics properties are set to `true` (by default)
- `feign.metrics.enabled=true` (for all clients)
- `feign.client.config.feignName.metrics.enabled=true` (for a single client)
* A `ObservationRegistry` bean is available
* feign micrometer properties are set to `true` (by default)
- `spring.cloud.openfeign.micrometer.enabled=true` (for all clients)
- `spring.cloud.openfeign.client.config.feignName.micrometer.enabled=true` (for a single client)
NOTE: If your application already uses Micrometer, enabling metrics is as simple as putting `feign-micrometer` onto your classpath.
NOTE: If your application already uses Micrometer, enabling this feature is as simple as putting `feign-micrometer` onto your classpath.
You can also disable the feature by either:
* excluding `feign-micrometer` from your classpath
* setting one of the feign metrics properties to `false`
- `feign.metrics.enabled=false`
- `feign.client.config.feignName.metrics.enabled=false`
* setting one of the feign micrometer properties to `false`
- `spring.cloud.openfeign.micrometer.enabled=false`
- `spring.cloud.openfeign.client.config.feignName.micrometer.enabled=false`
NOTE: `spring.cloud.openfeign.micrometer.enabled=false` disables Micrometer support for *all* Feign clients regardless of the value of the client-level flags: `spring.cloud.openfeign.client.config.feignName.micrometer.enabled`.
If you want to enable or disable Micrometer support per client, don't set `spring.cloud.openfeign.micrometer.enabled` and use `spring.cloud.openfeign.client.config.feignName.micrometer.enabled`.
NOTE: `feign.metrics.enabled=false` disables metrics support for *all* Feign clients regardless of the value of the client-level flags: `feign.client.config.feignName.metrics.enabled`.
If you want to enable or disable merics per client, don't set `feign.metrics.enabled` and use `feign.client.config.feignName.metrics.enabled`.
You can also customize the `MicrometerObservationCapability` by registering your own bean:
You can also customize the `MicrometerCapability` by registering your own bean:
[source,java,indent=0]
----
@Configuration
public class FooConfiguration {
@Bean
public MicrometerObservationCapability micrometerObservationCapability(ObservationRegistry registry) {
return new MicrometerObservationCapability(registry);
}
}
----
It is still possible to use `MicrometerCapability` with Feign (metrics-only support), you need to disable Micrometer support (`spring.cloud.openfeign.micrometer.enabled=false`) and create a `MicrometerCapability` bean:
[source,java,indent=0]
----
@ -645,6 +684,7 @@ public class FooConfiguration { @@ -645,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:
@ -659,14 +699,11 @@ public interface DemoClient { @@ -659,14 +699,11 @@ public interface DemoClient {
}
----
You can also disable the feature via property `feign.cache.enabled=false`.
You can also disable the feature via property `spring.cloud.openfeign.cache.enabled=false`.
[[feign-querymap-support]]
=== Feign @QueryMap support
The OpenFeign `@QueryMap` annotation provides support for POJOs to be used as
GET parameter maps. Unfortunately, the default OpenFeign QueryMap annotation is
incompatible with Spring because it lacks a `value` property.
Spring Cloud OpenFeign provides an equivalent `@SpringQueryMap` annotation, which
is used to annotate a POJO or Map parameter as a query parameter map.
@ -697,6 +734,7 @@ public interface DemoTemplate { @@ -697,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].
@ -717,6 +755,7 @@ public interface DemoTemplate { @@ -717,6 +755,7 @@ public interface DemoTemplate {
}
----
[[spring-matrixvariable-support]]
=== Spring @MatrixVariable Support
Spring Cloud OpenFeign provides support for the Spring `@MatrixVariable` annotation.
@ -747,6 +786,7 @@ public interface DemoTemplate { @@ -747,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.
@ -756,22 +796,22 @@ In the following example, the `CSV` format is used instead of the default `EXPLO @@ -756,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.
@ -783,36 +823,131 @@ To work around this problem you can use an `ObjectProvider` when autowiring your @@ -783,36 +823,131 @@ To work around this problem you can use an `ObjectProvider` when autowiring your
ObjectProvider<TestFeignClient> testFeignClient;
----
[[spring-data-support]]
=== Spring Data Support
You may consider enabling Jackson Modules for the support `org.springframework.data.domain.Page` and `org.springframework.data.domain.Sort` decoding.
If Jackson Databind and Spring Data Commons are on the classpath, converters for `org.springframework.data.domain.Page` and `org.springframework.data.domain.Sort` will be added automatically.
To disable this behaviour set
[source,java]
----
feign.autoconfiguration.jackson.enabled=true
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 through `POST /actuator/refresh`.
If Feign client refresh is enabled, each Feign client is created with:
* `feign.Request.Options` as a refresh-scoped bean. This means properties such as `connectTimeout` and `readTimeout` can be refreshed against any Feign client instance.
* A url wrapped under `org.springframework.cloud.openfeign.RefreshableUrl`. This means the URL of Feign client, if defined
with `spring.cloud.openfeign.client.config.\{feignName}.url` property, can be refreshed against any Feign client instance.
You can refresh these properties through `POST /actuator/refresh`.
By default, refresh behavior in Feign clients is disabled. Use the following property to enable refresh behavior:
[source,java]
----
feign.client.refresh-enabled=true
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:
----
feign.oauth2.enabled=true
spring.cloud.openfeign.oauth2.enabled=true
----
When the flag is set to true, and the oauth2 client context resource details are present, a bean of class `OAuth2FeignRequestInterceptor` is created. Before each request, the interceptor resolves the required access token and includes it as a header.
Sometimes, when load balancing is enabled for Feign clients, you may want to use load balancing for fetching access tokens, too. To do so, you should ensure that the load balancer is on the classpath (spring-cloud-starter-loadbalancer) and explicitly enable load balancing for OAuth2FeignRequestInterceptor by setting the following flag:
When the flag is set to true, and the oauth2 client context resource details are present, a bean of class `OAuth2AccessTokenInterceptor` is created. Before each request, the interceptor resolves the required access token and includes it as a header.
`OAuth2AccessTokenInterceptor` uses the `OAuth2AuthorizedClientManager` to get `OAuth2AuthorizedClient` that holds an `OAuth2AccessToken`. If the user has specified an OAuth2 `clientRegistrationId` using the `spring.cloud.openfeign.oauth2.clientRegistrationId` property, it will be used to retrieve the token. If the token is not retrieved or the `clientRegistrationId` has not been specified, the `serviceId` retrieved from the `url` host segment will be used.
TIP:: Using the `serviceId` as OAuth2 client registrationId is convenient for load-balanced Feign clients. For non-load-balanced ones, the property-based `clientRegistrationId` is a suitable approach.
TIP:: If you do not want to use the default setup for the `OAuth2AuthorizedClientManager`, you can just instantiate a bean of this type in your configuration.
[[transform-the-load-balanced-http-request]]
=== Transform the load-balanced HTTP request
You can use the selected `ServiceInstance` to transform the load-balanced HTTP Request.
For `Request`, you need to implement and define `LoadBalancerFeignRequestTransformer`, as follows:
[source,java,indent=0]
----
@Bean
public LoadBalancerFeignRequestTransformer transformer() {
return new LoadBalancerFeignRequestTransformer() {
@Override
public Request transformRequest(Request request, ServiceInstance instance) {
Map<String, Collection<String>> headers = new HashMap<>(request.headers());
headers.put("X-ServiceId", Collections.singletonList(instance.getServiceId()));
headers.put("X-InstanceId", Collections.singletonList(instance.getInstanceId()));
return Request.create(request.httpMethod(), request.url(), headers, request.body(), request.charset(),
request.requestTemplate());
}
};
}
----
If multiple transformers are defined, they are applied in the order in which beans are defined.
Alternatively, you can use `LoadBalancerFeignRequestTransformer.DEFAULT_ORDER` to specify the order.
[[x-forwarded-headers-support]]
=== X-Forwarded Headers Support
`X-Forwarded-Host` and `X-Forwarded-Proto` support can be enabled by setting following flag:
[source,properties]
----
feign.oauth2.load-balanced=true
spring.cloud.loadbalancer.x-forwarded.enabled=true
----
[[supported-ways-to-provide-url-to-a-feign-client]]
=== Supported Ways To Provide URL To A Feign Client
You can provide a URL to a Feign client in any of the following ways:
|===
|Case |Example |Details
|The URL is provided in the `@FeignClient` annotation.
|`@FeignClient(name="testClient", url="http://localhost:8081")`
|The URL is resolved from the `url` attribute of the annotation, without load-balancing.
|The URL is provided in the `@FeignClient` annotation and in the
configuration properties.
|`@FeignClient(name="testClient", url="http://localhost:8081")` and the property defined in `application.yml` as
`spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081`
|The URL is resolved from the `url` attribute of the annotation, without load-balancing.
The URL provided in the configuration properties remains unused.
|The URL is not provided in the `@FeignClient` annotation but is provided in configuration properties.
| `@FeignClient(name="testClient")` and the property defined in `application.yml` as
`spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081`
|The URL is resolved from configuration properties, without load-balancing. If
`spring.cloud.openfeign.client.refresh-enabled=true`, then the URL defined in configuration properties can be refreshed as described in <<Spring `@RefreshScope` Support,Spring RefreshScope Support>>.
|The URL is neither provided in the `@FeignClient` annotation nor in configuration properties.
|`@FeignClient(name="testClient")`
|The URL is resolved from `name` attribute of annotation, with load balancing.
|===
[[aot-and-native-image-support]]
=== AOT and Native Image Support
Spring Cloud OpenFeign supports Spring AOT transformations and native images, however, only with refresh mode disabled, Feign clients refresh disabled (default setting) and xref:spring-cloud-openfeign.adoc#attribute-resolution-mode[lazy `@FeignClient` attribute resolution] disabled (default setting).
WARNING: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, make sure to set `spring.cloud.refresh.enabled` to `false`.
TIP: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, ensure `spring.cloud.openfeign.client.refresh-enabled` has not been set to `true`.
TIP: If you want to run Spring Cloud OpenFeign clients in AOT or native image modes, ensure `spring.cloud.openfeign.lazy-attributes-resolution` has not been set to `true`.
TIP: However, if you set the `url` value via properties, it is possible to override the `@FeignClient` `url` value by running the image with `-Dspring.cloud.openfeign.client.config.[clientId].url=[url]` flag. In order to enable overriding, a `url` value also has to be set via properties and not `@FeignClient` attribute during buildtime.
[[configuration-properties]]
== Configuration properties
To see the list of all Spring Cloud OpenFeign related configuration properties please check link:appendix.html[the Appendix page].

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>3.1.4</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].

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

@ -1,38 +0,0 @@ @@ -1,38 +0,0 @@
|===
|Name | Default | Description
|feign.autoconfiguration.jackson.enabled | `+++false+++` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.
|feign.circuitbreaker.alphanumeric-ids.enabled | `+++false+++` | If true, Circuit Breaker ids will only contain alphanumeric characters to allow for configuration via configuration properties.
|feign.circuitbreaker.enabled | `+++false+++` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.
|feign.circuitbreaker.group.enabled | `+++false+++` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group.
|feign.client.config | |
|feign.client.decode-slash | `+++true+++` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`.
|feign.client.default-config | `+++default+++` |
|feign.client.default-to-properties | `+++true+++` |
|feign.client.refresh-enabled | `+++false+++` | Enables options value refresh capability for Feign.
|feign.compression.request.enabled | `+++false+++` | Enables the request sent by Feign to be compressed.
|feign.compression.request.mime-types | `+++[text/xml, application/xml, application/json]+++` | The list of supported mime types.
|feign.compression.request.min-request-size | `+++2048+++` | The minimum threshold content size.
|feign.compression.response.enabled | `+++false+++` | Enables the response from Feign to be compressed.
|feign.encoder.charset-from-content-type | `+++false+++` | Indicates whether the charset should be derived from the {@code Content-Type} header.
|feign.httpclient.connection-timeout | `+++2000+++` |
|feign.httpclient.connection-timer-repeat | `+++3000+++` |
|feign.httpclient.disable-ssl-validation | `+++false+++` |
|feign.httpclient.enabled | `+++true+++` | Enables the use of the Apache HTTP Client by Feign.
|feign.httpclient.follow-redirects | `+++true+++` |
|feign.httpclient.hc5.enabled | `+++false+++` | Enables the use of the Apache HTTP Client 5 by Feign.
|feign.httpclient.hc5.pool-concurrency-policy | | Pool concurrency policies.
|feign.httpclient.hc5.pool-reuse-policy | | Pool connection re-use policies.
|feign.httpclient.hc5.socket-timeout | `+++5+++` | Default value for socket timeout.
|feign.httpclient.hc5.socket-timeout-unit | | Default value for socket timeout unit.
|feign.httpclient.max-connections | `+++200+++` |
|feign.httpclient.max-connections-per-route | `+++50+++` |
|feign.httpclient.ok-http.read-timeout | `+++60s+++` | {@link OkHttpClient} read timeout; defaults to 60 seconds.
|feign.httpclient.time-to-live | `+++900+++` |
|feign.httpclient.time-to-live-unit | |
|feign.metrics.enabled | `+++true+++` | Enables metrics capability for Feign.
|feign.oauth2.enabled | `+++false+++` | Enables feign interceptor for managing oauth2 access token.
|feign.oauth2.load-balanced | `+++false+++` | Enables load balancing for oauth2 access token provider.
|feign.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%

17
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>3.1.4</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>3.1.4</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>3.1.4</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>
@ -64,13 +64,6 @@ @@ -64,13 +64,6 @@
</additionalConfig>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
@ -235,7 +228,7 @@ @@ -235,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>

65
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>3.1.4</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>
@ -61,11 +64,6 @@ @@ -61,11 +64,6 @@
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava-reactive-streams</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
@ -105,21 +103,24 @@ @@ -105,21 +103,24 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<exclusion>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<optional>true</optional>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<artifactId>feign-slf4j</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<artifactId>feign-micrometer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
@ -133,8 +134,8 @@ @@ -133,8 +134,8 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-java11</artifactId>
<optional>true</optional>
</dependency>
<dependency>
@ -142,25 +143,6 @@ @@ -142,25 +143,6 @@
<artifactId>okhttp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.activation</groupId>
<artifactId>javax.activation-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
@ -205,7 +187,7 @@ @@ -205,7 +187,7 @@
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.19.3</version>
<version>3.19.6</version>
<scope>test</scope>
</dependency>
<dependency>
@ -222,9 +204,24 @@ @@ -222,9 +204,24 @@
<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>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<profiles>
<profile>

3
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/DefaultFeignLoggerFactory.java

@ -21,10 +21,11 @@ import feign.slf4j.Slf4jLogger; @@ -21,10 +21,11 @@ import feign.slf4j.Slf4jLogger;
/**
* @author Venil Noronha
* @author Olga Maciaszek-Sharma
*/
public class DefaultFeignLoggerFactory implements FeignLoggerFactory {
private Logger logger;
private final Logger logger;
public DefaultFeignLoggerFactory(Logger logger) {
this.logger = logger;

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);
}

301
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.
@ -16,35 +16,42 @@ @@ -16,35 +16,42 @@
package org.springframework.cloud.openfeign;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import com.fasterxml.jackson.databind.Module;
import feign.Capability;
import feign.Client;
import feign.Feign;
import feign.RequestInterceptor;
import feign.Target;
import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient;
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.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.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;
@ -56,14 +63,9 @@ import org.springframework.cache.interceptor.CacheInterceptor; @@ -56,14 +63,9 @@ 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.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptorConfigurer;
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;
import org.springframework.cloud.openfeign.support.PageJacksonModule;
@ -72,12 +74,14 @@ import org.springframework.context.annotation.Bean; @@ -72,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.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import static org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptorBuilder.buildWithConfigurers;
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
@ -91,6 +95,8 @@ import static org.springframework.cloud.openfeign.security.OAuth2FeignRequestInt @@ -91,6 +95,8 @@ import static org.springframework.cloud.openfeign.security.OAuth2FeignRequestInt
* @author Kwangyong Kim
* @author Sam Kruglov
* @author Wojciech Mąka
* @author Dangzhicairang(小水牛)
* @author changjin wei(魏昌进)
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@ -109,14 +115,26 @@ public class FeignAutoConfiguration { @@ -109,14 +115,26 @@ 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
@ConditionalOnProperty(value = "feign.cache.enabled", matchIfMissing = true)
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)
public Capability cachingCapability(CacheInterceptor cacheInterceptor) {
return new CachingCapability(cacheInterceptor);
@ -124,7 +142,8 @@ public class FeignAutoConfiguration { @@ -124,7 +142,8 @@ public class FeignAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Module.class, Page.class, Sort.class })
@ConditionalOnProperty(value = "feign.autoconfiguration.jackson.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.cloud.openfeign.autoconfiguration.jackson.enabled", havingValue = "true",
matchIfMissing = true)
protected static class FeignJacksonConfiguration {
@Bean
@ -155,7 +174,7 @@ public class FeignAutoConfiguration { @@ -155,7 +174,7 @@ public class FeignAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.enabled", havingValue = "true")
protected static class CircuitBreakerPresentFeignTargeterConfiguration {
@Bean
@ -166,24 +185,26 @@ public class FeignAutoConfiguration { @@ -166,24 +185,26 @@ public class FeignAutoConfiguration {
@Bean
@ConditionalOnMissingBean(CircuitBreakerNameResolver.class)
@ConditionalOnProperty(value = "feign.circuitbreaker.alphanumeric-ids.enabled", havingValue = "false",
matchIfMissing = true)
@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled",
havingValue = "false")
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return new DefaultCircuitBreakerNameResolver();
}
@Bean
@ConditionalOnMissingBean(CircuitBreakerNameResolver.class)
@ConditionalOnProperty(value = "feign.circuitbreaker.alphanumeric-ids.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled",
havingValue = "true", matchIfMissing = true)
public CircuitBreakerNameResolver alphanumericCircuitBreakerNameResolver() {
return new AlphanumericCircuitBreakerNameResolver();
}
@SuppressWarnings("rawtypes")
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(CircuitBreakerFactory.class)
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,
@Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,
@Value("${spring.cloud.openfeign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,
CircuitBreakerNameResolver circuitBreakerNameResolver) {
return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,
circuitBreakerNameResolver);
@ -213,105 +234,63 @@ public class FeignAutoConfiguration { @@ -213,105 +234,63 @@ public class FeignAutoConfiguration {
// SC loadbalancer is not on the class path.
// see corresponding configurations in FeignLoadBalancerAutoConfiguration
// for load-balanced clients.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@Autowired(required = false)
private RegistryBuilder registryBuilder;
private CloseableHttpClient httpClient;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
this.httpClient = httpClientFactory.createBuilder().setConnectionManager(httpClientConnectionManager)
.setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
@PreDestroy
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@ConditionalOnProperty("spring.cloud.openfeign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean
public okhttp3.OkHttpClient.Builder okHttpClientBuilder() {
return new okhttp3.OkHttpClient.Builder();
}
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties) {
int maxTotalConnections = httpClientProperties.getMaxConnections();
long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
return new ConnectionPool(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool,
public okhttp3.OkHttpClient okHttpClient(okhttp3.OkHttpClient.Builder builder, ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {
boolean followRedirects = httpClientProperties.isFollowRedirects();
int connectTimeout = httpClientProperties.getConnectionTimeout();
boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
Duration readTimeout = httpClientProperties.getOkHttp().getReadTimeout();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects)
.readTimeout(readTimeout).connectionPool(connectionPool).build();
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)
.protocols(protocols).build();
return this.okHttpClient;
}
private void disableSsl(okhttp3.OkHttpClient.Builder builder) {
try {
X509TrustManager disabledTrustManager = new DisableValidationTrustManager();
TrustManager[] trustManagers = new TrustManager[1];
trustManagers[0] = disabledTrustManager;
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManagers, new java.security.SecureRandom());
SSLSocketFactory disabledSSLSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(disabledSSLSocketFactory, disabledTrustManager);
builder.hostnameVerifier(new TrustAllHostnames());
}
catch (NoSuchAlgorithmException | KeyManagementException e) {
LOG.warn("Error setting SSLSocketFactory in OKHttpClient", e);
}
}
@PreDestroy
public void destroy() {
if (this.okHttpClient != null) {
@ -326,12 +305,49 @@ public class FeignAutoConfiguration { @@ -326,12 +305,49 @@ public class FeignAutoConfiguration {
return new OkHttpClient(client);
}
/**
* A {@link X509TrustManager} that does not validate SSL certificates.
*/
class DisableValidationTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
/**
* A {@link HostnameVerifier} that does not validate any hostnames.
*/
class TrustAllHostnames implements HostnameVerifier {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}
}
// 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(ApacheHttp5Client.class)
@ConditionalOnMissingBean(org.apache.hc.client5.http.impl.classic.CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.hc5.enabled", havingValue = "true",
matchIfMissing = true)
@Import(org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.class)
protected static class HttpClient5FeignConfiguration {
@ -344,37 +360,62 @@ public class FeignAutoConfiguration { @@ -344,37 +360,62 @@ public class FeignAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OAuth2ClientContext.class)
@ConditionalOnProperty("feign.oauth2.enabled")
@Deprecated // spring-security-oauth2 reached EOL
@ConditionalOnClass(OAuth2AuthorizedClientManager.class)
@ConditionalOnProperty("spring.cloud.openfeign.oauth2.enabled")
protected static class Oauth2FeignConfiguration {
@ConditionalOnBean({ RetryLoadBalancerInterceptor.class, OAuth2ClientContext.class,
OAuth2ProtectedResourceDetails.class })
@ConditionalOnProperty(value = "feign.oauth2.load-balanced", havingValue = "true")
@Bean
public OAuth2FeignRequestInterceptorConfigurer retryLoadBalancerInterceptorInjectingConfigurer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return builder -> builder.withAccessTokenProviderInterceptors(loadBalancerInterceptor);
@ConditionalOnBean({ OAuth2AuthorizedClientService.class, ClientRegistrationRepository.class })
@ConditionalOnMissingBean
OAuth2AuthorizedClientManager feignOAuth2AuthorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
oAuth2AuthorizedClientService);
}
@ConditionalOnBean({ LoadBalancerInterceptor.class, OAuth2ClientContext.class,
OAuth2ProtectedResourceDetails.class })
@ConditionalOnProperty(value = "feign.oauth2.load-balanced", havingValue = "true")
@Bean
public OAuth2FeignRequestInterceptorConfigurer loadBalancerInterceptorInjectingConfigurer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return builder -> builder.withAccessTokenProviderInterceptors(loadBalancerInterceptor);
@ConditionalOnBean(OAuth2AuthorizedClientManager.class)
public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor(
@Value("${spring.cloud.openfeign.oauth2.clientRegistrationId:}") String clientRegistrationId,
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
return new OAuth2AccessTokenInterceptor(clientRegistrationId, oAuth2AuthorizedClientManager);
}
}
// 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(OAuth2FeignRequestInterceptor.class)
@ConditionalOnBean({ OAuth2ClientContext.class, OAuth2ProtectedResourceDetails.class })
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource, List<OAuth2FeignRequestInterceptorConfigurer> configurers) {
return buildWithConfigurers(oAuth2ClientContext, resource, configurers);
@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));
}
}

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

@ -77,7 +77,7 @@ public final class FeignCircuitBreaker { @@ -77,7 +77,7 @@ public final class FeignCircuitBreaker {
}
public <T> T target(Target<T> target, T fallback) {
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
return build(fallback != null ? new FallbackFactory.Default<>(fallback) : null).newInstance(target);
}
public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {

3
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerDisabledConditions.java

@ -31,7 +31,8 @@ class FeignCircuitBreakerDisabledConditions extends AnyNestedCondition { @@ -31,7 +31,8 @@ class FeignCircuitBreakerDisabledConditions extends AnyNestedCondition {
}
@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "false", matchIfMissing = true)
@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.enabled", havingValue = "false",
matchIfMissing = true)
static class CircuitBreakerDisabled {
}

40
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.
@ -36,6 +36,14 @@ import org.springframework.web.context.request.RequestContextHolder; @@ -36,6 +36,14 @@ import org.springframework.web.context.request.RequestContextHolder;
import static feign.Util.checkNotNull;
/**
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @author Niang
* @author Bohutskyi
* @author kim
* @author Vicasong
*/
class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
private final CircuitBreakerFactory factory;
@ -68,7 +76,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { @@ -68,7 +76,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
public Object invoke(final Object proxy, final Method method, final Object[] args) {
// early exit if the invoked method is from java.lang.Object
// code is the same as ReflectiveFeign.FeignInvocationHandler
if ("equals".equals(method.getName())) {
@ -122,9 +130,13 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { @@ -122,9 +130,13 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
private Supplier<Object> asSupplier(final Method method, final Object[] args) {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final Thread caller = Thread.currentThread();
return () -> {
boolean isAsync = caller != Thread.currentThread();
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
if (isAsync) {
RequestContextHolder.setRequestAttributes(requestAttributes);
}
return dispatch.get(method).invoke(args);
}
catch (RuntimeException throwable) {
@ -133,20 +145,25 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { @@ -133,20 +145,25 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
finally {
if (isAsync) {
RequestContextHolder.resetRequestAttributes();
}
}
};
}
/**
* 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) {
Map<Method, Method> result = new LinkedHashMap<Method, Method>();
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
@ -156,8 +173,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { @@ -156,8 +173,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
@Override
public boolean equals(Object obj) {
if (obj instanceof FeignCircuitBreakerInvocationHandler) {
FeignCircuitBreakerInvocationHandler other = (FeignCircuitBreakerInvocationHandler) obj;
if (obj instanceof FeignCircuitBreakerInvocationHandler other) {
return this.target.equals(other.target);
}
return false;

42
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,12 +43,11 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -39,12 +43,11 @@ 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)) {
if (!(feign instanceof FeignCircuitBreaker.Builder builder)) {
return feign.target(target);
}
FeignCircuitBreaker.Builder builder = (FeignCircuitBreaker.Builder) feign;
String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
@ -57,20 +60,20 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -57,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) {
@ -79,10 +82,27 @@ class FeignCircuitBreakerTargeter implements Targeter { @@ -79,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;
}

21
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClient.java

@ -64,27 +64,8 @@ public @interface FeignClient { @@ -64,27 +64,8 @@ public @interface FeignClient {
@AliasFor("value")
String name() default "";
/**
* @return the <code>@Qualifier</code> value for the feign client.
* @deprecated in favour of {@link #qualifiers()}.
*
* If both {@link #qualifier()} and {@link #qualifiers()} are present, we will use the
* latter, unless the array returned by {@link #qualifiers()} is empty or only
* contains <code>null</code> or whitespace values, in which case we'll fall back
* first to {@link #qualifier()} and, if that's also not present, to the default =
* <code>contextId + "FeignClient"</code>.
*/
@Deprecated
String qualifier() default "";
/**
* @return the <code>@Qualifiers</code> value for the feign client.
*
* If both {@link #qualifier()} and {@link #qualifiers()} are present, we will use the
* latter, unless the array returned by {@link #qualifiers()} is empty or only
* contains <code>null</code> or whitespace values, in which case we'll fall back
* first to {@link #qualifier()} and, if that's also not present, to the default =
* <code>contextId + "FeignClient"</code>.
*/
String[] qualifiers() default {};
@ -96,7 +77,7 @@ public @interface FeignClient { @@ -96,7 +77,7 @@ public @interface FeignClient {
/**
* @return whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;
boolean dismiss404() default false;
/**
* A custom configuration class for the feign client. Can contain override

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

@ -29,6 +29,7 @@ import org.springframework.context.ApplicationContext; @@ -29,6 +29,7 @@ import org.springframework.context.ApplicationContext;
* @author Sven Döring
* @author Matt King
* @author Sam Kruglov
* @author Olga Maciaszek-Sharma
*/
public class FeignClientBuilder {
@ -54,7 +55,7 @@ public class FeignClientBuilder { @@ -54,7 +55,7 @@ public class FeignClientBuilder {
*/
public static final class Builder<T> {
private FeignClientFactoryBean feignClientFactoryBean;
private final FeignClientFactoryBean feignClientFactoryBean;
private Builder(final ApplicationContext applicationContext, final Class<T> type, final String name) {
this(applicationContext, new FeignClientFactoryBean(), type, name);
@ -71,7 +72,7 @@ public class FeignClientBuilder { @@ -71,7 +72,7 @@ public class FeignClientBuilder {
this.feignClientFactoryBean.setInheritParentContext(true);
// preset default values - these values resemble the default values on the
// FeignClient annotation
this.url("").path("").decode404(false);
this.url("").path("").dismiss404(false);
}
public Builder<T> url(final String url) {
@ -101,8 +102,8 @@ public class FeignClientBuilder { @@ -101,8 +102,8 @@ public class FeignClientBuilder {
return this;
}
public Builder<T> decode404(final boolean decode404) {
this.feignClientFactoryBean.setDecode404(decode404);
public Builder<T> dismiss404(final boolean dismiss404) {
this.feignClientFactoryBean.setDismiss404(dismiss404);
return this;
}

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, "feign", "feign.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);
}
}

133
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 {
@ -79,7 +81,7 @@ public class FeignClientFactoryBean @@ -79,7 +81,7 @@ public class FeignClientFactoryBean
* lifecycle race condition.
***********************************/
private static Log LOG = LogFactory.getLog(FeignClientFactoryBean.class);
private static final Log LOG = LogFactory.getLog(FeignClientFactoryBean.class);
private Class<?> type;
@ -91,7 +93,7 @@ public class FeignClientFactoryBean @@ -91,7 +93,7 @@ public class FeignClientFactoryBean
private String path;
private boolean decode404;
private boolean dismiss404;
private boolean inheritParentContext = true;
@ -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,12 +221,16 @@ public class FeignClientFactoryBean @@ -210,12 +221,16 @@ 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);
}
if (decode404) {
builder.decode404();
if (dismiss404) {
builder.dismiss404();
}
ExceptionPropagationPolicy exceptionPropagationPolicy = getInheritedAwareOptional(context,
ExceptionPropagationPolicy.class);
@ -268,9 +283,13 @@ public class FeignClientFactoryBean @@ -268,9 +283,13 @@ public class FeignClientFactoryBean
}
}
if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
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);
@ -407,12 +426,12 @@ public class FeignClientFactoryBean @@ -407,12 +426,12 @@ public class FeignClientFactoryBean
* @return a {@link Feign} client created with the specified data and the context
* information
*/
@SuppressWarnings("unchecked")
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) {
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()) {
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
@ -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 (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
Targeter targeter = get(feignClientFactory, Targeter.class);
return targeter.target(this, builder, feignClientFactory, resolveTarget(feignClientFactory, contextId, url));
}
private String cleanPath() {
@ -467,6 +486,39 @@ public class FeignClientFactoryBean @@ -467,6 +486,39 @@ public class FeignClientFactoryBean
return path;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> HardCodedTarget<T> resolveTarget(FeignClientFactory context, String contextId, String url) {
if (StringUtils.hasText(url)) {
return new HardCodedTarget(type, name, url);
}
if (refreshableClient) {
RefreshableUrl refreshableUrl = context.getInstance(contextId,
RefreshableUrl.class.getCanonicalName() + "-" + contextId, RefreshableUrl.class);
if (Objects.nonNull(refreshableUrl) && StringUtils.hasText(refreshableUrl.getUrl())) {
return new RefreshableHardCodedTarget<>(type, name, refreshableUrl);
}
}
FeignClientProperties.FeignClientConfiguration config = findConfigByKey(contextId);
if (Objects.isNull(config) || !StringUtils.hasText(config.getUrl())) {
throw new IllegalStateException(
"Provide Feign client URL either in @FeignClient() or in config properties.");
}
return new PropertyBasedTarget(type, name, config);
}
private boolean isUrlAvailableInConfig(String contextId) {
FeignClientProperties.FeignClientConfiguration config = findConfigByKey(contextId);
return Objects.nonNull(config) && StringUtils.hasText(config.getUrl());
}
private FeignClientProperties.FeignClientConfiguration findConfigByKey(String configKey) {
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
: applicationContext.getBean(FeignClientProperties.class);
return properties.getConfig().get(configKey);
}
@Override
public Class<?> getObjectType() {
return type;
@ -517,12 +569,12 @@ public class FeignClientFactoryBean @@ -517,12 +569,12 @@ public class FeignClientFactoryBean
this.path = path;
}
public boolean isDecode404() {
return decode404;
public boolean isDismiss404() {
return dismiss404;
}
public void setDecode404(boolean decode404) {
this.decode404 = decode404;
public void setDismiss404(boolean dismiss404) {
this.dismiss404 = dismiss404;
}
public boolean isInheritParentContext() {
@ -567,6 +619,14 @@ public class FeignClientFactoryBean @@ -567,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) {
@ -577,7 +637,7 @@ public class FeignClientFactoryBean @@ -577,7 +637,7 @@ public class FeignClientFactoryBean
}
FeignClientFactoryBean that = (FeignClientFactoryBean) o;
return Objects.equals(applicationContext, that.applicationContext)
&& Objects.equals(beanFactory, that.beanFactory) && decode404 == that.decode404
&& Objects.equals(beanFactory, that.beanFactory) && dismiss404 == that.dismiss404
&& inheritParentContext == that.inheritParentContext && Objects.equals(fallback, that.fallback)
&& Objects.equals(fallbackFactory, that.fallbackFactory) && Objects.equals(name, that.name)
&& Objects.equals(path, that.path) && Objects.equals(type, that.type) && Objects.equals(url, that.url)
@ -589,15 +649,16 @@ public class FeignClientFactoryBean @@ -589,15 +649,16 @@ public class FeignClientFactoryBean
@Override
public int hashCode() {
return Objects.hash(applicationContext, beanFactory, decode404, inheritParentContext, fallback, fallbackFactory,
name, path, type, url, readTimeoutMillis, connectTimeoutMillis, followRedirects, refreshableClient);
return Objects.hash(applicationContext, beanFactory, dismiss404, inheritParentContext, fallback,
fallbackFactory, name, path, type, url, readTimeoutMillis, connectTimeoutMillis, followRedirects,
refreshableClient);
}
@Override
public String toString() {
return new StringBuilder("FeignClientFactoryBean{").append("type=").append(type).append(", ").append("name='")
.append(name).append("', ").append("url='").append(url).append("', ").append("path='").append(path)
.append("', ").append("decode404=").append(decode404).append(", ").append("inheritParentContext=")
.append("', ").append("dismiss404=").append(dismiss404).append(", ").append("inheritParentContext=")
.append(inheritParentContext).append(", ").append("applicationContext=").append(applicationContext)
.append(", ").append("beanFactory=").append(beanFactory).append(", ").append("fallback=")
.append(fallback).append(", ").append("fallbackFactory=").append(fallbackFactory).append("}")

10
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledCondition.java → spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientMicrometerEnabledCondition.java

@ -25,7 +25,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata; @@ -25,7 +25,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author Jonatan Ivanov
*/
class FeignClientMetricsEnabledCondition implements Condition {
class FeignClientMicrometerEnabledCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
@ -36,11 +36,11 @@ class FeignClientMetricsEnabledCondition implements Condition { @@ -36,11 +36,11 @@ class FeignClientMetricsEnabledCondition implements Condition {
.getConfig();
if (feignClientConfigMap != null) {
FeignClientProperties.FeignClientConfiguration feignClientConfig = feignClientConfigMap
.get(context.getEnvironment().getProperty("feign.client.name"));
.get(context.getEnvironment().getProperty("spring.cloud.openfeign.client.name"));
if (feignClientConfig != null) {
FeignClientProperties.MetricsProperties metrics = feignClientConfig.getMetrics();
if (metrics != null && metrics.getEnabled() != null) {
return metrics.getEnabled();
FeignClientProperties.MicrometerProperties micrometer = feignClientConfig.getMicrometer();
if (micrometer != null && micrometer.getEnabled() != null) {
return micrometer.getEnabled();
}
}
}

68
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;
@ -42,8 +43,10 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @@ -42,8 +43,10 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Jonatan Ivanov
* @author Olga Maciaszek-Sharma
* @author Hyeonmin Park
* @author Jasbir Singh
* @author Dominique Villard
*/
@ConfigurationProperties("feign.client")
@ConfigurationProperties("spring.cloud.openfeign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true;
@ -125,11 +128,13 @@ public class FeignClientProperties { @@ -125,11 +128,13 @@ public class FeignClientProperties {
private List<Class<RequestInterceptor>> requestInterceptors;
private Class<ResponseInterceptor> responseInterceptor;
private Map<String, Collection<String>> defaultRequestHeaders;
private Map<String, Collection<String>> defaultQueryParameters;
private Boolean decode404;
private Boolean dismiss404;
private Class<Decoder> decoder;
@ -143,10 +148,16 @@ public class FeignClientProperties { @@ -143,10 +148,16 @@ public class FeignClientProperties {
private Class<QueryMapEncoder> queryMapEncoder;
private MetricsProperties metrics;
private MicrometerProperties micrometer;
private Boolean followRedirects;
/**
* Allows setting Feign client host URL. This value will only be taken into
* account if the url is not set in the @FeignClient annotation.
*/
private String url;
public Logger.Level getLoggerLevel() {
return loggerLevel;
}
@ -195,6 +206,14 @@ public class FeignClientProperties { @@ -195,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;
}
@ -211,12 +230,12 @@ public class FeignClientProperties { @@ -211,12 +230,12 @@ public class FeignClientProperties {
this.defaultQueryParameters = defaultQueryParameters;
}
public Boolean getDecode404() {
return decode404;
public Boolean getDismiss404() {
return dismiss404;
}
public void setDecode404(Boolean decode404) {
this.decode404 = decode404;
public void setDismiss404(Boolean dismiss404) {
this.dismiss404 = dismiss404;
}
public Class<Decoder> getDecoder() {
@ -267,12 +286,12 @@ public class FeignClientProperties { @@ -267,12 +286,12 @@ public class FeignClientProperties {
this.queryMapEncoder = queryMapEncoder;
}
public MetricsProperties getMetrics() {
return metrics;
public MicrometerProperties getMicrometer() {
return micrometer;
}
public void setMetrics(MetricsProperties metrics) {
this.metrics = metrics;
public void setMicrometer(MicrometerProperties micrometer) {
this.micrometer = micrometer;
}
public Boolean isFollowRedirects() {
@ -283,6 +302,14 @@ public class FeignClientProperties { @@ -283,6 +302,14 @@ public class FeignClientProperties {
this.followRedirects = followRedirects;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -296,29 +323,32 @@ public class FeignClientProperties { @@ -296,29 +323,32 @@ 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(decode404, that.decode404) && Objects.equals(encoder, that.encoder)
&& 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)
&& Objects.equals(defaultRequestHeaders, that.defaultRequestHeaders)
&& Objects.equals(defaultQueryParameters, that.defaultQueryParameters)
&& Objects.equals(capabilities, that.capabilities)
&& Objects.equals(queryMapEncoder, that.queryMapEncoder) && Objects.equals(metrics, that.metrics)
&& Objects.equals(followRedirects, that.followRedirects);
&& Objects.equals(queryMapEncoder, that.queryMapEncoder)
&& Objects.equals(micrometer, that.micrometer)
&& Objects.equals(followRedirects, that.followRedirects) && Objects.equals(url, that.url);
}
@Override
public int hashCode() {
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors,
decode404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters,
defaultRequestHeaders, capabilities, queryMapEncoder, metrics, followRedirects);
responseInterceptor, dismiss404, encoder, decoder, contract, exceptionPropagationPolicy,
defaultQueryParameters, defaultRequestHeaders, capabilities, queryMapEncoder, micrometer,
followRedirects, url);
}
}
/**
* Metrics configuration for Feign Client.
* Micrometer configuration for Feign Client.
*/
public static class MetricsProperties {
public static class MicrometerProperties {
private Boolean enabled = true;
@ -339,7 +369,7 @@ public class FeignClientProperties { @@ -339,7 +369,7 @@ public class FeignClientProperties {
return false;
}
MetricsProperties that = (MetricsProperties) o;
MicrometerProperties that = (MicrometerProperties) o;
return Objects.equals(enabled, that.enabled);
}

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(this.name, that.name) && Arrays.equals(this.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(this.name, this.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(this.name).append("', ")
.append("configuration=").append(Arrays.toString(this.configuration)).append("}").toString();
return "FeignClientSpecification{" + "name='" + name + "', " + "className='" + className + "', "
+ "configuration=" + Arrays.toString(configuration) + "}";
}
}

25
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java

@ -29,8 +29,10 @@ import feign.codec.Encoder; @@ -29,8 +29,10 @@ import feign.codec.Encoder;
import feign.form.MultipartFormContentProcessor;
import feign.form.spring.SpringFormEncoder;
import feign.micrometer.MicrometerCapability;
import feign.micrometer.MicrometerObservationCapability;
import feign.optionals.OptionalDecoder;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
@ -214,7 +216,7 @@ public class FeignClientsConfiguration { @@ -214,7 +216,7 @@ public class FeignClientsConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
@ConditionalOnProperty("feign.circuitbreaker.enabled")
@ConditionalOnProperty("spring.cloud.openfeign.circuitbreaker.enabled")
protected static class CircuitBreakerPresentFeignBuilderConfiguration {
@Bean
@ -235,16 +237,23 @@ public class FeignClientsConfiguration { @@ -235,16 +237,23 @@ public class FeignClientsConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(type = "io.micrometer.core.instrument.MeterRegistry")
@ConditionalOnClass(name = "feign.micrometer.MicrometerCapability")
@ConditionalOnProperty(name = "feign.metrics.enabled", matchIfMissing = true)
@Conditional(FeignClientMetricsEnabledCondition.class)
protected static class MetricsConfiguration {
@ConditionalOnProperty(name = "spring.cloud.openfeign.micrometer.enabled", matchIfMissing = true)
@ConditionalOnClass({ MicrometerObservationCapability.class, MicrometerCapability.class, MeterRegistry.class })
@Conditional(FeignClientMicrometerEnabledCondition.class)
protected static class MicrometerConfiguration {
@Bean
@ConditionalOnMissingBean
public MicrometerCapability micrometerCapability(MeterRegistry meterRegistry) {
return new MicrometerCapability(meterRegistry);
@ConditionalOnBean(type = "io.micrometer.observation.ObservationRegistry")
public MicrometerObservationCapability micrometerObservationCapability(ObservationRegistry registry) {
return new MicrometerObservationCapability(registry);
}
@Bean
@ConditionalOnBean(type = "io.micrometer.core.instrument.MeterRegistry")
@ConditionalOnMissingBean({ MicrometerCapability.class, MicrometerObservationCapability.class })
public MicrometerCapability micrometerCapability(MeterRegistry registry) {
return new MicrometerCapability(registry);
}
}

94
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;
@ -106,7 +105,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -106,7 +105,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
host = new URI(url).getHost();
}
catch (URISyntaxException e) {
catch (URISyntaxException ignored) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
@ -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");
@ -187,9 +185,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -187,9 +185,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
@ -197,7 +194,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -197,7 +194,8 @@ 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);
}
@ -207,9 +205,62 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -207,9 +205,62 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
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();
@ -221,7 +272,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -221,7 +272,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
factoryBean.setDismiss404(Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
@ -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
@ -255,7 +305,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -255,7 +305,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
registerOptionsBeanDefinition(registry, contextId);
registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
}
private void validate(Map<String, Object> attributes) {
@ -406,9 +457,11 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -406,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());
@ -420,16 +473,17 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -420,16 +473,17 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
}
/**
* This method is meant to create {@link Request.Options} beans definition with
* refreshScope.
* This method registers beans definition with refreshScope.
* @param registry spring bean definition registry
* @param contextId name of feign client
* @param beanType type of bean
* @param factoryBeanType points to a relevant bean factory
*/
private void registerOptionsBeanDefinition(BeanDefinitionRegistry registry, String contextId) {
private void registerRefreshableBeanDefinition(BeanDefinitionRegistry registry, String contextId, Class<?> beanType,
Class<?> factoryBeanType) {
if (isClientRefreshEnabled()) {
String beanName = Request.Options.class.getCanonicalName() + "-" + contextId;
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(OptionsFactoryBean.class);
String beanName = beanType.getCanonicalName() + "-" + contextId;
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(factoryBeanType);
definitionBuilder.setScope("refresh");
definitionBuilder.addPropertyValue("contextId", contextId);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
@ -440,7 +494,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo @@ -440,7 +494,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
}
private boolean isClientRefreshEnabled() {
return environment.getProperty("feign.client.refresh-enabled", Boolean.class, false);
return environment.getProperty("spring.cloud.openfeign.client.refresh-enabled", Boolean.class, false);
}
}

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

@ -51,7 +51,7 @@ public class OptionsFactoryBean implements FactoryBean<Request.Options>, Applica @@ -51,7 +51,7 @@ public class OptionsFactoryBean implements FactoryBean<Request.Options>, Applica
}
@Override
public Request.Options getObject() throws Exception {
public Request.Options getObject() {
if (options != null) {
return options;
}

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;
}
}

28
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/HttpClient5DisabledConditions.java → spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/RefreshableHardCodedTarget.java

@ -16,27 +16,27 @@ @@ -16,27 +16,27 @@
package org.springframework.cloud.openfeign;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import feign.Target;
/**
* @author Nguyen Ky Thanh
* This target provides url wrapped under {@link Target}.
*
* @author Jasbir Singh
* @since 4.0.0
*/
public class HttpClient5DisabledConditions extends AnyNestedCondition {
public HttpClient5DisabledConditions() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
public class RefreshableHardCodedTarget<T> extends Target.HardCodedTarget<T> {
@ConditionalOnMissingClass("feign.hc5.ApacheHttp5Client")
static class ApacheHttp5ClientClassMissing {
private final RefreshableUrl refreshableUrl;
@SuppressWarnings("unchecked")
public RefreshableHardCodedTarget(Class type, String name, RefreshableUrl refreshableUrl) {
super(type, name, refreshableUrl.getUrl());
this.refreshableUrl = refreshableUrl;
}
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "false", matchIfMissing = true)
static class HttpClient5Disabled {
@Override
public String url() {
return refreshableUrl.getUrl();
}
}

38
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/RefreshableUrl.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
/*
* 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;
/**
* This class wraps url inside an object so that relevant proxy instance can be created
* using {@link RefreshableUrlFactoryBean}.
*
* @author Jasbir Singh
* @since 4.0.0
*/
public class RefreshableUrl {
private final String url;
public RefreshableUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
}

75
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/RefreshableUrlFactoryBean.java

@ -0,0 +1,75 @@ @@ -0,0 +1,75 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import java.util.Objects;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
/**
* This factory bean creates {@link RefreshableUrl} instance as per the applicable
* configurations.
*
* @author Jasbir Singh
* @since 4.0.0
*/
public class RefreshableUrlFactoryBean implements FactoryBean<RefreshableUrl>, ApplicationContextAware {
private ApplicationContext applicationContext;
private String contextId;
private RefreshableUrl refreshableUrl;
@Override
public Class<?> getObjectType() {
return RefreshableUrl.class;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public RefreshableUrl getObject() {
if (refreshableUrl != null) {
return refreshableUrl;
}
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (Objects.isNull(properties.getConfig())) {
return new RefreshableUrl(null);
}
FeignClientProperties.FeignClientConfiguration configuration = properties.getConfig().get(contextId);
if (Objects.isNull(configuration) || !StringUtils.hasText(configuration.getUrl())) {
return new RefreshableUrl(null);
}
refreshableUrl = new RefreshableUrl(FeignClientsRegistrar.getUrl(configuration.getUrl()));
return refreshableUrl;
}
public void setContextId(String contextId) {
this.contextId = contextId;
}
}

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

@ -21,10 +21,6 @@ import java.lang.annotation.Retention; @@ -21,10 +21,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import feign.QueryMap;
import org.springframework.core.annotation.AliasFor;
/**
* Spring MVC equivalent of OpenFeign's {@link feign.QueryMap} parameter annotation.
*
@ -36,18 +32,4 @@ import org.springframework.core.annotation.AliasFor; @@ -36,18 +32,4 @@ import org.springframework.core.annotation.AliasFor;
@Target({ ElementType.PARAMETER })
public @interface SpringQueryMap {
/**
* @see QueryMap#encoded()
* @return alias for {@link #encoded()}.
*/
@AliasFor("encoded")
boolean value() default false;
/**
* @see QueryMap#encoded()
* @return Specifies whether parameter names and values are already encoded.
*/
@AliasFor("value")
boolean encoded() default false;
}

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);
}

10
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/CookieValueParameterProcessor.java

@ -18,7 +18,7 @@ package org.springframework.cloud.openfeign.annotation; @@ -18,7 +18,7 @@ package org.springframework.cloud.openfeign.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import feign.MethodMetadata;
@ -30,8 +30,10 @@ import static feign.Util.checkState; @@ -30,8 +30,10 @@ import static feign.Util.checkState;
import static feign.Util.emptyToNull;
/**
* @{link CookieValue} annotation processor.
* {@link CookieValue} annotation processor.
*
* @author Gong Yi
* @author Olga Maciaszek-Sharma
*
*/
public class CookieValueParameterProcessor implements AnnotatedParameterProcessor {
@ -51,8 +53,8 @@ public class CookieValueParameterProcessor implements AnnotatedParameterProcesso @@ -51,8 +53,8 @@ public class CookieValueParameterProcessor implements AnnotatedParameterProcesso
String name = cookie.value().trim();
checkState(emptyToNull(name) != null, "Cookie.name() was empty on parameter %s", parameterIndex);
context.setParameterName(name);
String cookieExpression = data.template().headers().getOrDefault(HttpHeaders.COOKIE, Arrays.asList("")).stream()
.findFirst().orElse("");
String cookieExpression = data.template().headers()
.getOrDefault(HttpHeaders.COOKIE, Collections.singletonList("")).stream().findFirst().orElse("");
if (cookieExpression.length() == 0) {
cookieExpression = String.format("%s={%s}", name, name);
}

1
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java

@ -69,6 +69,7 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce @@ -69,6 +69,7 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce
return true;
}
@SuppressWarnings("unchecked")
private String expandMap(Object object) {
Map<String, Object> paramMap = (Map) object;

6
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/PathVariableParameterProcessor.java

@ -56,14 +56,14 @@ public class PathVariableParameterProcessor implements AnnotatedParameterProcess @@ -56,14 +56,14 @@ public class PathVariableParameterProcessor implements AnnotatedParameterProcess
MethodMetadata data = context.getMethodMetadata();
String varName = '{' + name + '}';
String varNameRegex = ".*\\{" + name + "(:[^}]+)?\\}.*";
if (!data.template().url().matches(varNameRegex) && !searchMapValues(data.template().queries(), varName)
&& !searchMapValues(data.template().headers(), varName)) {
if (!data.template().url().matches(varNameRegex) && !containsMapValues(data.template().queries(), varName)
&& !containsMapValues(data.template().headers(), varName)) {
data.formParams().add(name);
}
return true;
}
private <K, V> boolean searchMapValues(Map<K, Collection<V>> map, V search) {
private <K, V> boolean containsMapValues(Map<K, Collection<V>> map, V search) {
Collection<Collection<V>> values = map.values();
if (values == null) {
return false;

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

@ -28,6 +28,7 @@ import org.springframework.cloud.openfeign.SpringQueryMap; @@ -28,6 +28,7 @@ import org.springframework.cloud.openfeign.SpringQueryMap;
* {@link SpringQueryMap} parameter processor.
*
* @author Aram Peres
* @author Olga Maciaszek-Sharma
* @see AnnotatedParameterProcessor
*/
public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {
@ -45,7 +46,6 @@ public class QueryMapParameterProcessor implements AnnotatedParameterProcessor { @@ -45,7 +46,6 @@ public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {
MethodMetadata metadata = context.getMethodMetadata();
if (metadata.queryMapIndex() == null) {
metadata.queryMapIndex(paramIndex);
metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());
}
return true;
}

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();
}
}

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

@ -19,15 +19,14 @@ package org.springframework.cloud.openfeign.clientconfig; @@ -19,15 +19,14 @@ package org.springframework.cloud.openfeign.clientconfig;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import jakarta.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hc.client5.http.config.RequestConfig;
@ -36,6 +35,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -36,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;
@ -55,6 +55,7 @@ import org.springframework.context.annotation.Configuration; @@ -55,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)
@ -90,7 +91,11 @@ public class HttpClient5FeignConfiguration { @@ -90,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;
}
@ -111,11 +116,9 @@ public class HttpClient5FeignConfiguration { @@ -111,11 +116,9 @@ 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 e) {
LOG.warn("Error creating SSLContext", e);
}
catch (KeyManagementException e) {
catch (NoSuchAlgorithmException | KeyManagementException e) {
LOG.warn("Error creating SSLContext", e);
}
}
@ -131,10 +134,10 @@ public class HttpClient5FeignConfiguration { @@ -131,10 +134,10 @@ public class HttpClient5FeignConfiguration {
DisabledValidationTrustManager() {
}
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
}
public X509Certificate[] getAcceptedIssuers() {

125
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClientFeignConfiguration.java

@ -1,125 +0,0 @@ @@ -1,125 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.clientconfig;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Default configuration for {@link CloseableHttpClient}.
*
* @author Ryan Baxter
* @author Marcin Grzejszczak
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClientFeignConfiguration {
private static final Log LOG = LogFactory.getLog(HttpClientFeignConfiguration.class);
private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer",
true);
private CloseableHttpClient httpClient;
@Autowired(required = false)
private RegistryBuilder registryBuilder;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "true")
public CloseableHttpClient customHttpClient(HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement().useSystemProperties();
this.httpClient = createClient(builder, httpClientConnectionManager, httpClientProperties);
return this.httpClient;
}
@Bean
@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true)
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager,
httpClientProperties);
return this.httpClient;
}
private CloseableHttpClient createClient(HttpClientBuilder builder,
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig)
.setConnectionManager(httpClientConnectionManager).build();
return httpClient;
}
@PreDestroy
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
}

78
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/OkHttpFeignConfiguration.java

@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.clientconfig;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Defualt configuration for {@link OkHttpClient}.
*
* @author Ryan Baxter
* @author Marcin Grzejszczak
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
public class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
int maxTotalConnections = httpClientProperties.getMaxConnections();
long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {
boolean followRedirects = httpClientProperties.isFollowRedirects();
int connectTimeout = httpClientProperties.getConnectionTimeout();
Duration reaTimeout = httpClientProperties.getOkHttp().getReadTimeout();
this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation())
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects)
.readTimeout(reaTimeout).connectionPool(connectionPool).build();
return this.okHttpClient;
}
@PreDestroy
public void destroy() {
if (this.okHttpClient != null) {
this.okHttpClient.dispatcher().executorService().shutdown();
this.okHttpClient.connectionPool().evictAll();
}
}
}

11
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,27 +22,28 @@ import feign.Feign; @@ -22,27 +22,28 @@ 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)
@EnableConfigurationProperties(FeignClientEncodingProperties.class)
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(Client.class)
@ConditionalOnProperty(value = "feign.compression.response.enabled", matchIfMissing = false)
@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 {

15
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignClientEncodingProperties.java

@ -26,7 +26,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @@ -26,7 +26,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*
* @author Jakub Narloch
*/
@ConfigurationProperties("feign.compression.request")
@ConfigurationProperties("spring.cloud.openfeign.compression.request")
public class FeignClientEncodingProperties {
/**
@ -40,7 +40,7 @@ public class FeignClientEncodingProperties { @@ -40,7 +40,7 @@ public class FeignClientEncodingProperties {
private int minRequestSize = 2048;
public String[] getMimeTypes() {
return this.mimeTypes;
return mimeTypes;
}
public void setMimeTypes(String[] mimeTypes) {
@ -48,7 +48,7 @@ public class FeignClientEncodingProperties { @@ -48,7 +48,7 @@ public class FeignClientEncodingProperties {
}
public int getMinRequestSize() {
return this.minRequestSize;
return minRequestSize;
}
public void setMinRequestSize(int minRequestSize) {
@ -64,20 +64,19 @@ public class FeignClientEncodingProperties { @@ -64,20 +64,19 @@ public class FeignClientEncodingProperties {
return false;
}
FeignClientEncodingProperties that = (FeignClientEncodingProperties) o;
return Arrays.equals(this.mimeTypes, that.mimeTypes)
&& Objects.equals(this.minRequestSize, that.minRequestSize);
return Arrays.equals(mimeTypes, that.mimeTypes) && Objects.equals(minRequestSize, that.minRequestSize);
}
@Override
public int hashCode() {
return Objects.hash(this.mimeTypes, this.minRequestSize);
return Objects.hash(Arrays.hashCode(mimeTypes), minRequestSize);
}
@Override
public String toString() {
return new StringBuilder("FeignClientEncodingProperties{").append("mimeTypes=")
.append(Arrays.toString(this.mimeTypes)).append(", ").append("minRequestSize=")
.append(this.minRequestSize).append("}").toString();
.append(Arrays.toString(mimeTypes)).append(", ").append("minRequestSize=").append(minRequestSize)
.append("}").toString();
}
}

11
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,26 +20,27 @@ import feign.Feign; @@ -20,26 +20,27 @@ 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")
@ConditionalOnProperty("feign.compression.request.enabled")
// 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 {
}
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 the original author or authors.
* Copyright 2016-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

13
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/DefaultFeignLoadBalancerConfiguration.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.util.List;
import feign.Client;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -36,6 +38,7 @@ import org.springframework.context.annotation.Configuration; @@ -36,6 +38,7 @@ import org.springframework.context.annotation.Configuration;
* that uses {@link Client.Default} under the hood.
*
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
@ -46,9 +49,10 @@ class DefaultFeignLoadBalancerConfiguration { @@ -46,9 +49,10 @@ class DefaultFeignLoadBalancerConfiguration {
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}
@Bean
@ -58,9 +62,10 @@ class DefaultFeignLoadBalancerConfiguration { @@ -58,9 +62,10 @@ class DefaultFeignLoadBalancerConfiguration {
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
loadBalancedRetryFactory, loadBalancerClientFactory);
loadBalancedRetryFactory, loadBalancerClientFactory, transformers);
}
}

38
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignBlockingLoadBalancerClient.java

@ -19,6 +19,8 @@ package org.springframework.cloud.openfeign.loadbalancer; @@ -19,6 +19,8 @@ package org.springframework.cloud.openfeign.loadbalancer;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import feign.Client;
@ -49,6 +51,7 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils @@ -49,6 +51,7 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils
* {@link ServiceInstance} to use while resolving the request host.
*
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@ -62,9 +65,11 @@ public class FeignBlockingLoadBalancerClient implements Client { @@ -62,9 +65,11 @@ public class FeignBlockingLoadBalancerClient implements Client {
private final LoadBalancerClientFactory loadBalancerClientFactory;
private final List<LoadBalancerFeignRequestTransformer> transformers;
/**
* @deprecated in favour of
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory)}
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory, List)}
*/
@Deprecated
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
@ -72,13 +77,29 @@ public class FeignBlockingLoadBalancerClient implements Client { @@ -72,13 +77,29 @@ public class FeignBlockingLoadBalancerClient implements Client {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = Collections.emptyList();
}
/**
* @deprecated in favour of
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory, List)}
*/
@Deprecated
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = Collections.emptyList();
}
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = transformers;
}
@Override
@ -109,10 +130,9 @@ public class FeignBlockingLoadBalancerClient implements Client { @@ -109,10 +130,9 @@ public class FeignBlockingLoadBalancerClient implements Client {
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl);
LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
Request newRequest = buildRequest(request, reconstructedUrl, instance);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());
supportedLifecycleProcessors);
}
protected Request buildRequest(Request request, String reconstructedUrl) {
@ -120,6 +140,16 @@ public class FeignBlockingLoadBalancerClient implements Client { @@ -120,6 +140,16 @@ public class FeignBlockingLoadBalancerClient implements Client {
request.charset(), request.requestTemplate());
}
protected Request buildRequest(Request request, String reconstructedUrl, ServiceInstance instance) {
Request newRequest = buildRequest(request, reconstructedUrl);
if (transformers != null) {
for (LoadBalancerFeignRequestTransformer transformer : transformers) {
newRequest = transformer.transformRequest(newRequest, instance);
}
}
return newRequest;
}
// Visible for Sleuth instrumentation
public Client getDelegate() {
return delegate;

16
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.
@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
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.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@ -30,6 +31,7 @@ import org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientA @@ -30,6 +31,7 @@ import org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientA
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -39,6 +41,7 @@ import org.springframework.context.annotation.Import; @@ -39,6 +41,7 @@ import org.springframework.context.annotation.Import;
*
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@ConditionalOnClass(Feign.class)
@ -50,8 +53,15 @@ import org.springframework.context.annotation.Import; @@ -50,8 +53,15 @@ import org.springframework.context.annotation.Import;
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
HttpClient5FeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
@Import({ OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class,
Http2ClientFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean(XForwardedHeadersTransformer.class)
public XForwardedHeadersTransformer xForwarderHeadersFeignTransformer(LoadBalancerClientFactory factory) {
return new XForwardedHeadersTransformer(factory);
}
}

40
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/HttpClientFeignLoadBalancerConfiguration.java → spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/Http2ClientFeignLoadBalancerConfiguration.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,9 +16,11 @@ @@ -16,9 +16,11 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.net.http.HttpClient;
import java.util.List;
import feign.Client;
import feign.httpclient.ApacheHttpClient;
import org.apache.http.client.HttpClient;
import feign.http2client.Http2Client;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -29,37 +31,32 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; @@ -29,37 +31,32 @@ 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.cloud.openfeign.HttpClient5DisabledConditions;
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
* that uses {@link ApacheHttpClient} under the hood.
* that uses {@link Http2Client} under the hood.
*
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @since 2.2.0
* @author changjin wei(魏昌进)
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnClass({ Http2Client.class, HttpClient.class })
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
@Import(HttpClientFeignConfiguration.class)
@ConditionalOnProperty("spring.cloud.openfeign.http2client.enabled")
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
class HttpClientFeignLoadBalancerConfiguration {
class Http2ClientFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new Http2Client(httpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}
@Bean
@ -69,10 +66,11 @@ class HttpClientFeignLoadBalancerConfiguration { @@ -69,10 +66,11 @@ class HttpClientFeignLoadBalancerConfiguration {
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new Http2Client(httpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}
}

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

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.util.List;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import org.apache.hc.client5.http.classic.HttpClient;
@ -40,11 +42,14 @@ import org.springframework.context.annotation.Import; @@ -40,11 +42,14 @@ import org.springframework.context.annotation.Import;
* that uses {@link ApacheHttp5Client} under the hood.
*
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.hc5.enabled", havingValue = "true",
matchIfMissing = true)
@Import(HttpClient5FeignConfiguration.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
class HttpClient5FeignLoadBalancerConfiguration {
@ -53,9 +58,11 @@ class HttpClient5FeignLoadBalancerConfiguration { @@ -53,9 +58,11 @@ class HttpClient5FeignLoadBalancerConfiguration {
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient5,
LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new ApacheHttp5Client(httpClient5);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}
@Bean
@ -65,10 +72,11 @@ class HttpClient5FeignLoadBalancerConfiguration { @@ -65,10 +72,11 @@ class HttpClient5FeignLoadBalancerConfiguration {
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient5,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new ApacheHttp5Client(httpClient5);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}
}

47
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerFeignRequestTransformer.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.loadbalancer;
import feign.Request;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.annotation.Order;
/**
* Allows applications to transform the load-balanced {@link Request} given the chosen
* {@link org.springframework.cloud.client.ServiceInstance}.
*
* @author changjin wei(魏昌进)
*/
@Order(LoadBalancerFeignRequestTransformer.DEFAULT_ORDER)
public interface LoadBalancerFeignRequestTransformer {
/**
* Order for the {@link LoadBalancerFeignRequestTransformer}.
*/
int DEFAULT_ORDER = 0;
/**
* Allows transforming load-balanced requests based on the provided
* {@link ServiceInstance}.
* @param request Original request.
* @param instance ServiceInstance returned from LoadBalancer.
* @return New request or original request
*/
Request transformRequest(Request request, ServiceInstance instance);
}

20
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerUtils.java

@ -33,7 +33,7 @@ import org.springframework.cloud.client.loadbalancer.RequestData; @@ -33,7 +33,7 @@ import org.springframework.cloud.client.loadbalancer.RequestData;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
/**
* @author Olga Maciaszek-Sharma
@ -50,15 +50,14 @@ final class LoadBalancerUtils { @@ -50,15 +50,14 @@ final class LoadBalancerUtils {
static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced, boolean useRawStatusCodes)
throws IOException {
Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced) throws IOException {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));
try {
Response response = feignClient.execute(feignRequest, options);
if (loadBalanced) {
supportedLifecycleProcessors.forEach(
lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
lbRequest, lbResponse, buildResponseData(response, useRawStatusCodes))));
lbRequest, lbResponse, buildResponseData(response))));
}
return response;
}
@ -71,29 +70,26 @@ final class LoadBalancerUtils { @@ -71,29 +70,26 @@ final class LoadBalancerUtils {
}
}
static ResponseData buildResponseData(Response response, boolean useRawStatusCodes) {
static ResponseData buildResponseData(Response response) {
HttpHeaders responseHeaders = new HttpHeaders();
response.headers().forEach((key, value) -> responseHeaders.put(key, new ArrayList<>(value)));
if (useRawStatusCodes) {
return new ResponseData(responseHeaders, null, buildRequestData(response.request()), response.status());
}
return new ResponseData(HttpStatus.resolve(response.status()), responseHeaders, null,
return new ResponseData(HttpStatusCode.valueOf(response.status()), responseHeaders, null,
buildRequestData(response.request()));
}
static RequestData buildRequestData(Request request) {
HttpHeaders requestHeaders = new HttpHeaders();
request.headers().forEach((key, value) -> requestHeaders.put(key, new ArrayList<>(value)));
return new RequestData(HttpMethod.resolve(request.httpMethod().name()), URI.create(request.url()),
return new RequestData(HttpMethod.valueOf(request.httpMethod().name()), URI.create(request.url()),
requestHeaders, null, new HashMap<>());
}
static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean useRawStatusCodes) throws IOException {
Set<LoadBalancerLifecycle> supportedLifecycleProcessors) throws IOException {
return executeWithLoadBalancerLifecycleProcessing(feignClient, options, feignRequest, lbRequest, lbResponse,
supportedLifecycleProcessors, true, useRawStatusCodes);
supportedLifecycleProcessors, true);
}
}

19
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/OkHttpFeignLoadBalancerConfiguration.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.util.List;
import feign.Client;
import feign.okhttp.OkHttpClient;
@ -28,24 +30,22 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; @@ -28,24 +30,22 @@ 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.cloud.openfeign.clientconfig.OkHttpFeignConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
* that uses {@link OkHttpClient} under the hood.
*
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@ConditionalOnProperty("spring.cloud.openfeign.okhttp.enabled")
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@Import(OkHttpFeignConfiguration.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
class OkHttpFeignLoadBalancerConfiguration {
@ -53,9 +53,11 @@ class OkHttpFeignLoadBalancerConfiguration { @@ -53,9 +53,11 @@ class OkHttpFeignLoadBalancerConfiguration {
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}
@Bean
@ -65,10 +67,11 @@ class OkHttpFeignLoadBalancerConfiguration { @@ -65,10 +67,11 @@ class OkHttpFeignLoadBalancerConfiguration {
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}
}

63
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.
@ -20,6 +20,7 @@ import java.io.IOException; @@ -20,6 +20,7 @@ import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -65,6 +66,9 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils @@ -65,6 +66,9 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils
* load-balanced with Spring Cloud LoadBalancer.
*
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @author Wonsik Cheung
* @author Andriy Pikozh
* @since 2.2.6
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@ -80,9 +84,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -80,9 +84,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
private final LoadBalancerClientFactory loadBalancerClientFactory;
private final List<LoadBalancerFeignRequestTransformer> transformers;
/**
* @deprecated in favour of
* {@link RetryableFeignBlockingLoadBalancerClient#RetryableFeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancedRetryFactory, LoadBalancerClientFactory)}
* {@link RetryableFeignBlockingLoadBalancerClient#RetryableFeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancedRetryFactory, LoadBalancerClientFactory, List)}
*/
@Deprecated
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
@ -92,14 +98,31 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -92,14 +98,31 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
this.loadBalancerClient = loadBalancerClient;
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = Collections.emptyList();
}
/**
* @deprecated in favour of
* {@link RetryableFeignBlockingLoadBalancerClient#RetryableFeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancedRetryFactory, LoadBalancerClientFactory, List)}
*/
@Deprecated
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = Collections.emptyList();
}
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = transformers;
}
@Override
@ -122,10 +145,9 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -122,10 +145,9 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
new RetryableRequestContext(null, buildRequestData(request), hint));
// 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) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
ServiceInstance serviceInstance = lbContext.getServiceInstance();
if (serviceInstance == null) {
if (context instanceof LoadBalancedRetryContext lbContext) {
retrievedServiceInstance = lbContext.getServiceInstance();
if (retrievedServiceInstance == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. "
+ "Reattempting service instance selection");
@ -158,15 +180,15 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -158,15 +180,15 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
}
String reconstructedUrl = loadBalancerClient.reconstructURI(retrievedServiceInstance, originalUri)
.toString();
feignRequest = buildRequest(request, reconstructedUrl);
feignRequest = buildRequest(request, reconstructedUrl, retrievedServiceInstance);
}
}
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
retrievedServiceInstance);
LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
Response response = LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(delegate, options,
feignRequest, lbRequest, lbResponse, supportedLifecycleProcessors, retrievedServiceInstance != null,
loadBalancerProperties.isUseRawStatusCodeInResponseData());
feignRequest, lbRequest, lbResponse, supportedLifecycleProcessors,
retrievedServiceInstance != null);
int responseStatus = response.status();
if (retryPolicy != null && retryPolicy.retryableStatusCode(responseStatus)) {
if (LOG.isDebugEnabled()) {
@ -192,6 +214,16 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -192,6 +214,16 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
request.charset(), request.requestTemplate());
}
protected Request buildRequest(Request request, String reconstructedUrl, ServiceInstance instance) {
Request newRequest = buildRequest(request, reconstructedUrl);
if (transformers != null) {
for (LoadBalancerFeignRequestTransformer transformer : transformers) {
newRequest = transformer.transformRequest(newRequest, instance);
}
}
return newRequest;
}
private RetryTemplate buildRetryTemplate(String serviceId, Request request, LoadBalancedRetryPolicy retryPolicy) {
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(serviceId);
@ -201,8 +233,10 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -201,8 +233,10 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
retryTemplate.setListeners(retryListeners);
}
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new InterceptorRetryPolicy(toHttpRequest(request), retryPolicy, loadBalancerClient, serviceId));
retryTemplate.setRetryPolicy(
!loadBalancerClientFactory.getProperties(serviceId).getRetry().isEnabled() || retryPolicy == null
? new NeverRetryPolicy() : new InterceptorRetryPolicy(toHttpRequest(request), retryPolicy,
loadBalancerClient, serviceId));
return retryTemplate;
}
@ -215,12 +249,7 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { @@ -215,12 +249,7 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
return new HttpRequest() {
@Override
public HttpMethod getMethod() {
return HttpMethod.resolve(request.httpMethod().name());
}
@Override
public String getMethodValue() {
return getMethod().name();
return HttpMethod.valueOf(request.httpMethod().name());
}
@Override

63
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/XForwardedHeadersTransformer.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.loadbalancer;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import feign.Request;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
/**
* To add X-Forwarded-Host and X-Forwarded-Proto Headers.
*
* @author changjin wei(魏昌进)
*/
public class XForwardedHeadersTransformer implements LoadBalancerFeignRequestTransformer {
private final ReactiveLoadBalancer.Factory<ServiceInstance> factory;
public XForwardedHeadersTransformer(ReactiveLoadBalancer.Factory<ServiceInstance> factory) {
this.factory = factory;
}
@Override
public Request transformRequest(Request request, ServiceInstance instance) {
if (instance == null) {
return request;
}
LoadBalancerProperties.XForwarded xForwarded = factory.getProperties(instance.getServiceId()).getXForwarded();
if (xForwarded.isEnabled()) {
Map<String, Collection<String>> headers = new HashMap<>(request.headers());
URI uri = URI.create(request.url());
String xForwardedHost = uri.getHost();
String xForwardedProto = uri.getScheme();
headers.put("X-Forwarded-Host", Collections.singleton(xForwardedHost));
headers.put("X-Forwarded-Proto", Collections.singleton(xForwardedProto));
request = Request.create(request.httpMethod(), request.url(), headers, request.body(), request.charset(),
request.requestTemplate());
}
return request;
}
}

144
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java

@ -0,0 +1,144 @@ @@ -0,0 +1,144 @@
/*
* Copyright 2015-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.security;
import java.net.URI;
import java.util.Optional;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link RequestInterceptor} for OAuth2 Feign Requests. By default, it uses the
* {@link OAuth2AuthorizedClientManager } to get {@link OAuth2AuthorizedClient } that
* holds an {@link OAuth2AccessToken }. If the user has specified an OAuth2
* {@code clientRegistrationId} using the
* {@code spring.cloud.openfeign.oauth2.clientRegistrationId} property, it will be used to
* retrieve the token. If the token is not retrieved or the {@code clientRegistrationId}
* has not been specified, the {@code serviceId} retrieved from the {@code url} host
* segment will be used. This approach is convenient for load-balanced Feign clients. For
* non-load-balanced ones, the property-based {@code clientRegistrationId} is a suitable
* approach.
*
* @author Dangzhicairang(小水牛)
* @author Olga Maciaszek-Sharma
* @since 4.0.0
*/
public class OAuth2AccessTokenInterceptor implements RequestInterceptor {
/**
* The name of the token.
*/
public static final String BEARER = "Bearer";
/**
* The name of the header.
*/
public static final String AUTHORIZATION = "Authorization";
private final String tokenType;
private final String header;
private final String clientRegistrationId;
private final OAuth2AuthorizedClientManager authorizedClientManager;
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
public OAuth2AccessTokenInterceptor(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
this(null, oAuth2AuthorizedClientManager);
}
public OAuth2AccessTokenInterceptor(String clientRegistrationId,
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
this(BEARER, AUTHORIZATION, clientRegistrationId, oAuth2AuthorizedClientManager);
}
public OAuth2AccessTokenInterceptor(String tokenType, String header, String clientRegistrationId,
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
this.tokenType = tokenType;
this.header = header;
this.clientRegistrationId = clientRegistrationId;
this.authorizedClientManager = oAuth2AuthorizedClientManager;
}
@Override
public void apply(RequestTemplate template) {
OAuth2AccessToken token = getToken(template);
String extractedToken = String.format("%s %s", tokenType, token.getTokenValue());
template.header(header);
template.header(header, extractedToken);
}
public OAuth2AccessToken getToken(RequestTemplate template) {
// If specified, try to use them to get token.
if (StringUtils.hasText(clientRegistrationId)) {
OAuth2AccessToken token = getToken(clientRegistrationId);
if (token != null) {
return token;
}
}
// If not specified use host (synonymous with serviceId for load-balanced
// requests; non-load-balanced requests should use the method above).
OAuth2AccessToken token = getToken(getServiceId(template));
if (token != null) {
return token;
}
throw new IllegalStateException("OAuth2 token has not been successfully acquired.");
}
protected OAuth2AccessToken getToken(String clientRegistrationId) {
if (!StringUtils.hasText(clientRegistrationId)) {
return null;
}
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
if (principal == null) {
principal = ANONYMOUS_AUTHENTICATION;
}
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
.principal(principal).build();
OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest);
return Optional.ofNullable(authorizedClient).map(OAuth2AuthorizedClient::getAccessToken).orElse(null);
}
private static String getServiceId(RequestTemplate template) {
Target<?> feignTarget = template.feignTarget();
Assert.notNull(feignTarget, "FeignTarget may not be null.");
String url = feignTarget.url();
Assert.hasLength(url, "Url may not be empty.");
final URI originalUri = URI.create(url);
return originalUri.getHost();
}
}

183
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2FeignRequestInterceptor.java

@ -1,183 +0,0 @@ @@ -1,183 +0,0 @@
/*
* Copyright 2015-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.security;
import java.util.Arrays;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
/**
* Pre-defined custom RequestInterceptor for Feign Requests. It uses the
* {@link OAuth2ClientContext OAuth2ClientContext} provided from the environment and
* construct a new header on the request before it is made by Feign.
*
* @author Joao Pedro Evangelista
* @author Tim Ysewyn
* @since 3.0.0
*/
@Deprecated // spring-security-oauth2 reached EOL
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
/**
* The name of the token.
*/
public static final String BEARER = "Bearer";
/**
* The name of the header.
*/
public static final String AUTHORIZATION = "Authorization";
private final OAuth2ClientContext oAuth2ClientContext;
private final OAuth2ProtectedResourceDetails resource;
private final String tokenType;
private final String header;
private AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider>asList(
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
/**
* Default constructor which uses the provided OAuth2ClientContext and Bearer tokens
* within Authorization header.
* @param oAuth2ClientContext provided context
* @param resource type of resource to be accessed
*/
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
this(oAuth2ClientContext, resource, BEARER, AUTHORIZATION);
}
/**
* Fully customizable constructor for changing token type and header name, in cases of
* Bearer and Authorization is not the default such as "bearer", "authorization".
* @param oAuth2ClientContext current oAuth2 Context
* @param resource type of resource to be accessed
* @param tokenType type of token e.g. "token", "Bearer"
* @param header name of the header e.g. "Authorization", "authorization"
*/
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource, String tokenType, String header) {
this.oAuth2ClientContext = oAuth2ClientContext;
this.resource = resource;
this.tokenType = tokenType;
this.header = header;
}
/**
* Create a template with the header of provided name and extracted extract.
*
* @see RequestInterceptor#apply(RequestTemplate)
*/
@Override
public void apply(RequestTemplate template) {
template.header(header); // Clears out the header, no "clear" method available.
template.header(header, extract(tokenType));
}
/**
* Extracts the token extract id the access token exists or returning an empty extract
* if there is no one on the context it may occasionally causes Unauthorized response
* since the token extract is empty.
* @param tokenType type name of token
* @return token value from context if it exists otherwise empty String
*/
protected String extract(String tokenType) {
OAuth2AccessToken accessToken = getToken();
return String.format("%s %s", tokenType, accessToken.getValue());
}
/**
* Extract the access token within the request or try to acquire a new one by
* delegating it to {@link #acquireAccessToken()}.
* @return valid token
*/
public OAuth2AccessToken getToken() {
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
if (accessToken == null || accessToken.isExpired()) {
try {
accessToken = acquireAccessToken();
}
catch (UserRedirectRequiredException e) {
oAuth2ClientContext.setAccessToken(null);
String stateKey = e.getStateKey();
if (stateKey != null) {
Object stateToPreserve = e.getStateToPreserve();
if (stateToPreserve == null) {
stateToPreserve = "NONE";
}
oAuth2ClientContext.setPreservedState(stateKey, stateToPreserve);
}
throw e;
}
}
return accessToken;
}
/**
* Try to acquire the token using a access token provider.
* @return valid access token
* @throws UserRedirectRequiredException in case the user needs to be redirected to an
* approval page or login page
*/
protected OAuth2AccessToken acquireAccessToken() throws UserRedirectRequiredException {
AccessTokenRequest tokenRequest = oAuth2ClientContext.getAccessTokenRequest();
if (tokenRequest == null) {
throw new AccessTokenRequiredException(
"Cannot find valid context on request for resource '" + resource.getId() + "'.", resource);
}
String stateKey = tokenRequest.getStateKey();
if (stateKey != null) {
tokenRequest.setPreservedState(oAuth2ClientContext.removePreservedState(stateKey));
}
OAuth2AccessToken existingToken = oAuth2ClientContext.getAccessToken();
if (existingToken != null) {
oAuth2ClientContext.setAccessToken(existingToken);
}
OAuth2AccessToken obtainableAccessToken;
obtainableAccessToken = accessTokenProvider.obtainAccessToken(resource, tokenRequest);
if (obtainableAccessToken == null || obtainableAccessToken.getValue() == null) {
throw new IllegalStateException(
" Access token provider returned a null token, which is illegal according to the contract.");
}
oAuth2ClientContext.setAccessToken(obtainableAccessToken);
return obtainableAccessToken;
}
public void setAccessTokenProvider(AccessTokenProvider accessTokenProvider) {
this.accessTokenProvider = accessTokenProvider;
}
}

81
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2FeignRequestInterceptorBuilder.java

@ -1,81 +0,0 @@ @@ -1,81 +0,0 @@
/*
* Copyright 2015-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain;
import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;
/**
* Allows to customize pre-defined {@link OAuth2FeignRequestInterceptor} using configurer
* beans of class {@link OAuth2FeignRequestInterceptorConfigurer}. Each configurer
* instance can add {@link AccessTokenProvider} new {@link ClientHttpRequestInterceptor}
* instances.
*
* @author Wojciech Mąka
* @since 3.1.1
*/
public class OAuth2FeignRequestInterceptorBuilder {
private AccessTokenProvider accessTokenProvider;
private final List<ClientHttpRequestInterceptor> accessTokenProviderInterceptors = new ArrayList<>();
public OAuth2FeignRequestInterceptorBuilder() {
accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider>asList(
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
}
public OAuth2FeignRequestInterceptorBuilder withAccessTokenProviderInterceptors(
ClientHttpRequestInterceptor... interceptors) {
accessTokenProviderInterceptors.addAll(Arrays.asList(interceptors));
return this;
}
OAuth2FeignRequestInterceptor build(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
if (OAuth2AccessTokenSupport.class.isAssignableFrom(accessTokenProvider.getClass())) {
((OAuth2AccessTokenSupport) accessTokenProvider).setInterceptors(accessTokenProviderInterceptors);
}
final OAuth2FeignRequestInterceptor feignRequestInterceptor = new OAuth2FeignRequestInterceptor(
oAuth2ClientContext, resource);
feignRequestInterceptor.setAccessTokenProvider(accessTokenProvider);
return feignRequestInterceptor;
}
public static OAuth2FeignRequestInterceptor buildWithConfigurers(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource, List<OAuth2FeignRequestInterceptorConfigurer> buildConfigurers) {
final OAuth2FeignRequestInterceptorBuilder builder = new OAuth2FeignRequestInterceptorBuilder();
for (OAuth2FeignRequestInterceptorConfigurer configurer : buildConfigurers) {
configurer.customize(builder);
}
return builder.build(oAuth2ClientContext, resource);
}
}

35
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2FeignRequestInterceptorConfigurer.java

@ -1,35 +0,0 @@ @@ -1,35 +0,0 @@
/*
* Copyright 2015-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.security;
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
/**
* Interface for configurer beans working with
* {@link OAuth2FeignRequestInterceptorBuilder} in order to provide custom interceptors
* for {@link AccessTokenProvider} managed internally by
* {@link OAuth2FeignRequestInterceptor}.
*
* @author Wojciech Mąka
* @since 3.1.1
*/
@FunctionalInterface
public interface OAuth2FeignRequestInterceptorConfigurer {
void customize(OAuth2FeignRequestInterceptorBuilder requestInterceptorBuilder);
}

17
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/AbstractFormWriter.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.cloud.openfeign.support;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.function.Predicate;
@ -32,6 +33,7 @@ import static feign.form.ContentProcessor.CRLF; @@ -32,6 +33,7 @@ import static feign.form.ContentProcessor.CRLF;
/**
* @author Darren Foong
* @author Wu Daifu
*/
public abstract class AbstractFormWriter extends AbstractWriter {
@ -61,13 +63,18 @@ public abstract class AbstractFormWriter extends AbstractWriter { @@ -61,13 +63,18 @@ public abstract class AbstractFormWriter extends AbstractWriter {
protected abstract String writeAsString(Object object) throws IOException;
private boolean isTypeOrCollection(Object object, Predicate<Object> isType) {
if (object == null) {
return false;
}
if (object.getClass().isArray()) {
Object[] array = (Object[]) object;
return array.length > 1 && isType.test(array[0]);
int len = Array.getLength(object);
if (len > 0) {
Object one = Array.get(object, 0);
return len > 1 && one != null && isType.test(one);
}
return false;
}
else if (object instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) object;
else if (object instanceof Iterable<?> iterable) {
Iterator<?> iterator = iterable.iterator();
return iterator.hasNext() && isType.test(iterator.next());

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

@ -24,7 +24,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @@ -24,7 +24,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Olga Maciaszek-Sharma
* @since 2.2.8
*/
@ConfigurationProperties("feign.encoder")
@ConfigurationProperties("spring.cloud.openfeign.encoder")
public class FeignEncoderProperties {
/**

93
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,8 +31,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @@ -27,8 +31,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Ryan Baxter
* @author Nguyen Ky Thanh
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
*/
@ConfigurationProperties(prefix = "feign.httpclient")
@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;
}
}
}

12
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignUtils.java

@ -18,8 +18,6 @@ package org.springframework.cloud.openfeign.support; @@ -18,8 +18,6 @@ package org.springframework.cloud.openfeign.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpHeaders;
@ -44,16 +42,6 @@ public final class FeignUtils { @@ -44,16 +42,6 @@ public final class FeignUtils {
return httpHeaders;
}
static Map<String, Collection<String>> getHeaders(HttpHeaders httpHeaders) {
LinkedHashMap<String, Collection<String>> headers = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> entry : httpHeaders.entrySet()) {
headers.put(entry.getKey(), entry.getValue());
}
return headers;
}
static Collection<String> addTemplateParameter(Collection<String> possiblyNull, String paramName) {
Collection<String> params = ofNullable(possiblyNull).map(ArrayList::new).orElse(new ArrayList<>());
params.add(String.format("{%s}", paramName));

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 the original author or authors.
* Copyright 2016-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

6
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringEncoder.java

@ -79,8 +79,7 @@ public class PageableSpringEncoder implements Encoder { @@ -79,8 +79,7 @@ public class PageableSpringEncoder implements Encoder {
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (supports(object)) {
if (object instanceof Pageable) {
Pageable pageable = (Pageable) object;
if (object instanceof Pageable pageable) {
if (pageable.isPaged()) {
template.query(pageParameter, String.valueOf(pageable.getPageNumber()));
@ -91,8 +90,7 @@ public class PageableSpringEncoder implements Encoder { @@ -91,8 +90,7 @@ public class PageableSpringEncoder implements Encoder {
applySort(template, pageable.getSort());
}
}
else if (object instanceof Sort) {
Sort sort = (Sort) object;
else if (object instanceof Sort sort) {
applySort(template, sort);
}
}

6
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoder.java

@ -68,8 +68,7 @@ public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder { @@ -68,8 +68,7 @@ public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
if (supports(object)) {
Map<String, Object> queryMap = new HashMap<>();
if (object instanceof Pageable) {
Pageable pageable = (Pageable) object;
if (object instanceof Pageable pageable) {
if (pageable.isPaged()) {
queryMap.put(pageParameter, pageable.getPageNumber());
@ -80,8 +79,7 @@ public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder { @@ -80,8 +79,7 @@ public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
applySort(queryMap, pageable.getSort());
}
}
else if (object instanceof Sort) {
Sort sort = (Sort) object;
else if (object instanceof Sort sort) {
applySort(queryMap, sort);
}
return queryMap;

5
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/ResponseEntityDecoder.java

@ -39,7 +39,7 @@ import org.springframework.http.ResponseEntity; @@ -39,7 +39,7 @@ import org.springframework.http.ResponseEntity;
*/
public class ResponseEntityDecoder implements Decoder {
private Decoder decoder;
private final Decoder decoder;
public ResponseEntityDecoder(Decoder decoder) {
this.decoder = decoder;
@ -70,8 +70,7 @@ public class ResponseEntityDecoder implements Decoder { @@ -70,8 +70,7 @@ public class ResponseEntityDecoder implements Decoder {
}
private boolean isHttpEntity(Type type) {
if (type instanceof Class) {
Class c = (Class) type;
if (type instanceof Class c) {
return HttpEntity.class.isAssignableFrom(c);
}
return false;

6
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringDecoder.java

@ -87,17 +87,17 @@ public class SpringDecoder implements Decoder { @@ -87,17 +87,17 @@ public class SpringDecoder implements Decoder {
}
@Override
public HttpStatus getStatusCode() throws IOException {
public HttpStatus getStatusCode() {
return HttpStatus.valueOf(response.status());
}
@Override
public int getRawStatusCode() throws IOException {
public int getRawStatusCode() {
return response.status();
}
@Override
public String getStatusText() throws IOException {
public String getStatusText() {
return response.reason();
}

29
spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java

@ -24,6 +24,7 @@ import java.nio.charset.Charset; @@ -24,6 +24,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@ -49,7 +50,6 @@ import org.springframework.http.converter.HttpMessageConverter; @@ -49,7 +50,6 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import org.springframework.web.multipart.MultipartFile;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
@ -79,26 +79,7 @@ public class SpringEncoder implements Encoder { @@ -79,26 +79,7 @@ public class SpringEncoder implements Encoder {
private final ObjectProvider<HttpMessageConverterCustomizer> customizers;
public SpringEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
this(new SpringFormEncoder(), messageConverters);
}
/**
* @deprecated in favour of
* {@link SpringEncoder#SpringEncoder(SpringFormEncoder, ObjectFactory, FeignEncoderProperties, ObjectProvider)}
*/
@Deprecated
public SpringEncoder(SpringFormEncoder springFormEncoder, ObjectFactory<HttpMessageConverters> messageConverters) {
this(springFormEncoder, messageConverters, new FeignEncoderProperties());
}
/**
* @deprecated in favour of
* {@link SpringEncoder#SpringEncoder(SpringFormEncoder, ObjectFactory, FeignEncoderProperties, ObjectProvider)}
*/
@Deprecated
public SpringEncoder(SpringFormEncoder springFormEncoder, ObjectFactory<HttpMessageConverters> messageConverters,
FeignEncoderProperties encoderProperties) {
this(springFormEncoder, messageConverters, encoderProperties, new EmptyObjectProvider<>());
this(new SpringFormEncoder(), messageConverters, new FeignEncoderProperties(), new EmptyObjectProvider<>());
}
public SpringEncoder(SpringFormEncoder springFormEncoder, ObjectFactory<HttpMessageConverters> messageConverters,
@ -158,7 +139,7 @@ public class SpringEncoder implements Encoder { @@ -158,7 +139,7 @@ public class SpringEncoder implements Encoder {
request.headers(null);
// converters can modify headers, so update the request
// with the modified headers
request.headers(getHeaders(outputMessage.getHeaders()));
request.headers(new LinkedHashMap<>(outputMessage.getHeaders()));
// do not use charset for binary data and protobuf
Charset charset;
@ -255,7 +236,7 @@ public class SpringEncoder implements Encoder { @@ -255,7 +236,7 @@ public class SpringEncoder implements Encoder {
.anyMatch(mediaType -> mediaType.includes(contentType));
}
private final class FeignOutputMessage implements HttpOutputMessage {
protected final class FeignOutputMessage implements HttpOutputMessage {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@ -266,7 +247,7 @@ public class SpringEncoder implements Encoder { @@ -266,7 +247,7 @@ public class SpringEncoder implements Encoder {
}
@Override
public OutputStream getBody() throws IOException {
public OutputStream getBody() {
return outputStream;
}

42
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;
@ -108,7 +112,7 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource @@ -108,7 +112,7 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private boolean decodeSlash;
private final boolean decodeSlash;
public SpringMvcContract() {
this(Collections.emptyList());
@ -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) {
@ -195,12 +199,12 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource @@ -195,12 +199,12 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
if (CollectionFormat.class.isInstance(methodAnnotation)) {
if (methodAnnotation instanceof CollectionFormat) {
CollectionFormat collectionFormat = findMergedAnnotation(method, CollectionFormat.class);
data.template().collectionFormat(collectionFormat.value());
}
if (!RequestMapping.class.isInstance(methodAnnotation)
if (!(methodAnnotation instanceof RequestMapping)
&& !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
return;
}
@ -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]);

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

@ -3,81 +3,93 @@ @@ -3,81 +3,93 @@
],
"properties": [
{
"name": "feign.autoconfiguration.jackson.enabled",
"name": "spring.cloud.openfeign.autoconfiguration.jackson.enabled",
"type": "java.lang.Boolean",
"description": "If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.",
"defaultValue": "false"
"defaultValue": "true"
},
{
"name": "feign.circuitbreaker.enabled",
"name": "spring.cloud.openfeign.circuitbreaker.enabled",
"type": "java.lang.Boolean",
"description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.",
"defaultValue": "false"
},
{
"name": "feign.circuitbreaker.alphanumeric-ids.enabled",
"name": "spring.cloud.openfeign.circuitbreaker.group.enabled",
"type": "java.lang.Boolean",
"description": "If true, Circuit Breaker ids will only contain alphanumeric characters to allow for configuration via configuration properties.",
"description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group.",
"defaultValue": "false"
},
{
"name": "feign.circuitbreaker.group.enabled",
"name": "spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled",
"type": "java.lang.Boolean",
"description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group.",
"description": "If true, Circuit Breaker ids will only contain alphanumeric characters to allow for configuration via configuration properties.",
"defaultValue": "false"
},
{
"name": "feign.httpclient.enabled",
"name": "spring.cloud.openfeign.httpclient.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the Apache HTTP Client by Feign.",
"defaultValue": "true"
},
{
"name": "feign.httpclient.hc5.enabled",
"name": "spring.cloud.openfeign.httpclient.hc5.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the Apache HTTP Client 5 by Feign.",
"defaultValue": "false"
},
{
"name": "feign.okhttp.enabled",
"name": "spring.cloud.openfeign.okhttp.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the OK HTTP Client by Feign.",
"defaultValue": "false"
},
{
"name": "feign.compression.response.enabled",
"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",
"description": "Enables the response from Feign to be compressed.",
"defaultValue": "false"
},
{
"name": "feign.compression.request.enabled",
"name": "spring.cloud.openfeign.compression.request.enabled",
"type": "java.lang.Boolean",
"description": "Enables the request sent by Feign to be compressed.",
"defaultValue": "false"
},
{
"name": "feign.metrics.enabled",
"name": "spring.cloud.openfeign.micrometer.enabled",
"type": "java.lang.Boolean",
"description": "Enables metrics capability for Feign.",
"description": "Enables Micrometer capabilities for Feign.",
"defaultValue": "true"
},
{
"name": "feign.client.refresh-enabled",
"name": "spring.cloud.openfeign.client.refresh-enabled",
"type": "java.lang.Boolean",
"description": "Enables options value refresh capability for Feign.",
"defaultValue": "false"
},
{
"name": "feign.oauth2.enabled",
"name": "spring.cloud.openfeign.oauth2.enabled",
"type": "java.lang.Boolean",
"description": "Enables feign interceptor for managing oauth2 access token.",
"defaultValue": "false"
},
{
"name": "feign.oauth2.load-balanced",
"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": "Enables load balancing for oauth2 access token provider.",
"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

9
spring-cloud-openfeign-core/src/main/resources/META-INF/spring.factories → spring-cloud-openfeign-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration
org.springframework.cloud.openfeign.FeignAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration

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 {
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save