mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
271 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5356a1b1ac | |||
| f7307c9e07 | |||
| 098c4b1dd7 | |||
| 65dbfd09b4 | |||
| 203fa75196 | |||
| 9a56a8877f | |||
| e79a9a5bff | |||
| 2c3c3831c1 | |||
| c38e9896c7 | |||
| 3e0849a566 | |||
| f3a605b92c | |||
| 6fdff201e9 | |||
| c42778a05b | |||
| f140df881d | |||
| 12cf654c98 | |||
| 089e4e69f1 | |||
| 77755ff2fa | |||
| d0aa7ad524 | |||
| 072fc80cdb | |||
| 6f32ff489a | |||
| 24c8dfea1f | |||
| cdfe5816c8 | |||
| 0ff200b2f1 | |||
| 261dac87cc | |||
| fce2f49e46 | |||
| eca2b9657e | |||
| e48f37d956 | |||
| ddc397dd05 | |||
| 6b7f0bd4b6 | |||
| c97a895f09 | |||
| 3b13f2ed38 | |||
| a0eebca0cf | |||
| c6c64e6fe7 | |||
| 0ca393c0dc | |||
| 6681394886 | |||
| 172987c874 | |||
| 47a5ebfde6 | |||
| 43a113f067 | |||
| dc250e1cc1 | |||
| fe74fcfded | |||
| 2451bd62b0 | |||
| 624d6dd167 | |||
| 0ea96b4806 | |||
| c28a0d5627 | |||
| 61d045ce52 | |||
| c0bef2c693 | |||
| c3a0eaa95e | |||
| e12d1259d1 | |||
| 404c4d9d92 | |||
| 7785f94c4c | |||
| 4f6f2c0d41 | |||
| e6da2a86fc | |||
| 301087e510 | |||
| 6c054f88ea | |||
| f58c7d80cc | |||
| e5be10d53d | |||
| 524da905db | |||
| 4323c60513 | |||
| f6b608eecb | |||
| 4da1511ed3 | |||
| 7102c33661 | |||
| 43409b00d0 | |||
| d55abc6cf9 | |||
| 31806f3a6b | |||
| 8a84241c1e | |||
| 624be6d4e6 | |||
| b08883b65c | |||
| 542ba3517f | |||
| 8c6a7799be | |||
| 557dbba585 | |||
| e9de426eb5 | |||
| cda577d1aa | |||
| 8feb842df5 | |||
| ea2931f24a | |||
| 26d1c38d84 | |||
| 345daaabbc | |||
| 6c08d93992 | |||
| 6d7cd9c7dc | |||
| 73eb6f0660 | |||
| 31f298b929 | |||
| c01aab5850 | |||
| 61ef5a8930 | |||
| 3b53ee7038 | |||
| 89dd247b97 | |||
| 39dd1e4049 | |||
| 34f4ad3b71 | |||
| ea596aa211 | |||
| 58da30cd30 | |||
| cd33b4e35a | |||
| 20dea0dae2 | |||
| fee17e11ba | |||
| 65e1337d35 | |||
| a4c2f291d9 | |||
| f26483d272 | |||
| 481d036f7a | |||
| 2a2ef443a5 | |||
| 617833bec9 | |||
| 4d633c2ea8 | |||
| 010e8a303b | |||
| 78549d4963 | |||
| e4e6910122 | |||
| 2270df515b | |||
| 70886e32c0 | |||
| e509385eae | |||
| b7aafda872 | |||
| a89a88d17c | |||
| c6b6ccdc89 | |||
| 09b8feadc2 | |||
| d03ea0bf19 | |||
| a5a1ef6b30 | |||
| 1b60b86bb2 | |||
| 89ce63f1f3 | |||
| 7536980be9 | |||
| c1229b03a7 | |||
| a4135bac5c | |||
| bf08e0c1b4 | |||
| 9202c0ad41 | |||
| 22b6d66a28 | |||
| 9376e6322d | |||
| 0eb937a866 | |||
| 6250b64766 | |||
| 5b1278d03c | |||
| 4f02be263f | |||
| 05d9b52b19 | |||
| 59a125d06f | |||
| aebc48ee8d | |||
| f51be0a17e | |||
| 1d2b5a15c3 | |||
| 4baad16437 | |||
| abcc1dfc6c | |||
| 47c5cd208c | |||
| 5a24e94d2e | |||
| f90bdbef42 | |||
| f17527a48b | |||
| 64b0283042 | |||
| 25cedcfb99 | |||
| 610626aec6 | |||
| d8afe7a8ad | |||
| 32c80d5ae6 | |||
| 0241a02e66 | |||
| cbda46984c | |||
| 7263771552 | |||
| fab3633c75 | |||
| 26706f0039 | |||
| 3cf2cd70ab | |||
| 0ac04a7f86 | |||
| 370a4ee8b3 | |||
| 55361fa4f6 | |||
| 546ca9b834 | |||
| 09b476ac18 | |||
| 387e34d881 | |||
| 4a10bc3288 | |||
| 580adfcbed | |||
| d151931f86 | |||
| 16d8d69cf9 | |||
| 95ac0eae4a | |||
| cc73ccefef | |||
| 4d34444a69 | |||
| d4ddbd537b | |||
| 0b9b9b4602 | |||
| 40596d444c | |||
| cb5b9dcaed | |||
| c99e7f8616 | |||
| 8a8c8fe00e | |||
| f45e7b9b9b | |||
| ec1f5ca600 | |||
| 62efdfb89c | |||
| c3da43291b | |||
| 5aa576f5c6 | |||
| 0268180799 | |||
| da4547a27e | |||
| 97eddb769a | |||
| 0e0397a385 | |||
| fec4f9b8cb | |||
| 4e20cdeb43 | |||
| 7f27ba3902 | |||
| b3a45670f9 | |||
| 8ec27438e7 | |||
| bf3278596c | |||
| 3971632415 | |||
| 1e54916119 | |||
| 6e20af41bf | |||
| c68c62b6c6 | |||
| 494ed4e852 | |||
| 39cd31613b | |||
| ca2b3c170c | |||
| 019ce4418c | |||
| a0136a276a | |||
| 8d05028c2e | |||
| 4a68c44a27 | |||
| e702733c7b | |||
| 187f641409 | |||
| ac74116d76 | |||
| c5590ae9e6 | |||
| 39b551c334 | |||
| 23696b7db8 | |||
| 7b7cfb27a3 | |||
| f2889b1b43 | |||
| 802967fc98 | |||
| 22328905da | |||
| 67a81004a2 | |||
| 84963ba4d8 | |||
| 07472758ee | |||
| 47c9c7eb07 | |||
| 8568aa691c | |||
| b24221754a | |||
| 7a666cfd86 | |||
| 4a7c24d90f | |||
| d955549037 | |||
| b68f76c86e | |||
| 521cda009b | |||
| 08c9b5c96c | |||
| a942a6e172 | |||
| 9b1d5ea6e0 | |||
| 640b97f33c | |||
| 1fb99e4192 | |||
| 23d89362b0 | |||
| 048e4f0aa2 | |||
| b28153e1d7 | |||
| 88b9c0567d | |||
| c7c9da56da | |||
| 290a41d398 | |||
| 1b563f8ba4 | |||
| dea31dd55e | |||
| 06b91c4ea5 | |||
| a63ebe7e9d | |||
| c4b615052e | |||
| dca5f1dcd8 | |||
| a16e8c34b2 | |||
| 52b8c71dcd | |||
| f941754db6 | |||
| d4495a5654 | |||
| c7c61e13e1 | |||
| 4d722414da | |||
| 87dfa492af | |||
| cf9033a60d | |||
| 1e80694daf | |||
| e17c3d3be4 | |||
| 2e98a8a2a4 | |||
| 592f23674d | |||
| 0b3a05b463 | |||
| ef02f0bad8 | |||
| 836a0b3a40 | |||
| 09f210f5f7 | |||
| aa0fb97ba5 | |||
| 71a117c0fd | |||
| bd27ddf83a | |||
| c531a8a705 | |||
| cd7ba1835c | |||
| 5dc6a16c0b | |||
| 42a4f28962 | |||
| 521d4f24d0 | |||
| 039cbb3466 | |||
| d50c94fca9 | |||
| c1424acb94 | |||
| 36d1bc57d7 | |||
| b1c3b6e34b | |||
| eb8b7c4331 | |||
| 58bd057a24 | |||
| a78704af24 | |||
| d2000efbd8 | |||
| c23fd784a0 | |||
| 68a3993897 | |||
| dfc1b839f3 | |||
| 302c24aaa8 | |||
| 7357812b4e | |||
| 30087e1aea | |||
| cd5758e610 | |||
| b82a0dd31a | |||
| b7280400ee | |||
| a3345920ec |
@@ -0,0 +1,33 @@
|
||||
name: Send notification
|
||||
description: Sends a Google Chat message as a notification of the job's outcome
|
||||
inputs:
|
||||
webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
|
||||
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'success' }}
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'failure' }}
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'cancelled' }}
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
|
||||
@@ -18,11 +18,13 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Download BackportBot
|
||||
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
|
||||
- name: Backport
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Build and deploy snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
name: Build and deploy snapshot
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Build and publish
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
|
||||
repository: 'libs-snapshot-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
@@ -0,0 +1,78 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- id: ubuntu-latest
|
||||
name: Linux
|
||||
java:
|
||||
- version: 17
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 17
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: |
|
||||
${{ matrix.java.version }}
|
||||
${{ matrix.java.toolchain && '17' || '' }}
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: |
|
||||
git config --global core.autocrlf true
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Configure toolchain properties
|
||||
if: ${{ matrix.java.toolchain }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
|
||||
- name: Build
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew check antora
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
@@ -1,12 +1,14 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [ gh-pages ]
|
||||
tags: '**'
|
||||
branches:
|
||||
- 'main'
|
||||
- '*.x'
|
||||
- '!gh-pages'
|
||||
tags:
|
||||
- 'v*'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
actions: write
|
||||
@@ -15,8 +17,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -9,5 +9,5 @@ jobs:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v2
|
||||
|
||||
@@ -51,3 +51,5 @@ atlassian-ide-plugin.xml
|
||||
.vscode/
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
|
||||
@@ -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.10-librca
|
||||
java=17.0.11-librca
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
||||
+1
-1
@@ -18,7 +18,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
@@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
|
||||
## Access to Binaries
|
||||
|
||||
|
||||
@@ -89,9 +89,6 @@ configure([rootProject] + javaProjects) { project ->
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
|
||||
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", // com.ibm.*
|
||||
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.41
|
||||
javaFormatVersion=0.0.42
|
||||
|
||||
@@ -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.14.1");
|
||||
checkstyle.setToolVersion("10.17.0");
|
||||
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/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
== Spring Framework Concourse pipeline
|
||||
|
||||
NOTE: CI is being migrated to GitHub Actions.
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
|
||||
|
||||
@@ -9,7 +9,7 @@ case "$1" in
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
|
||||
;;
|
||||
java23)
|
||||
echo "https://download.java.net/java/early_access/jdk23/10/GPL/openjdk-23-ea+10_linux-x64_bin.tar.gz"
|
||||
echo "https://download.java.net/java/early_access/jdk23/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java21 java22
|
||||
for jdk in java17 java21 java23
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
|
||||
@@ -8,4 +8,3 @@ milestone: "6.1.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
task-timeout: 1h00m
|
||||
|
||||
+28
-170
@@ -5,12 +5,10 @@ anchors:
|
||||
password: ((github-ci-release-token))
|
||||
branch: ((branch))
|
||||
gradle-enterprise-task-params: &gradle-enterprise-task-params
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
|
||||
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USERNAME: ((sonatype-username))
|
||||
SONATYPE_PASSWORD: ((sonatype-password))
|
||||
SONATYPE_USERNAME: ((s01-user-token))
|
||||
SONATYPE_PASSWORD: ((s01-user-token-password))
|
||||
SONATYPE_URL: ((sonatype-url))
|
||||
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
|
||||
artifactory-task-params: &artifactory-task-params
|
||||
@@ -23,14 +21,6 @@ anchors:
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
slack-fail-params: &slack-fail-params
|
||||
text: >
|
||||
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
|
||||
[$TEXT_FILE_CONTENT]
|
||||
text_file: git-repo/build/build-scan-uri.txt
|
||||
silent: true
|
||||
icon_emoji: ":concourse:"
|
||||
username: concourse-ci
|
||||
changelog-task-params: &changelog-task-params
|
||||
name: generated-changelog/tag
|
||||
tag: generated-changelog/tag
|
||||
@@ -64,12 +54,6 @@ resource_types:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: cfcommunity/slack-notification-resource
|
||||
tag: latest
|
||||
resources:
|
||||
- name: git-repo
|
||||
type: git
|
||||
@@ -90,13 +74,6 @@ resources:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: every-morning
|
||||
type: time
|
||||
icon: alarm
|
||||
source:
|
||||
start: 8:00 AM
|
||||
stop: 9:00 AM
|
||||
location: Europe/Vienna
|
||||
- name: artifactory-repo
|
||||
type: artifactory-resource
|
||||
icon: package-variant
|
||||
@@ -105,35 +82,6 @@ resources:
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: repo-status-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: build
|
||||
- name: repo-status-jdk21-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk21-build
|
||||
- name: repo-status-jdk23-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk23-build
|
||||
- name: slack-alert
|
||||
type: slack-notification
|
||||
icon: slack
|
||||
source:
|
||||
url: ((slack-webhook-url))
|
||||
- name: github-pre-release
|
||||
type: github-release
|
||||
icon: briefcase-download-outline
|
||||
@@ -168,115 +116,6 @@ jobs:
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
- name: build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: true
|
||||
- put: repo-status-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: build-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/build-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- put: artifactory-repo
|
||||
params: &artifactory-params
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
repo: libs-snapshot-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-api-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- name: jdk21-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 21
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk23-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: false
|
||||
- put: repo-status-jdk23-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 23
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk23-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk23-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
@@ -290,9 +129,32 @@ jobs:
|
||||
RELEASE_TYPE: M
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
params: &artifactory-params
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
repo: libs-staging-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-api-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
@@ -337,7 +199,6 @@ jobs:
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
repo: libs-staging-local
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
@@ -382,7 +243,6 @@ jobs:
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
repo: libs-staging-local
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
@@ -427,8 +287,6 @@ jobs:
|
||||
<<: *changelog-task-params
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk21-build", "jdk23-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
--no-daemon --max-workers=4 check
|
||||
popd > /dev/null
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK23 \
|
||||
--no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
popd > /dev/null
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK23 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
|
||||
popd > /dev/null
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/build-pr.sh
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: distribution-repository
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/build-project.sh
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: distribution-repository
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
MAIN_TOOLCHAIN:
|
||||
TEST_TOOLCHAIN:
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/check-project.sh
|
||||
@@ -0,0 +1,39 @@
|
||||
antora:
|
||||
extensions:
|
||||
- require: '@springio/antora-extensions'
|
||||
root_component_name: 'framework'
|
||||
site:
|
||||
title: Spring Framework
|
||||
url: https://docs.spring.io/spring-framework/reference
|
||||
robots: allow
|
||||
git:
|
||||
ensure_git_suffix: false
|
||||
content:
|
||||
sources:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: ['main', '{6..9}.+({0..9}).x']
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
- '@springio/asciidoctor-extensions/include-code-extension'
|
||||
attributes:
|
||||
page-stackoverflow-url: https://stackoverflow.com/questions/tagged/spring
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
tabs-sync-option: '@'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
runtime:
|
||||
log:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
|
||||
@@ -10,27 +10,10 @@ apply from: "${rootDir}/gradle/ide.gradle"
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
antora {
|
||||
version = '3.2.0-alpha.2'
|
||||
playbook = 'cached-antora-playbook.yml'
|
||||
playbookProvider {
|
||||
repository = 'spring-projects/spring-framework'
|
||||
branch = 'docs-build'
|
||||
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
|
||||
checkLocalBranch = true
|
||||
}
|
||||
options = ['--clean', '--stacktrace']
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
|
||||
'ALGOLIA_APP_ID': '244V8V9FGG',
|
||||
'ALGOLIA_INDEX_NAME': 'framework-docs'
|
||||
]
|
||||
dependencies = [
|
||||
'@antora/atlas-extension': '1.0.0-alpha.1',
|
||||
'@antora/collector-extension': '1.0.0-alpha.3',
|
||||
'@asciidoctor/tabs': '1.0.0-beta.3',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0',
|
||||
'@springio/antora-extensions': '1.8.2',
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -221,8 +221,8 @@ NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in
|
||||
incorrectly using them in multi-threaded and multi-classloader environments. You
|
||||
should always consider wrapping a `ThreadLocal` in some other class and never directly use
|
||||
the `ThreadLocal` itself (except in the wrapper class). Also, you should
|
||||
always remember to correctly set and unset (where the latter simply involves a call to
|
||||
`ThreadLocal.set(null)`) the resource local to the thread. Unsetting should be done in
|
||||
always remember to correctly set and unset (where the latter involves a call to
|
||||
`ThreadLocal.remove()`) the resource local to the thread. Unsetting should be done in
|
||||
any case, since not unsetting it might result in problematic behavior. Spring's
|
||||
`ThreadLocal` support does this for you and should always be considered in favor of using
|
||||
`ThreadLocal` instances without other proper handling code.
|
||||
|
||||
@@ -19,6 +19,10 @@ you can do so. However, you should consider the following issues:
|
||||
since the CGLIB proxy instance is created through Objenesis. Only if your JVM does
|
||||
not allow for constructor bypassing, you might see double invocations and
|
||||
corresponding debug log entries from Spring's AOP support.
|
||||
* Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system.
|
||||
As a typical case, you cannot create a CGLIB proxy for a class from the `java.lang`
|
||||
package when deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
of the `<aop:config>` element to true, as follows:
|
||||
|
||||
@@ -15,7 +15,7 @@ Applying such optimizations early implies the following restrictions:
|
||||
|
||||
* The classpath is fixed and fully defined at build time.
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
|
||||
** `@Profile`, in particular profile-specific configuration, needs to be chosen at build time and is automatically enabled at runtime when AOT is enabled.
|
||||
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time.
|
||||
* Beans registered as singletons (using `registerSingleton`, typically from
|
||||
@@ -152,8 +152,23 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
fun dataSource() = SimpleDataSource()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
WARNING: Kotlin class names with backticks that use invalid Java identifiers (not starting with a letter, containing spaces, etc.) are not supported.
|
||||
|
||||
Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates.
|
||||
The AOT engine will convert the configuration class above to code similar to the following:
|
||||
|
||||
@@ -209,6 +224,17 @@ There is a bean definition for `dataSourceConfiguration` and one for `dataSource
|
||||
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
|
||||
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
|
||||
|
||||
[[aot.running]]
|
||||
== Running with AOT optimizations
|
||||
|
||||
AOT is a mandatory step to transform a Spring application to a native executable, so it
|
||||
is automatically enabled when running in this mode. It is possible to use those optimizations
|
||||
on the JVM by setting the `spring.aot.enabled` System property to `true`.
|
||||
|
||||
NOTE: When AOT optimizations are included, some decisions that have been taken at build-time
|
||||
are hard-coded in the application setup. For instance, profiles that have been enabled at
|
||||
build-time are automatically enabled at runtime as well.
|
||||
|
||||
[[aot.bestpractices]]
|
||||
== Best Practices
|
||||
|
||||
@@ -300,6 +326,32 @@ However, this is not a best practice and flagging the preferred constructor with
|
||||
|
||||
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
|
||||
[[aot.bestpractices.comlext-data-structure]]
|
||||
=== Avoid Complex Data Structure for Constructor Parameters and Properties
|
||||
|
||||
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
|
||||
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
|
||||
|
||||
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
|
||||
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
|
||||
Rather than using such structure, decomposing to simple types or referring to a bean that is built as such is recommended.
|
||||
|
||||
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
|
||||
To use it, register its fully qualified name in `META-INF/spring/aot.factories` using the `Delegate` as the key.
|
||||
|
||||
[[aot.bestpractices.custom-arguments]]
|
||||
=== Avoid Creating Bean with Custom Arguments
|
||||
|
||||
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
|
||||
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
[[aot.bestpractices.factory-bean]]
|
||||
=== FactoryBean
|
||||
|
||||
|
||||
@@ -1,33 +1,16 @@
|
||||
[[beans-annotation-config]]
|
||||
= Annotation-based Container Configuration
|
||||
|
||||
.Are annotations better than XML for configuring Spring?
|
||||
****
|
||||
The introduction of annotation-based configuration raised the question of whether this
|
||||
approach is "`better`" than XML. The short answer is "`it depends.`" The long answer is
|
||||
that each approach has its pros and cons, and, usually, it is up to the developer to
|
||||
decide which strategy suits them better. Due to the way they are defined, annotations
|
||||
provide a lot of context in their declaration, leading to shorter and more concise
|
||||
configuration. However, XML excels at wiring up components without touching their source
|
||||
code or recompiling them. Some developers prefer having the wiring close to the source
|
||||
while others argue that annotated classes are no longer POJOs and, furthermore, that the
|
||||
configuration becomes decentralized and harder to control.
|
||||
Spring provides comprehensive support for annotation-based configuration, operating on
|
||||
metadata in the component class itself by using annotations on the relevant class,
|
||||
method, or field declaration. As mentioned in
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[Example: The `AutowiredAnnotationBeanPostProcessor`],
|
||||
Spring uses `BeanPostProcessors` in conjunction with annotations to make the core IOC
|
||||
container aware of specific annotations.
|
||||
|
||||
No matter the choice, Spring can accommodate both styles and even mix them together.
|
||||
It is worth pointing out that through its xref:core/beans/java.adoc[JavaConfig] option, Spring lets
|
||||
annotations be used in a non-invasive way, without touching the target components'
|
||||
source code and that, in terms of tooling, all configuration styles are supported by
|
||||
{spring-site-tools}[Spring Tools] for Eclipse, Visual Studio Code, and Theia.
|
||||
****
|
||||
|
||||
An alternative to XML setup is provided by annotation-based configuration, which relies
|
||||
on bytecode metadata for wiring up components instead of XML declarations. Instead of
|
||||
using XML to describe a bean wiring, the developer moves the configuration into the
|
||||
component class itself by using annotations on the relevant class, method, or field
|
||||
declaration. As mentioned in xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[Example: The `AutowiredAnnotationBeanPostProcessor`], using a
|
||||
`BeanPostProcessor` in conjunction with annotations is a common means of extending the
|
||||
Spring IoC container. For example, the xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
|
||||
annotation provides the same capabilities as described in xref:core/beans/dependencies/factory-autowire.adoc[Autowiring Collaborators] but
|
||||
For example, the xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
|
||||
annotation provides the same capabilities as described in
|
||||
xref:core/beans/dependencies/factory-autowire.adoc[Autowiring Collaborators] but
|
||||
with more fine-grained control and wider applicability. In addition, Spring provides
|
||||
support for JSR-250 annotations, such as `@PostConstruct` and `@PreDestroy`, as well as
|
||||
support for JSR-330 (Dependency Injection for Java) annotations contained in the
|
||||
@@ -36,13 +19,16 @@ can be found in the xref:core/beans/standard-annotations.adoc[relevant section].
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Annotation injection is performed before XML injection. Thus, the XML configuration
|
||||
overrides the annotations for properties wired through both approaches.
|
||||
Annotation injection is performed before external property injection. Thus, external
|
||||
configuration (e.g. XML-specified bean properties) effectively overrides the annotations
|
||||
for properties when wired through mixed approaches.
|
||||
====
|
||||
|
||||
As always, you can register the post-processors as individual bean definitions, but they
|
||||
can also be implicitly registered by including the following tag in an XML-based Spring
|
||||
configuration (notice the inclusion of the `context` namespace):
|
||||
Technically, you can register the post-processors as individual bean definitions, but they
|
||||
are implicitly registered in an `AnnotationConfigApplicationContext` already.
|
||||
|
||||
In an XML-based Spring setup, you may include the following configuration tag to enable
|
||||
mixing and matching with annotation-based configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
+5
-7
@@ -153,17 +153,15 @@ Letting qualifier values select against target bean names, within the type-match
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point.
|
||||
If there is no other resolution indicator (such as a qualifier or a primary marker),
|
||||
for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses the
|
||||
same-named candidate, if any.
|
||||
(that is, the field name or parameter name) against the target bean names and chooses
|
||||
the same-named candidate, if any (either by bean name or by associated alias).
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
====
|
||||
|
||||
That said, if you intend to express annotation-driven injection by name, do not
|
||||
primarily use `@Autowired`, even if it is capable of selecting by bean name among
|
||||
type-matching candidates. Instead, use the JSR-250 `@Resource` annotation, which is
|
||||
semantically defined to identify a specific target component by its unique name, with
|
||||
the declared type being irrelevant for the matching process. `@Autowired` has rather
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
which is semantically defined to identify a specific target component by its unique name,
|
||||
with the declared type being irrelevant for the matching process. `@Autowired` has rather
|
||||
different semantics: After selecting candidate beans by type, the specified `String`
|
||||
qualifier value is considered within those type-selected candidates only (for example,
|
||||
matching an `account` qualifier against beans marked with the same qualifier label).
|
||||
|
||||
@@ -186,6 +186,15 @@ implementation type, consider declaring the most specific return type on your fa
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 4.3, `@Autowired` also considers self references for injection (that is, references
|
||||
back to the bean that is currently injected). Note that self injection is a fallback.
|
||||
In practice, you should use self references as a last resort only (for example, for
|
||||
calling other methods on the same instance through the bean's transactional proxy).
|
||||
Consider factoring out the affected methods to a separate delegate bean in such a scenario.
|
||||
====
|
||||
|
||||
You can also instruct Spring to provide all beans of a particular type from the
|
||||
`ApplicationContext` by adding the `@Autowired` annotation to a field or method that
|
||||
expects an array of that type, as the following example shows:
|
||||
@@ -268,6 +277,12 @@ use the same bean class). `@Order` values may influence priorities at injection
|
||||
but be aware that they do not influence singleton startup order, which is an
|
||||
orthogonal concern determined by dependency relationships and `@DependsOn` declarations.
|
||||
|
||||
Note that `@Order` annotations on configuration classes just influence the evaluation
|
||||
order within the overall set of configuration classes on startup. Such configuration-level
|
||||
order values do not affect the contained `@Bean` methods at all. For bean-level ordering,
|
||||
each `@Bean` method needs to have its own `@Order` annotation which applies within a
|
||||
set of multiple matches for the specific bean type (as returned by the factory method).
|
||||
|
||||
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
|
||||
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
|
||||
through `@Order` values in combination with `@Primary` on a single bean for each type.
|
||||
|
||||
@@ -2,36 +2,28 @@
|
||||
= Container Overview
|
||||
|
||||
The `org.springframework.context.ApplicationContext` interface represents the Spring IoC
|
||||
container and is responsible for instantiating, configuring, and assembling the
|
||||
beans. The container gets its instructions on what objects to
|
||||
instantiate, configure, and assemble by reading configuration metadata. The
|
||||
configuration metadata is represented in XML, Java annotations, or Java code. It lets
|
||||
you express the objects that compose your application and the rich interdependencies
|
||||
between those objects.
|
||||
container and is responsible for instantiating, configuring, and assembling the beans.
|
||||
The container gets its instructions on the components to instantiate, configure, and
|
||||
assemble by reading configuration metadata. The configuration metadata can be represented
|
||||
as annotated component classes, configuration classes with factory methods, or external
|
||||
XML files or Groovy scripts. With either format, you may compose your application and the
|
||||
rich interdependencies between those components.
|
||||
|
||||
Several implementations of the `ApplicationContext` interface are supplied
|
||||
with Spring. In stand-alone applications, it is common to create an
|
||||
instance of
|
||||
{spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
|
||||
or {spring-framework-api}/context/support/FileSystemXmlApplicationContext.html[`FileSystemXmlApplicationContext`].
|
||||
While XML has been the traditional format for defining configuration metadata, you can
|
||||
instruct the container to use Java annotations or code as the metadata format by
|
||||
providing a small amount of XML configuration to declaratively enable support for these
|
||||
additional metadata formats.
|
||||
Several implementations of the `ApplicationContext` interface are part of core Spring.
|
||||
In stand-alone applications, it is common to create an instance of
|
||||
{spring-framework-api}/context/annotation/AnnotationConfigApplicationContext.html[`AnnotationConfigApplicationContext`]
|
||||
or {spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`].
|
||||
|
||||
In most application scenarios, explicit user code is not required to instantiate one or
|
||||
more instances of a Spring IoC container. For example, in a web application scenario, a
|
||||
simple eight (or so) lines of boilerplate web descriptor XML in the `web.xml` file
|
||||
of the application typically suffices (see
|
||||
more instances of a Spring IoC container. For example, in a plain web application scenario,
|
||||
a simple boilerplate web descriptor XML in the `web.xml` file of the application suffices (see
|
||||
xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]).
|
||||
If you use the {spring-site-tools}[Spring Tools for Eclipse] (an Eclipse-powered
|
||||
development environment), you can easily create this boilerplate configuration with a
|
||||
few mouse clicks or keystrokes.
|
||||
In a Spring Boot scenario, the application context is implicitly bootstrapped for you
|
||||
based on common setup conventions.
|
||||
|
||||
The following diagram shows a high-level view of how Spring works. Your application classes
|
||||
are combined with configuration metadata so that, after the `ApplicationContext` is
|
||||
created and initialized, you have a fully configured and executable system or
|
||||
application.
|
||||
created and initialized, you have a fully configured and executable system or application.
|
||||
|
||||
.The Spring IoC container
|
||||
image::container-magic.png[]
|
||||
@@ -43,33 +35,25 @@ image::container-magic.png[]
|
||||
|
||||
As the preceding diagram shows, the Spring IoC container consumes a form of
|
||||
configuration metadata. This configuration metadata represents how you, as an
|
||||
application developer, tell the Spring container to instantiate, configure, and assemble
|
||||
the objects in your application.
|
||||
application developer, tell the Spring container to instantiate, configure,
|
||||
and assemble the components in your application.
|
||||
|
||||
Configuration metadata is traditionally supplied in a simple and intuitive XML format,
|
||||
which is what most of this chapter uses to convey key concepts and features of the
|
||||
Spring IoC container.
|
||||
|
||||
NOTE: XML-based metadata is not the only allowed form of configuration metadata.
|
||||
The Spring IoC container itself is totally decoupled from the format in which this
|
||||
configuration metadata is actually written. These days, many developers choose
|
||||
xref:core/beans/java.adoc[Java-based configuration] for their Spring applications.
|
||||
|
||||
For information about using other forms of metadata with the Spring container, see:
|
||||
xref:core/beans/java.adoc[Java-based configuration] for their Spring applications:
|
||||
|
||||
* xref:core/beans/annotation-config.adoc[Annotation-based configuration]: define beans using
|
||||
annotation-based configuration metadata.
|
||||
annotation-based configuration metadata on your application's component classes.
|
||||
* xref:core/beans/java.adoc[Java-based configuration]: define beans external to your application
|
||||
classes by using Java rather than XML files. To use these features, see the
|
||||
classes by using Java-based configuration classes. To use these features, see the
|
||||
{spring-framework-api}/context/annotation/Configuration.html[`@Configuration`],
|
||||
{spring-framework-api}/context/annotation/Bean.html[`@Bean`],
|
||||
{spring-framework-api}/context/annotation/Import.html[`@Import`],
|
||||
and {spring-framework-api}/context/annotation/DependsOn.html[`@DependsOn`] annotations.
|
||||
|
||||
Spring configuration consists of at least one and typically more than one bean
|
||||
definition that the container must manage. XML-based configuration metadata configures these
|
||||
beans as `<bean/>` elements inside a top-level `<beans/>` element. Java
|
||||
configuration typically uses `@Bean`-annotated methods within a `@Configuration` class.
|
||||
Spring configuration consists of at least one and typically more than one bean definition
|
||||
that the container must manage. Java configuration typically uses `@Bean`-annotated
|
||||
methods within a `@Configuration` class, each corresponding to one bean definition.
|
||||
|
||||
These bean definitions correspond to the actual objects that make up your application.
|
||||
Typically, you define service layer objects, persistence layer objects such as
|
||||
@@ -79,7 +63,14 @@ Typically, one does not configure fine-grained domain objects in the container,
|
||||
it is usually the responsibility of repositories and business logic to create and load
|
||||
domain objects.
|
||||
|
||||
The following example shows the basic structure of XML-based configuration metadata:
|
||||
|
||||
|
||||
[[beans-factory-xml]]
|
||||
=== XML as an External Configuration DSL
|
||||
|
||||
XML-based configuration metadata configures these beans as `<bean/>` elements inside
|
||||
a top-level `<beans/>` element. The following example shows the basic structure of
|
||||
XML-based configuration metadata:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -110,14 +101,9 @@ The value of the `id` attribute can be used to refer to collaborating objects. T
|
||||
for referring to collaborating objects is not shown in this example. See
|
||||
xref:core/beans/dependencies.adoc[Dependencies] for more information.
|
||||
|
||||
|
||||
|
||||
[[beans-factory-instantiation]]
|
||||
== Instantiating a Container
|
||||
|
||||
The location path or paths
|
||||
supplied to an `ApplicationContext` constructor are resource strings that let
|
||||
the container load configuration metadata from a variety of external resources, such
|
||||
For instantiating a container, the location path or paths to the XML resource files
|
||||
need to be supplied to a `ClassPathXmlApplicationContext` constructor that let the
|
||||
container load configuration metadata from a variety of external resources, such
|
||||
as the local file system, the Java `CLASSPATH`, and so on.
|
||||
|
||||
[tabs]
|
||||
@@ -209,9 +195,9 @@ xref:core/beans/dependencies.adoc[Dependencies].
|
||||
It can be useful to have bean definitions span multiple XML files. Often, each individual
|
||||
XML configuration file represents a logical layer or module in your architecture.
|
||||
|
||||
You can use the application context constructor to load bean definitions from all these
|
||||
You can use the `ClassPathXmlApplicationContext` constructor to load bean definitions from
|
||||
XML fragments. This constructor takes multiple `Resource` locations, as was shown in the
|
||||
xref:core/beans/basics.adoc#beans-factory-instantiation[previous section]. Alternatively,
|
||||
xref:core/beans/basics.adoc#beans-factory-xml[previous section]. Alternatively,
|
||||
use one or more occurrences of the `<import/>` element to load bean definitions from
|
||||
another file or files. The following example shows how to do so:
|
||||
|
||||
@@ -259,7 +245,7 @@ configuration features beyond plain bean definitions are available in a selectio
|
||||
of XML namespaces provided by Spring -- for example, the `context` and `util` namespaces.
|
||||
|
||||
|
||||
[[groovy-bean-definition-dsl]]
|
||||
[[beans-factory-groovy]]
|
||||
=== The Groovy Bean Definition DSL
|
||||
|
||||
As a further example for externalized configuration metadata, bean definitions can also
|
||||
@@ -420,4 +406,3 @@ a dependency on a specific bean through metadata (such as an autowiring annotati
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -75,6 +75,31 @@ lead to concurrent access exceptions, inconsistent state in the bean container,
|
||||
|
||||
|
||||
|
||||
[[beans-definition-overriding]]
|
||||
== Overriding Beans
|
||||
|
||||
Bean overriding is happening when a bean is registered using an identifier that is
|
||||
already allocated. While bean overriding is possible, it makes the configuration harder
|
||||
to read and this feature will be deprecated in a future release.
|
||||
|
||||
To disable bean overriding altogether, you can set the `allowBeanDefinitionOverriding`
|
||||
flag to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
|
||||
exception is thrown if bean overriding is used.
|
||||
|
||||
By default, the container logs every bean overriding at `INFO` level so that you can
|
||||
adapt your configuration accordingly. While not recommended, you can silence those logs
|
||||
by setting the `allowBeanDefinitionOverriding` flag to `true`.
|
||||
|
||||
.Java-configuration
|
||||
****
|
||||
If you use Java Configuration, a corresponding `@Bean` method always silently overrides
|
||||
a scanned bean class with the same component name as long as the return type of the
|
||||
`@Bean` method matches that bean class. This simply means that the container will call
|
||||
the `@Bean` factory method in favor of any pre-declared constructor on the bean class.
|
||||
****
|
||||
|
||||
|
||||
|
||||
[[beans-beanname]]
|
||||
== Naming Beans
|
||||
|
||||
@@ -234,6 +259,10 @@ For details about the mechanism for supplying arguments to the constructor (if r
|
||||
and setting object instance properties after the object is constructed, see
|
||||
xref:core/beans/dependencies/factory-collaborators.adoc[Injecting Dependencies].
|
||||
|
||||
NOTE: In the case of constructor arguments, the container can select a corresponding
|
||||
constructor among several overloaded constructors. That said, to avoid ambiguities,
|
||||
it is recommended to keep your constructor signatures as straightforward as possible.
|
||||
|
||||
|
||||
[[beans-factory-class-static-factory-method]]
|
||||
=== Instantiation with a Static Factory Method
|
||||
@@ -294,6 +323,24 @@ For details about the mechanism for supplying (optional) arguments to the factor
|
||||
and setting object instance properties after the object is returned from the factory,
|
||||
see xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail].
|
||||
|
||||
NOTE: In the case of factory method arguments, the container can select a corresponding
|
||||
method among several overloaded methods of the same name. That said, to avoid ambiguities,
|
||||
it is recommended to keep your factory method signatures as straightforward as possible.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
A typical problematic case with factory method overloading is Mockito with its many
|
||||
overloads of the `mock` method. Choose the most specific variant of `mock` possible:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="clientService" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg type="java.lang.Class" value="examples.ClientService"/>
|
||||
<constructor-arg type="java.lang.String" value="clientService"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[beans-factory-class-instance-factory-method]]
|
||||
=== Instantiation by Using an Instance Factory Method
|
||||
@@ -416,8 +463,8 @@ Kotlin::
|
||||
======
|
||||
|
||||
This approach shows that the factory bean itself can be managed and configured through
|
||||
dependency injection (DI). See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail]
|
||||
.
|
||||
dependency injection (DI).
|
||||
See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail].
|
||||
|
||||
NOTE: In Spring documentation, "factory bean" refers to a bean that is configured in the
|
||||
Spring container and that creates objects through an
|
||||
@@ -444,5 +491,3 @@ cases into account and returns the type of object that a `BeanFactory.getBean` c
|
||||
going to return for the same bean name.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ dataSource.url=jdbc:mysql:mydb
|
||||
----
|
||||
|
||||
This example file can be used with a container definition that contains a bean called
|
||||
`dataSource` that has `driver` and `url` properties.
|
||||
`dataSource` that has `driverClassName` and `url` properties.
|
||||
|
||||
Compound property names are also supported, as long as every component of the path
|
||||
except the final property being overridden is already non-null (presumably initialized
|
||||
|
||||
@@ -55,33 +55,34 @@ The preceding `AppConfig` class is equivalent to the following Spring `<beans/>`
|
||||
</beans>
|
||||
----
|
||||
|
||||
.Full @Configuration vs "`lite`" @Bean mode?
|
||||
.@Configuration classes with or without local calls between @Bean methods?
|
||||
****
|
||||
When `@Bean` methods are declared within classes that are not annotated with
|
||||
`@Configuration`, they are referred to as being processed in a "`lite`" mode. Bean methods
|
||||
declared on a bean that is not annotated with `@Configuration` are considered to be "`lite`",
|
||||
with a different primary purpose of the containing class and a `@Bean` method
|
||||
being a sort of bonus there. For example, service components may expose management views
|
||||
to the container through an additional `@Bean` method on each applicable component class.
|
||||
In such scenarios, `@Bean` methods are a general-purpose factory method mechanism.
|
||||
|
||||
Unlike full `@Configuration`, lite `@Bean` methods cannot declare inter-bean dependencies.
|
||||
Instead, they operate on their containing component's internal state and, optionally, on
|
||||
arguments that they may declare. Such a `@Bean` method should therefore not invoke other
|
||||
`@Bean` methods. Each such method is literally only a factory method for a particular
|
||||
bean reference, without any special runtime semantics. The positive side-effect here is
|
||||
that no CGLIB subclassing has to be applied at runtime, so there are no limitations in
|
||||
terms of class design (that is, the containing class may be `final` and so forth).
|
||||
|
||||
In common scenarios, `@Bean` methods are to be declared within `@Configuration` classes,
|
||||
ensuring that "`full`" mode is always used and that cross-method references therefore
|
||||
get redirected to the container's lifecycle management. This prevents the same
|
||||
`@Bean` method from accidentally being invoked through a regular Java call, which helps
|
||||
to reduce subtle bugs that can be hard to track down when operating in "`lite`" mode.
|
||||
ensuring that full configuration class processing applies and that cross-method
|
||||
references therefore get redirected to the container's lifecycle management.
|
||||
This prevents the same `@Bean` method from accidentally being invoked through a regular
|
||||
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 -,
|
||||
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).
|
||||
A custom Java call to such a method will not get intercepted by the container and
|
||||
therefore behaves just like a regular method call, creating a new instance every time
|
||||
rather than reusing an existing singleton (or scoped) instance for the given bean.
|
||||
|
||||
As a consequence, `@Bean` methods on classes without runtime proxying are not meant to
|
||||
declare inter-bean dependencies at all. Instead, they are expected to operate on their
|
||||
containing component's fields and, optionally, on arguments that a factory method may
|
||||
declare in order to receive autowired collaborators. Such a `@Bean` method therefore
|
||||
never needs to invoke other `@Bean` methods; every such call can be expressed through
|
||||
a factory method argument instead. The positive side-effect here is that no CGLIB
|
||||
subclassing has to be applied at runtime, reducing the overhead and the footprint.
|
||||
****
|
||||
|
||||
The `@Bean` and `@Configuration` annotations are discussed in depth in the following sections.
|
||||
First, however, we cover the various ways of creating a spring container by using
|
||||
First, however, we cover the various ways of creating a Spring container by using
|
||||
Java-based configuration.
|
||||
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ Java::
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public MethodValidationPostProcessor validationPostProcessor() {
|
||||
public static MethodValidationPostProcessor validationPostProcessor() {
|
||||
return new MethodValidationPostProcessor();
|
||||
}
|
||||
}
|
||||
@@ -341,7 +341,7 @@ xref:web/webflux/ann-rest-exceptions.adoc[Error Responses] sections.
|
||||
=== Method Validation Exceptions
|
||||
|
||||
By default, `jakarta.validation.ConstraintViolationException` is raised with the set of
|
||||
``ConstraintViolation``s returned by `jakarata.validation.Validator`. As an alternative,
|
||||
``ConstraintViolation``s returned by `jakarta.validation.Validator`. As an alternative,
|
||||
you can have `MethodValidationException` raised instead with ``ConstraintViolation``s
|
||||
adapted to `MessageSourceResolvable` errors. To enable set the following flag:
|
||||
|
||||
@@ -357,7 +357,7 @@ Java::
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public MethodValidationPostProcessor validationPostProcessor() {
|
||||
public static MethodValidationPostProcessor validationPostProcessor() {
|
||||
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
|
||||
processor.setAdaptConstraintViolations(true);
|
||||
return processor;
|
||||
|
||||
@@ -112,7 +112,7 @@ Java::
|
||||
this.actorMappingQuery = new ActorMappingQuery(dataSource);
|
||||
}
|
||||
|
||||
public Customer getCustomer(Long id) {
|
||||
public Actor getActor(Long id) {
|
||||
return actorMappingQuery.findObject(id);
|
||||
}
|
||||
----
|
||||
@@ -123,11 +123,11 @@ Kotlin::
|
||||
----
|
||||
private val actorMappingQuery = ActorMappingQuery(dataSource)
|
||||
|
||||
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
|
||||
fun getActor(id: Long) = actorMappingQuery.findObject(id)
|
||||
----
|
||||
======
|
||||
|
||||
The method in the preceding example retrieves the customer with the `id` that is passed in as the
|
||||
The method in the preceding example retrieves the actor with the `id` that is passed in as the
|
||||
only parameter. Since we want only one object to be returned, we call the `findObject` convenience
|
||||
method with the `id` as the parameter. If we had instead a query that returned a
|
||||
list of objects and took additional parameters, we would use one of the `execute`
|
||||
|
||||
+9
-7
@@ -74,10 +74,11 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Used at the class level as above, the annotation indicates a default for all methods of
|
||||
the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
|
||||
further details on which methods Spring considers transactional. Note that a class-level
|
||||
Used at the class level as above, the annotation indicates a default for all methods
|
||||
of the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See
|
||||
xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility]
|
||||
for further details on which methods Spring considers transactional. Note that a class-level
|
||||
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
|
||||
inherited methods need to be locally redeclared in order to participate in a
|
||||
subclass-level annotation.
|
||||
@@ -436,9 +437,10 @@ properties of the `@Transactional` annotation:
|
||||
| Optional array of exception name patterns that must not cause rollback.
|
||||
|===
|
||||
|
||||
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules] for further details
|
||||
on rollback rule semantics, patterns, and warnings regarding possible unintentional
|
||||
matches for pattern-based rollback rules.
|
||||
TIP: See
|
||||
xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules]
|
||||
for further details on rollback rule semantics, patterns, and warnings
|
||||
regarding possible unintentional matches for pattern-based rollback rules.
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor and in logging output.
|
||||
|
||||
+28
-2
@@ -23,9 +23,9 @@ As of Spring Framework 5.2, the default configuration also provides support for
|
||||
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
|
||||
This allows you to handle functional-style errors using Try and have the transaction
|
||||
automatically rolled back in case of a failure. For more information on Vavr's Try,
|
||||
refer to the [official Vavr documentation](https://www.vavr.io/vavr-docs/#_try).
|
||||
|
||||
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
|
||||
Here's an example of how to use Vavr's Try with a transactional method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
@@ -42,6 +42,32 @@ Java::
|
||||
----
|
||||
======
|
||||
|
||||
As of Spring Framework 6.1, there is also special treatment of `CompletableFuture`
|
||||
(and general `Future`) return values, triggering a rollback for such a handle if it
|
||||
was exceptionally completed at the time of being returned from the original method.
|
||||
This is intended for `@Async` methods where the actual method implementation may
|
||||
need to comply with a `CompletableFuture` signature (auto-adapted to an actual
|
||||
asynchronous handle for a call to the proxy by `@Async` processing at runtime),
|
||||
preferring exposure in the returned handle rather than rethrowing an exception:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Transactional @Async
|
||||
public CompletableFuture<String> myTransactionalMethod() {
|
||||
try {
|
||||
return CompletableFuture.completedFuture(delegate.myDataAccessOperation());
|
||||
}
|
||||
catch (DataAccessException ex) {
|
||||
return CompletableFuture.failedFuture(ex);
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Checked exceptions that are thrown from a transactional method do not result in a rollback
|
||||
in the default configuration. You can configure exactly which `Exception` types mark a
|
||||
transaction for rollback, including checked exceptions by specifying _rollback rules_.
|
||||
|
||||
@@ -29,8 +29,6 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem
|
||||
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
|
||||
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
|
||||
|
||||
Copyright © 2002 - 2024 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others,
|
||||
provided that you do not charge any fee for such copies and further provided that each
|
||||
copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
copy contains the Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
@@ -24,7 +24,7 @@ To create the archive, two additional JVM flags must be specified:
|
||||
* `-Dspring.context.exit=onRefresh`: starts and then immediately exits your Spring
|
||||
application as described above
|
||||
|
||||
To create a CDS archive, your JDK must have a base image. If you add the flags above to
|
||||
To create a CDS archive, your JDK/JRE must have a base image. If you add the flags above to
|
||||
your startup script, you may get a warning that looks like this:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
@@ -32,7 +32,8 @@ your startup script, you may get a warning that looks like this:
|
||||
-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded. Run with -Xlog:cds for more info.
|
||||
----
|
||||
|
||||
The base CDS archive can be created by issuing the following command:
|
||||
The base CDS archive is usually provided out-of-the-box, but can also be created if needed by issuing the following
|
||||
command:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
----
|
||||
@@ -44,6 +45,9 @@ The base CDS archive can be created by issuing the following command:
|
||||
Once the archive is available, add `-XX:SharedArchiveFile=application.jsa` to your startup
|
||||
script to use it, assuming an `application.jsa` file in the working directory.
|
||||
|
||||
To check if the CDS cache is effective, you can use (for testing purposes only, not in production) `-Xshare:on` which
|
||||
prints an error message and exits if CDS can't be enabled.
|
||||
|
||||
To figure out how effective the cache is, you can enable class loading logs by adding
|
||||
an extra attribute: `-Xlog:class+load:file=cds.log`. This creates a `cds.log` with every
|
||||
attempt to load a class and its source. Classes that are loaded from the cache should have
|
||||
@@ -58,8 +62,11 @@ a "shared objects file" source, as shown in the following example:
|
||||
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
|
||||
----
|
||||
|
||||
TIP: If you have a large number of classes that are not loaded from the cache, make sure that
|
||||
the JDK and classpath used by the commands that create the archive and start the application
|
||||
are identical. Note also that to effectively cache classes, the classpath should be specified
|
||||
as a list of JARs containing those classes, and avoid the usage of directories and `*`
|
||||
wildcard characters.
|
||||
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
|
||||
the following conditions are fulfilled when creating and using the archive:
|
||||
|
||||
- The very same JVM must used.
|
||||
- The classpath must be specified as a list of JARs, and avoid the usage of directories and `*` wildcard characters.
|
||||
- The timestamps of the JARs must be preserved.
|
||||
- When using the archive, the classpath must be the same than the one used to create the archive, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but won't be cached).
|
||||
|
||||
@@ -58,7 +58,7 @@ your `@Configuration` classes, as the following example shows:
|
||||
By default, the infrastructure looks for a bean named `jmsListenerContainerFactory`
|
||||
as the source for the factory to use to create message listener containers. In this
|
||||
case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder`
|
||||
method with a core poll size of three threads and a maximum pool size of ten threads.
|
||||
method with a core pool size of three threads and a maximum pool size of ten threads.
|
||||
|
||||
You can customize the listener container factory to use for each annotation or you can
|
||||
configure an explicit default by implementing the `JmsListenerConfigurer` interface.
|
||||
|
||||
@@ -71,8 +71,34 @@ This can be done with `method(HttpMethod)` or with the convenience methods `get(
|
||||
Next, the request URI can be specified with the `uri` methods.
|
||||
This step is optional and can be skipped if the `RestClient` is configured with a default URI.
|
||||
The URL is typically specified as a `String`, with optional URI template variables.
|
||||
String URLs are encoded by default, but this can be changed by building a client with a custom `uriBuilderFactory`.
|
||||
The following example configures a GET request to `https://example.com/orders/42`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
int id = 42;
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
....
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val id = 42
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
...
|
||||
----
|
||||
======
|
||||
|
||||
A function can also be used for more controls, such as specifying xref:web/webmvc/mvc-uri-building.adoc[request parameters].
|
||||
|
||||
String URLs are encoded by default, but this can be changed by building a client with a custom `uriBuilderFactory`.
|
||||
The URL can also be provided with a function or as a `java.net.URI`, both of which are not encoded.
|
||||
For more details on working with and encoding URIs, see xref:web/webmvc/mvc-uri-building.adoc[URI Links].
|
||||
|
||||
@@ -468,6 +494,9 @@ If no request factory is specified when the `RestClient` was built, it will use
|
||||
Otherwise, if the `java.net.http` module is loaded, it will use Java's `HttpClient`.
|
||||
Finally, it will resort to the simple default.
|
||||
|
||||
TIP: Note that the `SimpleClientHttpRequestFactory` may raise an exception when accessing the status of a response that represents an error (e.g. 401).
|
||||
If this is an issue, use any of the alternative request factories.
|
||||
|
||||
[[rest-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ existing Java application.
|
||||
|
||||
The Spring Framework provides a dedicated `ApplicationContext` that supports a Groovy-based
|
||||
Bean Definition DSL. For more details, see
|
||||
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[The Groovy Bean Definition DSL].
|
||||
xref:core/beans/basics.adoc#beans-factory-groovy[The Groovy Bean Definition DSL].
|
||||
|
||||
Further support for Groovy, including beans written in Groovy, refreshable script beans,
|
||||
and more is available in xref:languages/dynamic.adoc[Dynamic Language Support].
|
||||
|
||||
@@ -166,7 +166,7 @@ public class SampleConfiguration {
|
||||
@Bean
|
||||
@NotNull
|
||||
public SampleBean sampleBean$demo_kotlin_internal_test() {
|
||||
return new SampleBean();
|
||||
return new SampleBean();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
+2
-1
@@ -111,7 +111,8 @@ a mock service with Mockito:
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.example.AccountService"/>
|
||||
<constructor-arg type="java.lang.Class" value="org.example.AccountService"/>
|
||||
<constructor-arg type="java.lang.String" value="accountService"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[[spring-mvc-test-vs-end-to-end-integration-tests]]
|
||||
= MockMvc vs End-to-End Tests
|
||||
|
||||
MockMVc is built on Servlet API mock implementations from the
|
||||
MockMvc is built on Servlet API mock implementations from the
|
||||
`spring-test` module and does not rely on a running container. Therefore, there are
|
||||
some differences when compared to full end-to-end integration tests with an actual
|
||||
client and a live server running.
|
||||
|
||||
+10
-32
@@ -1,38 +1,16 @@
|
||||
[[spring-mvc-test-vs-streaming-response]]
|
||||
= Streaming Responses
|
||||
|
||||
The best way to test streaming responses such as Server-Sent Events is through the
|
||||
<<WebTestClient>> which can be used as a test client to connect to a `MockMvc` instance
|
||||
to perform tests on Spring MVC controllers without a running server. For example:
|
||||
You can use `WebTestClient` to test xref:testing/webtestclient.adoc#webtestclient-stream[streaming responses]
|
||||
such as Server-Sent Events. However, `MockMvcWebTestClient` doesn't support infinite
|
||||
streams because there is no way to cancel the server stream from the client side.
|
||||
To test infinite streams, you'll need to
|
||||
xref:testing/webtestclient.adoc#webtestclient-server-config[bind to] a running server,
|
||||
or when using Spring Boot,
|
||||
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server].
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
|
||||
|
||||
FluxExchangeResult<Person> exchangeResult = client.get()
|
||||
.uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType("text/event-stream")
|
||||
.returnResult(Person.class);
|
||||
|
||||
// Use StepVerifier from Project Reactor to test the streaming response
|
||||
|
||||
StepVerifier.create(exchangeResult.getResponseBody())
|
||||
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
|
||||
.thenCancel()
|
||||
.verify();
|
||||
----
|
||||
======
|
||||
|
||||
`WebTestClient` can also connect to a live server and perform full end-to-end integration
|
||||
tests. This is also supported in Spring Boot where you can
|
||||
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server].
|
||||
`MockMvcWebTestClient` does support asynchronous responses, and even streaming responses.
|
||||
The limitation is that it can't influence the server to stop, and therefore the server
|
||||
must finish writing the response on its own.
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
= Context Configuration with Groovy Scripts
|
||||
|
||||
To load an `ApplicationContext` for your tests by using Groovy scripts that use the
|
||||
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[Groovy Bean Definition DSL], you can annotate
|
||||
xref:core/beans/basics.adoc#beans-factory-groovy[Groovy Bean Definition DSL], you can annotate
|
||||
your test class with `@ContextConfiguration` and configure the `locations` or `value`
|
||||
attribute with an array that contains the resource locations of Groovy scripts. Resource
|
||||
lookup semantics for Groovy scripts are the same as those described for
|
||||
|
||||
@@ -672,13 +672,13 @@ Kotlin::
|
||||
val result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
.expectBody<Person>()
|
||||
.returnResult()
|
||||
|
||||
// For a response without a body
|
||||
val result = client.get().uri("/path")
|
||||
.exchange()
|
||||
.expectBody().isEmpty();
|
||||
.expectBody().isEmpty()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
`spring-webflux` depends on `reactor-core` and uses it internally to compose asynchronous
|
||||
logic and to provide Reactive Streams support. Generally, WebFlux APIs return `Flux` or
|
||||
`Mono` (since those are used internally) and leniently accept any Reactive Streams
|
||||
`Publisher` implementation as input. The use of `Flux` versus `Mono` is important, because
|
||||
it helps to express cardinality -- for example, whether a single or multiple asynchronous
|
||||
values are expected, and that can be essential for making decisions (for example, when
|
||||
encoding or decoding HTTP messages).
|
||||
`Publisher` implementation as input.
|
||||
When a `Publisher` is provided, it can be treated only as a stream with unknown semantics (0..N).
|
||||
If, however, the semantics are known, you should wrap it with `Flux` or `Mono.from(Publisher)` instead
|
||||
of passing the raw `Publisher`.
|
||||
The use of `Flux` versus `Mono` is important, because it helps to express cardinality --
|
||||
for example, whether a single or multiple asynchronous values are expected,
|
||||
and that can be essential for making decisions (for example, when encoding or decoding HTTP messages).
|
||||
|
||||
For annotated controllers, WebFlux transparently adapts to the reactive library chosen by
|
||||
the application. This is done with the help of the
|
||||
@@ -15,15 +18,3 @@ the application. This is done with the help of the
|
||||
provides pluggable support for reactive library and other asynchronous types. The registry
|
||||
has built-in support for RxJava 3, Kotlin coroutines and SmallRye Mutiny, but you can
|
||||
register others, too.
|
||||
|
||||
For functional APIs (such as <<webflux-fn>>, the `WebClient`, and others), the general rules
|
||||
for WebFlux APIs apply -- `Flux` and `Mono` as return values and a Reactive Streams
|
||||
`Publisher` as input. When a `Publisher`, whether custom or from another reactive library,
|
||||
is provided, it can be treated only as a stream with unknown semantics (0..N). If, however,
|
||||
the semantics are known, you can wrap it with `Flux` or `Mono.from(Publisher)` instead
|
||||
of passing the raw `Publisher`.
|
||||
|
||||
For example, given a `Publisher` that is not a `Mono`, the Jackson JSON message writer
|
||||
expects multiple values. If the media type implies an infinite stream (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are buffered into a list and rendered as a JSON array.
|
||||
@@ -97,8 +97,8 @@ Java::
|
||||
Mono<Person> result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.onStatus(HttpStatus::is4xxClientError, response -> ...)
|
||||
.onStatus(HttpStatus::is5xxServerError, response -> ...)
|
||||
.onStatus(HttpStatusCode::is4xxClientError, response -> ...)
|
||||
.onStatus(HttpStatusCode::is5xxServerError, response -> ...)
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
|
||||
@@ -109,8 +109,8 @@ Kotlin::
|
||||
val result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.onStatus(HttpStatus::is4xxClientError) { ... }
|
||||
.onStatus(HttpStatus::is5xxServerError) { ... }
|
||||
.onStatus(HttpStatusCode::is4xxClientError) { ... }
|
||||
.onStatus(HttpStatusCode::is5xxServerError) { ... }
|
||||
.awaitBody<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
A common requirement for REST services is to include details in the body of error
|
||||
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
|
||||
specification, {rfc-site}/rfc7807.html[RFC 7807].
|
||||
specification, {rfc-site}/rfc9457.html[RFC 9457].
|
||||
|
||||
The following are the main abstractions for this support:
|
||||
|
||||
- `ProblemDetail` -- representation for an RFC 7807 problem detail; a simple container
|
||||
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
|
||||
for both standard fields defined in the spec, and for non-standard ones.
|
||||
- `ErrorResponse` -- contract to expose HTTP error response details including HTTP
|
||||
status, response headers, and a body in the format of RFC 7807; this allows exceptions to
|
||||
status, response headers, and a body in the format of RFC 9457; this allows exceptions to
|
||||
encapsulate and expose the details of how they map to an HTTP response. All Spring WebFlux
|
||||
exceptions implement this.
|
||||
- `ErrorResponseException` -- basic `ErrorResponse` implementation that others
|
||||
@@ -28,7 +28,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
||||
[.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-render[See equivalent in the Servlet stack]#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
any `@RequestMapping` method to render an RFC 9457 response. This is processed as follows:
|
||||
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
@@ -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 7807 responses for Spring WebFlux exceptions and for any
|
||||
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
xref:web/webflux/controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
|
||||
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
|
||||
@@ -50,7 +50,7 @@ use a protected method to map any exception to a `ProblemDetail`.
|
||||
== Non-Standard Fields
|
||||
[.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-non-standard[See equivalent in the Servlet stack]#
|
||||
|
||||
You can extend an RFC 7807 response with non-standard fields in one of two ways.
|
||||
You can extend an RFC 9457 response with non-standard fields in one of two ways.
|
||||
|
||||
One, insert into the "properties" `Map` of `ProblemDetail`. When using the Jackson
|
||||
library, the Spring Framework registers `ProblemDetailJacksonMixin` that ensures this
|
||||
|
||||
@@ -338,7 +338,7 @@ Kotlin::
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
|
||||
// ...
|
||||
configurer.defaultCodecs().maxInMemorySize(512 * 1024)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -81,7 +81,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@JsonView` allows an array of view classes but you can only specify only one per
|
||||
NOTE: `@JsonView` allows an array of view classes but you can specify only one per
|
||||
controller method. Use a composite interface if you need to activate multiple views.
|
||||
|
||||
|
||||
|
||||
+14
-3
@@ -4,9 +4,20 @@
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-methods/return-types.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
The following table shows the supported controller method return values. Note that reactive
|
||||
types from libraries such as Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other] are
|
||||
types from libraries such as Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other] are
|
||||
generally supported for all return values.
|
||||
|
||||
For return types like `Flux`, when multiple values are expected, elements are streamed as they come
|
||||
and are not buffered. This is the default behavior, as keeping a potentially large amount of elements in memory
|
||||
is not efficient. If the media type implies an infinite stream (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are written individually and the flushing happens separately.
|
||||
|
||||
NOTE: If an error happens while an element is encoded to JSON, the response might have been written to and committed already
|
||||
and it is impossible at that point to render a proper error response.
|
||||
In some cases, applications can choose to trade memory efficiency for better handling such errors by buffering elements and encoding them all at once.
|
||||
Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated operator for that, `Flux#collectList()`.
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Controller method return value | Description
|
||||
@@ -24,11 +35,11 @@ generally supported for all return values.
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
|
||||
@@ -3,28 +3,40 @@
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-validation.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] support for
|
||||
`@RequestMapping` methods, including the option to use
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] for
|
||||
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
Validation may be applied at one of two levels:
|
||||
|
||||
First, resolvers for
|
||||
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
|
||||
resolvers validate a method argument individually if the method parameter is annotated
|
||||
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
|
||||
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
|
||||
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
|
||||
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
|
||||
directly on method parameters, or on the method (for the return value), then method
|
||||
validation must be applied, and that supersedes validation at the method argument level
|
||||
because method validation covers both method parameter constraints and nested constraints
|
||||
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
|
||||
|
||||
Applications must handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
method signature. The two exceptions, however are designed to be very similar, and can be
|
||||
handled with almost identical code. The main difference is that the former is for a single
|
||||
object while the latter is for a list of method parameters.
|
||||
|
||||
NOTE: `@Valid` is not a constraint annotation, but rather for nested constraints within
|
||||
an Object. Therefore, by itself `@Valid` does not lead to method validation. `@NotNull`
|
||||
on the other hand is a constraint, and adding it to an `@Valid` parameter leads to method
|
||||
validation. For nullability specifically, you may also use the `required` flag of
|
||||
`@RequestBody` or `@ModelAttribute`.
|
||||
|
||||
Method validation may be used in combination with `Errors` or `BindingResult` method
|
||||
parameters. However, the controller method is called only if all validation errors are on
|
||||
method parameters with an `Errors` immediately after. If there are validation errors on
|
||||
any other method parameter then `HandlerMethodValidationException` is raised.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally
|
||||
|
||||
@@ -36,7 +36,7 @@ to render the response content as XML. If the model contains multiple entries, y
|
||||
explicitly set the object to be serialized by using the `modelKey` bean property. If the
|
||||
model contains a single entry, it is serialized automatically.
|
||||
|
||||
You can customized XML mapping as needed by using JAXB or Jackson's provided
|
||||
You can customize XML mapping as needed by using JAXB or Jackson's provided
|
||||
annotations. When you need further control, you can inject a custom `XmlMapper`
|
||||
through the `ObjectMapper` property, for cases where custom XML
|
||||
you need to provide serializers and deserializers for specific types.
|
||||
|
||||
@@ -683,7 +683,7 @@ the HTML would be as follows:
|
||||
----
|
||||
|
||||
What if we want to display the entire list of errors for a given page? The next example
|
||||
shows that the `errors` tag also supports some basic wildcarding functionality.
|
||||
shows that the `errors` tag also supports some basic wildcard functionality.
|
||||
|
||||
* `path="{asterisk}"`: Displays all errors.
|
||||
* `path="lastName"`: Displays all errors associated with the `lastName` field.
|
||||
|
||||
@@ -500,8 +500,8 @@ The MVC configuration exposes the following options for asynchronous request pro
|
||||
|
||||
You can configure the following:
|
||||
|
||||
* Default timeout value for async requests, which if not set, depends
|
||||
on the underlying Servlet container.
|
||||
* The default timeout value for async requests depends
|
||||
on the underlying Servlet container, unless it is set explicitly.
|
||||
* `AsyncTaskExecutor` to use for blocking writes when streaming with
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] and for
|
||||
executing `Callable` instances returned from controller methods.
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
A common requirement for REST services is to include details in the body of error
|
||||
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
|
||||
specification, {rfc-site}/rfc7807[RFC 7807].
|
||||
specification, {rfc-site}/rfc9457[RFC 9457].
|
||||
|
||||
The following are the main abstractions for this support:
|
||||
|
||||
- `ProblemDetail` -- representation for an RFC 7807 problem detail; a simple container
|
||||
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
|
||||
for both standard fields defined in the spec, and for non-standard ones.
|
||||
- `ErrorResponse` -- contract to expose HTTP error response details including HTTP
|
||||
status, response headers, and a body in the format of RFC 7807; this allows exceptions to
|
||||
status, response headers, and a body in the format of RFC 9457; this allows exceptions to
|
||||
encapsulate and expose the details of how they map to an HTTP response. All Spring MVC
|
||||
exceptions implement this.
|
||||
- `ErrorResponseException` -- basic `ErrorResponse` implementation that others
|
||||
@@ -28,7 +28,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
||||
[.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-render[See equivalent in the Reactive stack]#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
any `@RequestMapping` method to render an RFC 9457 response. This is processed as follows:
|
||||
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
@@ -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 7807 responses for Spring WebFlux exceptions and for any
|
||||
To enable RFC 9457 responses for Spring WebFlux 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
|
||||
@@ -50,7 +50,7 @@ use a protected method to map any exception to a `ProblemDetail`.
|
||||
== Non-Standard Fields
|
||||
[.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-non-standard[See equivalent in the Reactive stack]#
|
||||
|
||||
You can extend an RFC 7807 response with non-standard fields in one of two ways.
|
||||
You can extend an RFC 9457 response with non-standard fields in one of two ways.
|
||||
|
||||
One, insert into the "properties" `Map` of `ProblemDetail`. When using the Jackson
|
||||
library, the Spring Framework registers `ProblemDetailJacksonMixin` that ensures this
|
||||
@@ -79,9 +79,11 @@ message code arguments for the "detail" field. `ResponseEntityExceptionHandler`
|
||||
these through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
and updates the corresponding `ProblemDetail` fields accordingly.
|
||||
|
||||
The default strategy for message codes follows the pattern:
|
||||
The default strategy for message codes is as follows:
|
||||
|
||||
`problemDetail.[type|title|detail].[fully qualified exception class name]`
|
||||
* "type": `problemDetail.type.[fully qualified exception class name]`
|
||||
* "title": `problemDetail.title.[fully qualified exception class name]`
|
||||
* "detail": `problemDetail.[fully qualified exception class name][suffix]`
|
||||
|
||||
An `ErrorResponse` may expose more than one message code, typically adding a suffix
|
||||
to the default message code. The table below lists message codes, and arguments for
|
||||
@@ -136,7 +138,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
|
||||
| `MethodArgumentNotValidException`
|
||||
| (default)
|
||||
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
|
||||
Message codes and arguments for each error are also resolvedvia `MessageSource`.
|
||||
Message codes and arguments for each error are also resolved via `MessageSource`.
|
||||
|
||||
| `MissingRequestHeaderException`
|
||||
| (default)
|
||||
|
||||
@@ -52,14 +52,10 @@ The following example shows how to achieve the same configuration in XML:
|
||||
</mvc:interceptors>
|
||||
----
|
||||
|
||||
NOTE: Interceptors are not ideally suited as a security layer due to the potential
|
||||
for a mismatch with annotated controller path matching, which can also match trailing
|
||||
slashes and path extensions transparently, along with other path matching options. Many
|
||||
of these options have been deprecated but the potential for a mismatch remains.
|
||||
Generally, we recommend using Spring Security which includes a dedicated
|
||||
https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#mvc-requestmatcher[MvcRequestMatcher]
|
||||
to align with Spring MVC path matching and also has a security firewall that blocks many
|
||||
unwanted characters in URL paths.
|
||||
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
|
||||
a mismatch with annotated controller path matching. Generally, we recommend using Spring
|
||||
Security, or alternatively a similar approach integrated with the Servlet filter chain,
|
||||
and applied as early as possible.
|
||||
|
||||
NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in
|
||||
turn detected by any `HandlerMapping` bean, including those from other frameworks.
|
||||
|
||||
+2
-2
@@ -228,11 +228,11 @@ level, xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[HandlerExceptionResolv
|
||||
See xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[ResponseEntity].
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
|
||||
+7
@@ -37,6 +37,13 @@ Kotlin::
|
||||
all controller methods. This is the effect of `@RestController`, which is nothing more
|
||||
than a meta-annotation marked with `@Controller` and `@ResponseBody`.
|
||||
|
||||
A `Resource` object can be returned for file content, copying the `InputStream`
|
||||
content of the provided resource to the response `OutputStream`. Note that the
|
||||
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
|
||||
close it after it has been copied to the response. If you are using `InputStreamResource`
|
||||
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
|
||||
(e.g. through a lambda expression that retrieves the actual `InputStream`).
|
||||
|
||||
You can use `@ResponseBody` with reactive types.
|
||||
See xref:web/webmvc/mvc-ann-async.adoc[Asynchronous Requests] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] for more details.
|
||||
|
||||
|
||||
+10
@@ -32,6 +32,16 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The body will usually be provided as a value object to be rendered to a corresponding
|
||||
response representation (e.g. JSON) by one of the registered `HttpMessageConverters`.
|
||||
|
||||
A `ResponseEntity<Resource>` can be returned for file content, copying the `InputStream`
|
||||
content of the provided resource to the response `OutputStream`. Note that the
|
||||
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
|
||||
close it after it has been copied to the response. If you are using `InputStreamResource`
|
||||
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
|
||||
(e.g. through a lambda expression that retrieves the actual `InputStream`).
|
||||
|
||||
Spring MVC supports using a single value xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type]
|
||||
to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive
|
||||
types for the body. This allows the following types of async responses:
|
||||
|
||||
+2
-2
@@ -23,11 +23,11 @@ supported for all return values.
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
|
||||
@@ -3,28 +3,40 @@
|
||||
|
||||
[.small]#xref:web/webflux/controller/ann-validation.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring MVC has built-in xref:core/validation/validator.adoc[Validation] support for
|
||||
`@RequestMapping` methods, including the option to use
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
Spring MVC has built-in xref:core/validation/validator.adoc[validation] for
|
||||
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
Validation may be applied at one of two levels:
|
||||
|
||||
First, resolvers for
|
||||
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
|
||||
resolvers validate a method argument individually if the method parameter is annotated
|
||||
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
|
||||
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
|
||||
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
|
||||
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
|
||||
directly on method parameters, or on the method (for the return value), then method
|
||||
validation must be applied, and that supersedes validation at the method argument level
|
||||
because method validation covers both method parameter constraints and nested constraints
|
||||
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
|
||||
|
||||
Applications must handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
method signature. The two exceptions, however are designed to be very similar, and can be
|
||||
handled with almost identical code. The main difference is that the former is for a single
|
||||
object while the latter is for a list of method parameters.
|
||||
|
||||
NOTE: `@Valid` is not a constraint annotation, but rather for nested constraints within
|
||||
an Object. Therefore, by itself `@Valid` does not lead to method validation. `@NotNull`
|
||||
on the other hand is a constraint, and adding it to an `@Valid` parameter leads to method
|
||||
validation. For nullability specifically, you may also use the `required` flag of
|
||||
`@RequestBody` or `@ModelAttribute`.
|
||||
|
||||
Method validation may be used in combination with `Errors` or `BindingResult` method
|
||||
parameters. However, the controller method is called only if all validation errors are on
|
||||
method parameters with an `Errors` immediately after. If there are validation errors on
|
||||
any other method parameter then `HandlerMethodValidationException` is raised.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webmvc/mvc-config/validation.adoc[WebMvc config], or locally through an
|
||||
|
||||
+18
-23
@@ -1,34 +1,29 @@
|
||||
[[mvc-handlermapping-interceptor]]
|
||||
= Interception
|
||||
|
||||
All `HandlerMapping` implementations support handler interceptors that are useful when
|
||||
you want to apply specific functionality to certain requests -- for example, checking for
|
||||
a principal. Interceptors must implement `HandlerInterceptor` from the
|
||||
`org.springframework.web.servlet` package with three methods that should provide enough
|
||||
flexibility to do all kinds of pre-processing and post-processing:
|
||||
All `HandlerMapping` implementations support handler interception which is useful when
|
||||
you want to apply functionality across requests. A `HandlerInterceptor` can implement the
|
||||
following:
|
||||
|
||||
* `preHandle(..)`: Before the actual handler is run
|
||||
* `postHandle(..)`: After the handler is run
|
||||
* `afterCompletion(..)`: After the complete request has finished
|
||||
* `preHandle(..)` -- callback before the actual handler is run that returns a boolean.
|
||||
If the method returns `true`, execution continues; if it returns `false`, the rest of the
|
||||
execution chain is bypassed and the handler is not called.
|
||||
* `postHandle(..)` -- callback after the handler is run.
|
||||
* `afterCompletion(..)` -- callback after the complete request has finished.
|
||||
|
||||
The `preHandle(..)` method returns a boolean value. You can use this method to break or
|
||||
continue the processing of the execution chain. When this method returns `true`, the
|
||||
handler execution chain continues. When it returns false, the `DispatcherServlet`
|
||||
assumes the interceptor itself has taken care of requests (and, for example, rendered an
|
||||
appropriate view) and does not continue executing the other interceptors and the actual
|
||||
handler in the execution chain.
|
||||
NOTE: For `@ResponseBody` and `ResponseEntity` controller methods, the response is written
|
||||
and committed within the `HandlerAdapter`, before `postHandle` is called. That means it is
|
||||
too late to change the response, such as to add an extra header. You can implement
|
||||
`ResponseBodyAdvice` and declare it as an
|
||||
xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it
|
||||
directly on `RequestMappingHandlerAdapter`.
|
||||
|
||||
See xref:web/webmvc/mvc-config/interceptors.adoc[Interceptors] in the section on MVC configuration for examples of how to
|
||||
configure interceptors. You can also register them directly by using setters on individual
|
||||
`HandlerMapping` implementations.
|
||||
|
||||
`postHandle` method is less useful with `@ResponseBody` and `ResponseEntity` methods for
|
||||
which the response is written and committed within the `HandlerAdapter` and before
|
||||
`postHandle`. That means it is too late to make any changes to the response, such as adding
|
||||
an extra header. For such scenarios, you can implement `ResponseBodyAdvice` and either
|
||||
declare it as an xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it directly on
|
||||
`RequestMappingHandlerAdapter`.
|
||||
|
||||
|
||||
|
||||
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
|
||||
a mismatch with annotated controller path matching. Generally, we recommend using Spring
|
||||
Security, or alternatively a similar approach integrated with the Servlet filter chain,
|
||||
and applied as early as possible.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ If configured with a task scheduler, the simple broker supports
|
||||
https://stomp.github.io/stomp-specification-1.2.html#Heart-beating[STOMP heartbeats].
|
||||
To configure a scheduler, you can declare your own `TaskScheduler` bean and set it through
|
||||
the `MessageBrokerRegistry`. Alternatively, you can use the one that is automatically
|
||||
declared in the built-in WebSocket configuration, however, you'll' need `@Lazy` to avoid
|
||||
declared in the built-in WebSocket configuration, however, you'll need `@Lazy` to avoid
|
||||
a cycle between the built-in WebSocket configuration and your
|
||||
`WebSocketMessageBrokerConfigurer`. For example:
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"antora": "3.2.0-alpha.4",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.2",
|
||||
"@antora/collector-extension": "1.0.0-alpha.3",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.11.1",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
}
|
||||
}
|
||||
@@ -8,26 +8,26 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.4"))
|
||||
api(platform("io.netty:netty-bom:4.1.107.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.7"))
|
||||
api(platform("io.netty:netty-bom:4.1.111.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.4"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.7"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.19"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.21"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.25.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.7"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.7"))
|
||||
api(platform("org.assertj:assertj-bom:3.26.0"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.10"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.10"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.11.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.12.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.6.1")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.6.2")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.librepdf:openpdf:1.3.42")
|
||||
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.10.1")
|
||||
@@ -55,9 +55,9 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.12.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.12.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.12.Final")
|
||||
api("io.undertow:undertow-core:2.3.13.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.13.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.13.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -103,19 +103,19 @@ dependencies {
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.19")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.19")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.19")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.19")
|
||||
api("org.aspectj:aspectjrt:1.9.21.1")
|
||||
api("org.aspectj:aspectjtools:1.9.21.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.21.1")
|
||||
api("org.awaitility:awaitility:4.2.0")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.24")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.24")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.24")
|
||||
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.1")
|
||||
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.eclipse.jetty:jetty-reactive-httpclient:4.0.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.4")
|
||||
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")
|
||||
@@ -130,7 +130,7 @@ dependencies {
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.2")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.4.6.0")
|
||||
api("org.jruby:jruby:9.4.7.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.14")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
@@ -139,7 +139,7 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.1")
|
||||
api("org.slf4j:slf4j-api:2.0.12")
|
||||
api("org.slf4j:slf4j-api:2.0.13")
|
||||
api("org.testng:testng:7.9.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.1.5-SNAPSHOT
|
||||
version=6.1.10
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
@@ -6,6 +6,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
|
||||
classpath.from(sourceSets["main"].runtimeClasspath)
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/"))
|
||||
packageListUrl.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/element-list"))
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://projectreactor.io/docs/core/release/api/"))
|
||||
@@ -21,6 +22,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/"))
|
||||
packageListUrl.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list"))
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"))
|
||||
|
||||
@@ -29,7 +29,7 @@ publishing {
|
||||
developer {
|
||||
id = "jhoeller"
|
||||
name = "Juergen Hoeller"
|
||||
email = "jhoeller@pivotal.io"
|
||||
email = "juergen.hoeller@broadcom.com"
|
||||
}
|
||||
}
|
||||
issueManagement {
|
||||
|
||||
@@ -87,12 +87,12 @@ rootProject.ext {
|
||||
gradle.taskGraph.afterTask { Task task, TaskState state ->
|
||||
if (!resolvedMainToolchain && task instanceof JavaCompile && task.javaCompiler.isPresent()) {
|
||||
def metadata = task.javaCompiler.get().metadata
|
||||
task.project.buildScan.value('Main toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
|
||||
task.project.develocity.buildScan.value('Main toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
|
||||
resolvedMainToolchain = true
|
||||
}
|
||||
if (testToolchainConfigured() && !resolvedTestToolchain && task instanceof Test && task.javaLauncher.isPresent()) {
|
||||
def metadata = task.javaLauncher.get().metadata
|
||||
task.project.buildScan.value('Test toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
|
||||
task.project.develocity.buildScan.value('Test toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
|
||||
resolvedTestToolchain = true
|
||||
}
|
||||
}
|
||||
|
||||
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.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ class EnableCachingIntegrationTests {
|
||||
// attempt was made to look up the AJ aspect. It's due to classpath issues
|
||||
// in integration-tests that it's not found.
|
||||
assertThatException().isThrownBy(ctx::refresh)
|
||||
.withMessageContaining("AspectJCachingConfiguration");
|
||||
.withMessageContaining("AspectJCachingConfiguration");
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vendored
+1
-2
@@ -541,8 +541,7 @@ public class EnvironmentSystemIntegrationTests {
|
||||
{
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.getEnvironment().setRequiredProperties("foo", "bar");
|
||||
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
|
||||
ctx::refresh);
|
||||
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(ctx::refresh);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
+2
-3
@@ -97,9 +97,8 @@ class EnableTransactionManagementIntegrationTests {
|
||||
// this test is a bit fragile, but gets the job done, proving that an
|
||||
// attempt was made to look up the AJ aspect. It's due to classpath issues
|
||||
// in integration-tests that it's not found.
|
||||
assertThatException()
|
||||
.isThrownBy(ctx::refresh)
|
||||
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
|
||||
assertThatException().isThrownBy(ctx::refresh)
|
||||
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.16.2"
|
||||
id "io.spring.ge.conventions" version "0.0.15"
|
||||
id "com.gradle.develocity" version "3.17.2"
|
||||
id "io.spring.ge.conventions" version "0.0.17"
|
||||
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ rootProject.children.each {project ->
|
||||
}
|
||||
|
||||
settings.gradle.projectsLoaded {
|
||||
gradleEnterprise {
|
||||
develocity {
|
||||
buildScan {
|
||||
File buildDir = settings.gradle.rootProject
|
||||
.getLayout().getBuildDirectory().getAsFile().get()
|
||||
|
||||
+45
-10
@@ -18,6 +18,7 @@ package org.springframework.aop.aspectj;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
@@ -41,6 +42,7 @@ import org.aspectj.weaver.tools.PointcutParameter;
|
||||
import org.aspectj.weaver.tools.PointcutParser;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
import org.aspectj.weaver.tools.ShadowMatch;
|
||||
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
|
||||
|
||||
import org.springframework.aop.ClassFilter;
|
||||
import org.springframework.aop.IntroductionAwareMethodMatcher;
|
||||
@@ -85,6 +87,8 @@ import org.springframework.util.StringUtils;
|
||||
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
|
||||
|
||||
private static final String AJC_MAGIC = "ajc$";
|
||||
|
||||
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = Set.of(
|
||||
PointcutPrimitive.EXECUTION,
|
||||
PointcutPrimitive.ARGS,
|
||||
@@ -102,6 +106,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
@Nullable
|
||||
private Class<?> pointcutDeclarationScope;
|
||||
|
||||
private boolean aspectCompiledByAjc;
|
||||
|
||||
private String[] pointcutParameterNames = new String[0];
|
||||
|
||||
private Class<?>[] pointcutParameterTypes = new Class<?>[0];
|
||||
@@ -115,6 +121,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
@Nullable
|
||||
private transient PointcutExpression pointcutExpression;
|
||||
|
||||
private transient boolean pointcutParsingFailed = false;
|
||||
|
||||
private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(32);
|
||||
|
||||
|
||||
@@ -131,7 +139,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
* @param paramTypes the parameter types for the pointcut
|
||||
*/
|
||||
public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
|
||||
this.pointcutDeclarationScope = declarationScope;
|
||||
setPointcutDeclarationScope(declarationScope);
|
||||
if (paramNames.length != paramTypes.length) {
|
||||
throw new IllegalStateException(
|
||||
"Number of pointcut parameter names must match number of pointcut parameter types");
|
||||
@@ -146,6 +154,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
*/
|
||||
public void setPointcutDeclarationScope(Class<?> pointcutDeclarationScope) {
|
||||
this.pointcutDeclarationScope = pointcutDeclarationScope;
|
||||
this.aspectCompiledByAjc = compiledByAjc(pointcutDeclarationScope);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,25 +179,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public ClassFilter getClassFilter() {
|
||||
obtainPointcutExpression();
|
||||
checkExpression();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodMatcher getMethodMatcher() {
|
||||
obtainPointcutExpression();
|
||||
checkExpression();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether this pointcut is ready to match,
|
||||
* lazily building the underlying AspectJ pointcut expression.
|
||||
* Check whether this pointcut is ready to match.
|
||||
*/
|
||||
private PointcutExpression obtainPointcutExpression() {
|
||||
private void checkExpression() {
|
||||
if (getExpression() == null) {
|
||||
throw new IllegalStateException("Must set property 'expression' before attempting to match");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily build the underlying AspectJ pointcut expression.
|
||||
*/
|
||||
private PointcutExpression obtainPointcutExpression() {
|
||||
if (this.pointcutExpression == null) {
|
||||
this.pointcutClassLoader = determinePointcutClassLoader();
|
||||
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
|
||||
@@ -265,10 +279,18 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public boolean matches(Class<?> targetClass) {
|
||||
PointcutExpression pointcutExpression = obtainPointcutExpression();
|
||||
if (this.pointcutParsingFailed) {
|
||||
// Pointcut parsing failed before below -> avoid trying again.
|
||||
return false;
|
||||
}
|
||||
if (this.aspectCompiledByAjc && compiledByAjc(targetClass)) {
|
||||
// ajc-compiled aspect class for ajc-compiled target class -> already weaved.
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
|
||||
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
|
||||
@@ -279,6 +301,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException | IllegalStateException | UnsupportedPointcutPrimitiveException ex) {
|
||||
this.pointcutParsingFailed = true;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Pointcut parser rejected expression [" + getExpression() + "]: " + ex);
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("PointcutExpression matching rejected target class", ex);
|
||||
}
|
||||
@@ -287,7 +315,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
|
||||
obtainPointcutExpression();
|
||||
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
|
||||
|
||||
// Special handling for this, target, @this, @target, @annotation
|
||||
@@ -325,7 +352,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
obtainPointcutExpression();
|
||||
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
|
||||
|
||||
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
|
||||
@@ -521,6 +547,15 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
return resolveExpression().contains("@annotation");
|
||||
}
|
||||
|
||||
private static boolean compiledByAjc(Class<?> clazz) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getName().startsWith(AJC_MAGIC)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
|
||||
+2
-30
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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,7 +18,6 @@ package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
@@ -56,8 +55,6 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
|
||||
|
||||
private static final String AJC_MAGIC = "ajc$";
|
||||
|
||||
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
|
||||
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
|
||||
|
||||
@@ -68,37 +65,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
|
||||
|
||||
|
||||
/**
|
||||
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
|
||||
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
|
||||
* is that aspects written in the code-style (AspectJ language) also have the annotation present
|
||||
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAspect(Class<?> clazz) {
|
||||
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
|
||||
}
|
||||
|
||||
private boolean hasAspectAnnotation(Class<?> clazz) {
|
||||
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to detect this as "code-style" AspectJ aspects should not be
|
||||
* interpreted by Spring AOP.
|
||||
*/
|
||||
static boolean compiledByAjc(Class<?> clazz) {
|
||||
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
|
||||
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
|
||||
// an implementation detail of the AspectJ compiler.
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getName().startsWith(AJC_MAGIC)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Class<?> aspectClass) throws AopConfigException {
|
||||
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
|
||||
@@ -115,6 +86,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find and return the first AspectJ annotation on the given method
|
||||
* (there <i>should</i> only be one anyway...).
|
||||
|
||||
+17
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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,12 +16,15 @@
|
||||
|
||||
package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -33,21 +36,33 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||
|
||||
private static final String AJC_MAGIC = "ajc$";
|
||||
|
||||
private static final boolean aspectjPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
|
||||
AspectJAdvisorBeanRegistrationAotProcessor.class.getClassLoader());
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
if (aspectjPresent) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
if (AbstractAspectJAdvisorFactory.compiledByAjc(beanClass)) {
|
||||
if (compiledByAjc(beanClass)) {
|
||||
return new AspectJAdvisorContribution(beanClass);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean compiledByAjc(Class<?> clazz) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getName().startsWith(AJC_MAGIC)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static class AspectJAdvisorContribution implements BeanRegistrationAotContribution {
|
||||
|
||||
|
||||
+11
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -124,10 +124,16 @@ public class AspectMetadata implements Serializable {
|
||||
* Extract contents from String of form {@code pertarget(contents)}.
|
||||
*/
|
||||
private String findPerClause(Class<?> aspectClass) {
|
||||
String str = aspectClass.getAnnotation(Aspect.class).value();
|
||||
int beginIndex = str.indexOf('(') + 1;
|
||||
int endIndex = str.length() - 1;
|
||||
return str.substring(beginIndex, endIndex);
|
||||
Aspect ann = aspectClass.getAnnotation(Aspect.class);
|
||||
if (ann == null) {
|
||||
return "";
|
||||
}
|
||||
String value = ann.value();
|
||||
int beginIndex = value.indexOf('(');
|
||||
if (beginIndex < 0) {
|
||||
return "";
|
||||
}
|
||||
return value.substring(beginIndex + 1, value.length() - 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+31
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,9 +22,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.aspectj.lang.reflect.PerClauseKind;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.framework.AopConfigException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -40,6 +43,8 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class BeanFactoryAspectJAdvisorsBuilder {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(BeanFactoryAspectJAdvisorsBuilder.class);
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
private final AspectJAdvisorFactory advisorFactory;
|
||||
@@ -102,30 +107,37 @@ public class BeanFactoryAspectJAdvisorsBuilder {
|
||||
continue;
|
||||
}
|
||||
if (this.advisorFactory.isAspect(beanType)) {
|
||||
aspectNames.add(beanName);
|
||||
AspectMetadata amd = new AspectMetadata(beanType, beanName);
|
||||
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
|
||||
MetadataAwareAspectInstanceFactory factory =
|
||||
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
|
||||
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
|
||||
if (this.beanFactory.isSingleton(beanName)) {
|
||||
this.advisorsCache.put(beanName, classAdvisors);
|
||||
try {
|
||||
AspectMetadata amd = new AspectMetadata(beanType, beanName);
|
||||
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
|
||||
MetadataAwareAspectInstanceFactory factory =
|
||||
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
|
||||
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
|
||||
if (this.beanFactory.isSingleton(beanName)) {
|
||||
this.advisorsCache.put(beanName, classAdvisors);
|
||||
}
|
||||
else {
|
||||
this.aspectFactoryCache.put(beanName, factory);
|
||||
}
|
||||
advisors.addAll(classAdvisors);
|
||||
}
|
||||
else {
|
||||
// Per target or per this.
|
||||
if (this.beanFactory.isSingleton(beanName)) {
|
||||
throw new IllegalArgumentException("Bean with name '" + beanName +
|
||||
"' is a singleton, but aspect instantiation model is not singleton");
|
||||
}
|
||||
MetadataAwareAspectInstanceFactory factory =
|
||||
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
|
||||
this.aspectFactoryCache.put(beanName, factory);
|
||||
advisors.addAll(this.advisorFactory.getAdvisors(factory));
|
||||
}
|
||||
advisors.addAll(classAdvisors);
|
||||
aspectNames.add(beanName);
|
||||
}
|
||||
else {
|
||||
// Per target or per this.
|
||||
if (this.beanFactory.isSingleton(beanName)) {
|
||||
throw new IllegalArgumentException("Bean with name '" + beanName +
|
||||
"' is a singleton, but aspect instantiation model is not singleton");
|
||||
catch (IllegalArgumentException | IllegalStateException | AopConfigException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Ignoring incompatible aspect [" + beanType.getName() + "]: " + ex);
|
||||
}
|
||||
MetadataAwareAspectInstanceFactory factory =
|
||||
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
|
||||
this.aspectFactoryCache.put(beanName, factory);
|
||||
advisors.addAll(this.advisorFactory.getAdvisors(factory));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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,6 +50,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConvertingComparator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -133,17 +134,19 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
|
||||
List<Advisor> advisors = new ArrayList<>();
|
||||
for (Method method : getAdvisorMethods(aspectClass)) {
|
||||
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
|
||||
// to getAdvisor(...) to represent the "current position" in the declared methods list.
|
||||
// However, since Java 7 the "current position" is not valid since the JDK no longer
|
||||
// returns declared methods in the order in which they are declared in the source code.
|
||||
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
|
||||
// discovered via reflection in order to support reliable advice ordering across JVM launches.
|
||||
// Specifically, a value of 0 aligns with the default value used in
|
||||
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
|
||||
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
|
||||
if (advisor != null) {
|
||||
advisors.add(advisor);
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, aspectClass))) {
|
||||
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
|
||||
// to getAdvisor(...) to represent the "current position" in the declared methods list.
|
||||
// However, since Java 7 the "current position" is not valid since the JDK no longer
|
||||
// returns declared methods in the order in which they are declared in the source code.
|
||||
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
|
||||
// discovered via reflection in order to support reliable advice ordering across JVM launches.
|
||||
// Specifically, a value of 0 aligns with the default value used in
|
||||
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
|
||||
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
|
||||
if (advisor != null) {
|
||||
advisors.add(advisor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +213,16 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
return null;
|
||||
}
|
||||
|
||||
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
|
||||
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
|
||||
try {
|
||||
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
|
||||
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
|
||||
}
|
||||
catch (IllegalArgumentException | IllegalStateException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Ignoring incompatible advice method: " + candidateAdviceMethod, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+2
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link BeanDefinitionParser} responsible for parsing the
|
||||
@@ -51,6 +52,7 @@ class SpringConfiguredBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
if (!parserContext.getRegistry().containsBeanDefinition(BEAN_CONFIGURER_ASPECT_BEAN_NAME)) {
|
||||
RootBeanDefinition def = new RootBeanDefinition();
|
||||
|
||||
@@ -369,8 +369,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
|
||||
advisor.validateInterfaces();
|
||||
// If the advisor passed validation, we can make the change.
|
||||
Class<?>[] ifcs = advisor.getInterfaces();
|
||||
for (Class<?> ifc : ifcs) {
|
||||
for (Class<?> ifc : advisor.getInterfaces()) {
|
||||
addInterface(ifc);
|
||||
}
|
||||
}
|
||||
@@ -489,20 +488,27 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
|
||||
*/
|
||||
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
|
||||
if (this.methodCache == null) {
|
||||
List<Object> cachedInterceptors;
|
||||
if (this.methodCache != null) {
|
||||
// Method-specific cache for method-specific pointcuts
|
||||
MethodCacheKey cacheKey = new MethodCacheKey(method);
|
||||
cachedInterceptors = this.methodCache.get(cacheKey);
|
||||
if (cachedInterceptors == null) {
|
||||
cachedInterceptors = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
|
||||
this, method, targetClass);
|
||||
this.methodCache.put(cacheKey, cachedInterceptors);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Shared cache since there are no method-specific advisors (see below).
|
||||
List<Object> cachedInterceptors = this.cachedInterceptors;
|
||||
cachedInterceptors = this.cachedInterceptors;
|
||||
if (cachedInterceptors == null) {
|
||||
cachedInterceptors = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
|
||||
this, method, targetClass);
|
||||
this.cachedInterceptors = cachedInterceptors;
|
||||
}
|
||||
return cachedInterceptors;
|
||||
}
|
||||
|
||||
// Method-specific cache for method-specific pointcuts
|
||||
return this.methodCache.computeIfAbsent(new MethodCacheKey(method), k ->
|
||||
this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass));
|
||||
return cachedInterceptors;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -94,14 +94,17 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
private static final int INVOKE_HASHCODE = 6;
|
||||
|
||||
|
||||
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
||||
|
||||
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
|
||||
"kotlinx.coroutines.reactor.MonoKt", CglibAopProxy.class.getClassLoader());
|
||||
|
||||
/** Logger available to subclasses; static to optimize serialization. */
|
||||
protected static final Log logger = LogFactory.getLog(CglibAopProxy.class);
|
||||
|
||||
/** Keeps track of the Classes that we have validated for final methods. */
|
||||
private static final Map<Class<?>, Boolean> validatedClasses = new WeakHashMap<>();
|
||||
|
||||
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
||||
|
||||
|
||||
/** The configuration used to configure this proxy. */
|
||||
protected final AdvisedSupport advised;
|
||||
@@ -421,7 +424,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
throw new AopInvocationException(
|
||||
"Null return value from advice does not match primitive return type for: " + method);
|
||||
}
|
||||
if (KotlinDetector.isSuspendingFunction(method)) {
|
||||
if (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method)) {
|
||||
return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
|
||||
CoroutinesUtils.asFlow(returnValue) :
|
||||
CoroutinesUtils.awaitSingleOrNull(returnValue, arguments[arguments.length - 1]);
|
||||
|
||||
@@ -75,6 +75,9 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||
|
||||
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
||||
|
||||
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
|
||||
"kotlinx.coroutines.reactor.MonoKt", JdkDynamicAopProxy.class.getClassLoader());
|
||||
|
||||
/** We use a static Log to avoid serialization issues. */
|
||||
private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);
|
||||
|
||||
@@ -234,7 +237,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||
throw new AopInvocationException(
|
||||
"Null return value from advice does not match primitive return type for: " + method);
|
||||
}
|
||||
if (KotlinDetector.isSuspendingFunction(method)) {
|
||||
if (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method)) {
|
||||
return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
|
||||
CoroutinesUtils.asFlow(retVal) : CoroutinesUtils.awaitSingleOrNull(retVal, args[args.length - 1]);
|
||||
}
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,9 @@ import java.util.List;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.AopConfigException;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
@@ -97,7 +99,13 @@ public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyC
|
||||
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
|
||||
extendAdvisors(eligibleAdvisors);
|
||||
if (!eligibleAdvisors.isEmpty()) {
|
||||
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
|
||||
try {
|
||||
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
throw new AopConfigException("Advisor sorting failed with unexpected bean creation, probably due " +
|
||||
"to custom use of the Ordered interface. Consider using the @Order annotation instead.", ex);
|
||||
}
|
||||
}
|
||||
return eligibleAdvisors;
|
||||
}
|
||||
|
||||
+2
@@ -270,6 +270,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
|
||||
Object cacheKey = getCacheKey(beanClass, beanName);
|
||||
|
||||
@@ -311,6 +312,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
|
||||
* @see #getAdvicesAndAdvisorsForBean
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
|
||||
if (bean != null) {
|
||||
Object cacheKey = getCacheKey(bean.getClass(), beanName);
|
||||
|
||||
+1
@@ -81,6 +81,7 @@ public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator {
|
||||
* @see #setBeanNames(String...)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
protected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) {
|
||||
return (isSupportedBeanName(beanClass, beanName) ?
|
||||
super.getCustomTargetSource(beanClass, beanName) : null);
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
if (beanClass.equals(ScopedProxyFactoryBean.class)) {
|
||||
|
||||
@@ -117,6 +117,7 @@ public class ScopedProxyFactoryBean extends ProxyConfig
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getObject() {
|
||||
if (this.proxy == null) {
|
||||
throw new FactoryBeanNotInitializedException();
|
||||
@@ -125,6 +126,7 @@ public class ScopedProxyFactoryBean extends ProxyConfig
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getObjectType() {
|
||||
if (this.proxy != null) {
|
||||
return this.proxy.getClass();
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.Set;
|
||||
import kotlin.coroutines.Continuation;
|
||||
import kotlin.coroutines.CoroutineContext;
|
||||
import kotlinx.coroutines.Job;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.AopInvocationException;
|
||||
@@ -65,6 +64,10 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public abstract class AopUtils {
|
||||
|
||||
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
|
||||
"kotlinx.coroutines.reactor.MonoKt", AopUtils.class.getClassLoader());
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
|
||||
* <p>This method additionally checks if the given object is an instance
|
||||
@@ -347,8 +350,8 @@ public abstract class AopUtils {
|
||||
// Use reflection to invoke the method.
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return KotlinDetector.isSuspendingFunction(method) ?
|
||||
KotlinDelegate.invokeSuspendingFunction(method, target, args) : method.invoke(target, args);
|
||||
return (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method) ?
|
||||
KotlinDelegate.invokeSuspendingFunction(method, target, args) : method.invoke(target, args));
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
// Invoked method threw a checked exception.
|
||||
@@ -364,18 +367,18 @@ public abstract class AopUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on Kotlin at runtime.
|
||||
*/
|
||||
private static class KotlinDelegate {
|
||||
|
||||
public static Publisher<?> invokeSuspendingFunction(Method method, Object target, Object... args) {
|
||||
public static Object invokeSuspendingFunction(Method method, @Nullable Object target, Object... args) {
|
||||
Continuation<?> continuation = (Continuation<?>) args[args.length -1];
|
||||
Assert.state(continuation != null, "No Continuation available");
|
||||
CoroutineContext context = continuation.getContext().minusKey(Job.Key);
|
||||
return CoroutinesUtils.invokeSuspendingFunction(context, method, target, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-13
@@ -23,8 +23,6 @@ import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import test.annotation.EmptySpringAnnotation;
|
||||
@@ -41,14 +39,13 @@ import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class AspectJExpressionPointcutTests {
|
||||
@@ -63,7 +60,7 @@ class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws NoSuchMethodException {
|
||||
void setup() throws NoSuchMethodException {
|
||||
getAge = TestBean.class.getMethod("getAge");
|
||||
setAge = TestBean.class.getMethod("setAge", int.class);
|
||||
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
|
||||
@@ -174,7 +171,7 @@ class AspectJExpressionPointcutTests {
|
||||
void testFriendlyErrorOnNoLocationClassMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.matches(ITestBean.class))
|
||||
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
|
||||
.withMessageContaining("expression");
|
||||
}
|
||||
|
||||
@@ -182,7 +179,7 @@ class AspectJExpressionPointcutTests {
|
||||
void testFriendlyErrorOnNoLocation2ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.matches(getAge, ITestBean.class))
|
||||
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
|
||||
.withMessageContaining("expression");
|
||||
}
|
||||
|
||||
@@ -190,13 +187,13 @@ class AspectJExpressionPointcutTests {
|
||||
void testFriendlyErrorOnNoLocation3ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.matches(getAge, ITestBean.class, (Object[]) null))
|
||||
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
|
||||
.withMessageContaining("expression");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMatchWithArgs() throws Exception {
|
||||
void testMatchWithArgs() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number)) && args(Double)";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -246,7 +243,7 @@ class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
void testInvalidExpression() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
|
||||
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
|
||||
}
|
||||
|
||||
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
|
||||
@@ -276,9 +273,7 @@ class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
void testWithUnsupportedPointcutPrimitive() {
|
||||
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
|
||||
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user