mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
306 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f19fa5d8e | |||
| 208d52d852 | |||
| 18c3b637e4 | |||
| 34c69bfc67 | |||
| 37d7af42ac | |||
| cc986cd2e8 | |||
| 0141725638 | |||
| 0c2ba4e38e | |||
| c6a9aa59a3 | |||
| ad949a7450 | |||
| 86b2617c7f | |||
| 760376c318 | |||
| 5b6abe4c13 | |||
| 7a839e988a | |||
| 46859d6391 | |||
| 573e74b8bd | |||
| 6c231804a0 | |||
| 7c3913050a | |||
| ec488282a8 | |||
| 911cdb2ad0 | |||
| 0f83c483bb | |||
| c9050607bc | |||
| 5e82ee6bd7 | |||
| 387677eae8 | |||
| 4a314867d7 | |||
| 020f556841 | |||
| 6ea3b5a0e8 | |||
| d764087dbf | |||
| 9ab43b138a | |||
| 09ae080b99 | |||
| 70a1b2fae3 | |||
| 90ddb40d7a | |||
| 143985e862 | |||
| 4bd280b87e | |||
| 5877a38fa1 | |||
| 94d29bac9f | |||
| a6f3f18d89 | |||
| 8334cb1abb | |||
| 2d88f18bf1 | |||
| 15a6641677 | |||
| 63c8e7cb5d | |||
| c5ecc50bfe | |||
| 0fd94f1b9f | |||
| 2472126475 | |||
| f3b50c9a58 | |||
| 0a6e666857 | |||
| 403362fe3c | |||
| 534f123fe8 | |||
| e421104cf3 | |||
| adfeba23e0 | |||
| 7789e12575 | |||
| 30d793cefe | |||
| 78cc5df748 | |||
| 5ffaea7a43 | |||
| 1633ad24f3 | |||
| 5faf2ed7f4 | |||
| d2733cea36 | |||
| 108caea385 | |||
| 7e9ac120ac | |||
| c64dae3623 | |||
| 559ea6c480 | |||
| 34315fc20f | |||
| 3bb4795d43 | |||
| aae2952a32 | |||
| f62251aebd | |||
| f92f9c1d5b | |||
| 2b38c00656 | |||
| 06721ba6c0 | |||
| b6a5402d88 | |||
| aff9ac72ec | |||
| f895d762cd | |||
| 634d1dd20b | |||
| 725b02a66d | |||
| 5a0bd9e5d4 | |||
| 6dd73ae3e4 | |||
| 4606337180 | |||
| f45a08d4f5 | |||
| d62ce2969a | |||
| ed3fd12210 | |||
| 6f596e2e84 | |||
| 6786e1c3e5 | |||
| 93134fd4d1 | |||
| d0ceefedc6 | |||
| e230ea537c | |||
| 94eb6006e8 | |||
| 2576702cda | |||
| 2099e046d3 | |||
| 2ee7a8e77a | |||
| 3c40e5e501 | |||
| 9e45178202 | |||
| d82e70e345 | |||
| 1109892134 | |||
| a8be11ebaf | |||
| dfc10c1a8d | |||
| bbb593db48 | |||
| b2134ee71f | |||
| 440a259b71 | |||
| 6174055910 | |||
| e31ce359a1 | |||
| b336bbe539 | |||
| 056757b493 | |||
| 79c5fec1be | |||
| 689782cbad | |||
| 2df90e32c0 | |||
| f53da04717 | |||
| 124b38450b | |||
| b07217ab67 | |||
| aa7e84c89f | |||
| bd1c7b379f | |||
| 6af19244a5 | |||
| 328dd71f6e | |||
| d04883f839 | |||
| 9f55296049 | |||
| 4591a67641 | |||
| 9797bc0acd | |||
| 9d3374b28d | |||
| 09086fc648 | |||
| 128e90064e | |||
| 16e3973118 | |||
| 5c37d07ad3 | |||
| 12891d1975 | |||
| 1ca941ba9a | |||
| 17a94fb110 | |||
| 2fcae65853 | |||
| 7a0fe7d14f | |||
| ccdaed594e | |||
| c41b0140cd | |||
| ceffda7874 | |||
| 49f9b40fba | |||
| d59991fcc9 | |||
| cf46f391d7 | |||
| 2b4c7d09b0 | |||
| 61138698c6 | |||
| 3548e872b9 | |||
| 305686dbf7 | |||
| ace2f0a3e5 | |||
| 1a573d6e3c | |||
| bb7a8006c5 | |||
| ba56c1a8f4 | |||
| 9107f7b592 | |||
| 1d7cb4fc13 | |||
| 84992536c5 | |||
| ff49b0b683 | |||
| 56c4d2d4ca | |||
| 174d0e4576 | |||
| dba4881318 | |||
| 16ec08aec8 | |||
| 462c2bd538 | |||
| 7f29f0e663 | |||
| 3898482d3f | |||
| bb7ce21076 | |||
| fd4dee7ce3 | |||
| ec037b1ec0 | |||
| 68c1e2ac92 | |||
| 819a7c86c1 | |||
| 9c1346d240 | |||
| fb6e86551a | |||
| 13ba770428 | |||
| a4d99d6201 | |||
| 3923150dad | |||
| 8eb24458f8 | |||
| cda7e98643 | |||
| e92809d470 | |||
| f477c1653d | |||
| 1cea1fe962 | |||
| 8b07f93620 | |||
| a9ecf90524 | |||
| 7536777a18 | |||
| 1b18928bf0 | |||
| 8c2b44bd6c | |||
| 2df8ea94e0 | |||
| 323e52b5a9 | |||
| ed994dcd97 | |||
| 4c3b435d23 | |||
| cfe2db0581 | |||
| 53afe27109 | |||
| b9e43d05bd | |||
| d80de043ce | |||
| 7c5b6f1e1c | |||
| a6b6d19545 | |||
| 9b58df8857 | |||
| 1cc767e90b | |||
| 34d6dd9b62 | |||
| 5499878de0 | |||
| a970fc16aa | |||
| 683733a682 | |||
| 11fe2987d0 | |||
| 90423a98b0 | |||
| e9dc6be51c | |||
| 6863b904b5 | |||
| 016a4c4260 | |||
| 233f755416 | |||
| b026680e1c | |||
| e47cc19964 | |||
| 15b1310470 | |||
| ee60eb7207 | |||
| cecebd0ef1 | |||
| aac4dbf420 | |||
| 82bc4ff71d | |||
| 356d5c2cf2 | |||
| 46c13adfa8 | |||
| 886ca7f2db | |||
| c463b937b8 | |||
| 4783c321d9 | |||
| 0dfcb44821 | |||
| 8c3d5621db | |||
| 12a6a84829 | |||
| 6662ecc8cc | |||
| 4b0446fbef | |||
| 384d2749c6 | |||
| 5150a9a6ad | |||
| a8c5885aff | |||
| 7d844274dc | |||
| 70ceb3abdf | |||
| 9181cce65f | |||
| 8b6523a35b | |||
| e7f349328d | |||
| fe5f5d53b2 | |||
| 033d277bcf | |||
| 227385083d | |||
| d280358e98 | |||
| e08a7ece61 | |||
| 24fa9ea394 | |||
| 4acd9ee69b | |||
| cd1aefa5e0 | |||
| 3c3b8c7aac | |||
| 5a68d1f029 | |||
| ff9b7678a7 | |||
| 799566573a | |||
| 467d5f3ca3 | |||
| 172c25fb0e | |||
| 8771b9ea21 | |||
| ff72652890 | |||
| a1503a59ee | |||
| 5f29df3ac3 | |||
| 25287205ba | |||
| 2bb4df79c3 | |||
| 36fd82f32f | |||
| 0f26f42da7 | |||
| c48fec885c | |||
| 7c4351ab0a | |||
| 07455b10f3 | |||
| a72855b2b3 | |||
| cdddf09c20 | |||
| 53b3b934fc | |||
| 9173e13f74 | |||
| 84be0d85b4 | |||
| c971276f34 | |||
| cd2fbb1ec5 | |||
| e8745522fc | |||
| 2bfb283c91 | |||
| 50b1fb0b15 | |||
| a985b73939 | |||
| 6ec7dcf2c1 | |||
| d94e04d97a | |||
| 5a44897c55 | |||
| 181db1db75 | |||
| 138bdc597d | |||
| ef4f1f0a71 | |||
| 51b89743e1 | |||
| 019f76468b | |||
| f802c0cf24 | |||
| 0da4ae96b4 | |||
| 2ba0022704 | |||
| c59ca087b4 | |||
| 9de2b947cb | |||
| e73e7564d0 | |||
| d927d64c40 | |||
| 8544435833 | |||
| 80ea1f62b6 | |||
| bc287918f2 | |||
| c9436992c9 | |||
| 4350fc21b3 | |||
| 59ed4686c5 | |||
| d5bebd5ced | |||
| 6d86b23fbe | |||
| 0f38c28e91 | |||
| 3505c4bcad | |||
| 58670db46e | |||
| 0cdb8a1657 | |||
| fe8c70dddb | |||
| 1ed74efa6b | |||
| eabd574038 | |||
| ea386c2398 | |||
| 6c6e8ad831 | |||
| 69b74d7645 | |||
| 5ce5647d09 | |||
| ebae02a92b | |||
| 99cb6ba0a7 | |||
| 2baf70768d | |||
| 724700d708 | |||
| 83ba7e69ec | |||
| c1236a3340 | |||
| a942362221 | |||
| 1a34b0dd87 | |||
| b0fcde9aa3 | |||
| a89db89fc0 | |||
| 3d0fffa8e4 | |||
| 71f872e8bb | |||
| 54a90b20ed | |||
| d7a3971403 | |||
| cfa3463cec | |||
| d2264b221a | |||
| 2c32601553 | |||
| ea8b18fbc7 | |||
| 3ab4ee2bba |
@@ -31,10 +31,11 @@ runs:
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
develocity-token-expiry: 4
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -20,7 +20,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
|
||||
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
|
||||
@@ -9,22 +9,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Set Up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: '17'
|
||||
- name: Check Out
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
- name: Build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
- name: Print JVM Thread Dumps When Cancelled
|
||||
if: cancelled()
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
|
||||
@@ -12,8 +12,9 @@ permissions:
|
||||
|
||||
jobs:
|
||||
update-antora-ui-spring:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on Supported Branches
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.1.x' ]
|
||||
@@ -25,8 +26,9 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
antora-file-path: 'framework-docs/antora-playbook.yml'
|
||||
update-antora-ui-spring-docs-build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on docs-build
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
|
||||
+2
-1
@@ -42,7 +42,8 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea
|
||||
.idea/*
|
||||
!.idea/icon.svg
|
||||
out
|
||||
test-output
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
Generated
+52
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 510 510" style="enable-background:new 0 0 510 510;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#6DB33F;}
|
||||
</style>
|
||||
<title>icon-framework</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_1-2">
|
||||
<path class="st0" d="M479.2,162.5c-5.6-34.3-20.5-64.4-43.9-87.8S382,36.5,347.6,30.9C320.1,11.2,288.5,0,255,0
|
||||
s-65.1,11.2-92.6,30.9C128,36.5,98,51.3,74.7,74.7C50.9,98.4,36.5,128.7,31,162.2C11.2,189.8,0,221.4,0,255s11.2,65.2,31,92.8
|
||||
c5.5,33.5,19.9,63.8,43.7,87.5c23.3,23.3,53.3,38.2,87.7,43.8C190,498.8,221.5,510,255,510s65-11.2,92.6-30.9
|
||||
c34.4-5.6,64.4-20.5,87.7-43.8c23.8-23.8,38.1-54.1,43.7-87.5c19.8-27.6,31-59.2,31-92.8S498.8,190,479.2,162.5z M473,224.2
|
||||
c-0.8,6-1.9,11.9-3.2,17.9c-7.2-22.4-17.1-43.8-29.4-63.8c-5.5-22.8-13.6-44.9-24.4-65.8c22.9,14.8,42.2,32.6,56.7,52.6
|
||||
C475.7,184.6,475.8,204.6,473,224.2z M444.7,199.6c9.3,17.7,16.6,36.3,21.9,55.6c-5.3,19.3-12.7,37.9-22,55.6
|
||||
C450.9,274,451,236.4,444.7,199.6L444.7,199.6z M175.4,341.1c6.5-4.5,13.7-7.8,21.3-9.8c32.2-7.5,46.7-9.9,66.3-18.4
|
||||
c36.8-15.9,74.8-53.3,84.6-94c-16.1,38.8-59.5,70.2-97.9,81.6c-26.2,7.8-79.8,13.6-80.1,13.6l-1.4-1.2
|
||||
c-30.6-17.4-27-85.8,31.8-104.1c25.7-8,55.9-0.7,83.5-5.4c29.4-5,64.4-24.5,79.9-52c12.3,46.4,26.1,118.5-9.7,160.2
|
||||
C309.4,363,211.8,318,175.4,341.1z M310.8,444.6c-17.7,9.4-36.5,16.8-55.8,22.1c-19.4-5.3-38.1-12.7-55.8-22.1
|
||||
C236.1,450.9,273.9,450.9,310.8,444.6z M117.6,392.4c13.1,13.1,27.3,25.1,42.3,35.8c-19-5.9-37.2-13.8-54.5-23.7
|
||||
c-9.9-17.3-17.8-35.7-23.7-54.7C92.4,365.1,104.5,379.3,117.6,392.4z M81.7,160.1c5.9-19,13.9-37.4,23.7-54.7
|
||||
c17.3-9.8,35.5-17.8,54.5-23.7c-15.1,10.8-29.2,22.7-42.3,35.8C104.5,130.7,92.4,144.9,81.7,160.1z M149.1,346.4
|
||||
c-8.5,0.2-15.5-6.5-15.7-15c-0.2-8.5,6.5-15.5,15-15.7c8.5-0.2,15.5,6.5,15.7,15c0,0,0,0.1,0,0.1
|
||||
C164.2,339.3,157.5,346.2,149.1,346.4L149.1,346.4z M199.2,65.4c17.7-9.4,36.5-16.8,55.8-22.1c19.4,5.3,38.1,12.7,55.8,22.1
|
||||
C273.9,59.1,236.1,59.1,199.2,65.4L199.2,65.4z M392.4,392.4c13.1-13.1,25.1-27.3,35.9-42.4c-5.9,19-13.9,37.3-23.7,54.5
|
||||
c-17.3,9.8-35.5,17.7-54.5,23.7C365.2,417.5,379.3,405.5,392.4,392.4L392.4,392.4z M392.4,117.6c-13.1-13.1-27.2-25.1-42.3-35.8
|
||||
c19,5.9,37.2,13.9,54.5,23.7c9.8,17.3,17.8,35.6,23.7,54.7C417.6,144.9,405.5,130.7,392.4,117.6L392.4,117.6z M430.5,79.5
|
||||
c19.4,19.3,32.6,43.6,39.3,71.1c-16.2-19.2-36.6-36.2-60.4-50c-13.8-23.8-30.8-44.2-50-60.4C387.1,46.9,411.2,60.1,430.5,79.5
|
||||
L430.5,79.5z M397.6,94c-20.8-10.7-42.8-18.8-65.6-24.2c-20-12.4-41.5-22.4-63.9-29.6c15.5-3.4,31.3-5.1,47.2-5.2
|
||||
c10,0,19.9,0.8,29.7,2.3C365.1,51.8,382.8,71.1,397.6,94z M255,6.8c27.6,0,53.8,8,77.4,22.2c-5.6-0.5-11.3-0.7-17.1-0.7
|
||||
c-20.4,0.1-40.6,2.8-60.3,8.1c-19.7-5.3-39.9-8-60.3-8.1c-5.8,0-11.5,0.2-17.1,0.7C201.2,14.7,227.4,6.8,255,6.8z M165,37.3
|
||||
c9.8-1.5,19.8-2.3,29.7-2.3c15.9,0.1,31.7,1.8,47.2,5.2C219.5,47.4,198,57.3,178,69.8c-22.7,5.4-44.8,13.6-65.6,24.2
|
||||
C127.2,71.1,144.9,51.8,165,37.3z M79.5,79.5c19.3-19.3,43.5-32.6,71.1-39.3c-19.2,16.2-36.2,36.6-50,60.4
|
||||
c-23.7,13.8-44,30.6-60.2,49.8C47,123.4,59.8,99.1,79.5,79.5z M37.4,164.9c14.6-20,33.8-37.7,56.6-52.4
|
||||
c-10.7,20.9-18.8,43-24.3,65.8c-12.3,20-22.2,41.4-29.4,63.8C34.6,215.4,33.5,189.3,37.4,164.9z M65.4,310.4
|
||||
c-9.3-17.6-16.6-36.2-21.9-55.4c5.3-19.2,12.6-37.8,21.9-55.4C59.1,236.3,59.1,273.7,65.4,310.4L65.4,310.4z M29.2,332.8
|
||||
c-14.4-23.7-22.4-50-22.4-77.8s8.1-54.1,22.4-77.8c-2.1,25,0.4,51.3,7.4,77.8C29.5,281.5,27,307.8,29.2,332.8z M40.3,268
|
||||
c7.2,22.3,17.1,43.7,29.4,63.7c5.5,22.8,13.6,44.9,24.3,65.8c-22.8-14.7-42-32.5-56.6-52.4C33.5,320.7,34.6,294.6,40.3,268z
|
||||
M79.5,430.5c-19.6-19.6-32.5-43.9-39.1-70.9c16.1,19.1,36.5,36,60.2,49.8c13.8,23.7,30.8,44.2,50,60.4
|
||||
C122.9,463.1,98.8,449.9,79.5,430.5z M112.5,416c20.8,10.7,42.8,18.8,65.5,24.2c20,12.4,41.5,22.4,63.9,29.6
|
||||
c-15.5,3.4-31.3,5.1-47.2,5.1c-10,0-19.9-0.8-29.7-2.3C144.9,458.1,127.2,438.9,112.5,416L112.5,416z M255,503.2
|
||||
c-27.6,0-53.8-8-77.4-22.2c5.6,0.5,11.3,0.7,17.1,0.7c20.4-0.1,40.6-2.8,60.3-8.1c19.7,5.3,39.9,8,60.3,8.1
|
||||
c5.8,0,11.5-0.2,17.1-0.7C308.8,495.3,282.6,503.2,255,503.2z M345,472.7c-9.8,1.5-19.8,2.3-29.7,2.3c-15.9-0.1-31.7-1.8-47.2-5.1
|
||||
c22.4-7.2,43.9-17.2,63.9-29.6c22.7-5.4,44.8-13.6,65.6-24.2C382.8,438.9,365.1,458.1,345,472.7z M430.5,430.5
|
||||
c-19.3,19.3-43.5,32.6-71.1,39.3c19.2-16.2,36.2-36.6,50-60.4c23.7-13.8,44-30.6,60.2-49.8C463.1,386.6,450.1,410.9,430.5,430.5
|
||||
L430.5,430.5z M472.6,345.1c-14.6,20-33.8,37.7-56.6,52.4c10.7-20.8,18.8-42.8,24.3-65.6c12.3-20,22.2-41.3,29.5-63.7
|
||||
C475.4,294.7,476.5,320.8,472.6,345.1L472.6,345.1z M473.5,255.3c2.6-9.9,4.7-20,6.1-30.1c2.2-15.7,2.7-31.7,1.4-47.5
|
||||
c14.2,23.6,22.2,49.8,22.2,77.4s-8.1,54.1-22.4,77.8C482.9,307.9,480.5,281.6,473.5,255.3L473.5,255.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.12-librca
|
||||
java=17.0.13-librca
|
||||
|
||||
+6
-5
@@ -16,7 +16,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
|
||||
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework#coc-ov-file).
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
|
||||
@@ -65,10 +65,6 @@ follow-up reports will need to be created as new issues with a fresh description
|
||||
|
||||
#### Submit a Pull Request
|
||||
|
||||
1. If you have not previously done so, please sign the
|
||||
[Contributor License Agreement](https://cla.spring.io/sign/spring). You will be reminded
|
||||
automatically when you submit the PR.
|
||||
|
||||
1. Should you create an issue first? No, just create the pull request and use the
|
||||
description to provide context and motivation, as you would for an issue. If you want
|
||||
to start a discussion first or have already created an issue, once a pull request is
|
||||
@@ -85,6 +81,11 @@ multiple edits or corrections of the same logical change. See
|
||||
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
|
||||
for an overview of streamlining the commit history.
|
||||
|
||||
1. All commits must include a _Signed-off-by_ trailer at the end of each commit message
|
||||
to indicate that the contributor agrees to the Developer Certificate of Origin.
|
||||
For additional details, please refer to the blog post
|
||||
[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring).
|
||||
|
||||
1. Format commit messages using 55 characters for the subject line, 72 characters per line
|
||||
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
|
||||
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
|
||||
|
||||
+6
-6
@@ -9,7 +9,7 @@ plugins {
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "3.1.0" apply false
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -25,17 +25,17 @@ configure(allprojects) { project ->
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://repo.spring.io/milestone"
|
||||
url = "https://repo.spring.io/milestone"
|
||||
content {
|
||||
// Netty 5 optional support
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
if (version.contains('-')) {
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
maven { url = "https://repo.spring.io/milestone" }
|
||||
}
|
||||
if (version.endsWith('-SNAPSHOT')) {
|
||||
maven { url "https://repo.spring.io/snapshot" }
|
||||
maven { url = "https://repo.spring.io/snapshot" }
|
||||
}
|
||||
}
|
||||
configurations.all {
|
||||
@@ -91,7 +91,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.11.3/api/",
|
||||
// "https://junit.org/junit5/docs/5.12.1/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.20.2");
|
||||
checkstyle.setToolVersion("10.21.4");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.gradle.api.attributes.Usage;
|
||||
import org.gradle.api.attributes.java.TargetJvmVersion;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.jvm.JvmTestSuite;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.testing.base.TestingExtension;
|
||||
|
||||
@@ -52,7 +53,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
|
||||
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
|
||||
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
|
||||
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
TaskProvider<Test> agentTest = project.getTasks().register(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
test.useJUnitPlatform(options -> {
|
||||
options.includeTags("RuntimeHintsTests");
|
||||
});
|
||||
@@ -63,7 +64,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
|
||||
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
|
||||
});
|
||||
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
|
||||
project.getTasks().named("check", task -> task.dependsOn(agentTest));
|
||||
project.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://repo.spring.io/release"
|
||||
url = "https://repo.spring.io/release"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ tasks.register('schemaZip', Zip) {
|
||||
archiveClassifier.set("schema")
|
||||
description = "Builds -${archiveClassifier} archive containing all " +
|
||||
"XSDs for deployment at https://springframework.org/schema."
|
||||
duplicatesStrategy DuplicatesStrategy.EXCLUDE
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
moduleProjects.each { module ->
|
||||
def Properties schemas = new Properties();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ javadoc {
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://repo.spring.io/release"
|
||||
url = "https://repo.spring.io/release"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ dependencies {
|
||||
api(project(":spring-aspects"))
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-context-support"))
|
||||
api(project(":spring-core-test"))
|
||||
api(project(":spring-jdbc"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-test"))
|
||||
@@ -66,9 +67,9 @@ dependencies {
|
||||
api("org.apache.activemq:activemq-ra:6.1.2")
|
||||
api("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
api("org.aspectj:aspectjweaver")
|
||||
api("org.assertj:assertj-core")
|
||||
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
api("org.junit.jupiter:junit-jupiter-api")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
}
|
||||
|
||||
@@ -444,9 +444,9 @@ through Java 8's `java.util.Optional`, as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
You can also use a `@Nullable` annotation (of any kind in any package -- for example,
|
||||
`javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety
|
||||
support:
|
||||
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
|
||||
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
|
||||
null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -477,6 +477,13 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
|
||||
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
|
||||
detects type-level annotations and consistently declares JSpecify in its own codebase.
|
||||
====
|
||||
|
||||
You can also use `@Autowired` for interfaces that are well-known resolvable
|
||||
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
|
||||
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
|
||||
|
||||
@@ -578,6 +578,8 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
|
||||
|
||||
The method signature once again declares the event type to which it listens,
|
||||
but, this time, with a flexible name and without implementing a specific listener interface.
|
||||
The event type can also be narrowed through generics as long as the actual event type
|
||||
|
||||
@@ -57,9 +57,9 @@ The following table describes these properties:
|
||||
In addition to bean definitions that contain information on how to create a specific
|
||||
bean, the `ApplicationContext` implementations also permit the registration of existing
|
||||
objects that are created outside the container (by users). This is done by accessing the
|
||||
ApplicationContext's `BeanFactory` through the `getBeanFactory()` method, which returns
|
||||
the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory` supports
|
||||
this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
|
||||
ApplicationContext's `BeanFactory` through the `getAutowireCapableBeanFactory()` method,
|
||||
which returns the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory`
|
||||
supports this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
|
||||
methods. However, typical applications work solely with beans defined through regular
|
||||
bean definition metadata.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
If a bean is a dependency of another bean, that usually means that one bean is set as a
|
||||
property of another. Typically you accomplish this with the
|
||||
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
|
||||
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element]
|
||||
in XML-based metadata or through xref:core/beans/dependencies/factory-autowire.adoc[autowiring].
|
||||
|
||||
However, sometimes dependencies between beans are less direct. An example is when a static
|
||||
|
||||
-7
@@ -120,8 +120,6 @@ dynamically generate a subclass that overrides the method.
|
||||
subclasses cannot be `final`, and the method to be overridden cannot be `final`, either.
|
||||
* Unit-testing a class that has an `abstract` method requires you to subclass the class
|
||||
yourself and to supply a stub implementation of the `abstract` method.
|
||||
* Concrete methods are also necessary for component scanning, which requires concrete
|
||||
classes to pick up.
|
||||
* A further key limitation is that lookup methods do not work with factory methods and
|
||||
in particular not with `@Bean` methods in configuration classes, since, in that case,
|
||||
the container is not in charge of creating the instance and therefore cannot create
|
||||
@@ -293,11 +291,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Note that you should typically declare such annotated lookup methods with a concrete
|
||||
stub implementation, in order for them to be compatible with Spring's component
|
||||
scanning rules where abstract classes get ignored by default. This limitation does not
|
||||
apply to explicitly registered or explicitly imported bean classes.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Another way of accessing differently scoped target beans is an `ObjectFactory`/
|
||||
|
||||
+2
-2
@@ -168,8 +168,8 @@ listings shows how to use the `parent` attribute:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- in the child (descendant) context -->
|
||||
<bean id="accountService" <!-- bean name is the same as the parent bean -->
|
||||
<!-- in the child (descendant) context, bean name is the same as the parent bean -->
|
||||
<bean id="accountService"
|
||||
class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="target">
|
||||
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
|
||||
|
||||
@@ -64,7 +64,7 @@ This prevents the same `@Bean` method from accidentally being invoked through a
|
||||
Java method call, which helps to reduce subtle bugs that can be hard to track down.
|
||||
|
||||
When `@Bean` methods are declared within classes that are not annotated with
|
||||
`@Configuration` - or when `@Configuration(proxyBeanMethods=false)` is declared -,
|
||||
`@Configuration`, or when `@Configuration(proxyBeanMethods=false)` is declared,
|
||||
they are referred to as being processed in a "lite" mode. In such scenarios,
|
||||
`@Bean` methods are effectively a general-purpose factory method mechanism without
|
||||
special runtime processing (that is, without generating a CGLIB subclass for it).
|
||||
|
||||
@@ -7,7 +7,7 @@ xref:overview.adoc[Overview] :: History, Design Philosophy, Feedback,
|
||||
Getting Started.
|
||||
xref:core.adoc[Core] :: IoC Container, Events, Resources, i18n,
|
||||
Validation, Data Binding, Type Conversion, SpEL, AOP, AOT.
|
||||
<<testing.adoc#testing, Testing>> :: Mock Objects, TestContext Framework,
|
||||
xref:testing.adoc[Testing] :: Mock Objects, TestContext Framework,
|
||||
Spring MVC Test, WebTestClient.
|
||||
xref:data-access.adoc[Data Access] :: Transactions, DAO Support,
|
||||
JDBC, R2DBC, O/R Mapping, XML Marshalling.
|
||||
|
||||
@@ -270,6 +270,7 @@ By default, the following `KeyValues` are created:
|
||||
== HTTP Client Instrumentation
|
||||
|
||||
HTTP client exchange observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
||||
This observation measures the entire HTTP request/response exchange, from connection establishment up to body deserialization.
|
||||
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
|
||||
|
||||
[[observability.http-client.resttemplate]]
|
||||
@@ -285,7 +286,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
@@ -313,7 +314,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of the HTTP request method or `"none"` if the request could not be created.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
@@ -342,7 +343,7 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|
||||
@@ -115,11 +115,15 @@ Finally, the body can be set to a callback function that writes to an `OutputStr
|
||||
|
||||
==== Retrieving the response
|
||||
|
||||
Once the request has been set up, the HTTP response is accessed by invoking `retrieve()`.
|
||||
The response body can be accessed by using `body(Class)` or `body(ParameterizedTypeReference)` for parameterized types like lists.
|
||||
Once the request has been set up, it can be sent by chaining method calls after `retrieve()`.
|
||||
For example, the response body can be accessed by using `retrieve().body(Class)` or `retrieve().body(ParameterizedTypeReference)` for parameterized types like lists.
|
||||
The `body` method converts the response contents into various types – for instance, bytes can be converted into a `String`, JSON can be converted into objects using Jackson, and so on (see <<rest-message-conversion>>).
|
||||
|
||||
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body.
|
||||
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body, with `retrieve().toEntity(Class)`
|
||||
|
||||
NOTE: Calling `retrieve()` by itself is a no-op and returns a `ResponseSpec`.
|
||||
Applications must invoke a terminal operation on the `ResponseSpec` to have any side effect.
|
||||
If consuming the response has no interest for your use case, you can use `retrieve().toBodilessEntity()`.
|
||||
|
||||
This sample shows how `RestClient` can be used to perform a simple `GET` request.
|
||||
|
||||
@@ -853,7 +857,7 @@ Start by creating the interface with `@HttpExchange` methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
interface RepositoryService {
|
||||
public interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/{owner}/{repo}")
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
@@ -904,7 +908,7 @@ For `RestTemplate`:
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
|
||||
interface RepositoryService {
|
||||
public interface RepositoryService {
|
||||
|
||||
@GetExchange
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
@@ -938,10 +942,10 @@ method parameters:
|
||||
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
|
||||
|
||||
| `@RequestHeader`
|
||||
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
|
||||
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
|
||||
individual value. Type conversion is supported for non-String values. This overrides
|
||||
the annotation's `headers` attribute.
|
||||
| Add a request header or multiple headers. The argument may be a single value,
|
||||
a `Collection<?>` of values, `Map<String, ?>`,`MultiValueMap<String, ?>`.
|
||||
Type conversion is supported for non-String values. Header values are added and
|
||||
do not override already added header values.
|
||||
|
||||
| `@PathVariable`
|
||||
| Add a variable for expand a placeholder in the request URL. The argument may be a
|
||||
@@ -988,6 +992,27 @@ parameter annotation) is set to `false`, or the parameter is marked optional as
|
||||
|
||||
|
||||
|
||||
[[rest-http-interface.custom-resolver]]
|
||||
=== Custom argument resolver
|
||||
|
||||
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters.
|
||||
This would take over the entire HTTP request and not improve the semantics of the interface.
|
||||
Instead of adding many method parameters, developers can combine them into a custom type
|
||||
and configure a dedicated `HttpServiceArgumentResolver` implementation.
|
||||
|
||||
In the following HTTP interface, we are using a custom `Search` type as a parameter:
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
|
||||
|
||||
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type
|
||||
and writes its data in the outgoing HTTP request.
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0]
|
||||
|
||||
Finally, we can use this argument resolver during the setup and use our HTTP interface.
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0]
|
||||
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
|
||||
@@ -14,16 +14,13 @@ For example, `@Autowired lateinit var thing: Thing` implies that a bean
|
||||
of type `Thing` must be registered in the application context, while `@Autowired lateinit var thing: Thing?`
|
||||
does not raise an error if such a bean does not exist.
|
||||
|
||||
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, Car)` implies
|
||||
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, car)` implies
|
||||
that a bean of type `Toy` must be registered in the application context, while a bean of
|
||||
type `Car` may or may not exist. The same behavior applies to autowired constructor parameters.
|
||||
|
||||
NOTE: If you use bean validation on classes with properties or a primary constructor
|
||||
parameters, you may need to use
|
||||
with parameters, you may need to use
|
||||
{kotlin-docs}/annotations.html#annotation-use-site-targets[annotation use-site targets],
|
||||
such as `@field:NotNull` or `@get:Size(min=5, max=15)`, as described in
|
||||
{stackoverflow-site}/a/35853200/1092077[this Stack Overflow response].
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+184
-44
@@ -1,42 +1,62 @@
|
||||
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
||||
= `@MockitoBean` and `@MockitoSpyBean`
|
||||
|
||||
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
|
||||
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
|
||||
respectively. In the latter case, an early instance of the original bean is captured and
|
||||
wrapped by the spy.
|
||||
{spring-framework-api}/test/context/bean/override/mockito/MockitoBean.html[`@MockitoBean`] and
|
||||
{spring-framework-api}/test/context/bean/override/mockito/MockitoSpyBean.html[`@MockitoSpyBean`]
|
||||
can be used in test classes to override a bean in the test's `ApplicationContext` with a
|
||||
Mockito _mock_ or _spy_, respectively. In the latter case, an early instance of the
|
||||
original bean is captured and wrapped by the spy.
|
||||
|
||||
By default, the annotated field's type is used to search for candidate beans to override.
|
||||
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to
|
||||
override. Alternatively, a candidate whose bean name matches the name of the field will
|
||||
match.
|
||||
The annotations can be applied in the following ways.
|
||||
|
||||
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
|
||||
exist. However, if you would like for the test to fail when a corresponding bean does not
|
||||
exist, you can set the `enforceOverride` attribute to `true` – for example,
|
||||
`@MockitoBean(enforceOverride = true)`.
|
||||
* On a non-static field in a test class or any of its superclasses.
|
||||
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
|
||||
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
|
||||
* At the type level on a test class or any superclass or implemented interface in the
|
||||
type hierarchy above the test class.
|
||||
* At the type level on an enclosing class for a `@Nested` test class or on any class or
|
||||
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
|
||||
class.
|
||||
|
||||
To use a by-name override rather than a by-type override, specify the `name` attribute
|
||||
of the annotation.
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
|
||||
is inferred from the type of the annotated field. If multiple candidates exist in the
|
||||
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
|
||||
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
|
||||
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
|
||||
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
|
||||
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
|
||||
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation –
|
||||
for example, `@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple
|
||||
candidates exist in the `ApplicationContext`, you can explicitly specify a bean name to
|
||||
mock or spy by setting the `name` attribute. Note, however, that the `types` attribute
|
||||
must contain a single type if an explicit bean `name` is configured – for example,
|
||||
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
|
||||
|
||||
To support reuse of mock configuration, `@MockitoBean` and `@MockitoSpyBean` may be used
|
||||
as meta-annotations to create custom _composed annotations_ – for example, to define
|
||||
common mock or spy configuration in a single annotation that can be reused across a test
|
||||
suite. `@MockitoBean` and `@MockitoSpyBean` can also be used as repeatable annotations at
|
||||
the type level — for example, to mock or spy several beans by name.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Qualifiers, including the name of the field, are used to determine if a separate
|
||||
Qualifiers, including the name of a field, are used to determine if a separate
|
||||
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
|
||||
the same bean in several tests, make sure to name the field consistently to avoid
|
||||
the same bean in several test classes, make sure to name the fields consistently to avoid
|
||||
creating unnecessary contexts.
|
||||
====
|
||||
|
||||
Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.
|
||||
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
|
||||
|
||||
By default, the `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
|
||||
If no existing bean matches, a new bean is created on the fly. As mentioned previously,
|
||||
you can switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to
|
||||
`true`.
|
||||
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy for bean overrides].
|
||||
If a corresponding bean does not exist, a new bean will be created. However, you can
|
||||
switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to `true` –
|
||||
for example, `@MockitoBean(enforceOverride = true)`.
|
||||
|
||||
The `@MockitoSpyBean` annotation uses the `WRAP`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy],
|
||||
and the original instance is wrapped in a Mockito spy. This strategy requires that
|
||||
exactly one candidate bean exists.
|
||||
|
||||
@@ -61,7 +81,11 @@ Such fields can therefore be `public`, `protected`, package-private (default vis
|
||||
or `private` depending on the needs or coding practices of the project.
|
||||
====
|
||||
|
||||
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
|
||||
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
|
||||
== `@MockitoBean` Examples
|
||||
|
||||
The following example shows how to use the default behavior of the `@MockitoBean`
|
||||
annotation.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -69,23 +93,26 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoBean // <1>
|
||||
CustomService customService;
|
||||
|
||||
// test case body...
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock.
|
||||
======
|
||||
|
||||
In the example above, we are creating a mock for `CustomService`. If more than one bean
|
||||
of that type exists, the bean named `customService` is considered. Otherwise, the test
|
||||
will fail, and you will need to provide a qualifier of some sort to identify which of the
|
||||
`CustomService` beans you want to override. If no such bean exists, a bean definition
|
||||
will be created with an auto-generated bean name.
|
||||
`CustomService` beans you want to override. If no such bean exists, a bean will be
|
||||
created with an auto-generated bean name.
|
||||
|
||||
The following example uses a by-name lookup, rather than a by-type lookup:
|
||||
The following example uses a by-name lookup, rather than a by-type lookup. If no bean
|
||||
named `service` exists, one is created.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -93,20 +120,20 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoBean("service") // <1>
|
||||
CustomService customService;
|
||||
|
||||
// test case body...
|
||||
// tests...
|
||||
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito `mock`.
|
||||
<1> Replace the bean named `service` with a Mockito mock.
|
||||
======
|
||||
|
||||
If no bean definition named `service` exists, one is created.
|
||||
|
||||
The following example shows how to use the default behavior of the `@MockitoSpyBean` annotation:
|
||||
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -114,14 +141,73 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MockitoBean(types = {OrderService.class, UserService.class}) // <1>
|
||||
@MockitoBean(name = "ps1", types = PrintingService.class) // <2>
|
||||
public @interface SharedMocks {
|
||||
}
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` mocks by-type.
|
||||
<2> Register `PrintingService` mock by-name.
|
||||
======
|
||||
|
||||
The following demonstrates how `@SharedMocks` can be used on a test class.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
@SharedMocks // <1>
|
||||
class BeanOverrideTests {
|
||||
|
||||
@Autowired OrderService orderService; // <2>
|
||||
|
||||
@Autowired UserService userService; // <2>
|
||||
|
||||
@Autowired PrintingService ps1; // <2>
|
||||
|
||||
// Inject other components that rely on the mocks.
|
||||
|
||||
@Test
|
||||
void testThatDependsOnMocks() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Register common mocks via the custom `@SharedMocks` annotation.
|
||||
<2> Optionally inject mocks to _stub_ or _verify_ them.
|
||||
======
|
||||
|
||||
TIP: The mocks can also be injected into `@Configuration` classes or other test-related
|
||||
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
|
||||
APIs.
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-mockitospybean-examples]]
|
||||
== `@MockitoSpyBean` Examples
|
||||
|
||||
The following example shows how to use the default behavior of the `@MockitoSpyBean`
|
||||
annotation.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoSpyBean // <1>
|
||||
CustomService customService;
|
||||
|
||||
// test case body...
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy.
|
||||
======
|
||||
|
||||
In the example above, we are wrapping the bean with type `CustomService`. If more than
|
||||
@@ -129,7 +215,7 @@ one bean of that type exists, the bean named `customService` is considered. Othe
|
||||
the test will fail, and you will need to provide a qualifier of some sort to identify
|
||||
which of the `CustomService` beans you want to spy.
|
||||
|
||||
The following example uses a by-name lookup, rather than a by-type lookup:
|
||||
The following example uses a by-name lookup, rather than a by-type lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -137,13 +223,67 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoSpyBean("service") // <1>
|
||||
CustomService customService;
|
||||
|
||||
// test case body...
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito `spy`.
|
||||
<1> Wrap the bean named `service` with a Mockito spy.
|
||||
======
|
||||
|
||||
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MockitoSpyBean(types = {OrderService.class, UserService.class}) // <1>
|
||||
@MockitoSpyBean(name = "ps1", types = PrintingService.class) // <2>
|
||||
public @interface SharedSpies {
|
||||
}
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` spies by-type.
|
||||
<2> Register `PrintingService` spy by-name.
|
||||
======
|
||||
|
||||
The following demonstrates how `@SharedSpies` can be used on a test class.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
@SharedSpies // <1>
|
||||
class BeanOverrideTests {
|
||||
|
||||
@Autowired OrderService orderService; // <2>
|
||||
|
||||
@Autowired UserService userService; // <2>
|
||||
|
||||
@Autowired PrintingService ps1; // <2>
|
||||
|
||||
// Inject other components that rely on the spies.
|
||||
|
||||
@Test
|
||||
void testThatDependsOnMocks() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Register common spies via the custom `@SharedSpies` annotation.
|
||||
<2> Optionally inject spies to _stub_ or _verify_ them.
|
||||
======
|
||||
|
||||
TIP: The spies can also be injected into `@Configuration` classes or other test-related
|
||||
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
|
||||
APIs.
|
||||
|
||||
+7
-4
@@ -1,8 +1,9 @@
|
||||
[[spring-testing-annotation-beanoverriding-testbean]]
|
||||
= `@TestBean`
|
||||
|
||||
`@TestBean` is used on a non-static field in a test class to override a specific bean in
|
||||
the test's `ApplicationContext` with an instance provided by a factory method.
|
||||
{spring-framework-api}/test/context/bean/override/convention/TestBean.html[`@TestBean`]
|
||||
is used on a non-static field in a test class to override a specific bean in the test's
|
||||
`ApplicationContext` with an instance provided by a factory method.
|
||||
|
||||
The associated factory method name is derived from the annotated field's name, or the
|
||||
bean name if specified. The factory method must be `static`, accept no arguments, and
|
||||
@@ -92,8 +93,10 @@ Java::
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Spring searches for the factory method to invoke in the test class, in the test class
|
||||
hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
|
||||
To locate the factory method to invoke, Spring searches in the class in which the
|
||||
`@TestBean` field is declared, in one of its superclasses, or in any implemented
|
||||
interfaces. If the `@TestBean` field is declared in a `@Nested` test class, the enclosing
|
||||
class hierarchy will also be searched.
|
||||
|
||||
Alternatively, a factory method in an external class can be referenced via its
|
||||
fully-qualified method name following the syntax `<fully-qualified class name>#<method name>`
|
||||
|
||||
+7
-6
@@ -2,8 +2,8 @@
|
||||
= Bean Overriding in Tests
|
||||
|
||||
Bean overriding in tests refers to the ability to override specific beans in the
|
||||
`ApplicationContext` for a test class, by annotating one or more non-static fields in the
|
||||
test class.
|
||||
`ApplicationContext` for a test class, by annotating the test class or one or more
|
||||
non-static fields in the test class.
|
||||
|
||||
NOTE: This feature is intended as a less risky alternative to the practice of registering
|
||||
a bean via `@Bean` with the `DefaultListableBeanFactory`
|
||||
@@ -42,15 +42,16 @@ The `spring-test` module registers implementations of the latter two
|
||||
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
||||
properties file].
|
||||
|
||||
The bean overriding infrastructure searches in test classes for any non-static field that
|
||||
is meta-annotated with `@BeanOverride` and instantiates the corresponding
|
||||
`BeanOverrideProcessor` which is responsible for creating an appropriate
|
||||
`BeanOverrideHandler`.
|
||||
The bean overriding infrastructure searches for annotations on test classes as well as
|
||||
annotations on non-static fields in test classes that are meta-annotated with
|
||||
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
|
||||
responsible for creating an appropriate `BeanOverrideHandler`.
|
||||
|
||||
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
|
||||
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
|
||||
defined by the corresponding `BeanOverrideStrategy`:
|
||||
|
||||
[[testcontext-bean-overriding-strategy]]
|
||||
`REPLACE`::
|
||||
Replaces the bean. Throws an exception if a corresponding bean does not exist.
|
||||
`REPLACE_OR_CREATE`::
|
||||
|
||||
@@ -7,16 +7,17 @@ by default, exactly in the following order:
|
||||
* `ServletTestExecutionListener`: Configures Servlet API mocks for a
|
||||
`WebApplicationContext`.
|
||||
* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext`
|
||||
annotation for "`before`" modes.
|
||||
annotation for "before" modes.
|
||||
* `ApplicationEventsTestExecutionListener`: Provides support for
|
||||
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
|
||||
* `BeanOverrideTestExecutionListener`: Provides support for xref:testing/testcontext-framework/bean-overriding.adoc[] .
|
||||
* `BeanOverrideTestExecutionListener`: Provides support for
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc[].
|
||||
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
|
||||
instance.
|
||||
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
|
||||
Micrometer's `ObservationRegistry`.
|
||||
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
||||
"`after`" modes.
|
||||
"after" modes.
|
||||
* `CommonCachesTestExecutionListener`: Clears resource caches in the test's
|
||||
`ApplicationContext` if necessary.
|
||||
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
||||
@@ -161,15 +162,16 @@ change from release to release -- for example, `SqlScriptsTestExecutionListener`
|
||||
introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener`
|
||||
was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
|
||||
Boot and Spring Security register their own default `TestExecutionListener`
|
||||
implementations by using the aforementioned xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism]
|
||||
.
|
||||
implementations by using the aforementioned
|
||||
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism].
|
||||
|
||||
To avoid having to be aware of and re-declare all default listeners, you can set the
|
||||
`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`.
|
||||
`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the
|
||||
default listeners. The merging algorithm ensures that duplicates are removed from the
|
||||
list and that the resulting set of merged listeners is sorted according to the semantics
|
||||
of `AnnotationAwareOrderComparator`, as described in xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
|
||||
of `AnnotationAwareOrderComparator`, as described in
|
||||
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
|
||||
If a listener implements `Ordered` or is annotated with `@Order`, it can influence the
|
||||
position in which it is merged with the defaults. Otherwise, locally declared listeners
|
||||
are appended to the list of default listeners when merged.
|
||||
|
||||
@@ -47,8 +47,7 @@ out-of-container tests for code that depends on environment-specific properties.
|
||||
The `org.springframework.mock.web` package contains a comprehensive set of Servlet API
|
||||
mock objects that are useful for testing web contexts, controllers, and filters. These
|
||||
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
|
||||
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock])
|
||||
or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]).
|
||||
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock]).
|
||||
|
||||
TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are
|
||||
based on the Servlet 6.0 API.
|
||||
|
||||
@@ -193,7 +193,7 @@ Kotlin::
|
||||
[[webtestclient-fn-config]]
|
||||
=== Bind to Router Function
|
||||
|
||||
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
|
||||
This setup allows you to test xref:web/webflux-functional.adoc[functional endpoints] via
|
||||
mock request and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-cors]]
|
||||
= CORS
|
||||
|
||||
[.small]#xref:web/webmvc-cors.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
|
||||
@@ -364,7 +365,7 @@ Kotlin::
|
||||
|
||||
You can apply CORS support through the built-in
|
||||
{spring-framework-api}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
|
||||
good fit with <<webflux-fn, functional endpoints>>.
|
||||
good fit with xref:web/webflux-functional.adoc[functional endpoints].
|
||||
|
||||
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
|
||||
Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-fn]]
|
||||
= Functional Endpoints
|
||||
|
||||
[.small]#xref:web/webmvc-functional.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-view]]
|
||||
= View Technologies
|
||||
|
||||
[.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
The rendering of views in Spring WebFlux is pluggable. Whether you decide to
|
||||
@@ -224,9 +225,9 @@ The following table shows the templating libraries that we have tested on differ
|
||||
|Scripting Library |Scripting Engine
|
||||
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|
||||
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|
||||
|===
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-websocket]]
|
||||
= WebSockets
|
||||
|
||||
[.small]#xref:web/websocket.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
This part of the reference documentation covers support for reactive-stack WebSocket
|
||||
|
||||
@@ -605,9 +605,4 @@ For method parameters and returns values, generally, `@HttpExchange` supports a
|
||||
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
|
||||
server-side specific parameter types. For details, see the list for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
|
||||
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
|
||||
pairs like in `@RequestMapping(headers={})` on the client side. On the server side,
|
||||
this extends to the full syntax that
|
||||
xref:#webflux-ann-requestmapping-params-and-headers[`@RequestMapping`] supports.
|
||||
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
|
||||
For further custom handling of method validation errors, you can extend
|
||||
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
|
||||
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
|
||||
The exception contains a list of``ParameterValidationResult``s that group validation errors
|
||||
The exception contains a list of ``ParameterValidationResult``s that group validation errors
|
||||
by method parameter. You can either iterate over those, or provide a visitor with callback
|
||||
methods by controller method parameter type:
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ On that foundation, Spring WebFlux provides a choice of two programming models:
|
||||
from the `spring-web` module. Both Spring MVC and WebFlux controllers support reactive
|
||||
(Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable
|
||||
difference is that WebFlux also supports reactive `@RequestBody` arguments.
|
||||
* <<webflux-fn>>: Lambda-based, lightweight, and functional programming model. You can think of
|
||||
* xref:web/webflux-functional.adoc[Functional Endpoints]: Lambda-based, lightweight, and functional programming model. You can think of
|
||||
this as a small library or a set of utilities that an application can use to route and
|
||||
handle requests. The big difference with annotated controllers is that the application
|
||||
is in charge of request handling from start to finish versus declaring intent through
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[mvc-cors]]
|
||||
= CORS
|
||||
|
||||
[.small]#xref:web/webflux-cors.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[[webmvc-fn]]
|
||||
= Functional Endpoints
|
||||
[.small]#<<web-reactive.adoc#webflux-fn, See equivalent in the Reactive stack>>#
|
||||
|
||||
[.small]#xref:web/webflux-functional.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions
|
||||
are used to route and handle requests and contracts are designed for immutability.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[[mvc-view]]
|
||||
= View Technologies
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webflux-view.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
The rendering of views in Spring MVC is pluggable. Whether you decide to use
|
||||
|
||||
@@ -130,6 +130,7 @@ the `Configuration` object.
|
||||
|
||||
[[mvc-view-freemarker-forms]]
|
||||
== Form Handling
|
||||
[.small]#xref:web/webflux-view.adoc#webflux-view-freemarker-forms[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring provides a tag library for use in JSPs that contains, among others, a
|
||||
`<spring:bind/>` element. This element primarily lets forms display values from
|
||||
|
||||
@@ -13,9 +13,9 @@ templating libraries on different script engines:
|
||||
|Scripting Library |Scripting Engine
|
||||
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|
||||
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|
||||
|===
|
||||
@@ -47,7 +47,7 @@ through https://www.webjars.org/[WebJars].
|
||||
|
||||
[[mvc-view-script-integrate]]
|
||||
== Script Templates
|
||||
[.small]#xref:web/webflux-view.adoc#webflux-view-script[See equivalent in the Reactive stack]#
|
||||
[.small]#xref:web/webflux-view.adoc#webflux-view-script-integrate[See equivalent in the Reactive stack]#
|
||||
|
||||
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
|
||||
the script files to load, what function to call to render templates, and so on.
|
||||
|
||||
@@ -37,7 +37,7 @@ already set.
|
||||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
|
||||
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
|
||||
To enable RFC 9457 responses for Spring MVC exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
|
||||
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
|
||||
|
||||
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
|
||||
For further custom handling of method validation errors, you can extend
|
||||
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
|
||||
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
|
||||
The exception contains a list of``ParameterValidationResult``s that group validation errors
|
||||
The exception contains a list of ``ParameterValidationResult``s that group validation errors
|
||||
by method parameter. You can either iterate over those, or provide a visitor with callback
|
||||
methods by controller method parameter type:
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ reference documentation, including:
|
||||
* {docs-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection]
|
||||
* {docs-spring-security}/features/exploits/headers.html[Security Response Headers]
|
||||
|
||||
https://hdiv.org/[HDIV] is another web security framework that integrates with Spring MVC.
|
||||
https://github.com/hdiv/hdiv[HDIV] is another web security framework that integrates with Spring MVC.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[[websocket]]
|
||||
= WebSockets
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webflux-websocket.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
This part of the reference documentation covers support for Servlet stack, WebSocket
|
||||
|
||||
@@ -19,6 +19,6 @@ from where they are handled according to their destination prefix. As the channe
|
||||
a `ThreadPoolExecutor`, messages are processed in different threads, and the resulting sequence
|
||||
of handling may not match the exact order in which they were received.
|
||||
|
||||
To enable ordered publishing, set the `setPreserveReceiveOrder` flag as follows:
|
||||
To enable ordered receiving, set the `setPreserveReceiveOrder` flag as follows:
|
||||
|
||||
include-code::./ReceiveOrderWebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"@antora/collector-extension": "1.0.0-alpha.3",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.2",
|
||||
"fast-xml-parser": "4.5.2",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
}
|
||||
}
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.integration.resthttpinterface.customresolver;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.support.RestClientAdapter;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.invoker.HttpRequestValues;
|
||||
import org.springframework.web.service.invoker.HttpServiceArgumentResolver;
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
|
||||
public class CustomHttpServiceArgumentResolver {
|
||||
|
||||
// tag::httpinterface[]
|
||||
public interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/search")
|
||||
List<Repository> searchRepository(Search search);
|
||||
|
||||
}
|
||||
// end::httpinterface[]
|
||||
|
||||
class Sample {
|
||||
|
||||
void sample() {
|
||||
// tag::usage[]
|
||||
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory
|
||||
.builderFor(adapter)
|
||||
.customArgumentResolver(new SearchQueryArgumentResolver())
|
||||
.build();
|
||||
RepositoryService repositoryService = factory.createClient(RepositoryService.class);
|
||||
|
||||
Search search = Search.create()
|
||||
.owner("spring-projects")
|
||||
.language("java")
|
||||
.query("rest")
|
||||
.build();
|
||||
List<Repository> repositories = repositoryService.searchRepository(search);
|
||||
// end::usage[]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// tag::argumentresolver[]
|
||||
static class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
|
||||
@Override
|
||||
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
if (parameter.getParameterType().equals(Search.class)) {
|
||||
Search search = (Search) argument;
|
||||
requestValues.addRequestParameter("owner", search.owner());
|
||||
requestValues.addRequestParameter("language", search.language());
|
||||
requestValues.addRequestParameter("query", search.query());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// end::argumentresolver[]
|
||||
|
||||
|
||||
record Search (String query, String owner, String language) {
|
||||
|
||||
static Builder create() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
|
||||
Builder query(String query) { return this;}
|
||||
|
||||
Builder owner(String owner) { return this;}
|
||||
|
||||
Builder language(String language) { return this;}
|
||||
|
||||
Search build() {
|
||||
return new Search(null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record Repository(String name) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.docs.integration.resthttpinterface.customresolver
|
||||
|
||||
import org.springframework.core.MethodParameter
|
||||
import org.springframework.web.client.RestClient
|
||||
import org.springframework.web.client.support.RestClientAdapter
|
||||
import org.springframework.web.service.annotation.GetExchange
|
||||
import org.springframework.web.service.invoker.HttpRequestValues
|
||||
import org.springframework.web.service.invoker.HttpServiceArgumentResolver
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory
|
||||
|
||||
class CustomHttpServiceArgumentResolver {
|
||||
|
||||
// tag::httpinterface[]
|
||||
interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/search")
|
||||
fun searchRepository(search: Search): List<Repository>
|
||||
|
||||
}
|
||||
// end::httpinterface[]
|
||||
|
||||
class Sample {
|
||||
fun sample() {
|
||||
// tag::usage[]
|
||||
val restClient = RestClient.builder().baseUrl("https://api.github.com/").build()
|
||||
val adapter = RestClientAdapter.create(restClient)
|
||||
val factory = HttpServiceProxyFactory
|
||||
.builderFor(adapter)
|
||||
.customArgumentResolver(SearchQueryArgumentResolver())
|
||||
.build()
|
||||
val repositoryService = factory.createClient<RepositoryService>(RepositoryService::class.java)
|
||||
|
||||
val search = Search(owner = "spring-projects", language = "java", query = "rest")
|
||||
val repositories = repositoryService.searchRepository(search)
|
||||
// end::usage[]
|
||||
repositories.size
|
||||
}
|
||||
}
|
||||
|
||||
// tag::argumentresolver[]
|
||||
class SearchQueryArgumentResolver : HttpServiceArgumentResolver {
|
||||
override fun resolve(
|
||||
argument: Any?,
|
||||
parameter: MethodParameter,
|
||||
requestValues: HttpRequestValues.Builder
|
||||
): Boolean {
|
||||
if (parameter.getParameterType() == Search::class.java) {
|
||||
val search = argument as Search
|
||||
requestValues.addRequestParameter("owner", search.owner)
|
||||
.addRequestParameter("language", search.language)
|
||||
.addRequestParameter("query", search.query)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
// end::argumentresolver[]
|
||||
|
||||
data class Search(val query: String, val owner: String, val language: String)
|
||||
|
||||
data class Repository(val name: String)
|
||||
}
|
||||
@@ -7,31 +7,31 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.2"))
|
||||
api(platform("io.netty:netty-bom:4.1.115.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.5"))
|
||||
api(platform("io.netty:netty-bom:4.1.119.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.1"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.4"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.4"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.26.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.15"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.15"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.17"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.17"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.11.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.14.2"))
|
||||
api(platform("org.junit:junit-bom:5.12.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.16.1"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.2.0")
|
||||
api("com.github.librepdf:openpdf:1.3.43")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.11.0")
|
||||
api("com.google.protobuf:protobuf-java-util:4.28.3")
|
||||
api("com.google.code.gson:gson:2.12.1")
|
||||
api("com.google.protobuf:protobuf-java-util:4.30.0")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
@@ -43,7 +43,7 @@ dependencies {
|
||||
api("com.sun.xml.bind:jaxb-core:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-impl:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.1.0")
|
||||
api("com.thoughtworks.qdox:qdox:2.2.0")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.21")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
@@ -100,8 +100,8 @@ dependencies {
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.2")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.3")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
|
||||
@@ -110,18 +110,18 @@ dependencies {
|
||||
api("org.aspectj:aspectjrt:1.9.22.1")
|
||||
api("org.aspectj:aspectjtools:1.9.22.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.22.1")
|
||||
api("org.awaitility:awaitility:4.2.2")
|
||||
api("org.awaitility:awaitility:4.3.0")
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.easymock:easymock:5.4.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
|
||||
api("org.easymock:easymock:5.5.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.33")
|
||||
api("org.freemarker:freemarker:2.3.34")
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
@@ -130,24 +130,24 @@ dependencies {
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.4")
|
||||
api("org.htmlunit:htmlunit:4.6.0")
|
||||
api("org.htmlunit:htmlunit:4.10.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.9.0")
|
||||
api("org.jruby:jruby:9.4.12.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.15")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.3")
|
||||
api("org.python:jython-standalone:2.7.4")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.26.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.26.0")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.29.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.29.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.16")
|
||||
api("org.testng:testng:7.10.2")
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
api("org.testng:testng:7.11.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
api("org.webjars:webjars-locator-lite:1.0.0")
|
||||
api("org.webjars:webjars-locator-core:0.59")
|
||||
api("org.webjars:webjars-locator-lite:1.1.0")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.0")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.0")
|
||||
api("org.yaml:snakeyaml:2.3")
|
||||
api("org.yaml:snakeyaml:2.4")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.1-SNAPSHOT
|
||||
version=6.2.5
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -86,8 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -206,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id "com.gradle.develocity" version "3.17.2"
|
||||
id "com.gradle.develocity" version "3.19"
|
||||
id "io.spring.ge.conventions" version "0.0.17"
|
||||
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
|
||||
}
|
||||
|
||||
+13
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -276,14 +276,18 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
|
||||
}
|
||||
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
|
||||
// May need to add implicit join point arg name...
|
||||
Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
|
||||
if (firstArgType == JoinPoint.class ||
|
||||
firstArgType == ProceedingJoinPoint.class ||
|
||||
firstArgType == JoinPoint.StaticPart.class) {
|
||||
String[] oldNames = this.argumentNames;
|
||||
this.argumentNames = new String[oldNames.length + 1];
|
||||
this.argumentNames[0] = "THIS_JOIN_POINT";
|
||||
System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
|
||||
for (int i = 0; i < this.aspectJAdviceMethod.getParameterCount(); i++) {
|
||||
Class<?> argType = this.aspectJAdviceMethod.getParameterTypes()[i];
|
||||
if (argType == JoinPoint.class ||
|
||||
argType == ProceedingJoinPoint.class ||
|
||||
argType == JoinPoint.StaticPart.class) {
|
||||
String[] oldNames = this.argumentNames;
|
||||
this.argumentNames = new String[oldNames.length + 1];
|
||||
System.arraycopy(oldNames, 0, this.argumentNames, 0, i);
|
||||
this.argumentNames[i] = "THIS_JOIN_POINT";
|
||||
System.arraycopy(oldNames, i, this.argumentNames, i + 1, oldNames.length - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -241,7 +241,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
try {
|
||||
int algorithmicStep = STEP_JOIN_POINT_BINDING;
|
||||
while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
|
||||
while (this.numberOfRemainingUnboundArguments > 0 && algorithmicStep < STEP_FINISHED) {
|
||||
switch (algorithmicStep++) {
|
||||
case STEP_JOIN_POINT_BINDING -> {
|
||||
if (!maybeBindThisJoinPoint()) {
|
||||
@@ -373,7 +373,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
if (this.returningName != null) {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates. " +
|
||||
"Consider compiling with -parameters in order to make declared parameter names available.");
|
||||
}
|
||||
|
||||
// We're all set... find the unbound parameter, and bind it.
|
||||
@@ -485,8 +486,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
*/
|
||||
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
|
||||
" unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -535,8 +536,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
private void maybeBindReferencePointcutParameter() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at reference pointcut binding stage, with no way to determine between them");
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
|
||||
" unbound args at reference pointcut binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -741,7 +742,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
* Simple record to hold the extracted text from a pointcut body, together
|
||||
* with the number of tokens consumed in extracting it.
|
||||
*/
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown in response to an ambiguous binding being detected when
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -46,8 +46,8 @@ public class AspectEntry implements ParseState.Entry {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'"
|
||||
: "ref='" + this.ref + "'");
|
||||
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'" :
|
||||
"ref='" + this.ref + "'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -125,7 +125,7 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple
|
||||
return null;
|
||||
};
|
||||
|
||||
return doSubmit(task, executor, invocation.getMethod().getReturnType());
|
||||
return doSubmit(task, executor, userMethod.getReturnType());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -198,8 +198,8 @@ public abstract class ClassFilters {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof NegateClassFilter that
|
||||
&& this.original.equals(that.original)));
|
||||
return (this == other || (other instanceof NegateClassFilter that &&
|
||||
this.original.equals(that.original)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -378,8 +378,8 @@ public abstract class MethodMatchers {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof NegateMethodMatcher that
|
||||
&& this.original.equals(that.original)));
|
||||
return (this == other || (other instanceof NegateMethodMatcher that &&
|
||||
this.original.equals(that.original)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.aop.aspectj;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractAspectJAdvice}.
|
||||
*
|
||||
* @author Joshua Chen
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class AbstractAspectJAdviceTests {
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withoutJoinPointParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithNoJoinPoint");
|
||||
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withJoinPointAsFirstParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsFirstParameter");
|
||||
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withJoinPointAsLastParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsLastParameter");
|
||||
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2", "THIS_JOIN_POINT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withJoinPointAsMiddleParameter() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsMiddleParameter");
|
||||
assertThat(advice).satisfies(hasArgumentNames("arg1", "THIS_JOIN_POINT", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withProceedingJoinPoint() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithProceedingJoinPoint");
|
||||
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setArgumentNamesFromStringArray_withStaticPart() {
|
||||
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithStaticPart");
|
||||
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
|
||||
}
|
||||
|
||||
private Consumer<AbstractAspectJAdvice> hasArgumentNames(String... argumentNames) {
|
||||
return advice -> assertThat(advice).extracting("argumentNames")
|
||||
.asInstanceOf(InstanceOfAssertFactories.array(String[].class))
|
||||
.containsExactly(argumentNames);
|
||||
}
|
||||
|
||||
private AbstractAspectJAdvice getAspectJAdvice(final String methodName) {
|
||||
AbstractAspectJAdvice advice = new TestAspectJAdvice(getMethod(methodName),
|
||||
mock(AspectJExpressionPointcut.class), mock(AspectInstanceFactory.class));
|
||||
advice.setArgumentNamesFromStringArray("arg1", "arg2");
|
||||
return advice;
|
||||
}
|
||||
|
||||
private Method getMethod(final String methodName) {
|
||||
return Arrays.stream(Sample.class.getDeclaredMethods())
|
||||
.filter(method -> method.getName().equals(methodName)).findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class TestAspectJAdvice extends AbstractAspectJAdvice {
|
||||
|
||||
public TestAspectJAdvice(Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut,
|
||||
AspectInstanceFactory aspectInstanceFactory) {
|
||||
super(aspectJAdviceMethod, pointcut, aspectInstanceFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeforeAdvice() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAfterAdvice() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Sample {
|
||||
|
||||
void methodWithNoJoinPoint(String arg1, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithJoinPointAsFirstParameter(JoinPoint joinPoint, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithJoinPointAsLastParameter(String arg1, String arg2, JoinPoint joinPoint) {
|
||||
}
|
||||
|
||||
void methodWithJoinPointAsMiddleParameter(String arg1, JoinPoint joinPoint, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithProceedingJoinPoint(ProceedingJoinPoint joinPoint, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
void methodWithStaticPart(JoinPoint.StaticPart staticPart, String arg1, String arg2) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+31
-34
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -203,7 +203,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
itb.getSpouse();
|
||||
|
||||
assertThat(maaif.isMaterialized()).isTrue();
|
||||
|
||||
assertThat(imapa.getDeclaredPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getAge"), null)).isTrue();
|
||||
|
||||
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(0);
|
||||
@@ -301,7 +300,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void bindingWithMultipleArgsDifferentlyOrdered() {
|
||||
ManyValuedArgs target = new ManyValuedArgs();
|
||||
ManyValuedArgs mva = createProxy(target, ManyValuedArgs.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
|
||||
|
||||
String a = "a";
|
||||
int b = 12;
|
||||
@@ -320,7 +319,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
NotLockable notLockableTarget = new NotLockable();
|
||||
assertThat(notLockableTarget).isNotInstanceOf(Lockable.class);
|
||||
NotLockable notLockable1 = createProxy(notLockableTarget, NotLockable.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
assertThat(notLockable1).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = (Lockable) notLockable1;
|
||||
assertThat(lockable.locked()).isFalse();
|
||||
@@ -329,7 +328,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
NotLockable notLockable2Target = new NotLockable();
|
||||
NotLockable notLockable2 = createProxy(notLockable2Target, NotLockable.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
assertThat(notLockable2).isInstanceOf(Lockable.class);
|
||||
Lockable lockable2 = (Lockable) notLockable2;
|
||||
assertThat(lockable2.locked()).isFalse();
|
||||
@@ -343,20 +342,19 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void introductionAdvisorExcludedFromTargetImplementingInterface() {
|
||||
assertThat(AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class)).isEmpty();
|
||||
assertThat(AopUtils.findAdvisorsThatCanApply(getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
|
||||
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void introductionOnTargetImplementingInterface() {
|
||||
CannotBeUnlocked target = new CannotBeUnlocked();
|
||||
Lockable proxy = createProxy(target, CannotBeUnlocked.class,
|
||||
// Ensure that we exclude
|
||||
AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class));
|
||||
assertThat(proxy).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = proxy;
|
||||
assertThat(lockable.locked()).as("Already locked").isTrue();
|
||||
@@ -370,8 +368,8 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
ArrayList<Object> target = new ArrayList<>();
|
||||
List<?> proxy = createProxy(target, List.class,
|
||||
AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
List.class));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
List.class));
|
||||
assertThat(proxy).as("Type pattern must have excluded mixin").isNotInstanceOf(Lockable.class);
|
||||
}
|
||||
|
||||
@@ -379,7 +377,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void introductionBasedOnAnnotationMatch() { // gh-9980
|
||||
AnnotatedTarget target = new AnnotatedTargetImpl();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
|
||||
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
|
||||
Object proxy = createProxy(target, AnnotatedTarget.class, advisors);
|
||||
assertThat(proxy).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = (Lockable) proxy;
|
||||
@@ -393,9 +391,9 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
|
||||
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
|
||||
advisors.addAll(getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
|
||||
Modifiable modifiable = (Modifiable) createProxy(target, ITestBean.class, advisors);
|
||||
assertThat(modifiable).isInstanceOf(Modifiable.class);
|
||||
@@ -426,7 +424,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
UnsupportedOperationException expectedException = new UnsupportedOperationException();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
assertThat(advisors).as("One advice method was found").hasSize(1);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(itb::getAge);
|
||||
@@ -439,12 +437,12 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
RemoteException expectedException = new RemoteException();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
assertThat(advisors).as("One advice method was found").hasSize(1);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
assertThatExceptionOfType(UndeclaredThrowableException.class)
|
||||
.isThrownBy(itb::getAge)
|
||||
.withCause(expectedException);
|
||||
.isThrownBy(itb::getAge)
|
||||
.withCause(expectedException);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -452,7 +450,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
TwoAdviceAspect twoAdviceAspect = new TwoAdviceAspect();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(twoAdviceAspect, "someBean"));
|
||||
aspectInstanceFactory(twoAdviceAspect, "someBean"));
|
||||
assertThat(advisors).as("Two advice methods found").hasSize(2);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
itb.setName("");
|
||||
@@ -466,7 +464,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void afterAdviceTypes() throws Exception {
|
||||
InvocationTrackingAspect aspect = new InvocationTrackingAspect();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
|
||||
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
|
||||
Echo echo = createProxy(new Echo(), Echo.class, advisors);
|
||||
|
||||
assertThat(aspect.invocations).isEmpty();
|
||||
@@ -475,7 +473,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
aspect.invocations.clear();
|
||||
assertThatExceptionOfType(FileNotFoundException.class)
|
||||
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
|
||||
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
|
||||
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
|
||||
}
|
||||
|
||||
@@ -487,7 +485,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
assertThat(Modifier.isAbstract(aspect.getClass().getSuperclass().getModifiers())).isFalse();
|
||||
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspect, "incrementingAspect"));
|
||||
|
||||
ITestBean proxy = createProxy(new TestBean("Jane", 42), ITestBean.class, advisors);
|
||||
assertThat(proxy.getAge()).isEqualTo(86); // (42 + 1) * 2
|
||||
}
|
||||
@@ -812,20 +809,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
invocations.add("before");
|
||||
}
|
||||
|
||||
@AfterReturning("echo()")
|
||||
void afterReturning() {
|
||||
invocations.add("after returning");
|
||||
}
|
||||
|
||||
@AfterThrowing("echo()")
|
||||
void afterThrowing() {
|
||||
invocations.add("after throwing");
|
||||
}
|
||||
|
||||
@After("echo()")
|
||||
void after() {
|
||||
invocations.add("after");
|
||||
}
|
||||
|
||||
@AfterReturning(pointcut = "this(target) && execution(* echo(*))", returning = "returnValue")
|
||||
void afterReturning(JoinPoint joinPoint, Echo target, Object returnValue) {
|
||||
invocations.add("after returning");
|
||||
}
|
||||
|
||||
@AfterThrowing(pointcut = "this(target) && execution(* echo(*))", throwing = "exception")
|
||||
void afterThrowing(JoinPoint joinPoint, Echo target, Throwable exception) {
|
||||
invocations.add("after throwing");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -967,7 +964,7 @@ abstract class AbstractMakeModifiable {
|
||||
class MakeITestBeanModifiable extends AbstractMakeModifiable {
|
||||
|
||||
@DeclareParents(value = "org.springframework.beans.testfixture.beans.ITestBean+",
|
||||
defaultImpl=ModifiableImpl.class)
|
||||
defaultImpl = ModifiableImpl.class)
|
||||
static MutableModifiable mixin;
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -99,7 +99,7 @@ abstract class AbstractProxyExceptionHandlingTests {
|
||||
assertThat(throwableSeenByInterceptor).isSameAs(undeclaredCheckedException);
|
||||
assertThat(throwableSeenByCaller)
|
||||
.isInstanceOf(UndeclaredThrowableException.class)
|
||||
.hasCauseReference(undeclaredCheckedException);
|
||||
.cause().isSameAs(undeclaredCheckedException);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -147,7 +147,7 @@ abstract class AbstractProxyExceptionHandlingTests {
|
||||
invokeProxy();
|
||||
assertThat(throwableSeenByCaller)
|
||||
.isInstanceOf(UndeclaredThrowableException.class)
|
||||
.hasCauseReference(undeclaredCheckedException);
|
||||
.cause().isSameAs(undeclaredCheckedException);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.aop.interceptor;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link AsyncExecutionInterceptor}.
|
||||
*
|
||||
* @author Bao Ngo
|
||||
* @since 7.0
|
||||
*/
|
||||
class AsyncExecutionInterceptorTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void invokeOnInterfaceWithGeneric() throws Throwable {
|
||||
AsyncExecutionInterceptor interceptor = spy(new AsyncExecutionInterceptor(null));
|
||||
FutureRunner impl = new FutureRunner();
|
||||
MethodInvocation mi = mock();
|
||||
given(mi.getThis()).willReturn(impl);
|
||||
given(mi.getMethod()).willReturn(GenericRunner.class.getMethod("run"));
|
||||
|
||||
interceptor.invoke(mi);
|
||||
ArgumentCaptor<Class<?>> classArgumentCaptor = ArgumentCaptor.forClass(Class.class);
|
||||
verify(interceptor).doSubmit(any(Callable.class), any(AsyncTaskExecutor.class), classArgumentCaptor.capture());
|
||||
assertThat(classArgumentCaptor.getValue()).isEqualTo(Future.class);
|
||||
}
|
||||
|
||||
|
||||
interface GenericRunner<O> {
|
||||
|
||||
O run();
|
||||
}
|
||||
|
||||
static class FutureRunner implements GenericRunner<Future<Void>> {
|
||||
@Override
|
||||
public Future<Void> run() {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -658,6 +658,14 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
|
||||
value = list.get(index);
|
||||
}
|
||||
else if (value instanceof Map map) {
|
||||
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
|
||||
// IMPORTANT: Do not pass full property name in here - property editors
|
||||
// must not kick in for map keys but rather only for map values.
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
|
||||
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
|
||||
value = map.get(convertedMapKey);
|
||||
}
|
||||
else if (value instanceof Iterable iterable) {
|
||||
// Apply index to Iterator in case of a Set/Collection/Iterable.
|
||||
int index = Integer.parseInt(key);
|
||||
@@ -685,14 +693,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
currIndex + ", accessed using property path '" + propertyName + "'");
|
||||
}
|
||||
}
|
||||
else if (value instanceof Map map) {
|
||||
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
|
||||
// IMPORTANT: Do not pass full property name in here - property editors
|
||||
// must not kick in for map keys but rather only for map values.
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
|
||||
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
|
||||
value = map.get(convertedMapKey);
|
||||
}
|
||||
else {
|
||||
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||
"Property referenced in indexed property path '" + propertyName +
|
||||
@@ -904,16 +904,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
private Object newValue(Class<?> type, @Nullable TypeDescriptor desc, String name) {
|
||||
try {
|
||||
if (type.isArray()) {
|
||||
Class<?> componentType = type.componentType();
|
||||
// TODO - only handles 2-dimensional arrays
|
||||
if (componentType.isArray()) {
|
||||
Object array = Array.newInstance(componentType, 1);
|
||||
Array.set(array, 0, Array.newInstance(componentType.componentType(), 0));
|
||||
return array;
|
||||
}
|
||||
else {
|
||||
return Array.newInstance(componentType, 0);
|
||||
}
|
||||
return createArray(type);
|
||||
}
|
||||
else if (Collection.class.isAssignableFrom(type)) {
|
||||
TypeDescriptor elementDesc = (desc != null ? desc.getElementTypeDescriptor() : null);
|
||||
@@ -937,6 +928,24 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the array for the given array type.
|
||||
* @param arrayType the desired type of the target array
|
||||
* @return a new array instance
|
||||
*/
|
||||
private static Object createArray(Class<?> arrayType) {
|
||||
Assert.notNull(arrayType, "Array type must not be null");
|
||||
Class<?> componentType = arrayType.componentType();
|
||||
if (componentType.isArray()) {
|
||||
Object array = Array.newInstance(componentType, 1);
|
||||
Array.set(array, 0, createArray(componentType));
|
||||
return array;
|
||||
}
|
||||
else {
|
||||
return Array.newInstance(componentType, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given property name into the corresponding property name tokens.
|
||||
* @param propertyName the property name to parse
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -45,4 +45,18 @@ public interface PropertyEditorRegistrar {
|
||||
*/
|
||||
void registerCustomEditors(PropertyEditorRegistry registry);
|
||||
|
||||
/**
|
||||
* Indicate whether this registrar exclusively overrides default editors
|
||||
* rather than registering custom editors, intended to be applied lazily.
|
||||
* <p>This has an impact on registrar handling in a bean factory: see
|
||||
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#addPropertyEditorRegistrar}.
|
||||
* @since 6.2.3
|
||||
* @see PropertyEditorRegistry#registerCustomEditor
|
||||
* @see PropertyEditorRegistrySupport#overrideDefaultEditor
|
||||
* @see PropertyEditorRegistrySupport#setDefaultEditorRegistrar
|
||||
*/
|
||||
default boolean overridesDefaultEditors() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -99,6 +99,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
|
||||
|
||||
private boolean configValueEditorsActive = false;
|
||||
|
||||
@Nullable
|
||||
private PropertyEditorRegistrar defaultEditorRegistrar;
|
||||
|
||||
@Nullable
|
||||
private Map<Class<?>, PropertyEditor> defaultEditors;
|
||||
|
||||
@@ -155,6 +158,19 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
|
||||
this.configValueEditorsActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a registrar for default editors, as a lazy way of overriding default editors.
|
||||
* <p>This is expected to be a collaborator with {@link PropertyEditorRegistrySupport},
|
||||
* downcasting the given {@link PropertyEditorRegistry} accordingly and calling
|
||||
* {@link #overrideDefaultEditor} for registering additional default editors on it.
|
||||
* @param registrar the registrar to call when default editors are actually needed
|
||||
* @since 6.2.3
|
||||
* @see #overrideDefaultEditor
|
||||
*/
|
||||
public void setDefaultEditorRegistrar(PropertyEditorRegistrar registrar) {
|
||||
this.defaultEditorRegistrar = registrar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default editor for the specified type with the given property editor.
|
||||
* <p>Note that this is different from registering a custom editor in that the editor
|
||||
@@ -184,6 +200,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
|
||||
if (!this.defaultEditorsActive) {
|
||||
return null;
|
||||
}
|
||||
if (this.overriddenDefaultEditors == null && this.defaultEditorRegistrar != null) {
|
||||
this.defaultEditorRegistrar.registerCustomEditors(this);
|
||||
}
|
||||
if (this.overriddenDefaultEditors != null) {
|
||||
PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
|
||||
if (editor != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -39,10 +39,14 @@ import org.springframework.util.StringUtils;
|
||||
* (which the methods defined on the ListableBeanFactory interface don't,
|
||||
* in contrast to the methods defined on the BeanFactory interface).
|
||||
*
|
||||
* <p><b>NOTE:</b> It is generally preferable to use {@link ObjectProvider#stream()}
|
||||
* via {@link BeanFactory#getBeanProvider} instead of this utility class.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @since 04.07.2003
|
||||
* @see BeanFactory#getBeanProvider
|
||||
*/
|
||||
public abstract class BeanFactoryUtils {
|
||||
|
||||
@@ -308,7 +312,7 @@ public abstract class BeanFactoryUtils {
|
||||
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
|
||||
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
|
||||
* @param lbf the bean factory
|
||||
* @param type type of bean to match
|
||||
* @param type the type of bean to match
|
||||
* @return the Map of matching bean instances, or an empty Map if none
|
||||
* @throws BeansException if a bean could not be created
|
||||
* @see ListableBeanFactory#getBeansOfType(Class)
|
||||
@@ -347,7 +351,7 @@ public abstract class BeanFactoryUtils {
|
||||
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
|
||||
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
|
||||
* @param lbf the bean factory
|
||||
* @param type type of bean to match
|
||||
* @param type the type of bean to match
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
|
||||
@@ -395,7 +399,7 @@ public abstract class BeanFactoryUtils {
|
||||
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
|
||||
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
|
||||
* @param lbf the bean factory
|
||||
* @param type type of bean to match
|
||||
* @param type the type of bean to match
|
||||
* @return the matching bean instance
|
||||
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
|
||||
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
|
||||
@@ -425,7 +429,7 @@ public abstract class BeanFactoryUtils {
|
||||
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
|
||||
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
|
||||
* @param lbf the bean factory
|
||||
* @param type type of bean to match
|
||||
* @param type the type of bean to match
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
|
||||
@@ -457,7 +461,7 @@ public abstract class BeanFactoryUtils {
|
||||
* <p>This version of {@code beanOfType} automatically includes
|
||||
* prototypes and FactoryBeans.
|
||||
* @param lbf the bean factory
|
||||
* @param type type of bean to match
|
||||
* @param type the type of bean to match
|
||||
* @return the matching bean instance
|
||||
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
|
||||
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
|
||||
@@ -481,7 +485,7 @@ public abstract class BeanFactoryUtils {
|
||||
* only raw FactoryBeans will be checked (which doesn't require initialization
|
||||
* of each FactoryBean).
|
||||
* @param lbf the bean factory
|
||||
* @param type type of bean to match
|
||||
* @param type the type of bean to match
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
|
||||
@@ -529,7 +533,7 @@ public abstract class BeanFactoryUtils {
|
||||
|
||||
/**
|
||||
* Extract a unique bean for the given type from the given Map of matching beans.
|
||||
* @param type type of bean to match
|
||||
* @param type the type of bean to match
|
||||
* @param matchingBeans all matching beans found
|
||||
* @return the unique bean instance
|
||||
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.beans.factory;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -45,6 +46,21 @@ import org.springframework.lang.Nullable;
|
||||
* Alternatively, you may implement the specific methods that your callers expect,
|
||||
* for example, just {@link #getObject()} or {@link #getIfAvailable()}.
|
||||
*
|
||||
* <p>Note that {@link #getObject()} never returns {@code null} - it will throw a
|
||||
* {@link NoSuchBeanDefinitionException} instead -, whereas {@link #getIfAvailable()}
|
||||
* will return {@code null} if no matching bean is present at all. However, both
|
||||
* methods will throw a {@link NoUniqueBeanDefinitionException} if more than one
|
||||
* matching bean is found without a clear unique winner (see below). Last but not
|
||||
* least, {@link #getIfUnique()} will return {@code null} both when no matching bean
|
||||
* is found and when more than one matching bean is found without a unique winner.
|
||||
*
|
||||
* <p>Uniqueness is generally up to the container's candidate resolution algorithm
|
||||
* but always honors the "primary" flag (with only one of the candidate beans marked
|
||||
* as primary) and the "fallback" flag (with only one of the candidate beans not
|
||||
* marked as fallback). The default-candidate flag is consistently taken into
|
||||
* account as well, even for non-annotation-based injection points, with a single
|
||||
* default candidate winning in case of no clear primary/fallback indication.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3
|
||||
* @param <T> the object type
|
||||
@@ -53,6 +69,18 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
|
||||
/**
|
||||
* A predicate for unfiltered type matches, including non-default candidates
|
||||
* but still excluding non-autowire candidates when used on injection points.
|
||||
* @since 6.2.3
|
||||
* @see #stream(Predicate)
|
||||
* @see #orderedStream(Predicate)
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
|
||||
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
Predicate<Class<?>> UNFILTERED = (clazz -> true);
|
||||
|
||||
|
||||
@Override
|
||||
default T getObject() throws BeansException {
|
||||
Iterator<T> it = iterator();
|
||||
@@ -175,7 +203,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
* if unique (not called otherwise)
|
||||
* @throws BeansException in case of creation errors
|
||||
* @since 5.0
|
||||
* @see #getIfAvailable()
|
||||
* @see #getIfUnique()
|
||||
*/
|
||||
default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
|
||||
T dependency = getIfUnique();
|
||||
@@ -198,6 +226,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
/**
|
||||
* Return a sequential {@link Stream} over all matching object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* <p>Note: The result may be filtered by default according to qualifiers on the
|
||||
* injection point versus target beans and the general autowire candidate status
|
||||
* of matching beans. For custom filtering against type-matching candidates, use
|
||||
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
|
||||
* @since 5.1
|
||||
* @see #iterator()
|
||||
* @see #orderedStream()
|
||||
@@ -219,6 +251,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
* {@link #stream()} method. You may override this to apply an
|
||||
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
|
||||
* if necessary.
|
||||
* <p>Note: The result may be filtered by default according to qualifiers on the
|
||||
* injection point versus target beans and the general autowire candidate status
|
||||
* of matching beans. For custom filtering against type-matching candidates, use
|
||||
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
|
||||
* @since 5.1
|
||||
* @see #stream()
|
||||
* @see org.springframework.core.OrderComparator
|
||||
@@ -227,4 +263,70 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
return stream().sorted(OrderComparator.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom-filtered {@link Stream} over all matching object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* @param customFilter a custom type filter for selecting beans among the raw
|
||||
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
|
||||
* any default filtering)
|
||||
* @since 6.2.3
|
||||
* @see #stream()
|
||||
* @see #orderedStream(Predicate)
|
||||
*/
|
||||
default Stream<T> stream(Predicate<Class<?>> customFilter) {
|
||||
return stream(customFilter, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom-filtered {@link Stream} over all matching object instances,
|
||||
* pre-ordered according to the factory's common order comparator.
|
||||
* @param customFilter a custom type filter for selecting beans among the raw
|
||||
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
|
||||
* any default filtering)
|
||||
* @since 6.2.3
|
||||
* @see #orderedStream()
|
||||
* @see #stream(Predicate)
|
||||
*/
|
||||
default Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
|
||||
return orderedStream(customFilter, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom-filtered {@link Stream} over all matching object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* @param customFilter a custom type filter for selecting beans among the raw
|
||||
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
|
||||
* any default filtering)
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @since 6.2.5
|
||||
* @see #stream(Predicate)
|
||||
* @see #orderedStream(Predicate, boolean)
|
||||
*/
|
||||
default Stream<T> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
if (!includeNonSingletons) {
|
||||
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
|
||||
}
|
||||
return stream().filter(obj -> customFilter.test(obj.getClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom-filtered {@link Stream} over all matching object instances,
|
||||
* pre-ordered according to the factory's common order comparator.
|
||||
* @param customFilter a custom type filter for selecting beans among the raw
|
||||
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
|
||||
* any default filtering)
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @since 6.2.5
|
||||
* @see #orderedStream()
|
||||
* @see #stream(Predicate)
|
||||
*/
|
||||
default Stream<T> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
if (!includeNonSingletons) {
|
||||
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
|
||||
}
|
||||
return orderedStream().filter(obj -> customFilter.test(obj.getClass()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -95,7 +95,7 @@ public abstract class BeanFactoryAnnotationUtils {
|
||||
// Full qualifier matching supported.
|
||||
return qualifiedBeanOfType(lbf, beanType, qualifier);
|
||||
}
|
||||
else if (beanFactory.containsBean(qualifier)) {
|
||||
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
|
||||
// Fallback: target bean at least found by bean name.
|
||||
return beanFactory.getBean(qualifier, beanType);
|
||||
}
|
||||
@@ -110,16 +110,16 @@ public abstract class BeanFactoryAnnotationUtils {
|
||||
/**
|
||||
* Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier
|
||||
* (for example, {@code <qualifier>} or {@code @Qualifier}) matching the given qualifier).
|
||||
* @param bf the factory to get the target bean from
|
||||
* @param beanFactory the factory to get the target bean from
|
||||
* @param beanType the type of bean to retrieve
|
||||
* @param qualifier the qualifier for selecting between multiple bean matches
|
||||
* @return the matching bean of type {@code T} (never {@code null})
|
||||
*/
|
||||
private static <T> T qualifiedBeanOfType(ListableBeanFactory bf, Class<T> beanType, String qualifier) {
|
||||
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType);
|
||||
private static <T> T qualifiedBeanOfType(ListableBeanFactory beanFactory, Class<T> beanType, String qualifier) {
|
||||
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, beanType);
|
||||
String matchingBean = null;
|
||||
for (String beanName : candidateBeans) {
|
||||
if (isQualifierMatch(qualifier::equals, beanName, bf)) {
|
||||
if (isQualifierMatch(qualifier::equals, beanName, beanFactory)) {
|
||||
if (matchingBean != null) {
|
||||
throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName);
|
||||
}
|
||||
@@ -127,11 +127,11 @@ public abstract class BeanFactoryAnnotationUtils {
|
||||
}
|
||||
}
|
||||
if (matchingBean != null) {
|
||||
return bf.getBean(matchingBean, beanType);
|
||||
return beanFactory.getBean(matchingBean, beanType);
|
||||
}
|
||||
else if (bf.containsBean(qualifier)) {
|
||||
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
|
||||
// Fallback: target bean at least found by bean name - probably a manually registered singleton.
|
||||
return bf.getBean(qualifier, beanType);
|
||||
return beanFactory.getBean(qualifier, beanType);
|
||||
}
|
||||
else {
|
||||
throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -212,9 +212,9 @@ public final class AotServices<T> implements Iterable<T> {
|
||||
}
|
||||
|
||||
private <T> Map<String, T> loadBeans(Class<T> type) {
|
||||
return (this.beanFactory != null) ? BeanFactoryUtils
|
||||
.beansOfTypeIncludingAncestors(this.beanFactory, type, true, false)
|
||||
: Collections.emptyMap();
|
||||
return (this.beanFactory != null ?
|
||||
BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type, true, false) :
|
||||
Collections.emptyMap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -69,8 +69,8 @@ class BeanDefinitionMethodGeneratorFactory {
|
||||
this.excludeFilters = loader.load(BeanRegistrationExcludeFilter.class);
|
||||
for (BeanRegistrationExcludeFilter excludeFilter : this.excludeFilters) {
|
||||
if (this.excludeFilters.getSource(excludeFilter) == Source.BEAN_FACTORY) {
|
||||
Assert.state(excludeFilter instanceof BeanRegistrationAotProcessor
|
||||
|| excludeFilter instanceof BeanFactoryInitializationAotProcessor,
|
||||
Assert.state(excludeFilter instanceof BeanRegistrationAotProcessor ||
|
||||
excludeFilter instanceof BeanFactoryInitializationAotProcessor,
|
||||
() -> "BeanRegistrationExcludeFilter bean of type %s must also implement an AOT processor interface"
|
||||
.formatted(excludeFilter.getClass().getName()));
|
||||
}
|
||||
|
||||
+4
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -234,8 +234,7 @@ public class InstanceSupplierCodeGenerator {
|
||||
|
||||
CodeBlock arguments = hasArguments ?
|
||||
new AutowiredArgumentsCodeGenerator(actualType, constructor)
|
||||
.generateCode(constructor.getParameterTypes(), (onInnerClass ? 1 : 0))
|
||||
: NO_ARGS;
|
||||
.generateCode(constructor.getParameterTypes(), (onInnerClass ? 1 : 0)) : NO_ARGS;
|
||||
|
||||
CodeBlock newInstance = generateNewInstanceCodeForConstructor(actualType, arguments);
|
||||
code.add(generateWithGeneratorCode(hasArguments, newInstance));
|
||||
@@ -325,8 +324,7 @@ public class InstanceSupplierCodeGenerator {
|
||||
boolean hasArguments = factoryMethod.getParameterCount() > 0;
|
||||
CodeBlock arguments = hasArguments ?
|
||||
new AutowiredArgumentsCodeGenerator(ClassUtils.getUserClass(targetClass), factoryMethod)
|
||||
.generateCode(factoryMethod.getParameterTypes())
|
||||
: NO_ARGS;
|
||||
.generateCode(factoryMethod.getParameterTypes()) : NO_ARGS;
|
||||
|
||||
CodeBlock newInstance = generateNewInstanceCodeForMethod(
|
||||
factoryBeanName, ClassUtils.getUserClass(targetClass), factoryMethodName, arguments);
|
||||
@@ -443,6 +441,7 @@ public class InstanceSupplierCodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
record ConstructorDescriptor(String beanName, Constructor<?> constructor, Class<?> publicType) {
|
||||
|
||||
Class<?> actualType() {
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -183,7 +183,11 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
|
||||
* on the given registry, fresh for each bean creation attempt. This avoids
|
||||
* the need for synchronization on custom editors; hence, it is generally
|
||||
* preferable to use this method instead of {@link #registerCustomEditor}.
|
||||
* <p>If the given registrar implements
|
||||
* {@link PropertyEditorRegistrar#overridesDefaultEditors()} to return {@code true},
|
||||
* it will be applied lazily (only when default editors are actually needed).
|
||||
* @param registrar the PropertyEditorRegistrar to register
|
||||
* @see PropertyEditorRegistrar#overridesDefaultEditors()
|
||||
*/
|
||||
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
|
||||
|
||||
|
||||
+6
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -991,7 +991,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
*/
|
||||
@Nullable
|
||||
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
|
||||
this.singletonLock.lock();
|
||||
boolean locked = this.singletonLock.tryLock();
|
||||
if (!locked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
|
||||
if (bw != null) {
|
||||
|
||||
+61
-24
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -137,6 +137,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
@Nullable
|
||||
private ConversionService conversionService;
|
||||
|
||||
/** Default PropertyEditorRegistrars to apply to the beans of this factory. */
|
||||
private final Set<PropertyEditorRegistrar> defaultEditorRegistrars = new LinkedHashSet<>(4);
|
||||
|
||||
/** Custom PropertyEditorRegistrars to apply to the beans of this factory. */
|
||||
private final Set<PropertyEditorRegistrar> propertyEditorRegistrars = new LinkedHashSet<>(4);
|
||||
|
||||
@@ -883,7 +886,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
@Override
|
||||
public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) {
|
||||
Assert.notNull(registrar, "PropertyEditorRegistrar must not be null");
|
||||
this.propertyEditorRegistrars.add(registrar);
|
||||
if (registrar.overridesDefaultEditors()) {
|
||||
this.defaultEditorRegistrars.add(registrar);
|
||||
}
|
||||
else {
|
||||
this.propertyEditorRegistrars.add(registrar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1114,6 +1122,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
setBeanExpressionResolver(otherFactory.getBeanExpressionResolver());
|
||||
setConversionService(otherFactory.getConversionService());
|
||||
if (otherFactory instanceof AbstractBeanFactory otherAbstractFactory) {
|
||||
this.defaultEditorRegistrars.addAll(otherAbstractFactory.defaultEditorRegistrars);
|
||||
this.propertyEditorRegistrars.addAll(otherAbstractFactory.propertyEditorRegistrars);
|
||||
this.customEditors.putAll(otherAbstractFactory.customEditors);
|
||||
this.typeConverter = otherAbstractFactory.typeConverter;
|
||||
@@ -1313,36 +1322,48 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
protected void registerCustomEditors(PropertyEditorRegistry registry) {
|
||||
if (registry instanceof PropertyEditorRegistrySupport registrySupport) {
|
||||
registrySupport.useConfigValueEditors();
|
||||
}
|
||||
if (!this.propertyEditorRegistrars.isEmpty()) {
|
||||
for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
|
||||
try {
|
||||
registrar.registerCustomEditors(registry);
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
Throwable rootCause = ex.getMostSpecificCause();
|
||||
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
|
||||
String bceBeanName = bce.getBeanName();
|
||||
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
|
||||
"] failed because it tried to obtain currently created bean '" +
|
||||
ex.getBeanName() + "': " + ex.getMessage());
|
||||
}
|
||||
onSuppressedException(ex);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
if (!this.defaultEditorRegistrars.isEmpty()) {
|
||||
// Optimization: lazy overriding of default editors only when needed
|
||||
registrySupport.setDefaultEditorRegistrar(new BeanFactoryDefaultEditorRegistrar());
|
||||
}
|
||||
}
|
||||
else if (!this.defaultEditorRegistrars.isEmpty()) {
|
||||
// Fallback: proactive overriding of default editors
|
||||
applyEditorRegistrars(registry, this.defaultEditorRegistrars);
|
||||
}
|
||||
|
||||
if (!this.propertyEditorRegistrars.isEmpty()) {
|
||||
applyEditorRegistrars(registry, this.propertyEditorRegistrars);
|
||||
}
|
||||
if (!this.customEditors.isEmpty()) {
|
||||
this.customEditors.forEach((requiredType, editorClass) ->
|
||||
registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyEditorRegistrars(PropertyEditorRegistry registry, Set<PropertyEditorRegistrar> registrars) {
|
||||
for (PropertyEditorRegistrar registrar : registrars) {
|
||||
try {
|
||||
registrar.registerCustomEditors(registry);
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
Throwable rootCause = ex.getMostSpecificCause();
|
||||
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
|
||||
String bceBeanName = bce.getBeanName();
|
||||
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
|
||||
"] failed because it tried to obtain currently created bean '" +
|
||||
ex.getBeanName() + "': " + ex.getMessage());
|
||||
}
|
||||
onSuppressedException(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a merged RootBeanDefinition, traversing the parent bean definition
|
||||
@@ -2095,4 +2116,20 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
final List<MergedBeanDefinitionPostProcessor> mergedDefinition = new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link PropertyEditorRegistrar} that delegates to the bean factory's
|
||||
* default registrars, adding exception handling for circular reference
|
||||
* scenarios where an editor tries to refer back to the currently created bean.
|
||||
*
|
||||
* @since 6.2.3
|
||||
*/
|
||||
class BeanFactoryDefaultEditorRegistrar implements PropertyEditorRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerCustomEditors(PropertyEditorRegistry registry) {
|
||||
applyEditorRegistrars(registry, defaultEditorRegistrars);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+41
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,7 +33,10 @@ import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -259,6 +262,43 @@ abstract class AutowireUtils {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the autowire-candidate status for the specified bean.
|
||||
* @param beanFactory the bean factory
|
||||
* @param beanName the name of the bean to check
|
||||
* @return whether the specified bean qualifies as an autowire candidate
|
||||
* @since 6.2.3
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
|
||||
*/
|
||||
public static boolean isAutowireCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
|
||||
try {
|
||||
return beanFactory.getMergedBeanDefinition(beanName).isAutowireCandidate();
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// A manually registered singleton instance not backed by a BeanDefinition.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the default-candidate status for the specified bean.
|
||||
* @param beanFactory the bean factory
|
||||
* @param beanName the name of the bean to check
|
||||
* @return whether the specified bean qualifies as a default candidate
|
||||
* @since 6.2.4
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
public static boolean isDefaultCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
|
||||
try {
|
||||
BeanDefinition mbd = beanFactory.getMergedBeanDefinition(beanName);
|
||||
return (!(mbd instanceof AbstractBeanDefinition abd) || abd.isDefaultCandidate());
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// A manually registered singleton instance not backed by a BeanDefinition.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reflective {@link InvocationHandler} for lazy access to the current target object.
|
||||
|
||||
+101
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -177,7 +177,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
/** Map from bean name to merged BeanDefinitionHolder. */
|
||||
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
|
||||
|
||||
// Set of bean definition names with a primary marker. */
|
||||
/** Set of bean definition names with a primary marker. */
|
||||
private final Set<String> primaryBeanNames = ConcurrentHashMap.newKeySet(16);
|
||||
|
||||
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
|
||||
@@ -487,14 +487,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return Arrays.stream(getBeanNamesForTypedStream(requiredType, allowEagerInit))
|
||||
return Arrays.stream(beanNamesForStream(requiredType, true, allowEagerInit))
|
||||
.map(name -> (T) getBean(name))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> orderedStream() {
|
||||
String[] beanNames = getBeanNamesForTypedStream(requiredType, allowEagerInit);
|
||||
String[] beanNames = beanNamesForStream(requiredType, true, allowEagerInit);
|
||||
if (beanNames.length == 0) {
|
||||
return Stream.empty();
|
||||
}
|
||||
@@ -508,6 +508,32 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
Stream<T> stream = matchingBeans.values().stream();
|
||||
return stream.sorted(adaptOrderComparator(matchingBeans));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
return Arrays.stream(beanNamesForStream(requiredType, includeNonSingletons, allowEagerInit))
|
||||
.filter(name -> customFilter.test(getType(name)))
|
||||
.map(name -> (T) getBean(name))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
String[] beanNames = beanNamesForStream(requiredType, includeNonSingletons, allowEagerInit);
|
||||
if (beanNames.length == 0) {
|
||||
return Stream.empty();
|
||||
}
|
||||
Map<String, T> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
|
||||
for (String beanName : beanNames) {
|
||||
if (customFilter.test(getType(beanName))) {
|
||||
Object beanInstance = getBean(beanName);
|
||||
if (!(beanInstance instanceof NullBean)) {
|
||||
matchingBeans.put(beanName, (T) beanInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -533,8 +559,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getBeanNamesForTypedStream(ResolvableType requiredType, boolean allowEagerInit) {
|
||||
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, allowEagerInit);
|
||||
private String[] beanNamesForStream(ResolvableType requiredType, boolean includeNonSingletons, boolean allowEagerInit) {
|
||||
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, includeNonSingletons, allowEagerInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1089,8 +1115,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
"without bootstrap executor configured - falling back to mainline initialization");
|
||||
}
|
||||
}
|
||||
|
||||
if (!mbd.isLazyInit()) {
|
||||
instantiateSingleton(beanName);
|
||||
try {
|
||||
instantiateSingleton(beanName);
|
||||
}
|
||||
catch (BeanCurrentlyInCreationException ex) {
|
||||
logger.info("Bean '" + beanName + "' marked for pre-instantiation (not lazy-init) " +
|
||||
"but currently initialized by other thread - skipping it in mainline thread");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1464,6 +1497,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
if (candidateName == null) {
|
||||
candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
|
||||
}
|
||||
if (candidateName == null) {
|
||||
candidateName = determineDefaultCandidate(candidates);
|
||||
}
|
||||
if (candidateName != null) {
|
||||
Object beanInstance = candidates.get(candidateName);
|
||||
if (beanInstance == null) {
|
||||
@@ -1885,8 +1921,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
candidates.put(candidateName, beanInstance);
|
||||
}
|
||||
}
|
||||
else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor streamDescriptor &&
|
||||
streamDescriptor.isOrdered())) {
|
||||
else if (containsSingleton(candidateName) ||
|
||||
(descriptor instanceof StreamDependencyDescriptor streamDescriptor && streamDescriptor.isOrdered())) {
|
||||
Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
|
||||
candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance));
|
||||
}
|
||||
@@ -1934,7 +1970,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
if (priorityCandidate != null) {
|
||||
return priorityCandidate;
|
||||
}
|
||||
// Step 4: pick directly registered dependency
|
||||
// Step 4: pick unique default-candidate
|
||||
String defaultCandidate = determineDefaultCandidate(candidates);
|
||||
if (defaultCandidate != null) {
|
||||
return defaultCandidate;
|
||||
}
|
||||
// Step 5: pick directly registered dependency
|
||||
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
|
||||
String candidateName = entry.getKey();
|
||||
Object beanInstance = entry.getValue();
|
||||
@@ -2095,6 +2136,28 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a unique "default-candidate" among remaining non-default candidates.
|
||||
* @param candidates a Map of candidate names and candidate instances
|
||||
* (or candidate classes if not created yet) that match the required type
|
||||
* @return the name of the default candidate, or {@code null} if none found
|
||||
* @since 6.2.4
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
@Nullable
|
||||
private String determineDefaultCandidate(Map<String, Object> candidates) {
|
||||
String defaultBeanName = null;
|
||||
for (String candidateBeanName : candidates.keySet()) {
|
||||
if (AutowireUtils.isDefaultCandidate(this, candidateBeanName)) {
|
||||
if (defaultBeanName != null) {
|
||||
return null;
|
||||
}
|
||||
defaultBeanName = candidateBeanName;
|
||||
}
|
||||
}
|
||||
return defaultBeanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given candidate name matches the bean name or the aliases
|
||||
* stored in this bean definition.
|
||||
@@ -2479,6 +2542,34 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
|
||||
return (result instanceof Stream stream ? stream : Stream.of(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Object> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
return Arrays.stream(beanNamesForStream(this.descriptor.getResolvableType(), includeNonSingletons, true))
|
||||
.filter(name -> AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, name))
|
||||
.filter(name -> customFilter.test(getType(name)))
|
||||
.map(name -> getBean(name))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Object> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
String[] beanNames = beanNamesForStream(this.descriptor.getResolvableType(), includeNonSingletons, true);
|
||||
if (beanNames.length == 0) {
|
||||
return Stream.empty();
|
||||
}
|
||||
Map<String, Object> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
|
||||
for (String beanName : beanNames) {
|
||||
if (AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, beanName) &&
|
||||
customFilter.test(getType(beanName))) {
|
||||
Object beanInstance = getBean(beanName);
|
||||
if (!(beanInstance instanceof NullBean)) {
|
||||
matchingBeans.put(beanName, beanInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+82
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -24,6 +24,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
@@ -100,6 +101,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
/** Names of beans currently excluded from in creation checks. */
|
||||
private final Set<String> inCreationCheckExclusions = ConcurrentHashMap.newKeySet(16);
|
||||
|
||||
/** Specific lock for lenient creation tracking. */
|
||||
private final Lock lenientCreationLock = new ReentrantLock();
|
||||
|
||||
/** Specific lock condition for lenient creation tracking. */
|
||||
private final Condition lenientCreationFinished = this.lenientCreationLock.newCondition();
|
||||
|
||||
/** Names of beans that are currently in lenient creation. */
|
||||
private final Set<String> singletonsInLenientCreation = new HashSet<>();
|
||||
|
||||
/** Flag that indicates whether we're currently within destroySingletons. */
|
||||
private volatile boolean singletonsCurrentlyInDestruction = false;
|
||||
|
||||
@@ -257,6 +267,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
Thread.currentThread().getName() + "\" while other thread holds " +
|
||||
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
|
||||
}
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
this.singletonsInLenientCreation.add(beanName);
|
||||
}
|
||||
finally {
|
||||
this.lenientCreationLock.unlock();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No specific locking indication (outside a coordinated bootstrap) and
|
||||
@@ -279,15 +296,57 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
|
||||
try {
|
||||
beforeSingletonCreation(beanName);
|
||||
}
|
||||
catch (BeanCurrentlyInCreationException ex) {
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
while ((singletonObject = this.singletonObjects.get(beanName)) == null) {
|
||||
if (!this.singletonsInLenientCreation.contains(beanName)) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
this.lenientCreationFinished.await();
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.lenientCreationLock.unlock();
|
||||
}
|
||||
if (singletonObject != null) {
|
||||
return singletonObject;
|
||||
}
|
||||
if (locked) {
|
||||
throw ex;
|
||||
}
|
||||
// Try late locking for waiting on specific bean to be finished.
|
||||
this.singletonLock.lock();
|
||||
locked = true;
|
||||
// Lock-created singleton object should have appeared in the meantime.
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject != null) {
|
||||
return singletonObject;
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
}
|
||||
|
||||
boolean newSingleton = false;
|
||||
boolean recordSuppressedExceptions = (locked && this.suppressedExceptions == null);
|
||||
if (recordSuppressedExceptions) {
|
||||
this.suppressedExceptions = new LinkedHashSet<>();
|
||||
}
|
||||
try {
|
||||
singletonObject = singletonFactory.getObject();
|
||||
newSingleton = true;
|
||||
// Leniently created singleton object could have appeared in the meantime.
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null) {
|
||||
singletonObject = singletonFactory.getObject();
|
||||
newSingleton = true;
|
||||
}
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// Has the singleton object implicitly appeared in the meantime ->
|
||||
@@ -311,8 +370,18 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
}
|
||||
afterSingletonCreation(beanName);
|
||||
}
|
||||
|
||||
if (newSingleton) {
|
||||
addSingleton(beanName, singletonObject);
|
||||
try {
|
||||
addSingleton(beanName, singletonObject);
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// Leniently accept same instance if implicitly appeared.
|
||||
Object object = this.singletonObjects.get(beanName);
|
||||
if (singletonObject != object) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return singletonObject;
|
||||
@@ -321,6 +390,14 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
if (locked) {
|
||||
this.singletonLock.unlock();
|
||||
}
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
this.singletonsInLenientCreation.remove(beanName);
|
||||
this.lenientCreationFinished.signalAll();
|
||||
}
|
||||
finally {
|
||||
this.lenientCreationLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+69
-44
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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,13 @@
|
||||
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
||||
/**
|
||||
* {@link AutowireCandidateResolver} implementation to use when no annotation
|
||||
@@ -36,46 +40,6 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
|
||||
*/
|
||||
public static final SimpleAutowireCandidateResolver INSTANCE = new SimpleAutowireCandidateResolver();
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
|
||||
return bdHolder.getBeanDefinition().isAutowireCandidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequired(DependencyDescriptor descriptor) {
|
||||
return descriptor.isRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasQualifier(DependencyDescriptor descriptor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getSuggestedName(DependencyDescriptor descriptor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getSuggestedValue(DependencyDescriptor descriptor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getLazyResolutionProxyClass(DependencyDescriptor descriptor, @Nullable String beanName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns {@code this} as-is.
|
||||
* @see #INSTANCE
|
||||
@@ -85,4 +49,65 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve a map of all beans of the given type, also picking up beans defined in
|
||||
* ancestor bean factories, with the specific condition that each bean actually
|
||||
* has autowire candidate status. This matches simple injection point resolution
|
||||
* as implemented by this {@link AutowireCandidateResolver} strategy, including
|
||||
* beans which are not marked as default candidates but excluding beans which
|
||||
* are not even marked as autowire candidates.
|
||||
* @param lbf the bean factory
|
||||
* @param type the type of bean to match
|
||||
* @return the Map of matching bean instances, or an empty Map if none
|
||||
* @throws BeansException if a bean could not be created
|
||||
* @since 6.2.3
|
||||
* @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class)
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
public static <T> Map<String, T> resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class<T> type) {
|
||||
return resolveAutowireCandidates(lbf, type, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a map of all beans of the given type, also picking up beans defined in
|
||||
* ancestor bean factories, with the specific condition that each bean actually
|
||||
* has autowire candidate status. This matches simple injection point resolution
|
||||
* as implemented by this {@link AutowireCandidateResolver} strategy, including
|
||||
* beans which are not marked as default candidates but excluding beans which
|
||||
* are not even marked as autowire candidates.
|
||||
* @param lbf the bean factory
|
||||
* @param type the type of bean to match
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
|
||||
* <i>objects created by FactoryBeans</i> (or by factory methods with a
|
||||
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
|
||||
* eagerly initialized to determine their type: So be aware that passing in "true"
|
||||
* for this flag will initialize FactoryBeans and "factory-bean" references.
|
||||
* @return the Map of matching bean instances, or an empty Map if none
|
||||
* @throws BeansException if a bean could not be created
|
||||
* @since 6.2.5
|
||||
* @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean)
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Map<String, T> resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class<T> type,
|
||||
boolean includeNonSingletons, boolean allowEagerInit) {
|
||||
|
||||
Map<String, T> candidates = new LinkedHashMap<>();
|
||||
for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type,
|
||||
includeNonSingletons, allowEagerInit)) {
|
||||
if (AutowireUtils.isAutowireCandidate(lbf, beanName)) {
|
||||
Object beanInstance = lbf.getBean(beanName);
|
||||
if (!(beanInstance instanceof NullBean)) {
|
||||
candidates.put(beanName, (T) beanInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -66,8 +66,8 @@ public abstract class AbstractBeanDefinitionParser implements BeanDefinitionPars
|
||||
String id = resolveId(element, definition, parserContext);
|
||||
if (!StringUtils.hasText(id)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
|
||||
+ "' when used as a top-level tag", element);
|
||||
"Id is required for element '" + parserContext.getDelegate().getLocalName(element) +
|
||||
"' when used as a top-level tag", element);
|
||||
}
|
||||
String[] aliases = null;
|
||||
if (shouldParseNameAsAliases()) {
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -135,4 +135,12 @@ public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate the use of {@link PropertyEditorRegistrySupport#overrideDefaultEditor} above.
|
||||
*/
|
||||
@Override
|
||||
public boolean overridesDefaultEditors() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -139,6 +139,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isReadableProperty("list")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("set")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("xxx")).isFalse();
|
||||
|
||||
@@ -146,6 +147,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isWritableProperty("list")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("set")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("myTestBeans")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("xxx")).isFalse();
|
||||
|
||||
@@ -161,6 +163,14 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isReadableProperty("map[key4][0].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map[key4][1]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map[key4][1].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("map[key999]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key1]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key1].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][0]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][0].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][1]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key2][1].name")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("iterableMap[key999]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
|
||||
assertThat(accessor.isReadableProperty("array[key1]")).isFalse();
|
||||
@@ -177,6 +187,14 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.isWritableProperty("map[key4][0].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map[key4][1]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map[key4][1].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("map[key999]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key1]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key1].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][0]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][0].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][1]")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key2][1].name")).isTrue();
|
||||
assertThat(accessor.isWritableProperty("iterableMap[key999]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
|
||||
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
|
||||
assertThat(accessor.isWritableProperty("array[key1]")).isFalse();
|
||||
@@ -1395,6 +1413,9 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
|
||||
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
|
||||
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("nameC");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("nameA");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("nameB");
|
||||
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZ");
|
||||
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
@@ -1409,6 +1430,9 @@ abstract class AbstractPropertyAccessorTests {
|
||||
pvs.add("map[key4][0].name", "nameA");
|
||||
pvs.add("map[key4][1].name", "nameB");
|
||||
pvs.add("map[key5[foo]].name", "name10");
|
||||
pvs.add("iterableMap[key1].name", "newName1");
|
||||
pvs.add("iterableMap[key2][0].name", "newName2A");
|
||||
pvs.add("iterableMap[key2][1].name", "newName2B");
|
||||
pvs.add("myTestBeans[0].name", "nameZZ");
|
||||
accessor.setPropertyValues(pvs);
|
||||
assertThat(tb0.getName()).isEqualTo("name5");
|
||||
@@ -1428,6 +1452,9 @@ abstract class AbstractPropertyAccessorTests {
|
||||
assertThat(accessor.getPropertyValue("map[key4][0].name")).isEqualTo("nameA");
|
||||
assertThat(accessor.getPropertyValue("map[key4][1].name")).isEqualTo("nameB");
|
||||
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name10");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("newName1");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("newName2A");
|
||||
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("newName2B");
|
||||
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZZ");
|
||||
}
|
||||
|
||||
|
||||
+42
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -103,6 +103,27 @@ class BeanWrapperAutoGrowingTests {
|
||||
assertThat(bean.getThreeDimensionalArray()[1][2][3]).isInstanceOf(Bean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPropertyValueAutoGrow3dArrayList() {
|
||||
assertThat(wrapper.getPropertyValue("threeDimensionalArrayList[1][2][3][4]")).isNotNull();
|
||||
assertThat(bean.getThreeDimensionalArrayList()).hasSize(2);
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(1)).hasNumberOfRows(3);
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(1)[2]).hasNumberOfRows(4);
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(1)[2][3]).hasSize(5);
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(1)[2][3][4]).isInstanceOf(Bean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPropertyValueAutoGrow3dArrayListForDefault3dArray() {
|
||||
assertThat(wrapper.getPropertyValue("threeDimensionalArrayList[0]")).isNotNull();
|
||||
assertThat(bean.getThreeDimensionalArrayList()).hasSize(1);
|
||||
|
||||
// Default 3-dimensional array should be [[[]]]
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(0)).hasNumberOfRows(1);
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(0)[0]).hasNumberOfRows(1);
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(0)[0][0]).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setPropertyValueAutoGrow2dArray() {
|
||||
Bean newBean = new Bean();
|
||||
@@ -123,6 +144,16 @@ class BeanWrapperAutoGrowingTests {
|
||||
.extracting(Bean::getProp).isEqualTo("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setPropertyValueAutoGrow3dArrayList() {
|
||||
Bean newBean = new Bean();
|
||||
newBean.setProp("enigma");
|
||||
wrapper.setPropertyValue("threeDimensionalArrayList[0][1][2][3]", newBean);
|
||||
assertThat(bean.getThreeDimensionalArrayList().get(0)[1][2][3])
|
||||
.isInstanceOf(Bean.class)
|
||||
.extracting(Bean::getProp).isEqualTo("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPropertyValueAutoGrowList() {
|
||||
assertThat(wrapper.getPropertyValue("list[0]")).isNotNull();
|
||||
@@ -215,6 +246,8 @@ class BeanWrapperAutoGrowingTests {
|
||||
|
||||
private Bean[][][] threeDimensionalArray;
|
||||
|
||||
private List<Bean[][][]> threeDimensionalArrayList;
|
||||
|
||||
private List<Bean> list;
|
||||
|
||||
private List<List<Bean>> nestedList;
|
||||
@@ -269,6 +302,14 @@ class BeanWrapperAutoGrowingTests {
|
||||
this.threeDimensionalArray = threeDimensionalArray;
|
||||
}
|
||||
|
||||
public List<Bean[][][]> getThreeDimensionalArrayList() {
|
||||
return threeDimensionalArrayList;
|
||||
}
|
||||
|
||||
public void setThreeDimensionalArrayList(List<Bean[][][]> threeDimensionalArrayList) {
|
||||
this.threeDimensionalArrayList = threeDimensionalArrayList;
|
||||
}
|
||||
|
||||
public List<Bean> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
+1
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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,7 +23,6 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
@@ -56,9 +55,6 @@ class BeanFactoryLockingTests {
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Thread thread = new Thread(() -> {
|
||||
// Fail for circular reference from other thread
|
||||
assertThatExceptionOfType(BeanCurrentlyInCreationException.class).isThrownBy(() ->
|
||||
beanFactory.getBean(ThreadDuringInitialization.class));
|
||||
// Leniently create unrelated other bean outside of singleton lock
|
||||
assertThat(beanFactory.getBean(TestBean.class).getName()).isEqualTo("tb");
|
||||
// Creation attempt in other thread was successful
|
||||
|
||||
+126
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -113,6 +113,7 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Chris Beams
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class DefaultListableBeanFactoryTests {
|
||||
|
||||
@@ -1515,31 +1516,37 @@ class DefaultListableBeanFactoryTests {
|
||||
bd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.LOWEST_PRECEDENCE);
|
||||
lbf.registerBeanDefinition("bean1", bd1);
|
||||
GenericBeanDefinition bd2 = new GenericBeanDefinition();
|
||||
bd2.setBeanClass(TestBean.class);
|
||||
bd2.setBeanClass(DerivedTestBean.class);
|
||||
bd2.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "highest"))));
|
||||
bd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE);
|
||||
bd2.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
lbf.registerBeanDefinition("bean2", bd2);
|
||||
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName))
|
||||
.containsExactly("highest", "lowest");
|
||||
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz))
|
||||
.map(TestBean::getName)).containsExactly("lowest");
|
||||
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName))
|
||||
.containsExactly("highest", "lowest");
|
||||
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED, false).map(TestBean::getName))
|
||||
.containsExactly("lowest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderFromAttributeOverrideAnnotation() {
|
||||
void orderFromAttributeOverridesAnnotation() {
|
||||
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
RootBeanDefinition rbd1 = new RootBeanDefinition(LowestPrecedenceTestBeanFactoryBean.class);
|
||||
rbd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE);
|
||||
lbf.registerBeanDefinition("lowestPrecedenceFactory", rbd1);
|
||||
RootBeanDefinition rbd2 = new RootBeanDefinition(HighestPrecedenceTestBeanFactoryBean.class);
|
||||
rbd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.LOWEST_PRECEDENCE);
|
||||
rbd2.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
lbf.registerBeanDefinition("highestPrecedenceFactory", rbd2);
|
||||
GenericBeanDefinition bd1 = new GenericBeanDefinition();
|
||||
bd1.setFactoryBeanName("highestPrecedenceFactory");
|
||||
lbf.registerBeanDefinition("bean1", bd1);
|
||||
GenericBeanDefinition bd2 = new GenericBeanDefinition();
|
||||
bd2.setFactoryBeanName("lowestPrecedenceFactory");
|
||||
lbf.registerBeanDefinition("bean2", bd2);
|
||||
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName))
|
||||
.containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean");
|
||||
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName))
|
||||
.containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean");
|
||||
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED, false).map(TestBean::getName))
|
||||
.containsExactly("fromLowestPrecedenceTestBeanFactoryBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1652,12 +1659,94 @@ class DefaultListableBeanFactoryTests {
|
||||
bd2.setPrimary(true);
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", bd2);
|
||||
lbf.registerSingleton("bd3", new TestBean());
|
||||
|
||||
TestBean bean = lbf.getBean(TestBean.class);
|
||||
assertThat(bean.getBeanName()).isEqualTo("bd2");
|
||||
assertThat(lbf.containsSingleton("bd1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeWithUniqueNonFallbackDefinition() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
|
||||
bd1.setLazyInit(true);
|
||||
bd1.setFallback(true);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
|
||||
bd2.setFallback(true);
|
||||
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", bd2);
|
||||
lbf.registerBeanDefinition("bd3", bd3);
|
||||
|
||||
TestBean bean = lbf.getBean(TestBean.class);
|
||||
assertThat(bean.getBeanName()).isEqualTo("bd3");
|
||||
assertThat(lbf.containsSingleton("bd1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeWithPrimaryAndUniqueNonFallbackDefinition() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
|
||||
bd1.setLazyInit(true);
|
||||
bd1.setFallback(true);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
|
||||
bd2.setPrimary(true);
|
||||
bd2.setFallback(true);
|
||||
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", bd2);
|
||||
lbf.registerBeanDefinition("bd3", bd3);
|
||||
|
||||
TestBean bean = lbf.getBean(TestBean.class);
|
||||
assertThat(bean.getBeanName()).isEqualTo("bd2");
|
||||
assertThat(lbf.containsSingleton("bd1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeWithUniqueNonFallbackAndUniqueNonDefaultDefinition() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
|
||||
bd1.setLazyInit(true);
|
||||
bd1.setFallback(true);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
|
||||
bd2.setFallback(true);
|
||||
bd2.setDefaultCandidate(false);
|
||||
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
|
||||
bd3.setDefaultCandidate(false);
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", bd2);
|
||||
lbf.registerBeanDefinition("bd3", bd3);
|
||||
|
||||
TestBean bean = lbf.getBean(TestBean.class);
|
||||
assertThat(bean.getBeanName()).isEqualTo("bd3");
|
||||
assertThat(lbf.containsSingleton("bd1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeWithUniqueNonDefaultDefinition() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
|
||||
bd1.setDefaultCandidate(false);
|
||||
bd1.setLazyInit(true);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", bd2);
|
||||
|
||||
TestBean bean = lbf.getBean(TestBean.class);
|
||||
assertThat(bean.getBeanName()).isEqualTo("bd2");
|
||||
assertThat(lbf.containsSingleton("bd1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeWithUniqueNonDefaultSingleton() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
|
||||
bd1.setDefaultCandidate(false);
|
||||
bd1.setLazyInit(true);
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerSingleton("bd2", new TestBean());
|
||||
|
||||
TestBean bean = lbf.getBean(TestBean.class);
|
||||
assertThat(bean.getBeanName()).isNull();
|
||||
assertThat(lbf.containsSingleton("bd1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void getFactoryBeanByTypeWithPrimary() {
|
||||
@@ -1898,7 +1987,6 @@ class DefaultListableBeanFactoryTests {
|
||||
void getBeanByTypeInstanceWithAmbiguity() {
|
||||
RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(ConstructorDependency.class);
|
||||
bd2.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bd2.getConstructorArgumentValues().addGenericArgumentValue("43");
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", bd2);
|
||||
@@ -1934,6 +2022,15 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(resolved).hasSize(2);
|
||||
assertThat(resolved).contains(lbf.getBean("bd1"));
|
||||
assertThat(resolved).contains(lbf.getBean("bd2"));
|
||||
|
||||
resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet());
|
||||
assertThat(resolved).hasSize(2);
|
||||
assertThat(resolved).contains(lbf.getBean("bd1"));
|
||||
assertThat(resolved).contains(lbf.getBean("bd2"));
|
||||
|
||||
resolved = provider.stream(ObjectProvider.UNFILTERED, false).collect(Collectors.toSet());
|
||||
assertThat(resolved).hasSize(1);
|
||||
assertThat(resolved).contains(lbf.getBean("bd2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1983,6 +2080,14 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(resolved).hasSize(2);
|
||||
assertThat(resolved).contains(lbf.getBean("bd1"));
|
||||
assertThat(resolved).contains(lbf.getBean("bd2"));
|
||||
|
||||
resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet());
|
||||
assertThat(resolved).hasSize(2);
|
||||
assertThat(resolved).contains(lbf.getBean("bd1"));
|
||||
assertThat(resolved).contains(lbf.getBean("bd2"));
|
||||
|
||||
resolved = provider.stream(ObjectProvider.UNFILTERED, false).collect(Collectors.toSet());
|
||||
assertThat(resolved).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2378,11 +2483,20 @@ class DefaultListableBeanFactoryTests {
|
||||
parentBf.registerBeanDefinition("highPriorityTestBean", bd2);
|
||||
|
||||
ObjectProvider<TestBean> testBeanProvider = lbf.getBeanProvider(ResolvableType.forClass(TestBean.class));
|
||||
List<TestBean> resolved = testBeanProvider.orderedStream().toList();
|
||||
assertThat(resolved).containsExactly(
|
||||
assertThat(testBeanProvider.orderedStream()).containsExactly(
|
||||
lbf.getBean("highPriorityTestBean", TestBean.class),
|
||||
lbf.getBean("lowPriorityTestBean", TestBean.class),
|
||||
lbf.getBean("plainTestBean", TestBean.class));
|
||||
assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly(
|
||||
lbf.getBean("highPriorityTestBean", TestBean.class),
|
||||
lbf.getBean("lowPriorityTestBean", TestBean.class));
|
||||
assertThat(testBeanProvider.stream()).containsExactly(
|
||||
lbf.getBean("plainTestBean", TestBean.class),
|
||||
lbf.getBean("lowPriorityTestBean", TestBean.class),
|
||||
lbf.getBean("highPriorityTestBean", TestBean.class));
|
||||
assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly(
|
||||
lbf.getBean("lowPriorityTestBean", TestBean.class),
|
||||
lbf.getBean("highPriorityTestBean", TestBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+128
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -48,6 +48,7 @@ import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
@@ -64,6 +65,8 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
|
||||
import org.springframework.beans.testfixture.beans.DerivedTestBean;
|
||||
import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
import org.springframework.beans.testfixture.beans.IndexedTestBean;
|
||||
import org.springframework.beans.testfixture.beans.NestedTestBean;
|
||||
@@ -1605,8 +1608,16 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
|
||||
testBeans = bean.streamTestBeans();
|
||||
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
|
||||
testBeans = bean.sortedTestBeans();
|
||||
testBeans = bean.streamTestBeansInOrder();
|
||||
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
|
||||
testBeans = bean.allTestBeans();
|
||||
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
|
||||
testBeans = bean.allTestBeansInOrder();
|
||||
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
|
||||
testBeans = bean.allSingletonBeans();
|
||||
assertThat(testBeans).isEmpty();
|
||||
testBeans = bean.allSingletonBeansInOrder();
|
||||
assertThat(testBeans).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1632,7 +1643,19 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
testBeans = bean.streamTestBeans();
|
||||
assertThat(testBeans).hasSize(1);
|
||||
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
|
||||
testBeans = bean.sortedTestBeans();
|
||||
testBeans = bean.streamTestBeansInOrder();
|
||||
assertThat(testBeans).hasSize(1);
|
||||
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
|
||||
testBeans = bean.allTestBeans();
|
||||
assertThat(testBeans).hasSize(1);
|
||||
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
|
||||
testBeans = bean.allTestBeansInOrder();
|
||||
assertThat(testBeans).hasSize(1);
|
||||
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
|
||||
testBeans = bean.allSingletonBeans();
|
||||
assertThat(testBeans).hasSize(1);
|
||||
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
|
||||
testBeans = bean.allSingletonBeansInOrder();
|
||||
assertThat(testBeans).hasSize(1);
|
||||
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
|
||||
}
|
||||
@@ -1656,7 +1679,15 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(testBeans).isEmpty();
|
||||
testBeans = bean.streamTestBeans();
|
||||
assertThat(testBeans).isEmpty();
|
||||
testBeans = bean.sortedTestBeans();
|
||||
testBeans = bean.streamTestBeansInOrder();
|
||||
assertThat(testBeans).isEmpty();
|
||||
testBeans = bean.allTestBeans();
|
||||
assertThat(testBeans).isEmpty();
|
||||
testBeans = bean.allTestBeansInOrder();
|
||||
assertThat(testBeans).isEmpty();
|
||||
testBeans = bean.allSingletonBeans();
|
||||
assertThat(testBeans).isEmpty();
|
||||
testBeans = bean.allSingletonBeansInOrder();
|
||||
assertThat(testBeans).isEmpty();
|
||||
}
|
||||
|
||||
@@ -1678,7 +1709,11 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.iterateTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.forEachTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.streamTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.sortedTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.streamTestBeansInOrder()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.allTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.allTestBeansInOrder()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.allSingletonBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.allSingletonBeansInOrder()).containsExactly(testBean1, testBean2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1706,7 +1741,11 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.iterateTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.forEachTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.streamTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.sortedTestBeans()).containsExactly(testBean2, testBean1);
|
||||
assertThat(bean.streamTestBeansInOrder()).containsExactly(testBean2, testBean1);
|
||||
assertThat(bean.allTestBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.allTestBeansInOrder()).containsExactly(testBean2, testBean1);
|
||||
assertThat(bean.allSingletonBeans()).containsExactly(testBean1, testBean2);
|
||||
assertThat(bean.allSingletonBeansInOrder()).containsExactly(testBean2, testBean1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1718,12 +1757,67 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.registerBeanDefinition("testBean1", tb1);
|
||||
RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class);
|
||||
tb2.setFactoryMethodName("newTestBean2");
|
||||
tb2.setLazyInit(true);
|
||||
tb2.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("testBean2", tb2);
|
||||
|
||||
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
|
||||
assertThat(bean.sortedTestBeans()).containsExactly(bf.getBean("testBean2", TestBean.class),
|
||||
assertThat(bean.streamTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
|
||||
bf.getBean("testBean1", TestBean.class));
|
||||
assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
|
||||
bf.getBean("testBean1", TestBean.class));
|
||||
assertThat(bean.allSingletonBeansInOrder()).containsExactly(bf.getBean("testBean1", TestBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void objectProviderInjectionWithNonCandidatesInStream() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
|
||||
RootBeanDefinition tb1 = new RootBeanDefinition(TestBeanFactory.class);
|
||||
tb1.setFactoryMethodName("newTestBean1");
|
||||
bf.registerBeanDefinition("testBean1", tb1);
|
||||
RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class);
|
||||
tb2.setFactoryMethodName("newTestBean2");
|
||||
tb2.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("testBean2", tb2);
|
||||
|
||||
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
|
||||
RootBeanDefinition tb3 = new RootBeanDefinition(TestBean.class);
|
||||
tb3.setAutowireCandidate(false);
|
||||
tb3.setLazyInit(true);
|
||||
parent.registerBeanDefinition("testBean3", tb3);
|
||||
RootBeanDefinition tb4 = new RootBeanDefinition(DerivedTestBean.class);
|
||||
tb4.setDefaultCandidate(false);
|
||||
tb4.setLazyInit(true);
|
||||
parent.registerBeanDefinition("testBean4", tb4);
|
||||
bf.setParentBeanFactory(parent);
|
||||
|
||||
RootBeanDefinition tb5 = new RootBeanDefinition(NullFactoryMethods.class);
|
||||
tb5.setFactoryMethodName("createTestBean");
|
||||
bf.registerBeanDefinition("testBean5", tb5);
|
||||
|
||||
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
|
||||
assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
|
||||
bf.getBean("testBean2", TestBean.class));
|
||||
assertThat(bean.streamTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
|
||||
bf.getBean("testBean1", TestBean.class));
|
||||
assertThat(bf.containsSingleton("testBean3")).isFalse();
|
||||
assertThat(bean.plainTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
|
||||
bf.getBean("testBean2", TestBean.class));
|
||||
assertThat(bean.plainTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
|
||||
bf.getBean("testBean1", TestBean.class));
|
||||
assertThat(bf.containsSingleton("testBean4")).isFalse();
|
||||
assertThat(bean.allTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
|
||||
bf.getBean("testBean2", TestBean.class), bf.getBean("testBean4", TestBean.class));
|
||||
assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
|
||||
bf.getBean("testBean1", TestBean.class), bf.getBean("testBean4", TestBean.class));
|
||||
assertThat(bean.allSingletonBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
|
||||
bf.getBean("testBean4", TestBean.class));
|
||||
assertThat(bean.allSingletonBeansInOrder()).containsExactly(bf.getBean("testBean1", TestBean.class),
|
||||
bf.getBean("testBean4", TestBean.class));
|
||||
|
||||
Map<String, TestBean> typeMatches = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, TestBean.class);
|
||||
assertThat(typeMatches.remove("testBean3")).isNotNull();
|
||||
Map<String, TestBean> candidates = SimpleAutowireCandidateResolver.resolveAutowireCandidates(bf, TestBean.class);
|
||||
assertThat(candidates).containsExactlyEntriesOf(typeMatches);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2300,7 +2394,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@SuppressWarnings("unchecked")
|
||||
void genericsBasedConstructorInjectionWithNonTypedTarget() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
@@ -3304,9 +3398,33 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
return this.testBean.stream().toList();
|
||||
}
|
||||
|
||||
public List<TestBean> sortedTestBeans() {
|
||||
public List<TestBean> streamTestBeansInOrder() {
|
||||
return this.testBean.orderedStream().toList();
|
||||
}
|
||||
|
||||
public List<TestBean> plainTestBeans() {
|
||||
return this.testBean.stream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)).toList();
|
||||
}
|
||||
|
||||
public List<TestBean> plainTestBeansInOrder() {
|
||||
return this.testBean.orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)).toList();
|
||||
}
|
||||
|
||||
public List<TestBean> allTestBeans() {
|
||||
return this.testBean.stream(ObjectProvider.UNFILTERED).toList();
|
||||
}
|
||||
|
||||
public List<TestBean> allTestBeansInOrder() {
|
||||
return this.testBean.orderedStream(ObjectProvider.UNFILTERED).toList();
|
||||
}
|
||||
|
||||
public List<TestBean> allSingletonBeans() {
|
||||
return this.testBean.stream(ObjectProvider.UNFILTERED, false).toList();
|
||||
}
|
||||
|
||||
public List<TestBean> allSingletonBeansInOrder() {
|
||||
return this.testBean.orderedStream(ObjectProvider.UNFILTERED, false).toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ class ParameterResolutionTests {
|
||||
}
|
||||
|
||||
private void assertAutowirableParameters(Executable executable) {
|
||||
int startIndex = (executable instanceof Constructor)
|
||||
&& ClassUtils.isInnerClass(executable.getDeclaringClass()) ? 1 : 0;
|
||||
int startIndex = (executable instanceof Constructor) &&
|
||||
ClassUtils.isInnerClass(executable.getDeclaringClass()) ? 1 : 0;
|
||||
Parameter[] parameters = executable.getParameters();
|
||||
for (int parameterIndex = startIndex; parameterIndex < parameters.length; parameterIndex++) {
|
||||
Parameter parameter = parameters[parameterIndex];
|
||||
|
||||
+4
-4
@@ -103,8 +103,8 @@ class BeanInstanceSupplierTests {
|
||||
RegisteredBean registerBean = source.registerBean(this.beanFactory);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> resolver.get(registerBean)).withMessage(
|
||||
"Constructor with parameter types [java.io.InputStream] cannot be found on "
|
||||
+ SingleArgConstructor.class.getName());
|
||||
"Constructor with parameter types [java.io.InputStream] cannot be found on " +
|
||||
SingleArgConstructor.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -151,8 +151,8 @@ class BeanInstanceSupplierTests {
|
||||
RegisteredBean registerBean = source.registerBean(this.beanFactory);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> resolver.get(registerBean)).withMessage(
|
||||
"Factory method 'single' with parameter types [java.io.InputStream] declared on class "
|
||||
+ SingleArgFactory.class.getName() + " cannot be found");
|
||||
"Factory method 'single' with parameter types [java.io.InputStream] declared on class " +
|
||||
SingleArgFactory.class.getName() + " cannot be found");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -86,8 +86,8 @@ class DefaultBeanRegistrationCodeFragmentsTests {
|
||||
BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean);
|
||||
assertThatExceptionOfType(AotBeanProcessingException.class)
|
||||
.isThrownBy(() -> codeFragments.getTarget(registeredBean))
|
||||
.withMessageContaining("Error processing bean with name 'testBean' defined in my test resource: "
|
||||
+ "instance supplier is not supported");
|
||||
.withMessageContaining("Error processing bean with name 'testBean' defined in my test resource: " +
|
||||
"instance supplier is not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+10
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -50,10 +50,15 @@ class DefaultSingletonBeanRegistryTests {
|
||||
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
|
||||
assertThat(tb2Flag.get()).isTrue();
|
||||
|
||||
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
|
||||
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
|
||||
assertThat(beanRegistry.getSingletonCount()).isEqualTo(2);
|
||||
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb", "tb2");
|
||||
TestBean tb3 = (TestBean) beanRegistry.getSingleton("tb3", () -> {
|
||||
TestBean newTb = new TestBean();
|
||||
beanRegistry.registerSingleton("tb3", newTb);
|
||||
return newTb;
|
||||
});
|
||||
assertThat(beanRegistry.getSingleton("tb3")).isSameAs(tb3);
|
||||
|
||||
assertThat(beanRegistry.getSingletonCount()).isEqualTo(3);
|
||||
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb", "tb2", "tb3");
|
||||
|
||||
beanRegistry.destroySingletons();
|
||||
assertThat(beanRegistry.getSingletonCount()).isZero();
|
||||
|
||||
+30
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -49,6 +50,8 @@ public class IndexedTestBean {
|
||||
|
||||
private SortedMap sortedMap;
|
||||
|
||||
private IterableMap iterableMap;
|
||||
|
||||
private MyTestBeans myTestBeans;
|
||||
|
||||
|
||||
@@ -73,6 +76,9 @@ public class IndexedTestBean {
|
||||
TestBean tb6 = new TestBean("name6", 0);
|
||||
TestBean tb7 = new TestBean("name7", 0);
|
||||
TestBean tb8 = new TestBean("name8", 0);
|
||||
TestBean tbA = new TestBean("nameA", 0);
|
||||
TestBean tbB = new TestBean("nameB", 0);
|
||||
TestBean tbC = new TestBean("nameC", 0);
|
||||
TestBean tbX = new TestBean("nameX", 0);
|
||||
TestBean tbY = new TestBean("nameY", 0);
|
||||
TestBean tbZ = new TestBean("nameZ", 0);
|
||||
@@ -88,6 +94,12 @@ public class IndexedTestBean {
|
||||
this.map.put("key2", tb5);
|
||||
this.map.put("key.3", tb5);
|
||||
List list = new ArrayList();
|
||||
list.add(tbA);
|
||||
list.add(tbB);
|
||||
this.iterableMap = new IterableMap<>();
|
||||
this.iterableMap.put("key1", tbC);
|
||||
this.iterableMap.put("key2", list);
|
||||
list = new ArrayList();
|
||||
list.add(tbX);
|
||||
list.add(tbY);
|
||||
this.map.put("key4", list);
|
||||
@@ -152,6 +164,14 @@ public class IndexedTestBean {
|
||||
this.sortedMap = sortedMap;
|
||||
}
|
||||
|
||||
public IterableMap getIterableMap() {
|
||||
return this.iterableMap;
|
||||
}
|
||||
|
||||
public void setIterableMap(IterableMap iterableMap) {
|
||||
this.iterableMap = iterableMap;
|
||||
}
|
||||
|
||||
public MyTestBeans getMyTestBeans() {
|
||||
return myTestBeans;
|
||||
}
|
||||
@@ -161,6 +181,15 @@ public class IndexedTestBean {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class IterableMap<K,V> extends LinkedHashMap<K,V> implements Iterable<V> {
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return values().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyTestBeans implements Iterable<TestBean> {
|
||||
|
||||
private final Collection<TestBean> testBeans;
|
||||
|
||||
+2
-2
@@ -93,8 +93,8 @@ class MetadataCollector {
|
||||
|
||||
private boolean shouldBeMerged(ItemMetadata itemMetadata) {
|
||||
String sourceType = itemMetadata.getType();
|
||||
return (sourceType != null && !deletedInCurrentBuild(sourceType)
|
||||
&& !processedInCurrentBuild(sourceType));
|
||||
return (sourceType != null && !deletedInCurrentBuild(sourceType) &&
|
||||
!processedInCurrentBuild(sourceType));
|
||||
}
|
||||
|
||||
private boolean deletedInCurrentBuild(String sourceType) {
|
||||
|
||||
+2
-2
@@ -42,8 +42,8 @@ class Metadata {
|
||||
ItemMetadata itemMetadata = metadata.getItems().stream()
|
||||
.filter(item -> item.getType().equals(type))
|
||||
.findFirst().orElse(null);
|
||||
return itemMetadata != null && itemMetadata.getStereotypes().size() == stereotypes.size()
|
||||
&& itemMetadata.getStereotypes().containsAll(stereotypes);
|
||||
return (itemMetadata != null && itemMetadata.getStereotypes().size() == stereotypes.size() &&
|
||||
itemMetadata.getStereotypes().containsAll(stereotypes));
|
||||
}, "Candidates with type %s and stereotypes %s", type, stereotypes);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -79,8 +79,8 @@ abstract class AbstractJCacheKeyOperation<A extends Annotation> extends Abstract
|
||||
for (CacheParameterDetail keyParameterDetail : this.keyParameterDetails) {
|
||||
int parameterPosition = keyParameterDetail.getParameterPosition();
|
||||
if (parameterPosition >= values.length) {
|
||||
throw new IllegalStateException("Values mismatch, key parameter at position "
|
||||
+ parameterPosition + " cannot be matched against " + values.length + " value(s)");
|
||||
throw new IllegalStateException("Values mismatch, key parameter at position " +
|
||||
parameterPosition + " cannot be matched against " + values.length + " value(s)");
|
||||
}
|
||||
result.add(keyParameterDetail.toCacheInvocationParameter(values[parameterPosition]));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user