mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-18 09:59:03 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8107a2b561 | ||
|
|
94afeedad6 | ||
|
|
bb7ea37f1b | ||
|
|
9e0d1c734e | ||
|
|
3178df92bd | ||
|
|
c1aa1b7405 | ||
|
|
8c1b366bda | ||
|
|
d571c4097d | ||
|
|
803517c0cb | ||
|
|
8a511726cd | ||
|
|
4898ed3ad8 | ||
|
|
f768641c08 | ||
|
|
01a23e32b5 | ||
|
|
9d1156f1fd | ||
|
|
bcd78a2ccc | ||
|
|
b96592e4af | ||
|
|
9a396c8ed4 | ||
|
|
1ea92339e2 | ||
|
|
d3d8e05fa9 | ||
|
|
280861e7ee | ||
|
|
e2fae069dc | ||
|
|
3c6b001349 | ||
|
|
4acd6d6a7e | ||
|
|
26174aa9a0 | ||
|
|
486db005a1 | ||
|
|
6e260bc78e | ||
|
|
35d8c4d06f | ||
|
|
c1d241928c | ||
|
|
4a803961bc | ||
|
|
2b5229ff8f | ||
|
|
85c8bb674c | ||
|
|
2d478f7d9b | ||
|
|
8c151f5887 | ||
|
|
ea2a26206c | ||
|
|
2b276311eb | ||
|
|
b5a358019f | ||
|
|
e0144da4fe | ||
|
|
8ced135f49 | ||
|
|
5b33c7e2ce | ||
|
|
6316c06a97 | ||
|
|
ad83d5ebd9 | ||
|
|
34ed7a5e22 | ||
|
|
4a92dd6ed1 | ||
|
|
7cdb326623 | ||
|
|
f2fe79e56d | ||
|
|
ea42275dcf | ||
|
|
7a0612dd4f | ||
|
|
ee7a0d48c5 | ||
|
|
ec6b925191 | ||
|
|
751aa19671 | ||
|
|
c07da25bdf | ||
|
|
7da197de66 | ||
|
|
04b3bdc1b3 | ||
|
|
f3764f6d62 | ||
|
|
41db0fe5a3 | ||
|
|
772d361cf2 | ||
|
|
8a64fe9c45 | ||
|
|
dcc24a2325 | ||
|
|
b823aed17c | ||
|
|
fce57adc31 | ||
|
|
8e783e2ec9 | ||
|
|
178eb17191 | ||
|
|
3656241ff1 | ||
|
|
133a372f91 | ||
|
|
74a4b1a694 | ||
|
|
6e5cf0ce45 | ||
|
|
a99f4dd43c | ||
|
|
3170dd5714 | ||
|
|
495fd6b3a5 | ||
|
|
8d4208f030 | ||
|
|
82cf15c60f | ||
|
|
37c8f41633 | ||
|
|
0e9a1d72f5 | ||
|
|
0acdf80830 | ||
|
|
15d7a3b327 | ||
|
|
90ad7f947d | ||
|
|
49d2a202da | ||
|
|
df10251539 | ||
|
|
507406c3cd | ||
|
|
6dcdf19169 | ||
|
|
76239d083b | ||
|
|
40ea92621d | ||
|
|
b0149b842b | ||
|
|
a4720ccf77 | ||
|
|
15ef2b21f0 | ||
|
|
48971139c0 | ||
|
|
94beae1779 | ||
|
|
a7b1b59cbd | ||
|
|
996e3d3f18 | ||
|
|
73f5ddddcd | ||
|
|
675f25de72 | ||
|
|
692dbc9160 | ||
|
|
8647e90bc7 | ||
|
|
b9379e33d5 | ||
|
|
a784dbe286 | ||
|
|
3b492f3908 | ||
|
|
07cbd482a0 | ||
|
|
dadd474d21 | ||
|
|
0d08f8dfaf | ||
|
|
baae93f20a | ||
|
|
d186b381b9 | ||
|
|
ac0f8be0d8 | ||
|
|
6e3dc633f0 | ||
|
|
35921cc01f | ||
|
|
062032373e | ||
|
|
1994e0ebd0 | ||
|
|
dc7fc89aec | ||
|
|
f2a7f13d13 | ||
|
|
f8a2bdad87 | ||
|
|
9aeda49273 | ||
|
|
abd323d428 | ||
|
|
176bc2a133 | ||
|
|
8df51ad6cc | ||
|
|
957df686c4 | ||
|
|
11bb7b54e5 | ||
|
|
70ca103b22 | ||
|
|
7f1966f5f5 | ||
|
|
b556766e1a | ||
|
|
0c281fd1ce | ||
|
|
63f0894621 | ||
|
|
0c966029e2 | ||
|
|
05619b7450 | ||
|
|
f3ba3c9e1f | ||
|
|
89e62e7e31 | ||
|
|
e4d5ec9c0d | ||
|
|
f5564e7e31 | ||
|
|
6d04ea9e84 | ||
|
|
0f5bd82c5d | ||
|
|
a894818c9e | ||
|
|
9b42a40a2a | ||
|
|
719311f09b | ||
|
|
28a78170b5 | ||
|
|
8fa7d88a0c | ||
|
|
b1d025d2c6 | ||
|
|
181a5d3403 | ||
|
|
ae4214aa95 | ||
|
|
4f086322d0 | ||
|
|
e255ccca7d | ||
|
|
b5de644cbb | ||
|
|
7de2b24d81 | ||
|
|
b90624472e | ||
|
|
1600e2479b | ||
|
|
233725c8f5 | ||
|
|
8fb2f72282 | ||
|
|
a2feb9ffe6 | ||
|
|
27aa9e46c5 | ||
|
|
56d706d591 | ||
|
|
bbfe6a0473 | ||
|
|
13a43e76cb | ||
|
|
16e82cd693 | ||
|
|
516a2ca511 | ||
|
|
9726c7ed5f | ||
|
|
7e0818f4e9 | ||
|
|
ed13afa0d4 | ||
|
|
c08e5e9c1c | ||
|
|
97e9ddb2d7 | ||
|
|
f0e69a702c | ||
|
|
d1470bbb25 | ||
|
|
347f23a73f | ||
|
|
99b3e205d1 | ||
|
|
78f05d8f8e | ||
|
|
996b337f37 | ||
|
|
66ffc04fe5 | ||
|
|
ade96d275d | ||
|
|
072fa3f43d | ||
|
|
78dcdab3fc | ||
|
|
872b1addeb | ||
|
|
98d552f15f | ||
|
|
4d6e88dc98 | ||
|
|
0044c4c8a3 | ||
|
|
b00f691655 | ||
|
|
6ac642e301 | ||
|
|
11bdb43aad | ||
|
|
4074155d76 | ||
|
|
1277279527 | ||
|
|
d0331a049a | ||
|
|
7de9c8d58a | ||
|
|
bae022a0fc | ||
|
|
325c3e1ec7 | ||
|
|
67e5ea9509 | ||
|
|
41b8f13e91 | ||
|
|
167afd91be | ||
|
|
12f9a5c2a5 | ||
|
|
1a89363a5c | ||
|
|
34a307d4a6 | ||
|
|
0e4842062b | ||
|
|
76089feeb4 | ||
|
|
ad5bd67b31 | ||
|
|
0ee636af7f | ||
|
|
c64f2ee104 | ||
|
|
5059bbd58e | ||
|
|
cc0ca1b6a5 | ||
|
|
ee81785afc | ||
|
|
846a6a8f7c | ||
|
|
0d706f8da6 | ||
|
|
924849f55b | ||
|
|
136f78ebd0 | ||
|
|
0fc724b348 | ||
|
|
292959e2ca | ||
|
|
5383520388 | ||
|
|
0b5a9ea30a | ||
|
|
298e1db625 | ||
|
|
3f7f75abdb |
@@ -1,20 +0,0 @@
|
||||
name: Await HTTP Resource
|
||||
description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)'
|
||||
inputs:
|
||||
url:
|
||||
description: 'URL of the resource to await'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Await HTTP resource
|
||||
shell: bash
|
||||
run: |
|
||||
url=${{ inputs.url }}
|
||||
echo "Waiting for $url"
|
||||
until curl --fail --head --silent ${{ inputs.url }} > /dev/null
|
||||
do
|
||||
echo "."
|
||||
sleep 60
|
||||
done
|
||||
echo "$url is available"
|
||||
@@ -1,6 +1,18 @@
|
||||
name: 'Build'
|
||||
description: 'Builds the project, optionally publishing it to a local deployment repository'
|
||||
inputs:
|
||||
commercial-release-repository-url:
|
||||
description: 'URL of the release repository'
|
||||
required: false
|
||||
commercial-repository-password:
|
||||
description: 'Password for authentication with the commercial repository'
|
||||
required: false
|
||||
commercial-repository-username:
|
||||
description: 'Username for authentication with the commercial repository'
|
||||
required: false
|
||||
commercial-snapshot-repository-url:
|
||||
description: 'URL of the snapshot repository'
|
||||
required: false
|
||||
develocity-access-key:
|
||||
description: 'Access key for authentication with ge.spring.io'
|
||||
required: false
|
||||
@@ -46,11 +58,21 @@ runs:
|
||||
id: build
|
||||
if: ${{ inputs.publish == 'false' }}
|
||||
shell: bash
|
||||
env:
|
||||
COMMERCIAL_RELEASE_REPO_URL: ${{ inputs.commercial-release-repository-url }}
|
||||
COMMERCIAL_REPO_PASSWORD: ${{ inputs.commercial-repository-password }}
|
||||
COMMERCIAL_REPO_USERNAME: ${{ inputs.commercial-repository-username }}
|
||||
COMMERCIAL_SNAPSHOT_REPO_URL: ${{ inputs.commercial-snapshot-repository-url }}
|
||||
run: ./gradlew check antora
|
||||
- name: Publish
|
||||
id: publish
|
||||
if: ${{ inputs.publish == 'true' }}
|
||||
shell: bash
|
||||
env:
|
||||
COMMERCIAL_RELEASE_REPO_URL: ${{ inputs.commercial-release-repository-url }}
|
||||
COMMERCIAL_REPO_PASSWORD: ${{ inputs.commercial-repository-password }}
|
||||
COMMERCIAL_REPO_USERNAME: ${{ inputs.commercial-repository-username }}
|
||||
COMMERCIAL_SNAPSHOT_REPO_URL: ${{ inputs.commercial-snapshot-repository-url }}
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Read Version From gradle.properties
|
||||
id: read-version
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
name: Create GitHub Release
|
||||
description: 'Create the release on GitHub with a changelog'
|
||||
inputs:
|
||||
commercial:
|
||||
description: 'Whether to generate the changelog for the commercial release'
|
||||
required: true
|
||||
latest:
|
||||
description: 'Whether the release is the latest release'
|
||||
required: false
|
||||
default: 'false'
|
||||
milestone:
|
||||
description: 'Name of the GitHub milestone for which a release will be created'
|
||||
required: true
|
||||
@@ -15,13 +22,13 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
|
||||
uses: spring-io/github-changelog-generator@f7d7a87a3e7c627ecb8c26cf086c38ac5a939721 #v0.0.14
|
||||
with:
|
||||
config-file: .github/actions/create-github-release/changelog-generator.yml
|
||||
config-file: ${{ inputs.commercial && '.github/actions/create-github-release/changelog-generator-commercial.yml' || '.github/actions/create-github-release/changelog-generator-oss.yml' }}
|
||||
milestone: ${{ inputs.milestone }}
|
||||
token: ${{ inputs.token }}
|
||||
- name: Create GitHub Release
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md ${{ inputs.pre-release == 'true' && '--prerelease' || '' }}
|
||||
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md ${{ inputs.pre-release == 'true' && '--prerelease' || format('--latest={0}', inputs.latest) }}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework-commercial
|
||||
sections:
|
||||
- title: ":warning: Attention Required"
|
||||
labels:
|
||||
- "for: upgrade-attention"
|
||||
summary:
|
||||
mode: "member-comment"
|
||||
config:
|
||||
prefix: "Attention Required:"
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
- title: ":lady_beetle: Bug Fixes"
|
||||
labels:
|
||||
- "type: bug"
|
||||
- "type: regression"
|
||||
- title: ":notebook_with_decorative_cover: Documentation"
|
||||
labels:
|
||||
- "type: documentation"
|
||||
- title: ":hammer: Dependency Upgrades"
|
||||
sort: "title"
|
||||
labels:
|
||||
- "type: dependency-upgrade"
|
||||
contributors:
|
||||
exclude:
|
||||
names:
|
||||
- "bclozel"
|
||||
- "jhoeller"
|
||||
- "rstoyanchev"
|
||||
- "sbrannen"
|
||||
- "sdeleuze"
|
||||
- "snicoll"
|
||||
-2
@@ -27,9 +27,7 @@ changelog:
|
||||
names:
|
||||
- "bclozel"
|
||||
- "jhoeller"
|
||||
- "poutsma"
|
||||
- "rstoyanchev"
|
||||
- "sbrannen"
|
||||
- "sdeleuze"
|
||||
- "simonbasle"
|
||||
- "snicoll"
|
||||
@@ -29,27 +29,25 @@ runs:
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
|
||||
java-version: |
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
25
|
||||
${{ inputs.java-toolchain == 'true' && '25' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # 6.2.0
|
||||
with:
|
||||
cache-provider: basic
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
develocity-token-expiry: 4
|
||||
- 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
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $GRADLE_USER_HOME/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $GRADLE_USER_HOME/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $GRADLE_USER_HOME/gradle.properties
|
||||
- name: Configure Toolchain Properties
|
||||
if: ${{ inputs.java-toolchain == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ inputs.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', inputs.java-version) }} >> $HOME/.gradle/gradle.properties
|
||||
echo toolchainVersion=${{ inputs.java-version }} >> $GRADLE_USER_HOME/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-detect=false >> $GRADLE_USER_HOME/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-download=false >> $GRADLE_USER_HOME/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $GRADLE_USER_HOME/gradle.properties
|
||||
@@ -0,0 +1,7 @@
|
||||
name: Build Release
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build Release
|
||||
shell: bash
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository publishAllPublicationsToDeploymentRepository
|
||||
@@ -0,0 +1,16 @@
|
||||
artifactory:
|
||||
artifacts:
|
||||
- pattern: "/**/framework-api-*.zip"
|
||||
properties:
|
||||
zip.deployed: "false"
|
||||
zip.name: "spring-framework"
|
||||
- pattern: "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
zip.type: "docs"
|
||||
- pattern: "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
zip.type: "schema"
|
||||
maven-central:
|
||||
excludes:
|
||||
- "org/springframework/framework-api/**"
|
||||
- "org/springframework/framework-docs/**"
|
||||
@@ -0,0 +1,7 @@
|
||||
name: Test Release
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Test Release
|
||||
shell: bash
|
||||
run: ./gradlew check
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Sync to Maven Central
|
||||
description: 'Syncs a release to Maven Central and waits for it to be available for use'
|
||||
inputs:
|
||||
central-token-password:
|
||||
description: 'Password for authentication with central.sonatype.com'
|
||||
required: true
|
||||
central-token-username:
|
||||
description: 'Username for authentication with central.sonatype.com'
|
||||
required: true
|
||||
jfrog-cli-config-token:
|
||||
description: 'Config token for the JFrog CLI'
|
||||
required: true
|
||||
spring-framework-version:
|
||||
description: 'Version of Spring Framework that is being synced to Central'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
shell: bash
|
||||
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
|
||||
- name: Sync
|
||||
uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0
|
||||
with:
|
||||
token: ${{ inputs.central-token-password }}
|
||||
token-name: ${{ inputs.central-token-username }}
|
||||
- name: Await
|
||||
uses: ./.github/actions/await-http-resource
|
||||
with:
|
||||
url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/spring-context/{0}/spring-context-{0}.jar', inputs.spring-framework-version) }}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"aql": {
|
||||
"items.find": {
|
||||
"$and": [
|
||||
{
|
||||
"@build.name": "${buildName}",
|
||||
"@build.number": "${buildNumber}",
|
||||
"path": {
|
||||
"$nmatch": "org/springframework/framework-api/*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": "nexus/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
workflow:
|
||||
generator:
|
||||
project:
|
||||
java:
|
||||
versions:
|
||||
primary: 25
|
||||
workflows:
|
||||
release-train:
|
||||
build:
|
||||
env:
|
||||
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
|
||||
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
|
||||
COMMERCIAL_RELEASE_REPO_URL: vars.COMMERCIAL_RELEASE_REPO_URL
|
||||
test:
|
||||
env:
|
||||
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
|
||||
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
|
||||
COMMERCIAL_RELEASE_REPO_URL: vars.COMMERCIAL_RELEASE_REPO_URL
|
||||
@@ -7,27 +7,15 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
backport-issue:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
- name: Create Backport Issue
|
||||
uses: spring-io/backport-bot@v0.0.3
|
||||
with:
|
||||
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
|
||||
env:
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -8,51 +8,70 @@ concurrency:
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
name: Build and Deploy Snapshot
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' || github.repository == 'spring-projects/spring-framework-commercial' }}
|
||||
runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
commercial-release-repository-url: ${{ vars.COMMERCIAL_RELEASE_REPO_URL }}
|
||||
commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}
|
||||
commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}
|
||||
commercial-snapshot-repository-url: ${{ vars.COMMERCIAL_SNAPSHOT_REPO_URL }}
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
uses: spring-io/artifactory-deploy-action@aba148f1541e09adcf5735af90029fac9a6d3083 # v0.0.5
|
||||
with:
|
||||
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
|
||||
build-name: 'spring-framework-7.0.x'
|
||||
build-name: ${{ vars.COMMERCIAL && format('spring-framework-commercial-{0}', '7.0.x') || format('spring-framework-{0}', '7.0.x') }}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
project: ${{ vars.COMMERCIAL && 'spring' }}
|
||||
repository: ${{ vars.COMMERCIAL && 'spring-enterprise-maven-dev-local' || 'libs-snapshot-local' }}
|
||||
uri: ${{ vars.COMMERCIAL_DEPLOY_REPO_URL || 'https://repo.spring.io' }}
|
||||
username: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_USERNAME || secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_PASSWORD || secrets.ARTIFACTORY_PASSWORD }}
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
- name: Send Notification
|
||||
if: always()
|
||||
uses: ./.github/actions/send-notification
|
||||
with:
|
||||
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
run-name: ${{ format('{0} | Linux | Java 25', github.ref_name) }}
|
||||
status: ${{ job.status }}
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
trigger-docs-build:
|
||||
name: Trigger Docs Build
|
||||
needs: build-and-deploy-snapshot
|
||||
if: ${{ !vars.COMMERCIAL }} # remove when commercial support
|
||||
permissions:
|
||||
actions: write
|
||||
runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Run Deploy Docs Workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-deploy-snapshot
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}
|
||||
commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
opensource-repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
opensource-repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
|
||||
|
||||
@@ -7,10 +7,9 @@ jobs:
|
||||
name: Build Pull Request
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
@@ -19,7 +18,7 @@ jobs:
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
path: '**/build/reports/'
|
||||
@@ -2,31 +2,33 @@ name: CI
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
ci:
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' || github.repository == 'spring-projects/spring-framework-commercial' }}
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- id: ubuntu-latest
|
||||
- id: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }}
|
||||
name: Linux
|
||||
java:
|
||||
- version: 17
|
||||
toolchain: false
|
||||
toolchain: true
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 25
|
||||
toolchain: false
|
||||
- version: 26
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 17
|
||||
version: 25
|
||||
steps:
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
@@ -35,11 +37,15 @@ jobs:
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
commercial-release-repository-url: ${{ vars.COMMERCIAL_RELEASE_REPO_URL }}
|
||||
commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}
|
||||
commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}
|
||||
commercial-snapshot-repository-url: ${{ vars.COMMERCIAL_SNAPSHOT_REPO_URL }}
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
java-early-access: ${{ matrix.java.early-access || 'false' }}
|
||||
java-distribution: ${{ matrix.java.distribution }}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- '*.x'
|
||||
- '!gh-pages'
|
||||
tags:
|
||||
- 'v*'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
actions: write
|
||||
jobs:
|
||||
build:
|
||||
name: Dispatch docs deployment
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
- name: Dispatch (partial build)
|
||||
if: github.ref_type == 'branch'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
|
||||
- name: Dispatch (full build)
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Release Milestone
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.0.0-M[1-9]
|
||||
- v7.0.0-RC[1-9]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
trigger-docs-build:
|
||||
name: Trigger Docs Build
|
||||
permissions:
|
||||
actions: write
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Determine Version
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
- name: Run Deploy Docs Workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }}
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- trigger-docs-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
commercial: ${{ vars.COMMERCIAL }}
|
||||
milestone: ${{ needs.trigger-docs-build.outputs.version }}
|
||||
pre-release: true
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.0.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
trigger-docs-build:
|
||||
name: Trigger Docs Build
|
||||
permissions:
|
||||
actions: write
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Determine Version
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
- name: Run Deploy Docs Workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }}
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- trigger-docs-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
commercial: ${{ vars.COMMERCIAL }}
|
||||
latest: true
|
||||
milestone: ${{ needs.trigger-docs-build.outputs.version }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -1,95 +0,0 @@
|
||||
name: Release Milestone
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.0.0-M[1-9]
|
||||
- v7.0.0-RC[1-9]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-stage-release:
|
||||
name: Build and Stage Release
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
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
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-staging-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
sync-to-maven-central:
|
||||
name: Sync to Maven Central
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
|
||||
central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
|
||||
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
promote-release:
|
||||
name: Promote Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- sync-to-maven-central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- promote-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
milestone: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
pre-release: true
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -0,0 +1,93 @@
|
||||
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
|
||||
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
|
||||
|
||||
name: "Release Train – Build"
|
||||
run-name: "${{ inputs.callback-ref }} – Build"
|
||||
"on":
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
callback:
|
||||
description: "Repository to which a callback should be made upon completion"
|
||||
required: true
|
||||
type: "string"
|
||||
callback-ref:
|
||||
description: "Ref in the callback repository to which a callback should be made upon completion"
|
||||
required: true
|
||||
type: "string"
|
||||
release-train-maven-repository-url:
|
||||
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
|
||||
required: true
|
||||
type: "string"
|
||||
permissions:
|
||||
contents: "read"
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
jobs:
|
||||
build-release:
|
||||
name: "Build Release"
|
||||
runs-on: "ubuntu22-2-8"
|
||||
steps:
|
||||
- name: "Prevent Re-runs"
|
||||
id: "prevent-re-runs"
|
||||
run: |-
|
||||
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
|
||||
echo "Re-runs are prohibited. Use the 'Release Train – Retry' workflow to retry build failures"
|
||||
exit 1
|
||||
fi
|
||||
- name: "Set up Java"
|
||||
id: "set-up-java"
|
||||
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
|
||||
with:
|
||||
distribution: "liberica"
|
||||
java-version: "25"
|
||||
- name: "Check Out Code"
|
||||
id: "check-out-code"
|
||||
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
|
||||
- name: "Build Release"
|
||||
id: "build-release"
|
||||
uses: "./.github/actions/release-train-build"
|
||||
env:
|
||||
COMMERCIAL_RELEASE_REPO_URL: "${{ vars.COMMERCIAL_RELEASE_REPO_URL }}"
|
||||
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
|
||||
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
|
||||
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
|
||||
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
|
||||
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
|
||||
- name: "Upload Deployment Repository"
|
||||
id: "upload-deployment-repository"
|
||||
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
|
||||
with:
|
||||
name: "deployment-repository"
|
||||
path: "deployment-repository/**"
|
||||
- name: "Upload Deployment Spec"
|
||||
id: "upload-deployment-spec"
|
||||
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
|
||||
with:
|
||||
archive: "false"
|
||||
if-no-files-found: "ignore"
|
||||
name: "deployment-spec"
|
||||
path: ".github/actions/release-train-build/deployment-spec.yml"
|
||||
- name: "Save Build System Caches"
|
||||
id: "save-build-system-caches"
|
||||
uses: "actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
|
||||
with:
|
||||
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
|
||||
path: |-
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
- name: "Send Callback"
|
||||
id: "send-callback"
|
||||
if: "${{ !cancelled() }}"
|
||||
env:
|
||||
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
|
||||
run: |-
|
||||
gh workflow run callback \
|
||||
--repo ${{ inputs.callback }} \
|
||||
--ref ${{ inputs.callback-ref }} \
|
||||
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
|
||||
--field deployment-repository-artifact-identifier=${{ steps.upload-deployment-repository.outputs.artifact-id }} \
|
||||
--field deployment-spec-artifact-identifier=${{ steps.upload-deployment-spec.outputs.artifact-id }} \
|
||||
--field release-branch=${{ github.ref_name }} \
|
||||
--field release-repository=${{ github.repository }} \
|
||||
--field result=${{ job.status == 'success' && 'built' || 'build-failed' }} \
|
||||
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@@ -0,0 +1,55 @@
|
||||
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
|
||||
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
|
||||
|
||||
name: "Release Train – Join"
|
||||
run-name: "${{ inputs.release-train }} – Join"
|
||||
"on":
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
deployment-destination:
|
||||
description: "Destination to which the release should be deployed"
|
||||
options:
|
||||
- "Maven Central"
|
||||
- "Spring Enterprise"
|
||||
required: true
|
||||
type: "choice"
|
||||
release-train:
|
||||
description: "Release train"
|
||||
required: true
|
||||
type: "string"
|
||||
release-train-repository:
|
||||
default: "spring-io/release-train"
|
||||
description: "Release train repository"
|
||||
required: true
|
||||
type: "string"
|
||||
permissions:
|
||||
contents: "none"
|
||||
jobs:
|
||||
join-release-train:
|
||||
name: "Join Release Train"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "Join Release Train"
|
||||
id: "join-release-train"
|
||||
env:
|
||||
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
|
||||
run: |-
|
||||
run_url=$(
|
||||
gh workflow run join \
|
||||
--repo ${{ inputs.release-train-repository }} \
|
||||
--ref ${{ inputs.release-train }} \
|
||||
--field commit-hash=${{ github.sha }} \
|
||||
--field deployment-destination=${{ inputs.deployment-destination == 'Maven Central' && 'maven-central' || 'spring-enterprise' }} \
|
||||
--field release-branch=${{ github.ref_name }} \
|
||||
--field release-repository=${{ github.repository }}
|
||||
)
|
||||
echo "Dispatched workflow run. Waiting for $run_url to complete."
|
||||
run_id=${run_url##*/}
|
||||
watch_exit_code=0
|
||||
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
|
||||
if [[ $watch_exit_code -eq 0 ]]; then
|
||||
echo "Workflow run succeeded."
|
||||
else
|
||||
echo "Workflow run failed."
|
||||
fi
|
||||
exit $watch_exit_code
|
||||
@@ -0,0 +1,46 @@
|
||||
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
|
||||
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
|
||||
|
||||
name: "Release Train – Leave"
|
||||
run-name: "${{ inputs.release-train }} – Leave"
|
||||
"on":
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-train:
|
||||
description: "Release train"
|
||||
required: true
|
||||
type: "string"
|
||||
release-train-repository:
|
||||
default: "spring-io/release-train"
|
||||
description: "Release train repository"
|
||||
required: true
|
||||
type: "string"
|
||||
permissions:
|
||||
contents: "none"
|
||||
jobs:
|
||||
leave:
|
||||
name: "Leave"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "Leave"
|
||||
id: "leave"
|
||||
env:
|
||||
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
|
||||
run: |-
|
||||
run_url=$(
|
||||
gh workflow run leave \
|
||||
--repo ${{ inputs.release-train-repository }} \
|
||||
--ref ${{ inputs.release-train }} \
|
||||
--field release-branch=${{ github.ref_name }} \
|
||||
--field release-repository=${{ github.repository }}
|
||||
)
|
||||
echo "Dispatched workflow run. Waiting for $run_url to complete."
|
||||
run_id=${run_url##*/}
|
||||
watch_exit_code=0
|
||||
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
|
||||
if [[ $watch_exit_code -eq 0 ]]; then
|
||||
echo "Workflow run succeeded."
|
||||
else
|
||||
echo "Workflow run failed."
|
||||
fi
|
||||
exit $watch_exit_code
|
||||
@@ -0,0 +1,47 @@
|
||||
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
|
||||
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
|
||||
|
||||
name: "Release Train – Ready"
|
||||
run-name: "${{ inputs.release-train }} – Ready"
|
||||
"on":
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-train:
|
||||
description: "Release train"
|
||||
required: true
|
||||
type: "string"
|
||||
release-train-repository:
|
||||
default: "spring-io/release-train"
|
||||
description: "Release train repository"
|
||||
required: true
|
||||
type: "string"
|
||||
permissions:
|
||||
contents: "none"
|
||||
jobs:
|
||||
ready:
|
||||
name: "Ready"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "Ready"
|
||||
id: "ready"
|
||||
env:
|
||||
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
|
||||
run: |-
|
||||
run_url=$(
|
||||
gh workflow run ready \
|
||||
--repo ${{ inputs.release-train-repository }} \
|
||||
--ref ${{ inputs.release-train }} \
|
||||
--field commit-hash=${{ github.sha }} \
|
||||
--field release-branch=${{ github.ref_name }} \
|
||||
--field release-repository=${{ github.repository }}
|
||||
)
|
||||
echo "Dispatched workflow run. Waiting for $run_url to complete."
|
||||
run_id=${run_url##*/}
|
||||
watch_exit_code=0
|
||||
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
|
||||
if [[ $watch_exit_code -eq 0 ]]; then
|
||||
echo "Workflow run succeeded."
|
||||
else
|
||||
echo "Workflow run failed."
|
||||
fi
|
||||
exit $watch_exit_code
|
||||
@@ -0,0 +1,34 @@
|
||||
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
|
||||
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
|
||||
|
||||
name: "Release Train – Retry"
|
||||
run-name: "${{ inputs.release-train }} – Retry"
|
||||
"on":
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-train:
|
||||
description: "Release train"
|
||||
required: true
|
||||
type: "string"
|
||||
release-train-repository:
|
||||
default: "spring-io/release-train"
|
||||
description: "Release train repository"
|
||||
required: true
|
||||
type: "string"
|
||||
permissions:
|
||||
contents: "none"
|
||||
jobs:
|
||||
trigger-retry:
|
||||
name: "Trigger Retry"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "Trigger Retry"
|
||||
id: "trigger-retry"
|
||||
env:
|
||||
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
|
||||
run: |-
|
||||
gh workflow run retry \
|
||||
--repo ${{ inputs.release-train-repository }} \
|
||||
--ref ${{ inputs.release-train }} \
|
||||
--field release-branch=${{ github.ref_name }} \
|
||||
--field release-repository=${{ github.repository }}
|
||||
@@ -0,0 +1,84 @@
|
||||
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
|
||||
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
|
||||
|
||||
name: "Release Train – Test"
|
||||
run-name: "${{ inputs.callback-ref }} – Test"
|
||||
"on":
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
callback:
|
||||
description: "Repository to which a callback should be made upon completion"
|
||||
required: true
|
||||
type: "string"
|
||||
callback-ref:
|
||||
description: "Ref in the callback repository to which a callback should be made upon completion"
|
||||
required: true
|
||||
type: "string"
|
||||
release-train-maven-repository-url:
|
||||
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
|
||||
required: true
|
||||
type: "string"
|
||||
permissions:
|
||||
contents: "read"
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
jobs:
|
||||
test-release:
|
||||
name: "Test Release"
|
||||
runs-on: "ubuntu22-2-8"
|
||||
steps:
|
||||
- name: "Prevent Re-runs"
|
||||
id: "prevent-re-runs"
|
||||
run: |-
|
||||
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
|
||||
echo "Re-runs are prohibited. Use the 'Release Train – Retry' workflow to retry test failures"
|
||||
exit 1
|
||||
fi
|
||||
- name: "Set up Java"
|
||||
id: "set-up-java"
|
||||
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
|
||||
with:
|
||||
distribution: "liberica"
|
||||
java-version: "25"
|
||||
- name: "Check Out Code"
|
||||
id: "check-out-code"
|
||||
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
|
||||
- name: "Restore Build System Caches"
|
||||
id: "restore-build-system-caches"
|
||||
uses: "actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
|
||||
with:
|
||||
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
|
||||
path: |-
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
- name: "Test Release"
|
||||
id: "test-release"
|
||||
uses: "./.github/actions/release-train-test"
|
||||
env:
|
||||
COMMERCIAL_RELEASE_REPO_URL: "${{ vars.COMMERCIAL_RELEASE_REPO_URL }}"
|
||||
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
|
||||
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
|
||||
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
|
||||
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
|
||||
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
|
||||
- name: "Send Callback"
|
||||
id: "send-callback"
|
||||
if: "${{ !cancelled() }}"
|
||||
env:
|
||||
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
|
||||
run: |-
|
||||
gh workflow run callback \
|
||||
--repo ${{ inputs.callback }} \
|
||||
--ref ${{ inputs.callback-ref }} \
|
||||
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
|
||||
--field release-branch=${{ github.ref_name }} \
|
||||
--field release-repository=${{ github.repository }} \
|
||||
--field result=${{ job.status == 'success' && 'tested' || 'test-failed' }} \
|
||||
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
- name: "Upload Build System Reports"
|
||||
id: "upload-build-system-reports"
|
||||
if: "${{ failure() }}"
|
||||
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
|
||||
with:
|
||||
name: "build-system-reports"
|
||||
path: "**/build/reports"
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.0.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-stage-release:
|
||||
name: Build and Stage Release
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
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
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-staging-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
sync-to-maven-central:
|
||||
name: Sync to Maven Central
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
|
||||
central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
|
||||
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
promote-release:
|
||||
name: Promote Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- sync-to-maven-central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- promote-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
milestone: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -12,31 +12,39 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
commercial-repository-password:
|
||||
description: 'Password for authentication with the commercial repository'
|
||||
required: false
|
||||
commercial-repository-username:
|
||||
description: 'Username for authentication with the commercial repository'
|
||||
required: false
|
||||
google-chat-webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
repository-password:
|
||||
description: 'Password for authentication with the repository'
|
||||
opensource-repository-password:
|
||||
description: 'Password for authentication with the open-source repository'
|
||||
required: false
|
||||
repository-username:
|
||||
description: 'Username for authentication with the repository'
|
||||
opensource-repository-username:
|
||||
description: 'Username for authentication with the open-source repository'
|
||||
required: false
|
||||
token:
|
||||
description: 'Token to use for authentication with GitHub'
|
||||
required: true
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: 'v0.0.2'
|
||||
ref: 'v0.0.3'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
@@ -46,8 +54,9 @@ jobs:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-provider: basic
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
@@ -56,15 +65,17 @@ jobs:
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
- name: Run Release Verification Tests
|
||||
env:
|
||||
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
|
||||
RVT_RELEASE_TYPE: oss
|
||||
RVT_COMMERCIAL_REPOSITORY_PASSWORD: ${{ secrets.commercial-repository-password }}
|
||||
RVT_COMMERCIAL_REPOSITORY_USERNAME: ${{ secrets.commercial-repository-username }}
|
||||
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.opensource-repository-password }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.opensource-repository-username }}
|
||||
RVT_RELEASE_TYPE: ${{ vars.COMMERCIAL && 'commercial' || 'oss' }}
|
||||
RVT_STAGING: ${{ inputs.staging }}
|
||||
RVT_VERSION: ${{ inputs.version }}
|
||||
run: ./gradlew spring-framework-release-verification-tests:test
|
||||
- name: Upload Build Reports on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
@@ -52,6 +52,9 @@ atlassian-ide-plugin.xml
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Claude artifacts
|
||||
.claude/*
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
|
||||
+2
-10
@@ -6,7 +6,7 @@ plugins {
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'com.gradleup.shadow' version "9.2.2" apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'io.spring.nullability' version '0.0.11' apply false
|
||||
id 'io.spring.nullability' version '0.0.15' apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -18,16 +18,8 @@ description = "Spring Framework"
|
||||
|
||||
configure(allprojects) { project ->
|
||||
apply plugin: "org.springframework.build.localdev"
|
||||
apply plugin: "org.springframework.build.repositories"
|
||||
group = "org.springframework"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
if (version.contains('-')) {
|
||||
maven { url = "https://repo.spring.io/milestone" }
|
||||
}
|
||||
if (version.endsWith('-SNAPSHOT')) {
|
||||
maven { url = "https://repo.spring.io/snapshot" }
|
||||
}
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
cacheChangingModulesFor 0, "seconds"
|
||||
|
||||
@@ -50,6 +50,10 @@ gradlePlugin {
|
||||
id = "org.springframework.build.multiReleaseJar"
|
||||
implementationClass = "org.springframework.build.multirelease.MultiReleaseJarPlugin"
|
||||
}
|
||||
repositoriesPlugin {
|
||||
id = "org.springframework.build.repositories"
|
||||
implementationClass = "org.springframework.build.RepositoriesPlugin"
|
||||
}
|
||||
optionalDependenciesPlugin {
|
||||
id = "org.springframework.build.optional-dependencies"
|
||||
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package org.springframework.build;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@@ -35,6 +38,7 @@ import org.gradle.api.plugins.quality.CheckstylePlugin;
|
||||
* {@link Plugin} that applies conventions for checkstyle.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class CheckstyleConventions {
|
||||
|
||||
@@ -48,9 +52,10 @@ public class CheckstyleConventions {
|
||||
configureNoHttpPlugin(project);
|
||||
}
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize()
|
||||
.set("checkstyleNohttp".equals(checkstyle.getName()) ? "1536m" : "1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("13.4.2");
|
||||
checkstyle.setToolVersion("14.1.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +69,9 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**", "**/.classpath",
|
||||
"**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**", "buildSrc/build/**");
|
||||
"**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**", "buildSrc/build/**",
|
||||
".claude/**");
|
||||
excludeGitIgnoredPaths(project, noHttp);
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
@@ -76,4 +83,48 @@ public class CheckstyleConventions {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Additionally exclude everything matched by the root {@code .gitignore} file,
|
||||
* so that new ignored paths (build output, IDE metadata, local git worktrees,
|
||||
* etc.) are automatically kept out of nohttp scanning without having to
|
||||
* remember to mirror every {@code .gitignore} change here as well.
|
||||
* <p>Negated patterns (lines starting with {@code !}) are not supported and are
|
||||
* simply skipped, since there is no useful Ant-glob equivalent for them here.
|
||||
*/
|
||||
private static void excludeGitIgnoredPaths(Project project, NoHttpExtension noHttp) {
|
||||
File gitignore = project.getRootProject().file(".gitignore");
|
||||
if (!gitignore.exists()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (String line : Files.readAllLines(gitignore.toPath())) {
|
||||
String pattern = line.strip();
|
||||
if (pattern.isEmpty() || pattern.startsWith("#") || pattern.startsWith("!")) {
|
||||
continue;
|
||||
}
|
||||
boolean directoryOnly = pattern.endsWith("/");
|
||||
if (directoryOnly) {
|
||||
pattern = pattern.substring(0, pattern.length() - 1);
|
||||
}
|
||||
// A '/' anywhere but a (now removed) trailing position anchors the
|
||||
// pattern to the repository root; otherwise it matches at any depth.
|
||||
boolean anchored = pattern.contains("/");
|
||||
if (pattern.startsWith("/")) {
|
||||
pattern = pattern.substring(1);
|
||||
}
|
||||
String rootPattern = anchored ? pattern : "**/" + pattern;
|
||||
if (directoryOnly) {
|
||||
noHttp.getSource().exclude(rootPattern + "/**");
|
||||
}
|
||||
else {
|
||||
// The pattern may match either a file or a directory, so exclude both.
|
||||
noHttp.getSource().exclude(rootPattern, rootPattern + "/**");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException("Failed to read .gitignore for nohttp exclusions", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* Plugin that configures the OSS, commercial and release train repositories in the build.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class RepositoriesPlugin implements Plugin <Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
configureOssRepositories(project);
|
||||
configureCommercialRepositories(project);
|
||||
configureReleaseTrainRepository(project);
|
||||
}
|
||||
|
||||
private void configureOssRepositories(Project project) {
|
||||
project.getRepositories().mavenCentral();
|
||||
if (project.getVersion().toString().contains("-")) {
|
||||
project.getRepositories().maven(repository -> {
|
||||
repository.setName("spring-oss-milestone");
|
||||
repository.setUrl("https://repo.spring.io/milestone/");
|
||||
});
|
||||
}
|
||||
if (project.getVersion().toString().endsWith("-SNAPSHOT")) {
|
||||
project.getRepositories().maven(repository -> {
|
||||
repository.setName("spring-oss-snapshot");
|
||||
repository.setUrl("https://repo.spring.io/snapshot/");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void configureCommercialRepositories(Project project) {
|
||||
String releaseRepositoryUrl = getEnv("COMMERCIAL_RELEASE_REPO_URL");
|
||||
if (releaseRepositoryUrl != null) {
|
||||
project.getRepositories().maven((repository) -> {
|
||||
repository.setName("spring-commercial-release");
|
||||
repository.setUrl(releaseRepositoryUrl);
|
||||
repository.credentials((creds) -> {
|
||||
creds.setUsername(System.getenv("COMMERCIAL_REPO_USERNAME"));
|
||||
creds.setPassword(System.getenv("COMMERCIAL_REPO_PASSWORD"));
|
||||
});
|
||||
});
|
||||
}
|
||||
String snapshotRepositoryUrl = getEnv("COMMERCIAL_SNAPSHOT_REPO_URL");
|
||||
if (snapshotRepositoryUrl != null && project.getVersion().toString().endsWith("-SNAPSHOT")) {
|
||||
project.getRepositories().maven((repository) -> {
|
||||
repository.setName("spring-commercial-snapshot");
|
||||
repository.setUrl(snapshotRepositoryUrl);
|
||||
repository.credentials((creds) -> {
|
||||
creds.setUsername(System.getenv("COMMERCIAL_REPO_USERNAME"));
|
||||
creds.setPassword(System.getenv("COMMERCIAL_REPO_PASSWORD"));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void configureReleaseTrainRepository(Project project) {
|
||||
String releaseTrainRepositoryUrl = getEnv("RELEASE_TRAIN_MAVEN_REPOSITORY_URL");
|
||||
if (releaseTrainRepositoryUrl != null) {
|
||||
project.getRepositories().maven(repository -> {
|
||||
repository.setName("spring-release-train");
|
||||
repository.setUrl(releaseTrainRepositoryUrl);
|
||||
repository.credentials((creds) -> {
|
||||
creds.setUsername(System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME"));
|
||||
creds.setPassword(System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD"));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the environment variable's value, or {@code null} if it is unset or blank.
|
||||
*/
|
||||
private static String getEnv(String name) {
|
||||
String value = System.getenv(name);
|
||||
return (value != null && !value.isBlank()) ? value : null;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class TestConventions {
|
||||
"junit.platform.discovery.issue.severity.critical", "INFO"
|
||||
));
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
test.systemProperty("testGroups", project.findProperty("testGroups"));
|
||||
}
|
||||
test.jvmArgs(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
@@ -82,7 +82,7 @@ class TestConventions {
|
||||
|
||||
private void configureByteBuddyAgent(Project project) {
|
||||
if (project.hasProperty("byteBuddyVersion")) {
|
||||
String byteBuddyVersion = (String) project.getProperties().get("byteBuddyVersion");
|
||||
String byteBuddyVersion = (String) project.findProperty("byteBuddyVersion");
|
||||
Configuration byteBuddyAgentConfig = project.getConfigurations().create("byteBuddyAgentConfig");
|
||||
byteBuddyAgentConfig.setTransitive(false);
|
||||
Dependency byteBuddyAgent = project.getDependencies().create("net.bytebuddy:byte-buddy-agent:" + byteBuddyVersion);
|
||||
|
||||
@@ -15,8 +15,8 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
moduleProjects.each { moduleProject ->
|
||||
javadoc moduleProject
|
||||
rootProject.ext.moduleProjects.each { moduleProject ->
|
||||
javadoc project(moduleProject.path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ javadoc {
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
classpath += files(rootProject.ext.moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ tasks.register('schemaZip', Zip) {
|
||||
description = "Builds -${archiveClassifier} archive containing all " +
|
||||
"XSDs for deployment at https://springframework.org/schema."
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
moduleProjects.each { module ->
|
||||
rootProject.ext.moduleProjects.each { module ->
|
||||
def Properties schemas = new Properties();
|
||||
|
||||
module.sourceSets.main.resources.find {
|
||||
|
||||
@@ -8,7 +8,7 @@ group = "org.springframework"
|
||||
dependencies {
|
||||
constraints {
|
||||
parent.moduleProjects.sort { "$it.name" }.each {
|
||||
api it
|
||||
api project(it.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,12 @@ expressions used in XML bean definitions, `@Value`, etc.
|
||||
| The mode to use when compiling expressions for the
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
|
||||
|
||||
| `spring.expression.maxBigPowerBits`
|
||||
| The default maximum number of bits permitted in the result of a `BigDecimal` or
|
||||
`BigInteger` power operation within a
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
expression.
|
||||
|
||||
| `spring.expression.maxOperations`
|
||||
| The default maximum number of operations permitted during
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
|
||||
@@ -38,7 +38,7 @@ In common with most `FactoryBean` implementations provided with Spring, the
|
||||
`ProxyFactoryBean` class is itself a JavaBean. Its properties are used to:
|
||||
|
||||
* Specify the target you want to proxy.
|
||||
* Specify whether to use CGLIB (described later and see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
* Specify whether to use CGLIB (described later and see also <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
|
||||
|
||||
Some key properties are inherited from `org.springframework.aop.framework.ProxyConfig`
|
||||
(the superclass for all AOP proxy factories in Spring). These key properties include
|
||||
@@ -46,7 +46,7 @@ the following:
|
||||
|
||||
* `proxyTargetClass`: `true` if the target class is to be proxied, rather than the
|
||||
target class's interfaces. If this property value is set to `true`, then CGLIB proxies
|
||||
are created (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
are created (but see also <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
|
||||
* `optimize`: Controls whether or not aggressive optimizations are applied to proxies
|
||||
created through CGLIB. You should not blithely use this setting unless you fully
|
||||
understand how the relevant AOP proxy handles optimization. This is currently used
|
||||
@@ -64,7 +64,7 @@ the following:
|
||||
Other properties specific to `ProxyFactoryBean` include the following:
|
||||
|
||||
* `proxyInterfaces`: An array of `String` interface names. If this is not supplied, a CGLIB
|
||||
proxy for the target class is used (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
proxy for the target class is used (but see also <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
|
||||
* `interceptorNames`: A `String` array of `Advisor`, interceptor, or other advice names to
|
||||
apply. Ordering is significant, on a first come-first served basis. That is to say
|
||||
that the first interceptor in the list is the first to be able to intercept the
|
||||
@@ -76,7 +76,7 @@ factories. You cannot mention bean references here, since doing so results in th
|
||||
+
|
||||
You can append an interceptor name with an asterisk (`*`). Doing so results in the
|
||||
application of all advisor beans with names that start with the part before the asterisk
|
||||
to be applied. You can find an example of using this feature in xref:core/aop-api/pfb.adoc#aop-global-advisors[Using "`Global`" Advisors].
|
||||
to be applied. You can find an example of using this feature in <<aop-global-advisors,Using "`Global`" Advisors>>.
|
||||
|
||||
* singleton: Whether or not the factory should return a single object, no matter how
|
||||
often the `getObject()` method is called. Several `FactoryBean` implementations offer
|
||||
|
||||
@@ -394,7 +394,7 @@ execution-only semantics. You only need to be aware of this difference if you co
|
||||
`@AspectJ` aspects written for Spring and use `proceed` with arguments with the AspectJ
|
||||
compiler and weaver. There is a way to write such aspects that is 100% compatible across
|
||||
both Spring AOP and AspectJ, and this is discussed in the
|
||||
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-proceeding-with-the-call[following section on advice parameters].
|
||||
<<aop-ataspectj-advice-proceeding-with-the-call,following section on advice parameters>>.
|
||||
====
|
||||
|
||||
The value returned by the around advice is the return value seen by the caller of the
|
||||
@@ -722,7 +722,7 @@ of determining parameter names, an exception will be thrown.
|
||||
|
||||
`AspectJAnnotationParameterNameDiscoverer` :: Uses parameter names that have been explicitly
|
||||
specified by the user via the `argNames` attribute in the corresponding advice or
|
||||
pointcut annotation. See xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names-explicit[Explicit Argument Names] for details.
|
||||
pointcut annotation. See <<aop-ataspectj-advice-params-names-explicit,Explicit Argument Names>> for details.
|
||||
`KotlinReflectionParameterNameDiscoverer` :: Uses Kotlin reflection APIs to determine
|
||||
parameter names. This discoverer is only used if such APIs are present on the classpath.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
|
||||
@@ -10,7 +10,7 @@ of advice parameters.
|
||||
|
||||
To use the aop namespace tags described in this section, you need to import the
|
||||
`spring-aop` schema, as described in xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]
|
||||
. See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema]
|
||||
. See xref:core/appendix/xsd-schemas.adoc#xsd-schemas-aop[the AOP schema]
|
||||
for how to import the tags in the `aop` namespace.
|
||||
|
||||
Within your Spring configurations, all aspect and advisor elements must be placed within
|
||||
@@ -204,7 +204,7 @@ Before advice runs before a matched method execution. It is declared inside an
|
||||
----
|
||||
|
||||
In the example above, `dataAccessOperation` is the `id` of a _named pointcut_ defined at
|
||||
the top (`<aop:config>`) level (see xref:core/aop/schema.adoc#aop-schema-pointcuts[Declaring a Pointcut]).
|
||||
the top (`<aop:config>`) level (see <<aop-schema-pointcuts,Declaring a Pointcut>>).
|
||||
|
||||
NOTE: As we noted in the discussion of the @AspectJ style, using _named pointcuts_ can
|
||||
significantly improve the readability of your code. See xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions] for
|
||||
|
||||
@@ -9,12 +9,12 @@ alone.
|
||||
Spring ships with a small AspectJ aspect library, which is available stand-alone in your
|
||||
distribution as `spring-aspects.jar`. You need to add this to your classpath in order
|
||||
to use the aspects in it.
|
||||
xref:core/aop/using-aspectj.adoc#aop-atconfigurable[Using AspectJ to Dependency Inject Domain Objects with Spring]
|
||||
and xref:core/aop/using-aspectj.adoc#aop-ajlib-other[Other Spring aspects for AspectJ]
|
||||
<<aop-atconfigurable,Using AspectJ to Dependency Inject Domain Objects with Spring>>
|
||||
and <<aop-ajlib-other,Other Spring aspects for AspectJ>>
|
||||
discuss the content of this library and how you can use it.
|
||||
xref:core/aop/using-aspectj.adoc#aop-aj-configure[Configuring AspectJ Aspects by Using Spring IoC]
|
||||
<<aop-aj-configure,Configuring AspectJ Aspects by Using Spring IoC>>
|
||||
discusses how to dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally,
|
||||
xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]
|
||||
<<aop-aj-ltw,Load-time Weaving with AspectJ in the Spring Framework>>
|
||||
provides an introduction to load-time weaving for Spring applications that use AspectJ.
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ types in AspectJ
|
||||
For this to work, the annotated types must be woven with the AspectJ weaver. You can
|
||||
either use a build-time Ant or Maven task to do this (see, for example, the
|
||||
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
|
||||
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
|
||||
Environment Guide]) or load-time weaving (see <<aop-aj-ltw,Load-time Weaving with AspectJ in the Spring Framework>>). The
|
||||
`AnnotationBeanConfigurerAspect` itself needs to be configured by Spring (in order to obtain
|
||||
a reference to the bean factory that is to be used to configure new objects). You can define
|
||||
the related configuration as follows:
|
||||
@@ -376,7 +376,7 @@ per-`ClassLoader` basis, which is more fine-grained and which can make more
|
||||
sense in a 'single-JVM-multiple-application' environment (such as is found in a typical
|
||||
application server environment).
|
||||
|
||||
Further, xref:core/aop/using-aspectj.adoc#aop-aj-ltw-environments[in certain environments], this support enables
|
||||
Further, <<aop-aj-ltw-environments,in certain environments>>, this support enables
|
||||
load-time weaving without making any modifications to the application server's launch
|
||||
script that is needed to add `-javaagent:path/to/aspectjweaver.jar` or (as we describe
|
||||
later in this section) `-javaagent:path/to/spring-instrument.jar`. Developers configure
|
||||
@@ -400,7 +400,7 @@ tool to that specific area immediately afterwards.
|
||||
NOTE: The example presented here uses XML configuration. You can also configure and
|
||||
use @AspectJ with xref:core/beans/java.adoc[Java configuration]. Specifically, you can use the
|
||||
`@EnableLoadTimeWeaving` annotation as an alternative to `<context:load-time-weaver/>`
|
||||
(see xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[below] for details).
|
||||
(see <<aop-aj-ltw-spring,below>> for details).
|
||||
|
||||
The following example shows the profiling aspect, which is not fancy.
|
||||
It is a time-based profiler that uses the @AspectJ-style of aspect declaration:
|
||||
@@ -719,7 +719,7 @@ for AspectJ LTW:
|
||||
* `spring-aop.jar`
|
||||
* `aspectjweaver.jar`
|
||||
|
||||
If you use the xref:core/aop/using-aspectj.adoc#aop-aj-ltw-environments-generic[Spring-provided agent to enable instrumentation]
|
||||
If you use the <<aop-aj-ltw-environments-generic,Spring-provided agent to enable instrumentation>>
|
||||
, you also need:
|
||||
|
||||
* `spring-instrument.jar`
|
||||
@@ -836,7 +836,7 @@ containers.
|
||||
Tomcat and JBoss/WildFly provide a general app `ClassLoader` that is capable of local
|
||||
instrumentation. Spring's native LTW may leverage those ClassLoader implementations
|
||||
to provide AspectJ weaving.
|
||||
You can simply enable load-time weaving, as xref:core/aop/using-aspectj.adoc[described earlier].
|
||||
You can simply enable load-time weaving, as <<aop-using-aspectj,described earlier>>.
|
||||
Specifically, you do not need to modify the JVM launch script to add
|
||||
`-javaagent:path/to/spring-instrument.jar`.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Applying such optimizations early implies the following restrictions:
|
||||
* As we cannot rely on the instance, make sure that the bean type is as precise as
|
||||
possible.
|
||||
|
||||
TIP: See also the xref:core/aot.adoc#aot.bestpractices[] section.
|
||||
TIP: See also the <<aot.bestpractices>> section.
|
||||
|
||||
When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets.
|
||||
A Spring AOT processed application typically generates:
|
||||
|
||||
@@ -14,11 +14,11 @@ Spring distribution, you should first read the previous section on xref:core/app
|
||||
|
||||
To create new XML configuration extensions:
|
||||
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-schema[Author] an XML schema to describe your custom element(s).
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-namespacehandler[Code] a custom `NamespaceHandler` implementation.
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-parser[Code] one or more `BeanDefinitionParser` implementations
|
||||
. <<xsd-custom-schema,Author>> an XML schema to describe your custom element(s).
|
||||
. <<xsd-custom-namespacehandler,Code>> a custom `NamespaceHandler` implementation.
|
||||
. <<xsd-custom-parser,Code>> one or more `BeanDefinitionParser` implementations
|
||||
(this is where the real work is done).
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-registration[Register] your new artifacts with Spring.
|
||||
. <<xsd-custom-registration,Register>> your new artifacts with Spring.
|
||||
|
||||
For a unified example, we create an
|
||||
XML extension (a custom XML element) that lets us configure objects of the type
|
||||
@@ -553,7 +553,7 @@ Kotlin::
|
||||
|
||||
This works nicely, but it exposes a lot of Spring plumbing to the end user. What we are
|
||||
going to do is write a custom extension that hides away all of this Spring plumbing.
|
||||
If we stick to xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-introduction[the steps described previously], we start off
|
||||
If we stick to <<xsd-custom-introduction,the steps described previously>>, we start off
|
||||
by creating the XSD schema to define the structure of our custom tag, as the following
|
||||
listing shows:
|
||||
|
||||
@@ -580,7 +580,7 @@ listing shows:
|
||||
</xsd:schema>
|
||||
----
|
||||
|
||||
Again following xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-introduction[the process described earlier],
|
||||
Again following <<xsd-custom-introduction,the process described earlier>>,
|
||||
we then create a custom `NamespaceHandler`:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -43,7 +43,7 @@ An `@Autowired` annotation on such a constructor is not necessary if the target
|
||||
defines only one constructor. However, if several constructors are available and there is
|
||||
no primary or default constructor, at least one of the constructors must be annotated
|
||||
with `@Autowired` in order to instruct the container which one to use. See the discussion
|
||||
on xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution]
|
||||
on <<beans-autowired-annotation-constructor-resolution,constructor resolution>>
|
||||
for details.
|
||||
====
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ XML configuration file represents a logical layer or module in your architecture
|
||||
|
||||
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-xml[previous section]. Alternatively,
|
||||
<<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:
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ supported as a marker for automatic exception translation in your persistence la
|
||||
|
||||
Many of the annotations provided by Spring can be used as meta-annotations in your
|
||||
own code. A meta-annotation is an annotation that can be applied to another annotation.
|
||||
For example, the `@Service` annotation mentioned xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[earlier]
|
||||
For example, the `@Service` annotation mentioned <<beans-stereotype-annotations,earlier>>
|
||||
is meta-annotated with `@Component`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
@@ -483,7 +483,7 @@ When a component is autodetected as part of the scanning process, its bean name
|
||||
generated by the `BeanNameGenerator` strategy known to that scanner.
|
||||
|
||||
By default, the `AnnotationBeanNameGenerator` is used. For Spring
|
||||
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
|
||||
<<beans-stereotype-annotations,stereotype annotations>>,
|
||||
if you supply a name via the annotation's `value` attribute that name will be used as
|
||||
the name in the corresponding bean definition. This convention also applies when the
|
||||
`@jakarta.inject.Named` annotation is used instead of Spring stereotype annotations.
|
||||
|
||||
@@ -282,7 +282,7 @@ class and the `ApplicationListener` interface. If a bean that implements the
|
||||
Essentially, this is the standard Observer design pattern.
|
||||
|
||||
TIP: As of Spring 4.2, the event infrastructure has been significantly improved and offers
|
||||
an xref:core/beans/context-introduction.adoc#context-functionality-events-annotation[annotation-based model] as well as the
|
||||
an <<context-functionality-events-annotation,annotation-based model>> as well as the
|
||||
ability to publish any arbitrary event (that is, an object that does not necessarily
|
||||
extend from `ApplicationEvent`). When such an object is published, we wrap it in an
|
||||
event for you.
|
||||
@@ -698,7 +698,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This feature is not supported for
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-events-async[asynchronous listeners].
|
||||
<<context-functionality-events-async,asynchronous listeners>>.
|
||||
|
||||
The `handleBlockedListEvent()` method publishes a new `ListUpdateEvent` for every
|
||||
`BlockedListEvent` that it handles. If you need to publish several events, you can return
|
||||
|
||||
@@ -27,10 +27,10 @@ The following table describes these properties:
|
||||
| Property| Explained in...
|
||||
|
||||
| Class
|
||||
| xref:core/beans/definition.adoc#beans-factory-class[Instantiating Beans]
|
||||
| <<beans-factory-class,Instantiating Beans>>
|
||||
|
||||
| Name
|
||||
| xref:core/beans/definition.adoc#beans-beanname[Naming Beans]
|
||||
| <<beans-beanname,Naming Beans>>
|
||||
|
||||
| Scope
|
||||
| xref:core/beans/factory-scopes.adoc[Bean Scopes]
|
||||
@@ -205,7 +205,7 @@ If you use XML-based configuration metadata, you specify the type (or class) of
|
||||
that is to be instantiated in the `class` attribute of the `<bean/>` element. This
|
||||
`class` attribute (which, internally, is a `Class` property on a `BeanDefinition`
|
||||
instance) is usually mandatory. (For exceptions, see
|
||||
xref:core/beans/definition.adoc#beans-factory-class-instance-factory-method[Instantiation by Using an Instance Factory Method]
|
||||
<<beans-factory-class-instance-factory-method,Instantiation by Using an Instance Factory Method>>
|
||||
and xref:core/beans/child-bean-definitions.adoc[Bean Definition Inheritance].)
|
||||
You can use the `Class` property in one of two ways:
|
||||
|
||||
@@ -344,7 +344,7 @@ overloads of the `mock` method. Choose the most specific variant of `mock` possi
|
||||
[[beans-factory-class-instance-factory-method]]
|
||||
=== Instantiation by Using an Instance Factory Method
|
||||
|
||||
Similar to instantiation through a xref:core/beans/definition.adoc#beans-factory-class-static-factory-method[static factory method]
|
||||
Similar to instantiation through a <<beans-factory-class-static-factory-method,static factory method>>
|
||||
, instantiation with an instance factory method invokes a non-static
|
||||
method of an existing bean from the container to create a new bean. To use this
|
||||
mechanism, leave the `class` attribute empty and, in the `factory-bean` attribute,
|
||||
@@ -467,8 +467,8 @@ See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies a
|
||||
|
||||
NOTE: In Spring documentation, "factory bean" refers to a bean that is configured in the
|
||||
Spring container and that creates objects through an
|
||||
xref:core/beans/definition.adoc#beans-factory-class-instance-factory-method[instance] or
|
||||
xref:core/beans/definition.adoc#beans-factory-class-static-factory-method[static] factory method. By contrast,
|
||||
<<beans-factory-class-instance-factory-method,instance>> or
|
||||
<<beans-factory-class-static-factory-method,static>> factory method. By contrast,
|
||||
`FactoryBean` (notice the capitalization) refers to a Spring-specific
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-extension-factorybean[`FactoryBean`] implementation class.
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ In the latter scenario, you have several options:
|
||||
* Abandon autowiring in favor of explicit wiring.
|
||||
* Avoid autowiring for a bean definition by setting its `autowire-candidate` attributes
|
||||
to `false`, as described in the
|
||||
xref:core/beans/dependencies/factory-autowire.adoc#beans-factory-autowire-candidate[next section].
|
||||
<<beans-factory-autowire-candidate,next section>>.
|
||||
* Designate a single bean definition as the primary candidate by setting the
|
||||
`primary` attribute of its `<bean/>` element to `true`.
|
||||
* Implement the more fine-grained control available with annotation-based configuration,
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ to test, particularly when the dependencies are on interfaces or abstract base c
|
||||
which allow for stub or mock implementations to be used in unit tests.
|
||||
|
||||
DI exists in two major variants:
|
||||
xref:core/beans/dependencies/factory-collaborators.adoc#beans-constructor-injection[Constructor-based dependency injection]
|
||||
and xref:core/beans/dependencies/factory-collaborators.adoc#beans-setter-injection[Setter-based dependency injection].
|
||||
<<beans-constructor-injection,Constructor-based dependency injection>>
|
||||
and <<beans-setter-injection,Setter-based dependency injection>>.
|
||||
|
||||
|
||||
[[beans-constructor-injection]]
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ You can read more about the motivation for Method Injection in
|
||||
Lookup method injection is the ability of the container to override methods on
|
||||
container-managed beans and return the lookup result for another named bean in the
|
||||
container. The lookup typically involves a prototype bean, as in the scenario described
|
||||
in xref:core/beans/dependencies/factory-method-injection.adoc[the preceding section]. The Spring Framework
|
||||
in <<beans-factory-method-injection,the preceding section>>. The Spring Framework
|
||||
implements this method injection by using bytecode generation from the CGLIB library to
|
||||
dynamically generate a subclass that overrides the method.
|
||||
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ The following example shows various values being set:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example uses the xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-p-namespace[p-namespace] for even more succinct
|
||||
The following example uses the <<beans-p-namespace,p-namespace>> for even more succinct
|
||||
XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -539,7 +539,7 @@ three approaches at the same time.
|
||||
== XML Shortcut with the c-namespace
|
||||
|
||||
Similar to the
|
||||
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-p-namespace[XML Shortcut with the p-namespace],
|
||||
<<beans-p-namespace,XML Shortcut with the p-namespace>>,
|
||||
the c-namespace, introduced in Spring 3.1, allows inlined attributes for configuring
|
||||
the constructor arguments rather then nested `constructor-arg` elements.
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
The {spring-framework-api}/core/env/Environment.html[`Environment`] interface
|
||||
is an abstraction integrated in the container that models two key
|
||||
aspects of the application environment: xref:core/beans/environment.adoc#beans-definition-profiles[profiles]
|
||||
and xref:core/beans/environment.adoc#beans-property-source-abstraction[properties].
|
||||
aspects of the application environment: <<beans-definition-profiles,profiles>>
|
||||
and <<beans-property-source-abstraction,properties>>.
|
||||
|
||||
A profile is a named, logical group of bean definitions to be registered with the
|
||||
container only if the given profile is active. Beans may be assigned to a profile
|
||||
@@ -473,7 +473,7 @@ Kotlin::
|
||||
In addition, you can also declaratively activate profiles through the
|
||||
`spring.profiles.active` property, which may be specified through system environment
|
||||
variables, JVM system properties, servlet context parameters in `web.xml`, or even as an
|
||||
entry in JNDI (see xref:core/beans/environment.adoc#beans-property-source-abstraction[`PropertySource` Abstraction]). In integration tests, active
|
||||
entry in JNDI (see <<beans-property-source-abstraction,`PropertySource` Abstraction>>). In integration tests, active
|
||||
profiles can be declared by using the `@ActiveProfiles` annotation in the `spring-test`
|
||||
module (see xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[context configuration with environment profiles]
|
||||
).
|
||||
@@ -553,7 +553,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
If xref:#beans-definition-profiles-enable[no profile is active], the `dataSource` is
|
||||
If <<beans-definition-profiles-enable,no profile is active>>, the `dataSource` is
|
||||
created. You can see this as a way to provide a default definition for one or more
|
||||
beans. If any profile is enabled, the default profile does not apply.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ interface. If you write your own `BeanPostProcessor`, you should consider implem
|
||||
the `Ordered` interface, too. For further details, see the javadoc of the
|
||||
{spring-framework-api}/beans/factory/config/BeanPostProcessor.html[`BeanPostProcessor`]
|
||||
and {spring-framework-api}/core/Ordered.html[`Ordered`] interfaces. See also the note on
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-programmatically-registering-beanpostprocessors[programmatic registration of `BeanPostProcessor` instances].
|
||||
<<beans-factory-programmatically-registering-beanpostprocessors,programmatic registration of `BeanPostProcessor` instances>>.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -39,7 +39,7 @@ another container, even if both containers are part of the same hierarchy.
|
||||
|
||||
To change the actual bean definition (that is, the blueprint that defines the bean),
|
||||
you instead need to use a `BeanFactoryPostProcessor`, as described in
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-extension-factory-postprocessors[Customizing Configuration Metadata with a `BeanFactoryPostProcessor`].
|
||||
<<beans-factory-extension-factory-postprocessors,Customizing Configuration Metadata with a `BeanFactoryPostProcessor`>>.
|
||||
====
|
||||
|
||||
The `org.springframework.beans.factory.config.BeanPostProcessor` interface consists of
|
||||
@@ -329,7 +329,7 @@ and {spring-framework-api}/core/Ordered.html[`Ordered`] interfaces for more deta
|
||||
If you want to change the actual bean instances (that is, the objects that are created
|
||||
from the configuration metadata), then you instead need to use a `BeanPostProcessor`
|
||||
(described earlier in
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[Customizing Beans by Using a `BeanPostProcessor`]).
|
||||
<<beans-factory-extension-bpp,Customizing Beans by Using a `BeanPostProcessor`>>).
|
||||
While it is technically possible to work with bean instances within a `BeanFactoryPostProcessor`
|
||||
(for example, by using `BeanFactory.getBean()`), doing so causes premature bean instantiation,
|
||||
violating the standard container lifecycle. This may cause negative side effects, such as
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
The Spring Framework provides a number of interfaces you can use to customize the nature
|
||||
of a bean. This section groups them as follows:
|
||||
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle[Lifecycle Callbacks]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-aware[`ApplicationContextAware` and `BeanNameAware`]
|
||||
* xref:core/beans/factory-nature.adoc#aware-list[Other `Aware` Interfaces]
|
||||
* <<beans-factory-lifecycle,Lifecycle Callbacks>>
|
||||
* <<beans-factory-aware,`ApplicationContextAware` and `BeanNameAware`>>
|
||||
* <<aware-list,Other `Aware` Interfaces>>
|
||||
|
||||
|
||||
[[beans-factory-lifecycle]]
|
||||
@@ -252,7 +252,7 @@ of a `<bean>` element a special `(inferred)` value, which instructs Spring to au
|
||||
detect a public `close` or `shutdown` method on the bean class for a specific bean definition.
|
||||
You can also set this special `(inferred)` value on the `default-destroy-method` attribute
|
||||
of a `<beans>` element to apply this behavior to an entire set of bean definitions (see
|
||||
xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-default-init-destroy-methods[Default Initialization and Destroy Methods]).
|
||||
<<beans-factory-lifecycle-default-init-destroy-methods,Default Initialization and Destroy Methods>>).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -276,7 +276,7 @@ callback method names on every bean. This means that you, as an application deve
|
||||
can write your application classes and use an initialization callback called `init()`,
|
||||
without having to configure an `init-method="init"` attribute with each bean definition.
|
||||
The Spring IoC container calls that method when the bean is created (and in accordance
|
||||
with the standard lifecycle callback contract xref:core/beans/factory-nature.adoc#beans-factory-lifecycle[described previously]).
|
||||
with the standard lifecycle callback contract <<beans-factory-lifecycle,described previously>>).
|
||||
This feature also enforces a consistent naming convention for initialization and
|
||||
destroy method callbacks.
|
||||
|
||||
@@ -367,8 +367,8 @@ interacts directly with the raw target bean.
|
||||
|
||||
As of Spring 2.5, you have three options for controlling bean lifecycle behavior:
|
||||
|
||||
* The xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-initializingbean[`InitializingBean`] and
|
||||
xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-disposablebean[`DisposableBean`] callback interfaces
|
||||
* The <<beans-factory-lifecycle-initializingbean,`InitializingBean`>> and
|
||||
<<beans-factory-lifecycle-disposablebean,`DisposableBean`>> callback interfaces
|
||||
* Custom `init()` and `destroy()` methods
|
||||
* The xref:core/beans/annotation-config/postconstruct-and-predestroy-annotations.adoc[`@PostConstruct` and `@PreDestroy` annotations]
|
||||
. You can combine these mechanisms to control a given bean.
|
||||
@@ -378,7 +378,7 @@ configured with a different method name, then each configured method is run in t
|
||||
order listed after this note. However, if the same method name is configured -- for example,
|
||||
`init()` for an initialization method -- for more than one of these lifecycle mechanisms,
|
||||
that method is run once, as explained in the
|
||||
xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-default-init-destroy-methods[preceding section].
|
||||
<<beans-factory-lifecycle-default-init-destroy-methods,preceding section>>.
|
||||
|
||||
Multiple lifecycle mechanisms configured for the same bean, with different
|
||||
initialization methods, are called as follows:
|
||||
@@ -669,7 +669,7 @@ init-method.
|
||||
[[aware-list]]
|
||||
== Other `Aware` Interfaces
|
||||
|
||||
Besides `ApplicationContextAware` and `BeanNameAware` (discussed xref:core/beans/factory-nature.adoc#beans-factory-aware[earlier]),
|
||||
Besides `ApplicationContextAware` and `BeanNameAware` (discussed <<beans-factory-aware,earlier>>),
|
||||
Spring offers a wide range of `Aware` callback interfaces that let beans indicate to the container
|
||||
that they require a certain infrastructure dependency. As a general rule, the name indicates the
|
||||
dependency type. The following table summarizes the most important `Aware` interfaces:
|
||||
@@ -681,7 +681,7 @@ dependency type. The following table summarizes the most important `Aware` inter
|
||||
|
||||
| `ApplicationContextAware`
|
||||
| Declaring `ApplicationContext`.
|
||||
| xref:core/beans/factory-nature.adoc#beans-factory-aware[`ApplicationContextAware` and `BeanNameAware`]
|
||||
| <<beans-factory-aware,`ApplicationContextAware` and `BeanNameAware`>>
|
||||
|
||||
| `ApplicationEventPublisherAware`
|
||||
| Event publisher of the enclosing `ApplicationContext`.
|
||||
@@ -697,7 +697,7 @@ dependency type. The following table summarizes the most important `Aware` inter
|
||||
|
||||
| `BeanNameAware`
|
||||
| Name of the declaring bean.
|
||||
| xref:core/beans/factory-nature.adoc#beans-factory-aware[`ApplicationContextAware` and `BeanNameAware`]
|
||||
| <<beans-factory-aware,`ApplicationContextAware` and `BeanNameAware`>>
|
||||
|
||||
| `LoadTimeWeaverAware`
|
||||
| Defined weaver for processing class definition at load time.
|
||||
|
||||
@@ -14,7 +14,7 @@ through configuration instead of having to bake in the scope of an object at the
|
||||
class level. Beans can be defined to be deployed in one of a number of scopes.
|
||||
The Spring Framework supports six scopes, four of which are available only if
|
||||
you use a web-aware `ApplicationContext`. You can also create
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-custom[a custom scope.]
|
||||
<<beans-factory-scopes-custom,a custom scope.>>
|
||||
|
||||
The following table describes the supported scopes:
|
||||
|
||||
@@ -24,23 +24,23 @@ The following table describes the supported scopes:
|
||||
|===
|
||||
| Scope| Description
|
||||
|
||||
| xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton]
|
||||
| <<beans-factory-scopes-singleton,singleton>>
|
||||
| (Default) Scopes a single bean definition to a single object instance for each Spring IoC
|
||||
container.
|
||||
|
||||
| xref:core/beans/factory-scopes.adoc#beans-factory-scopes-prototype[prototype]
|
||||
| <<beans-factory-scopes-prototype,prototype>>
|
||||
| Scopes a single bean definition to any number of object instances.
|
||||
|
||||
| xref:core/beans/factory-scopes.adoc#beans-factory-scopes-request[request]
|
||||
| <<beans-factory-scopes-request,request>>
|
||||
| Scopes a single bean definition to the lifecycle of a single HTTP request. That is,
|
||||
each HTTP request has its own instance of a bean created off the back of a single bean
|
||||
definition. Only valid in the context of a web-aware Spring `ApplicationContext`.
|
||||
|
||||
| xref:core/beans/factory-scopes.adoc#beans-factory-scopes-session[session]
|
||||
| <<beans-factory-scopes-session,session>>
|
||||
| Scopes a single bean definition to the lifecycle of an HTTP `Session`. Only valid in
|
||||
the context of a web-aware Spring `ApplicationContext`.
|
||||
|
||||
| xref:core/beans/factory-scopes.adoc#beans-factory-scopes-application[application]
|
||||
| <<beans-factory-scopes-application,application>>
|
||||
| Scopes a single bean definition to the lifecycle of a `ServletContext`. Only valid in
|
||||
the context of a web-aware Spring `ApplicationContext`.
|
||||
|
||||
@@ -53,7 +53,7 @@ NOTE: A thread scope is available but is not registered by default. For more inf
|
||||
see the documentation for
|
||||
{spring-framework-api}/context/support/SimpleThreadScope.html[`SimpleThreadScope`].
|
||||
For instructions on how to register this or any other custom scope, see
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-custom-using[Using a Custom Scope].
|
||||
<<beans-factory-scopes-custom-using,Using a Custom Scope>>.
|
||||
|
||||
|
||||
[[beans-factory-scopes-singleton]]
|
||||
@@ -432,7 +432,7 @@ understand the "`why`" as well as the "`how`" behind it:
|
||||
|
||||
To create such a proxy, you insert a child `<aop:scoped-proxy/>` element into a
|
||||
scoped bean definition (see
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection-proxies[Choosing the Type of Proxy to Create]
|
||||
<<beans-factory-scopes-other-injection-proxies,Choosing the Type of Proxy to Create>>
|
||||
and xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]).
|
||||
|
||||
Why do definitions of beans scoped at the `request`, `session` and custom-scope
|
||||
|
||||
@@ -19,7 +19,7 @@ You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
To declare a bean, you can annotate a method with the `@Bean` annotation. You use this
|
||||
method to register a bean definition within an `ApplicationContext` of the type specified
|
||||
by the method's return type. By default, the bean name is the same as the method name
|
||||
(unless a different xref:#beans-java-customizing-bean-naming[bean name generator] is
|
||||
(unless a different <<beans-java-customizing-bean-naming,bean name generator>> is
|
||||
configured). The following example shows a `@Bean` method declaration:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -8,10 +8,10 @@ Jetty uses pooled byte buffers with a callback to be released, and so on.
|
||||
The `spring-core` module provides a set of abstractions to work with various byte buffer
|
||||
APIs as follows:
|
||||
|
||||
* xref:core/databuffer-codec.adoc#databuffers-factory[`DataBufferFactory`] abstracts the creation of a data buffer.
|
||||
* xref:core/databuffer-codec.adoc#databuffers-buffer[`DataBuffer`] represents a byte buffer, which may be
|
||||
xref:core/databuffer-codec.adoc#databuffers-buffer-pooled[pooled].
|
||||
* xref:core/databuffer-codec.adoc#databuffers-utils[`DataBufferUtils`] offers utility methods for data buffers.
|
||||
* <<databuffers-factory,`DataBufferFactory`>> abstracts the creation of a data buffer.
|
||||
* <<databuffers-buffer,`DataBuffer`>> represents a byte buffer, which may be
|
||||
<<databuffers-buffer-pooled,pooled>>.
|
||||
* <<databuffers-utils,`DataBufferUtils`>> offers utility methods for data buffers.
|
||||
* <<Codecs>> decode or encode data buffer streams into higher level objects.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ Below is a partial list of benefits:
|
||||
* Read and write with independent positions, i.e. not requiring a call to `flip()` to
|
||||
alternate between read and write.
|
||||
* Capacity expanded on demand as with `java.lang.StringBuilder`.
|
||||
* Pooled buffers and reference counting via xref:core/databuffer-codec.adoc#databuffers-buffer-pooled[`PooledDataBuffer`].
|
||||
* Pooled buffers and reference counting via <<databuffers-buffer-pooled,`PooledDataBuffer`>>.
|
||||
* View a buffer as `java.nio.ByteBuffer`, `InputStream`, or `OutputStream`.
|
||||
* Determine the index, or the last index, for a given byte.
|
||||
|
||||
@@ -101,7 +101,7 @@ xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] in the WebFlux sect
|
||||
== Using `DataBuffer`
|
||||
|
||||
When working with data buffers, special care must be taken to ensure buffers are released
|
||||
since they may be xref:core/databuffer-codec.adoc#databuffers-buffer-pooled[pooled]. We'll use codecs to illustrate
|
||||
since they may be <<databuffers-buffer-pooled,pooled>>. We'll use codecs to illustrate
|
||||
how that works but the concepts apply more generally. Let's see what codecs must do
|
||||
internally to manage data buffers.
|
||||
|
||||
|
||||
@@ -253,6 +253,175 @@ properties. Alternatively, configure custom accessors via
|
||||
`SimpleEvaluationContext.forPropertyAccessors(...)`, potentially disable assignment, and
|
||||
optionally activate method resolution and/or a type converter through the builder.
|
||||
|
||||
[[expressions-evaluation-context-security]]
|
||||
=== Security Considerations
|
||||
|
||||
SpEL is a powerful expression language that can invoke constructors and methods, read and
|
||||
write properties and fields, and reference beans – all backed by reflection. Because of
|
||||
this power, evaluating a SpEL expression obtained from an untrusted source is inherently
|
||||
dangerous and should generally be avoided, since doing so can effectively grant that
|
||||
source the ability to execute arbitrary code within the application, regardless of which
|
||||
`EvaluationContext` implementation is used.
|
||||
|
||||
Throughout this section, a source of a SpEL expression is considered "trusted" only if it
|
||||
is a developer of the application or an administrator responsible for configuring or
|
||||
operating the application. Any other source of a SpEL expression must be treated as
|
||||
untrusted – for example, an expression supplied by an end user of the application or
|
||||
received from an external system.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
`StandardEvaluationContext` exposes the complete SpEL language and must *never* be used
|
||||
to evaluate an expression obtained from an untrusted source.
|
||||
====
|
||||
|
||||
Although `SimpleEvaluationContext` restricts the SpEL language to a subset of its
|
||||
features, that restriction is provided on a best-effort basis and does not guarantee that
|
||||
expression evaluation is safe. Since an expression can potentially invoke any property,
|
||||
method, or function reachable via the configured root object, property accessors, method
|
||||
resolvers, variables, and functions, care must be taken if you choose to evaluate
|
||||
expressions from an untrusted source. It is therefore the responsibility of the code that
|
||||
configures an `EvaluationContext` – for example, by supplying a root object or by
|
||||
registering property accessors, resolvers, variables, or functions – to ensure that none
|
||||
of the objects reachable via the context expose operations that would be dangerous if
|
||||
invoked by an expression from an untrusted source.
|
||||
|
||||
Furthermore, a property "getter" reachable from an expression is not necessarily a pure,
|
||||
side-effect-free read operation. A JavaBean-style accessor (such as `getName()` or
|
||||
`isActive()`) and a plain accessor method used to support data classes such as Java
|
||||
records and Kotlin data classes (such as `name()`) are indistinguishable from a method
|
||||
that performs an action and happens to return a value (that is, a method which is
|
||||
*accessor-shaped*). For example, the `public boolean delete()` method in `java.io.File`
|
||||
looks like a plain accessor method to SpEL. Specifically, neither
|
||||
`ReflectivePropertyAccessor` nor `DataBindingPropertyAccessor` can determine whether such
|
||||
a method is free of side effects. Moreover, restricting a `SimpleEvaluationContext` to
|
||||
read-only data binding governs only whether *assignment* to a property is permitted: it
|
||||
does not verify that reading a property is side-effect-free. When exposing a root object
|
||||
or other reachable object to an untrusted expression, you must ensure that none of its
|
||||
accessor-shaped methods perform an action that would be unsafe if triggered by that
|
||||
expression.
|
||||
|
||||
[NOTE]
|
||||
.What makes a method "accessor-shaped"?
|
||||
====
|
||||
A method is accessor-shaped if it is `public`, takes no arguments, and returns a value –
|
||||
the same shape that `ReflectivePropertyAccessor` and `DataBindingPropertyAccessor` look
|
||||
for when resolving a property "getter" by name. That shape says nothing about whether
|
||||
invoking the method is actually free of side effects. For example, the following methods
|
||||
are all accessor-shaped, but only some of them are safe to invoke as a property read.
|
||||
|
||||
Side-effect-free (safe to expose as properties):
|
||||
|
||||
* `getName()` and `isActive()`: conventional JavaBean-style accessors.
|
||||
* `name()` and `active()`: plain accessor methods used by data classes such as Java
|
||||
records and Kotlin data classes.
|
||||
|
||||
Side-effecting (unsafe to expose as properties, despite the identical shape):
|
||||
|
||||
* `java.io.File#delete()`: deletes the underlying file and returns whether the deletion
|
||||
succeeded.
|
||||
* `java.util.Queue#poll()`: removes and returns the head element, mutating the queue.
|
||||
* `java.util.concurrent.atomic.AtomicInteger#incrementAndGet()`: increments and returns
|
||||
a counter, mutating it.
|
||||
|
||||
If an untrusted expression can reference `someFile.delete`, `someQueue.poll`, or
|
||||
`someCounter.incrementAndGet` as a property, SpEL invokes the corresponding method just
|
||||
as readily as it would invoke a genuine getter.
|
||||
====
|
||||
|
||||
[[expressions-evaluation-context-object-design]]
|
||||
=== Object Design
|
||||
|
||||
Similar to the design guidance for
|
||||
xref:web/webmvc/mvc-data-binding.adoc#mvc-data-binding-design[web data binding], you
|
||||
should carefully design any object that may be reached from a SpEL expression evaluated
|
||||
against untrusted input. This applies not only to the root object supplied to an
|
||||
`EvaluationContext` but also to every object that such an expression can navigate to from
|
||||
that root object – for example, an object returned by a property, a method, an index
|
||||
operation, a variable, or a function.
|
||||
|
||||
When exposing an object to expressions from an untrusted source, consider the following
|
||||
recommendations.
|
||||
|
||||
Use a dedicated type::
|
||||
Prefer a dedicated type, designed specifically to be evaluated against untrusted
|
||||
expressions, over passing an existing domain or infrastructure type "as is". A
|
||||
dedicated type lets you control exactly which properties and methods are reachable from
|
||||
an expression, rather than exposing the full surface area of a class such as a JPA
|
||||
entity, `java.io.File`, or a JDBC `Connection` – most of which were never designed with
|
||||
SpEL evaluation in mind.
|
||||
|
||||
Prefer immutability::
|
||||
An immutable type – for example, a Java record or a Kotlin data class exposing only
|
||||
`val` properties – rules out property writes and eliminates any concern that a "getter"
|
||||
might mutate state as a side effect, since there is no mutable state to affect.
|
||||
Immutability does not, on its own, rule out an accessor-shaped method with an external
|
||||
side effect (such as a network call or a file system operation), but it removes an
|
||||
entire class of risk.
|
||||
|
||||
Limit scope::
|
||||
Expose only the properties and methods that the expression is expected to use, and
|
||||
nothing more. Because a `PropertyAccessor` cannot restrict access to specific
|
||||
properties or methods on a per-expression basis, every accessor-shaped method reachable
|
||||
on an exposed object is reachable by any expression that can reach that object –
|
||||
regardless of which property or method the application intended the expression to use.
|
||||
|
||||
Audit accessor-shaped methods::
|
||||
Review every accessor-shaped method exposed by a type before making it reachable from
|
||||
an untrusted expression, keeping the <<expressions-evaluation-context-security,
|
||||
security considerations>> discussed above in mind. None of the reachable methods should
|
||||
perform an action that would be unsafe if triggered by that expression.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
These recommendations apply transitively. If the root object exposes a property or method
|
||||
that returns another object, and an untrusted expression can navigate to it (for example,
|
||||
`rootObject.child.grandchild`), the nested object is just as reachable as the root object
|
||||
itself and must meet the same design requirements. The same is true for an object reached
|
||||
via indexing (for example, `rootObject.items[0]` or `rootObject.items['key']`): whatever
|
||||
is returned by the index operation is just as reachable as any other nested object.
|
||||
====
|
||||
|
||||
[[expressions-evaluation-context-lifecycle]]
|
||||
=== Lifecycle and Reuse
|
||||
|
||||
For performance, the AST nodes that make up a parsed `Expression` may cache the specific
|
||||
`PropertyAccessor`, `IndexAccessor`, `MethodExecutor`, or `ConstructorExecutor` that
|
||||
satisfied a previous evaluation, so that later evaluations of the same node can avoid
|
||||
asking every registered accessor or resolver in turn. Understanding this caching behavior
|
||||
is essential to using `Expression` and `EvaluationContext` correctly, in addition to the
|
||||
<<expressions-evaluation-context-security,security considerations>> discussed previously.
|
||||
|
||||
A parsed `Expression` is designed to be created once and evaluated repeatedly, and doing
|
||||
so is both supported and encouraged. In particular:
|
||||
|
||||
* A parsed `Expression` may be evaluated against different root objects, and against
|
||||
different `EvaluationContext` instances of the *same type and with equivalent
|
||||
configuration* – for example, several `StandardEvaluationContext` instances each
|
||||
registering the same kind of custom `PropertyAccessor`. Changing the accessors or
|
||||
resolvers registered with a context between evaluations of the same expression is
|
||||
atypical and generally not advised, but is expected to work correctly: the registered
|
||||
state of the *current* context is what is consulted, not a snapshot taken during an
|
||||
earlier evaluation.
|
||||
* A parsed `Expression` must *not* be evaluated first against a context with one set of
|
||||
security implications and later against a context with different, typically more
|
||||
restrictive, security implications – for example, first against a
|
||||
`StandardEvaluationContext` and later against a `SimpleEvaluationContext`. Doing so is
|
||||
analogous to executing a database query on behalf of an administrator, caching the
|
||||
resulting administrator-privileged execution plan, and then reusing that cached plan for
|
||||
a lower-privileged user while expecting the lower-privileged user's restrictions to
|
||||
apply: cached state from the first, more permissive evaluation may be reused during the
|
||||
second, and the second context's restrictions cannot be reliably enforced as a result.
|
||||
If the same expression string must be evaluated under contexts with different security
|
||||
implications, parse it into *distinct* `Expression` instances, one per context.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Reusing a single parsed `Expression` across `EvaluationContext` instances with different
|
||||
security implications is not a supported usage pattern and must be avoided, regardless of
|
||||
which `EvaluationContext` implementations are involved.
|
||||
====
|
||||
|
||||
[[expressions-type-conversion]]
|
||||
=== Type Conversion
|
||||
|
||||
@@ -319,17 +488,23 @@ Kotlin::
|
||||
|
||||
It is possible to configure the SpEL expression parser by using a parser configuration
|
||||
object (`org.springframework.expression.spel.SpelParserConfiguration`). The configuration
|
||||
object controls the behavior of some of the expression components. For example, if you
|
||||
index into a collection and the element at the specified index is `null`, SpEL can
|
||||
automatically create the element. This is useful when using expressions made up of a
|
||||
chain of property references. Similarly, if you index into a collection and specify an
|
||||
index that is greater than the current size of the collection, SpEL can automatically
|
||||
grow the collection to accommodate that index. In order to add an element at the
|
||||
specified index, SpEL will try to create the element using the element type's default
|
||||
constructor before setting the specified value. If the element type does not have a
|
||||
default constructor, `null` will be added to the collection. If there is no built-in
|
||||
converter or custom converter that knows how to set the value, `null` will remain in the
|
||||
collection at the specified index. The following example demonstrates how to
|
||||
object controls the behavior of some of the expression components. To create a
|
||||
`SpelParserConfiguration` instance, favor `SpelParserConfiguration.builder()` over the
|
||||
numerous constructors in `SpelParserConfiguration`, since the builder only requires
|
||||
configuration of the properties that need to deviate from their sensible defaults --
|
||||
or use `SpelParserConfiguration.withDefaults()` if none of those defaults need to be
|
||||
overridden.
|
||||
|
||||
For example, if you index into a collection and the element at the specified index is
|
||||
`null`, SpEL can automatically create the element. This is useful when using expressions
|
||||
made up of a chain of property references. Similarly, if you index into a collection and
|
||||
specify an index that is greater than the current size of the collection, SpEL can
|
||||
automatically grow the collection to accommodate that index. In order to add an element
|
||||
at the specified index, SpEL will try to create the element using the element type's
|
||||
default constructor before setting the specified value. If the element type does not
|
||||
have a default constructor, `null` will be added to the collection. If there is no
|
||||
built-in converter or custom converter that knows how to set the value, `null` will
|
||||
remain in the collection at the specified index. The following example demonstrates how to
|
||||
automatically grow a `List`.
|
||||
|
||||
[tabs]
|
||||
@@ -342,10 +517,10 @@ Java::
|
||||
public List<String> list;
|
||||
}
|
||||
|
||||
// Turn on:
|
||||
// - auto null reference initialization
|
||||
// - auto collection growing
|
||||
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
|
||||
SpelParserConfiguration config = SpelParserConfiguration.builder()
|
||||
.autoGrowNullReferences()
|
||||
.autoGrowCollections()
|
||||
.build();
|
||||
|
||||
ExpressionParser parser = new SpelExpressionParser(config);
|
||||
|
||||
@@ -367,10 +542,10 @@ Kotlin::
|
||||
var list: List<String>? = null
|
||||
}
|
||||
|
||||
// Turn on:
|
||||
// - auto null reference initialization
|
||||
// - auto collection growing
|
||||
val config = SpelParserConfiguration(true, true)
|
||||
val config = SpelParserConfiguration.builder()
|
||||
.autoGrowNullReferences()
|
||||
.autoGrowCollections()
|
||||
.build()
|
||||
|
||||
val parser = SpelExpressionParser(config)
|
||||
|
||||
@@ -387,7 +562,8 @@ Kotlin::
|
||||
|
||||
By default, a SpEL expression cannot contain more than 10,000 characters; however, the
|
||||
`maxExpressionLength` is configurable. If you create a `SpelExpressionParser`
|
||||
programmatically, you can specify a custom `maxExpressionLength` when creating the
|
||||
programmatically, you can specify a custom `maxExpressionLength` via
|
||||
`SpelParserConfiguration.builder().maximumExpressionLength(...)` when creating the
|
||||
`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to
|
||||
set the `maxExpressionLength` used for parsing SpEL expressions within an
|
||||
`ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can
|
||||
@@ -398,13 +574,29 @@ xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
Similarly, the number of operations performed during the evaluation of a SpEL expression
|
||||
cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If
|
||||
you create a `SpelExpressionParser` programmatically (the recommend approach), you can
|
||||
specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that
|
||||
you provide to the `SpelExpressionParser`. If you are not able to configure an explicit
|
||||
value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system
|
||||
property or Spring property named `spring.expression.maxOperations` to the maximum number
|
||||
of operations required by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
specify a custom `maxOperations` value via
|
||||
`SpelParserConfiguration.builder().maximumOperations(...)` when creating the
|
||||
`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you are not
|
||||
able to configure an explicit value for `maxOperations` via `SpelParserConfiguration`,
|
||||
you can set a JVM system property or Spring property named
|
||||
`spring.expression.maxOperations` to the maximum number of operations required by your
|
||||
application (see xref:appendix.adoc#appendix-spring-properties[Supported Spring
|
||||
Properties]).
|
||||
|
||||
In addition, the result of a `BigDecimal` or `BigInteger` power operation within a SpEL
|
||||
expression cannot exceed 1,000,000 bits by default – approximately equivalent to a
|
||||
decimal number with 300,000 digits. Power operations involving large base values or large
|
||||
exponents can be computationally expensive, and this limit ensures that evaluations
|
||||
remain bounded; however, the `maximumBigPowerBits` value is configurable. If you create a
|
||||
`SpelExpressionParser` programmatically (the recommended approach), you can specify a
|
||||
custom `maximumBigPowerBits` value via
|
||||
`SpelParserConfiguration.builder().maximumBigPowerBits(...)` when creating the
|
||||
`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. To remove this
|
||||
limit entirely, pass `Integer.MAX_VALUE` as the `maximumBigPowerBits` value. If you are
|
||||
not able to configure an explicit value for `maximumBigPowerBits` via
|
||||
`SpelParserConfiguration`, you can set a JVM system property or Spring property named
|
||||
`spring.expression.maxBigPowerBits` to the maximum result size in bits (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
[[expressions-spel-compilation]]
|
||||
== SpEL Compilation
|
||||
@@ -443,9 +635,9 @@ only 3ms using the compiled version of the expression.
|
||||
|
||||
The compiler is not turned on by default, but you can turn it on in either of two
|
||||
different ways. You can turn it on by using the parser configuration process
|
||||
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed
|
||||
earlier]) or by using a Spring property when SpEL usage is embedded inside another
|
||||
component. This section discusses both of these options.
|
||||
(<<expressions-parser-configuration,discussed earlier>>) or by using a Spring property
|
||||
when SpEL usage is embedded inside another component. This section discusses both of
|
||||
these options.
|
||||
|
||||
The compiler can operate in one of three modes, which are captured in the
|
||||
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows.
|
||||
@@ -487,8 +679,10 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
|
||||
this.getClass().getClassLoader());
|
||||
SpelParserConfiguration config = SpelParserConfiguration.builder()
|
||||
.compilerMode(SpelCompilerMode.IMMEDIATE)
|
||||
.compilerClassLoader(getClass().getClassLoader())
|
||||
.build();
|
||||
|
||||
SpelExpressionParser parser = new SpelExpressionParser(config);
|
||||
|
||||
@@ -503,8 +697,10 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
|
||||
this.javaClass.classLoader)
|
||||
val config = SpelParserConfiguration.builder()
|
||||
.compilerMode(SpelCompilerMode.IMMEDIATE)
|
||||
.compilerClassLoader(javaClass.classLoader)
|
||||
.build()
|
||||
|
||||
val parser = SpelExpressionParser(config)
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
The Spring Expression Language supports the following kinds of operators:
|
||||
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-relational[Relational Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-logical[Logical Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-string[String Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-mathematical[Mathematical Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-assignment[The Assignment Operator]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-overloaded[Overloaded Operators]
|
||||
* <<expressions-operators-relational,Relational Operators>>
|
||||
* <<expressions-operators-logical,Logical Operators>>
|
||||
* <<expressions-operators-string,String Operators>>
|
||||
* <<expressions-operators-mathematical,Mathematical Operators>>
|
||||
* <<expressions-assignment,The Assignment Operator>>
|
||||
* <<expressions-operators-overloaded,Overloaded Operators>>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -81,6 +81,13 @@ public void sendNotification() {
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
When `delay` is `0` combined with a positive `jitter`, the delay never grows
|
||||
regardless of any configured `multiplier`, so the full configured `jitter` is
|
||||
applied directly as a random delay in the range from `0` to `min(jitter, maxDelay)`.
|
||||
====
|
||||
|
||||
Last but not least, `@Retryable` also works for reactive methods with a reactive return
|
||||
type, decorating the pipeline with Reactor's retry capabilities:
|
||||
|
||||
@@ -263,6 +270,13 @@ and an exponential back-off strategy with a bit of jitter.
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
When `delay` is zero combined with a positive `jitter`, the delay never grows
|
||||
regardless of any configured `multiplier`, so the full configured `jitter` is
|
||||
applied directly as a random delay in the range from zero to `min(jitter, maxDelay)`.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Although the factory methods and builder API for `RetryPolicy` cover most common
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
This chapter covers how Spring handles resources and how you can work with resources in
|
||||
Spring. It includes the following topics:
|
||||
|
||||
* xref:core/resources.adoc#resources-introduction[Introduction]
|
||||
* xref:core/resources.adoc#resources-resource[The `Resource` Interface]
|
||||
* xref:core/resources.adoc#resources-implementations[Built-in `Resource` Implementations]
|
||||
* xref:core/resources.adoc#resources-resourceloader[The `ResourceLoader` Interface]
|
||||
* xref:core/resources.adoc#resources-resourcepatternresolver[The `ResourcePatternResolver` Interface]
|
||||
* xref:core/resources.adoc#resources-resourceloaderaware[The `ResourceLoaderAware` Interface]
|
||||
* xref:core/resources.adoc#resources-as-dependencies[Resources as Dependencies]
|
||||
* xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths]
|
||||
* <<resources-introduction,Introduction>>
|
||||
* <<resources-resource,The `Resource` Interface>>
|
||||
* <<resources-implementations,Built-in `Resource` Implementations>>
|
||||
* <<resources-resourceloader,The `ResourceLoader` Interface>>
|
||||
* <<resources-resourcepatternresolver,The `ResourcePatternResolver` Interface>>
|
||||
* <<resources-resourceloaderaware,The `ResourceLoaderAware` Interface>>
|
||||
* <<resources-as-dependencies,Resources as Dependencies>>
|
||||
* <<resources-app-ctx,Application Contexts and Resource Paths>>
|
||||
|
||||
|
||||
[[resources-introduction]]
|
||||
@@ -126,13 +126,13 @@ For example, a `UrlResource` wraps a URL and uses the wrapped `URL` to do its wo
|
||||
|
||||
Spring includes several built-in `Resource` implementations:
|
||||
|
||||
* xref:core/resources.adoc#resources-implementations-urlresource[`UrlResource`]
|
||||
* xref:core/resources.adoc#resources-implementations-classpathresource[`ClassPathResource`]
|
||||
* xref:core/resources.adoc#resources-implementations-filesystemresource[`FileSystemResource`]
|
||||
* xref:core/resources.adoc#resources-implementations-pathresource[`PathResource`]
|
||||
* xref:core/resources.adoc#resources-implementations-servletcontextresource[`ServletContextResource`]
|
||||
* xref:core/resources.adoc#resources-implementations-inputstreamresource[`InputStreamResource`]
|
||||
* xref:core/resources.adoc#resources-implementations-bytearrayresource[`ByteArrayResource`]
|
||||
* <<resources-implementations-urlresource,`UrlResource`>>
|
||||
* <<resources-implementations-classpathresource,`ClassPathResource`>>
|
||||
* <<resources-implementations-filesystemresource,`FileSystemResource`>>
|
||||
* <<resources-implementations-pathresource,`PathResource`>>
|
||||
* <<resources-implementations-servletcontextresource,`ServletContextResource`>>
|
||||
* <<resources-implementations-inputstreamresource,`InputStreamResource`>>
|
||||
* <<resources-implementations-bytearrayresource,`ByteArrayResource`>>
|
||||
|
||||
For a complete list of `Resource` implementations available in Spring, consult the
|
||||
"All Known Implementing Classes" section of the
|
||||
@@ -350,7 +350,7 @@ objects:
|
||||
|
||||
| file:
|
||||
| `\file:///data/config.xml`
|
||||
| Loaded as a `URL` from the filesystem. See also xref:core/resources.adoc#resources-filesystemresource-caveats[`FileSystemResource` Caveats].
|
||||
| Loaded as a `URL` from the filesystem. See also <<resources-filesystemresource-caveats,`FileSystemResource` Caveats>>.
|
||||
|
||||
| https:
|
||||
| `\https://myserver/logo.png`
|
||||
@@ -384,11 +384,11 @@ for all matching resources from the class path. Note that the resource location
|
||||
expected to be a path without placeholders in this case -- for example,
|
||||
`classpath*:/config/beans.xml`. JAR files or different directories in the class path can
|
||||
contain multiple files with the same path and the same name. See
|
||||
xref:core/resources.adoc#resources-app-ctx-wildcards-in-resource-paths[Wildcards in Application Context Constructor Resource Paths] and its subsections for further details
|
||||
<<resources-app-ctx-wildcards-in-resource-paths,Wildcards in Application Context Constructor Resource Paths>> and its subsections for further details
|
||||
on wildcard support with the `classpath*:` resource prefix.
|
||||
|
||||
A passed-in `ResourceLoader` (for example, one supplied via
|
||||
xref:core/resources.adoc#resources-resourceloaderaware[`ResourceLoaderAware`] semantics) can be checked whether
|
||||
<<resources-resourceloaderaware,`ResourceLoaderAware`>> semantics) can be checked whether
|
||||
it implements this extended interface too.
|
||||
|
||||
`PathMatchingResourcePatternResolver` is a standalone implementation that is usable
|
||||
@@ -452,7 +452,7 @@ For more information, see xref:core/beans/annotation-config/autowired.adoc[Using
|
||||
|
||||
NOTE: To load one or more `Resource` objects for a resource path that contains wildcards
|
||||
or makes use of the special `classpath*:` resource prefix, consider having an instance of
|
||||
xref:core/resources.adoc#resources-resourcepatternresolver[`ResourcePatternResolver`] autowired into your
|
||||
<<resources-resourcepatternresolver,`ResourcePatternResolver`>> autowired into your
|
||||
application components instead of `ResourceLoader`.
|
||||
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
= Data Binding
|
||||
|
||||
Data binding is useful for binding user input to a target object where user input is a map
|
||||
with property paths as keys, following xref:data-binding-conventions[JavaBeans conventions].
|
||||
with property paths as keys, following <<data-binding-conventions,JavaBeans conventions>>.
|
||||
`DataBinder` is the main class that supports this, and it provides two ways to bind user
|
||||
input:
|
||||
|
||||
- xref:data-binding-constructor-binding[Constructor binding] - bind user input to a
|
||||
- <<data-binding-constructor-binding,Constructor binding>> - bind user input to a
|
||||
public data constructor, looking up constructor argument values in the user input.
|
||||
- xref:data-binding-property-binding[Property binding] - bind user input to setters,
|
||||
- <<data-binding-property-binding,Property binding>> - bind user input to setters,
|
||||
matching keys from the user input to properties of the target object structure.
|
||||
|
||||
You can apply both constructor and property binding or only one.
|
||||
@@ -32,7 +32,7 @@ WebFlux support a custom name mapping through the `@BindParam` annotation on con
|
||||
parameters or fields if present. If necessary, you can also configure a `NameResolver` on
|
||||
`DataBinder` to customize the argument name to use.
|
||||
|
||||
xref:data-binding-conventions[Type conversion] is applied as needed to convert user input.
|
||||
<<data-binding-conventions,Type conversion>> is applied as needed to convert user input.
|
||||
If the constructor parameter is an object, it is constructed recursively in the same
|
||||
manner, but through a nested property path. That means constructor binding creates both
|
||||
the target object and any objects it contains.
|
||||
@@ -103,7 +103,7 @@ details. The below table shows some examples of these conventions:
|
||||
(This next section is not vitally important to you if you do not plan to work with
|
||||
the `BeanWrapper` directly. If you use only the `DataBinder` and the `BeanFactory`
|
||||
and their default implementations, you should skip ahead to the
|
||||
xref:core/validation/data-binding.adoc#data-binding-conversion[section on `PropertyEditors`].)
|
||||
<<data-binding-conversion,section on `PropertyEditors`>>.)
|
||||
|
||||
The following two example classes use the `BeanWrapper` to get and set
|
||||
properties:
|
||||
@@ -447,7 +447,7 @@ where it can be automatically detected and applied.
|
||||
Note that all bean factories and application contexts automatically use a number of
|
||||
built-in property editors, through their use of a `BeanWrapper` to
|
||||
handle property conversions. The standard property editors that the `BeanWrapper`
|
||||
registers are listed in the xref:core/validation/data-binding.adoc#data-binding-conversion[previous section].
|
||||
registers are listed in the <<data-binding-conversion,previous section>>.
|
||||
Additionally, ``ApplicationContext``s also override or add additional editors to handle
|
||||
resource lookups in a manner appropriate to the specific application context type.
|
||||
|
||||
@@ -576,7 +576,7 @@ You can write a corresponding registrar and reuse it in each case.
|
||||
`PropertyEditorRegistry`, an interface that is implemented by the Spring `BeanWrapper`
|
||||
(and `DataBinder`). `PropertyEditorRegistrar` instances are particularly convenient
|
||||
when used in conjunction with `CustomEditorConfigurer` (described
|
||||
xref:core/validation/data-binding.adoc#data-binding-conversion-customeditor-registration[here]), which exposes a property
|
||||
<<data-binding-conversion-customeditor-registration,here>>), which exposes a property
|
||||
called `setPropertyEditorRegistrars(..)`. `PropertyEditorRegistrar` instances added
|
||||
to a `CustomEditorConfigurer` in this fashion can easily be shared with `DataBinder` and
|
||||
Spring MVC controllers. Furthermore, it avoids the need for synchronization on custom
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
This part of the appendix lists XML schemas for data access, including the following:
|
||||
|
||||
* xref:data-access/appendix.adoc#xsd-schemas-tx[The `tx` Schema]
|
||||
* xref:data-access/appendix.adoc#xsd-schemas-jdbc[The `jdbc` Schema]
|
||||
* <<xsd-schemas-tx,The `tx` Schema>>
|
||||
* <<xsd-schemas-jdbc,The `jdbc` Schema>>
|
||||
|
||||
[[xsd-schemas-tx]]
|
||||
=== The `tx` Schema
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
This section covers:
|
||||
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-datasource[Using `DataSource`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceUtils[Using `DataSourceUtils`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-SmartDataSource[Implementing `SmartDataSource`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-AbstractDataSource[Extending `AbstractDataSource`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-SingleConnectionDataSource[Using `SingleConnectionDataSource`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-DriverManagerDataSource[Using `DriverManagerDataSource`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-TransactionAwareDataSourceProxy[Using `TransactionAwareDataSourceProxy`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceTransactionManager[Using `DataSourceTransactionManager` / `JdbcTransactionManager`]
|
||||
* <<jdbc-datasource,Using `DataSource`>>
|
||||
* <<jdbc-DataSourceUtils,Using `DataSourceUtils`>>
|
||||
* <<jdbc-SmartDataSource,Implementing `SmartDataSource`>>
|
||||
* <<jdbc-AbstractDataSource,Extending `AbstractDataSource`>>
|
||||
* <<jdbc-SingleConnectionDataSource,Using `SingleConnectionDataSource`>>
|
||||
* <<jdbc-DriverManagerDataSource,Using `DriverManagerDataSource`>>
|
||||
* <<jdbc-TransactionAwareDataSourceProxy,Using `TransactionAwareDataSourceProxy`>>
|
||||
* <<jdbc-DataSourceTransactionManager,Using `DataSourceTransactionManager` / `JdbcTransactionManager`>>
|
||||
|
||||
|
||||
[[jdbc-datasource]]
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
This section covers how to use the JDBC core classes to control basic JDBC processing,
|
||||
including error handling. It includes the following topics:
|
||||
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate[Using `JdbcTemplate`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-NamedParameterJdbcTemplate[Using `NamedParameterJdbcTemplate`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-JdbcClient[Unified JDBC Query/Update Operations: `JdbcClient`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-SQLExceptionTranslator[Using `SQLExceptionTranslator`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-statements-executing[Running Statements]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-statements-querying[Running Queries]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-updates[Updating the Database]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-auto-generated-keys[Retrieving Auto-generated Keys]
|
||||
* <<jdbc-JdbcTemplate,Using `JdbcTemplate`>>
|
||||
* <<jdbc-NamedParameterJdbcTemplate,Using `NamedParameterJdbcTemplate`>>
|
||||
* <<jdbc-JdbcClient,Unified JDBC Query/Update Operations: `JdbcClient`>>
|
||||
* <<jdbc-SQLExceptionTranslator,Using `SQLExceptionTranslator`>>
|
||||
* <<jdbc-statements-executing,Running Statements>>
|
||||
* <<jdbc-statements-querying,Running Queries>>
|
||||
* <<jdbc-updates,Updating the Database>>
|
||||
* <<jdbc-auto-generated-keys,Retrieving Auto-generated Keys>>
|
||||
|
||||
|
||||
[[jdbc-JdbcTemplate]]
|
||||
@@ -349,7 +349,7 @@ The `JdbcTemplate` is stateful, in that it maintains a reference to a `DataSourc
|
||||
this state is not conversational state.
|
||||
|
||||
A common practice when using the `JdbcTemplate` class (and the associated
|
||||
xref:data-access/jdbc/core.adoc#jdbc-NamedParameterJdbcTemplate[`NamedParameterJdbcTemplate`] class) is to
|
||||
<<jdbc-NamedParameterJdbcTemplate,`NamedParameterJdbcTemplate`>> class) is to
|
||||
configure a `DataSource` in your Spring configuration file and then dependency-inject
|
||||
that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is created in
|
||||
the setter for the `DataSource` or in the constructor. This leads to DAOs that resemble the following:
|
||||
@@ -574,7 +574,7 @@ functionality that is present only in the `JdbcTemplate` class, you can use the
|
||||
`getJdbcOperations()` method to access the wrapped `JdbcTemplate` through the
|
||||
`JdbcOperations` interface.
|
||||
|
||||
See also xref:data-access/jdbc/core.adoc#jdbc-jdbctemplate-idioms[`JdbcTemplate` Best Practices]
|
||||
See also <<jdbc-jdbctemplate-idioms,`JdbcTemplate` Best Practices>>
|
||||
for guidelines on using the `NamedParameterJdbcTemplate` class in the context of an application.
|
||||
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ for further details on all supported options.
|
||||
This section covers how to select one of the three embedded databases that Spring
|
||||
supports. It includes the following topics:
|
||||
|
||||
* xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-using-HSQL[Using HSQL]
|
||||
* xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-using-H2[Using H2]
|
||||
* xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-using-Derby[Using Derby]
|
||||
* <<jdbc-embedded-database-using-HSQL,Using HSQL>>
|
||||
* <<jdbc-embedded-database-using-H2,Using H2>>
|
||||
* <<jdbc-embedded-database-using-Derby,Using Derby>>
|
||||
|
||||
[[jdbc-embedded-database-using-HSQL]]
|
||||
=== Using HSQL
|
||||
@@ -143,7 +143,7 @@ can be useful for one-offs when the embedded database does not need to be reused
|
||||
classes. However, if you wish to create an embedded database that is shared within a test suite,
|
||||
consider using the xref:testing/testcontext-framework.adoc[Spring TestContext Framework] and
|
||||
configuring the embedded database as a bean in the Spring `ApplicationContext` as described
|
||||
in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database[Creating an Embedded Database].
|
||||
in <<jdbc-embedded-database,Creating an Embedded Database>>.
|
||||
The following listing shows the test template:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -10,7 +10,7 @@ procedures and run update, delete, and insert statements.
|
||||
[NOTE]
|
||||
====
|
||||
Many Spring developers believe that the various RDBMS operation classes described below
|
||||
(with the exception of the xref:data-access/jdbc/object.adoc#jdbc-StoredProcedure[`StoredProcedure`] class) can often
|
||||
(with the exception of the <<jdbc-StoredProcedure,`StoredProcedure`>> class) can often
|
||||
be replaced with straight `JdbcTemplate` calls. Often, it is simpler to write a DAO
|
||||
method that calls a method on a `JdbcTemplate` directly (as opposed to
|
||||
encapsulating a query as a full-blown class).
|
||||
@@ -266,7 +266,7 @@ The SQL type is specified using the `java.sql.Types` constants.
|
||||
|
||||
The first line (with the `SqlParameter`) declares an IN parameter. You can use IN parameters
|
||||
both for stored procedure calls and for queries using the `SqlQuery` and its
|
||||
subclasses (covered in xref:data-access/jdbc/object.adoc#jdbc-SqlQuery[Understanding `SqlQuery`]).
|
||||
subclasses (covered in <<jdbc-SqlQuery,Understanding `SqlQuery`>>).
|
||||
|
||||
The second line (with the `SqlOutParameter`) declares an `out` parameter to be used in the
|
||||
stored procedure call. There is also an `SqlInOutParameter` for `InOut` parameters
|
||||
|
||||
@@ -479,7 +479,7 @@ returned `out` parameters.
|
||||
Earlier in this chapter, we described how parameters are deduced from metadata, but you can declare them
|
||||
explicitly if you wish. You can do so by creating and configuring `SimpleJdbcCall` with
|
||||
the `declareParameters` method, which takes a variable number of `SqlParameter` objects
|
||||
as input. See the xref:data-access/jdbc/simple.adoc#jdbc-params[next section] for details on how to define an `SqlParameter`.
|
||||
as input. See the <<jdbc-params,next section>> for details on how to define an `SqlParameter`.
|
||||
|
||||
NOTE: Explicit declarations are necessary if the database you use is not a Spring-supported
|
||||
database. Currently, Spring supports metadata lookup of stored procedure calls for the
|
||||
|
||||
@@ -35,7 +35,7 @@ JDBC, the `JdbcTemplate` class mentioned in a xref:data-access/jdbc/core.adoc#jd
|
||||
provides connection handling and proper conversion of `SQLException` to the
|
||||
`DataAccessException` hierarchy, including translation of database-specific SQL error
|
||||
codes to meaningful exception classes. For ORM technologies, see the
|
||||
xref:data-access/orm/general.adoc#orm-exception-translation[next section] for how to get the same exception
|
||||
<<orm-exception-translation,next section>> for how to get the same exception
|
||||
translation benefits.
|
||||
|
||||
When it comes to transaction management, the `JdbcTemplate` class hooks in to the Spring
|
||||
|
||||
@@ -26,7 +26,7 @@ To avoid tying application objects to hard-coded resource lookups, you can defin
|
||||
resources (such as a JDBC `DataSource` or a Hibernate `SessionFactory`) as beans in the
|
||||
Spring container. Application objects that need to access resources receive references
|
||||
to such predefined instances through bean references, as illustrated in the DAO
|
||||
definition in the xref:data-access/orm/hibernate.adoc#orm-hibernate-straight[next section].
|
||||
definition in the <<orm-hibernate-straight,next section>>.
|
||||
|
||||
The following excerpt from an XML application context definition shows how to set up a
|
||||
JDBC `DataSource` and a Hibernate `SessionFactory` on top of it:
|
||||
|
||||
@@ -14,9 +14,9 @@ the underlying implementation in order to provide additional features.
|
||||
The Spring JPA support offers three ways of setting up the JPA `EntityManagerFactory`
|
||||
that is used by the application to obtain an entity manager.
|
||||
|
||||
* xref:data-access/orm/jpa.adoc#orm-jpa-setup-lemfb[Using `LocalEntityManagerFactoryBean`]
|
||||
* xref:data-access/orm/jpa.adoc#orm-jpa-setup-jndi[Obtaining an EntityManagerFactory from JNDI]
|
||||
* xref:data-access/orm/jpa.adoc#orm-jpa-setup-lcemfb[Using `LocalContainerEntityManagerFactoryBean`]
|
||||
* <<orm-jpa-setup-lemfb,Using `LocalEntityManagerFactoryBean`>>
|
||||
* <<orm-jpa-setup-jndi,Obtaining an EntityManagerFactory from JNDI>>
|
||||
* <<orm-jpa-setup-lcemfb,Using `LocalContainerEntityManagerFactoryBean`>>
|
||||
|
||||
[[orm-jpa-setup-lemfb]]
|
||||
=== Using `LocalEntityManagerFactoryBean`
|
||||
@@ -519,7 +519,7 @@ Spring JPA also lets a configured `JpaTransactionManager` expose a JPA transacti
|
||||
to JDBC access code that accesses the same `DataSource`, provided that the registered
|
||||
`JpaDialect` supports retrieval of the underlying JDBC `Connection`. Spring provides
|
||||
dialects for the EclipseLink and Hibernate JPA implementations. See the
|
||||
xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on `JpaDialect`.
|
||||
<<orm-jpa-dialect,next section>> for details on `JpaDialect`.
|
||||
|
||||
For JTA-style lazy retrieval of actual resource connections, Spring provides a
|
||||
corresponding `DataSource` proxy class for the target connection pool: see
|
||||
@@ -621,7 +621,7 @@ seamlessly integrating with `@Bean` style configuration (no `FactoryBean` involv
|
||||
====
|
||||
`LocalSessionFactoryBean` and `LocalSessionFactoryBuilder` support background
|
||||
bootstrapping, just as the JPA `LocalContainerEntityManagerFactoryBean` does.
|
||||
See xref:data-access/orm/jpa.adoc#orm-jpa-setup-background[Background Bootstrapping] for an introduction.
|
||||
See <<orm-jpa-setup-background,Background Bootstrapping>> for an introduction.
|
||||
|
||||
On `LocalSessionFactoryBean`, this is available through the `bootstrapExecutor`
|
||||
property. On the programmatic `LocalSessionFactoryBuilder`, an overloaded
|
||||
|
||||
@@ -17,9 +17,9 @@ stream, or a SAX handler.
|
||||
|
||||
Some of the benefits of using Spring for your O/X mapping needs are:
|
||||
|
||||
* xref:data-access/oxm.adoc#oxm-ease-of-configuration[Ease of configuration]
|
||||
* xref:data-access/oxm.adoc#oxm-consistent-interfaces[Consistent Interfaces]
|
||||
* xref:data-access/oxm.adoc#oxm-consistent-exception-hierarchy[Consistent Exception Hierarchy]
|
||||
* <<oxm-ease-of-configuration,Ease of configuration>>
|
||||
* <<oxm-consistent-interfaces,Consistent Interfaces>>
|
||||
* <<oxm-consistent-exception-hierarchy,Consistent Exception Hierarchy>>
|
||||
|
||||
[[oxm-ease-of-configuration]]
|
||||
=== Ease of configuration
|
||||
@@ -52,7 +52,7 @@ These runtime exceptions wrap the original exception so that no information is l
|
||||
[[oxm-marshaller-unmarshaller]]
|
||||
== `Marshaller` and `Unmarshaller`
|
||||
|
||||
As stated in the xref:data-access/oxm.adoc#oxm-introduction[introduction], a marshaller serializes an object
|
||||
As stated in the <<oxm-introduction,introduction>>, a marshaller serializes an object
|
||||
to XML, and an unmarshaller deserializes XML stream to an object. This section describes
|
||||
the two Spring interfaces used for this purpose.
|
||||
|
||||
@@ -334,8 +334,8 @@ preamble of the XML configuration file. The following example shows how to do so
|
||||
|
||||
The schema makes the following elements available:
|
||||
|
||||
* xref:data-access/oxm.adoc#oxm-jaxb2-xsd[`jaxb2-marshaller`]
|
||||
* xref:data-access/oxm.adoc#oxm-jibx-xsd[`jibx-marshaller`]
|
||||
* <<oxm-jaxb2-xsd,`jaxb2-marshaller`>>
|
||||
* <<oxm-jibx-xsd,`jibx-marshaller`>>
|
||||
|
||||
Each tag is explained in its respective marshaller's section. As an example, though,
|
||||
the configuration of a JAXB2 marshaller might resemble the following:
|
||||
@@ -354,7 +354,7 @@ The JAXB binding compiler translates a W3C XML Schema into one or more Java clas
|
||||
generate a schema from annotated Java classes.
|
||||
|
||||
Spring supports the JAXB 2.0 API as XML marshalling strategies, following the
|
||||
`Marshaller` and `Unmarshaller` interfaces described in xref:data-access/oxm.adoc#oxm-marshaller-unmarshaller[`Marshaller` and `Unmarshaller`].
|
||||
`Marshaller` and `Unmarshaller` interfaces described in <<oxm-marshaller-unmarshaller,`Marshaller` and `Unmarshaller`>>.
|
||||
The corresponding integration classes reside in the `org.springframework.oxm.jaxb`
|
||||
package.
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ The Spring Framework's R2DBC abstraction framework consists of two different pac
|
||||
|
||||
* `core`: The `org.springframework.r2dbc.core` package contains the `DatabaseClient`
|
||||
class plus a variety of related classes. See
|
||||
xref:data-access/r2dbc.adoc#r2dbc-core[Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling].
|
||||
<<r2dbc-core,Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling>>.
|
||||
|
||||
* `connection`: The `org.springframework.r2dbc.connection` package contains a utility class
|
||||
for easy `ConnectionFactory` access and various simple `ConnectionFactory` implementations
|
||||
that you can use for testing and running unmodified R2DBC. See
|
||||
xref:data-access/r2dbc.adoc#r2dbc-connections[Controlling Database Connections].
|
||||
<<r2dbc-connections,Controlling Database Connections>>.
|
||||
|
||||
|
||||
[[r2dbc-core]]
|
||||
@@ -26,12 +26,12 @@ xref:data-access/r2dbc.adoc#r2dbc-connections[Controlling Database Connections].
|
||||
This section covers how to use the R2DBC core classes to control basic R2DBC processing,
|
||||
including error handling. It includes the following topics:
|
||||
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient[Using `DatabaseClient`]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-examples-statement[Executing Statements]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-examples-query[Querying (`SELECT`)]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-examples-update[Updating (`INSERT`, `UPDATE`, and `DELETE`) with `DatabaseClient`]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-filter[Statement Filters]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-auto-generated-keys[Retrieving Auto-generated Keys]
|
||||
* <<r2dbc-DatabaseClient,Using `DatabaseClient`>>
|
||||
* <<r2dbc-DatabaseClient-examples-statement,Executing Statements>>
|
||||
* <<r2dbc-DatabaseClient-examples-query,Querying (`SELECT`)>>
|
||||
* <<r2dbc-DatabaseClient-examples-update,Updating (`INSERT`, `UPDATE`, and `DELETE`) with `DatabaseClient`>>
|
||||
* <<r2dbc-DatabaseClient-filter,Statement Filters>>
|
||||
* <<r2dbc-auto-generated-keys,Retrieving Auto-generated Keys>>
|
||||
|
||||
[[r2dbc-DatabaseClient]]
|
||||
=== Using `DatabaseClient`
|
||||
@@ -680,11 +680,11 @@ Kotlin::
|
||||
|
||||
This section covers:
|
||||
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-ConnectionFactory[Using `ConnectionFactory`]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-ConnectionFactoryUtils[Using `ConnectionFactoryUtils`]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-SingleConnectionFactory[Using `SingleConnectionFactory`]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-TransactionAwareConnectionFactoryProxy[Using `TransactionAwareConnectionFactoryProxy`]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-R2dbcTransactionManager[Using `R2dbcTransactionManager`]
|
||||
* <<r2dbc-ConnectionFactory,Using `ConnectionFactory`>>
|
||||
* <<r2dbc-ConnectionFactoryUtils,Using `ConnectionFactoryUtils`>>
|
||||
* <<r2dbc-SingleConnectionFactory,Using `SingleConnectionFactory`>>
|
||||
* <<r2dbc-TransactionAwareConnectionFactoryProxy,Using `TransactionAwareConnectionFactoryProxy`>>
|
||||
* <<r2dbc-R2dbcTransactionManager,Using `R2dbcTransactionManager`>>
|
||||
|
||||
[[r2dbc-ConnectionFactory]]
|
||||
=== Using `ConnectionFactory`
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ 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]
|
||||
<<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
|
||||
@@ -360,7 +360,7 @@ properties of the `@Transactional` annotation:
|
||||
|===
|
||||
| Property| Type| Description
|
||||
|
||||
| xref:data-access/transaction/declarative/annotations.adoc#tx-multiple-tx-mgrs-with-attransactional[value]
|
||||
| <<tx-multiple-tx-mgrs-with-attransactional,value>>
|
||||
| `String`
|
||||
| Optional qualifier that specifies the transaction manager to be used.
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ infrastructure.
|
||||
|
||||
NOTE: The preceding definition of the `dataSource` bean uses the `<jndi-lookup/>` tag
|
||||
from the `jee` namespace. For more information see
|
||||
xref:integration/appendix.adoc#xsd-schemas-jee[The JEE Schema].
|
||||
xref:integration/appendix.adoc#appendix.xsd-schemas-jee[The JEE Schema].
|
||||
|
||||
NOTE: If you use JTA, your transaction manager definition should look the same, regardless
|
||||
of what data access technology you use, be it JDBC, Hibernate JPA, or any other supported
|
||||
|
||||
@@ -98,7 +98,7 @@ through its `key` attribute. You can use xref:core/expressions.adoc[SpEL] to pic
|
||||
arguments of interest (or their nested properties), perform operations, or even
|
||||
invoke arbitrary methods without having to write any code or implement any interface.
|
||||
This is the recommended approach over the
|
||||
xref:integration/cache/annotations.adoc#cache-annotations-cacheable-default-key[default generator],
|
||||
<<cache-annotations-cacheable-default-key,default generator>>,
|
||||
since methods tend to be quite different in signatures as the code base grows. While the
|
||||
default strategy might work for some methods, it rarely works for all methods.
|
||||
|
||||
@@ -160,7 +160,7 @@ For applications that work with several cache managers, you can set the
|
||||
<1> Specifying `anotherCacheManager`.
|
||||
|
||||
You can also replace the `CacheResolver` entirely in a fashion similar to that of
|
||||
replacing xref:integration/cache/annotations.adoc#cache-annotations-cacheable-key[key generation].
|
||||
replacing <<cache-annotations-cacheable-key,key generation>>.
|
||||
The resolution is requested for every cache operation, letting the implementation
|
||||
actually resolve the caches to use based on runtime arguments. The following example
|
||||
shows how to specify a `CacheResolver`:
|
||||
@@ -684,5 +684,5 @@ preceding code:
|
||||
|
||||
Even though `@SlowService` is not a Spring annotation, the container automatically picks
|
||||
up its declaration at runtime and understands its meaning. Note that, as mentioned
|
||||
xref:integration/cache/annotations.adoc#cache-annotation-enable[earlier],
|
||||
<<cache-annotation-enable,earlier>>,
|
||||
annotation-driven behavior needs to be enabled.
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ or eviction contracts.
|
||||
== Ehcache-based Cache
|
||||
|
||||
Ehcache 3.x is fully JSR-107 compliant and no dedicated support is required for it. See
|
||||
xref:integration/cache/store-configuration.adoc#cache-store-configuration-jsr107[JSR-107 Cache] for details.
|
||||
<<cache-store-configuration-jsr107,JSR-107 Cache>> for details.
|
||||
|
||||
|
||||
[[cache-store-configuration-caffeine]]
|
||||
|
||||
@@ -25,7 +25,7 @@ See xref:integration/jms/annotated.adoc#jms-annotated-support[Enable Listener En
|
||||
|
||||
In a fashion similar to a Message-Driven Bean (MDB) in the EJB world, the Message-Driven
|
||||
POJO (MDP) acts as a receiver for JMS messages. The one restriction (but see
|
||||
xref:integration/jms/receiving.adoc#jms-receiving-async-message-listener-adapter[Using `MessageListenerAdapter`])
|
||||
<<jms-receiving-async-message-listener-adapter,Using `MessageListenerAdapter`>>)
|
||||
on an MDP is that it must implement the `jakarta.jms.MessageListener` interface.
|
||||
Note that, if your POJO receives messages on multiple threads, it is important to
|
||||
ensure that your implementation is thread-safe.
|
||||
|
||||
@@ -206,8 +206,8 @@ boilerplate JMS infrastructure concerns to the framework.
|
||||
There are two standard JMS message listener containers packaged with Spring, each with
|
||||
its specialized feature set.
|
||||
|
||||
* xref:integration/jms/using.adoc#jms-mdp-simple[`SimpleMessageListenerContainer`]
|
||||
* xref:integration/jms/using.adoc#jms-mdp-default[`DefaultMessageListenerContainer`]
|
||||
* <<jms-mdp-simple,`SimpleMessageListenerContainer`>>
|
||||
* <<jms-mdp-default,`DefaultMessageListenerContainer`>>
|
||||
|
||||
[[jms-mdp-simple]]
|
||||
=== Using `SimpleMessageListenerContainer`
|
||||
@@ -258,7 +258,7 @@ a simple `BackOff` implementation retries every five seconds. You can specify
|
||||
a custom `BackOff` implementation for more fine-grained recovery options. See
|
||||
{spring-framework-api}/util/backoff/ExponentialBackOff.html[`ExponentialBackOff`] for an example.
|
||||
|
||||
NOTE: Like its sibling (xref:integration/jms/using.adoc#jms-mdp-simple[`SimpleMessageListenerContainer`]),
|
||||
NOTE: Like its sibling (<<jms-mdp-simple,`SimpleMessageListenerContainer`>>),
|
||||
`DefaultMessageListenerContainer` supports native JMS transactions and allows for
|
||||
customizing the acknowledgment mode. If feasible for your scenario, This is strongly
|
||||
recommended over externally managed transactions -- that is, if you can live with
|
||||
|
||||
@@ -33,7 +33,7 @@ the export happens or disable automatic registration by setting the `autoStartup
|
||||
[[jmx-exporting-mbeanserver]]
|
||||
== Creating an MBeanServer
|
||||
|
||||
The configuration shown in the xref:integration/jmx/exporting.adoc[preceding section] assumes that the
|
||||
The configuration shown in the <<jmx-exporting,preceding section>> assumes that the
|
||||
application is running in an environment that has one (and only one) `MBeanServer`
|
||||
already running. In this case, Spring tries to locate the running `MBeanServer` and
|
||||
register your beans with that server (if any). This behavior is useful when your
|
||||
|
||||
@@ -108,7 +108,7 @@ In the preceding example, you can see that the `AnnotationTestBean` class is ann
|
||||
with `@ManagedResource` and that this `@ManagedResource` annotation is configured
|
||||
with a set of attributes. These attributes can be used to configure various aspects
|
||||
of the MBean that is generated by the `MBeanExporter` and are explained in greater
|
||||
detail later in xref:integration/jmx/interface.adoc#jmx-interface-metadata-types[Spring JMX Annotations].
|
||||
detail later in <<jmx-interface-metadata-types,Spring JMX Annotations>>.
|
||||
|
||||
Both the `age` and `name` properties are annotated with `@ManagedAttribute`,
|
||||
but, in the case of the `age` property, only the getter method is annotated.
|
||||
@@ -305,7 +305,7 @@ it. The only downside with this approach is that the name of the `AnnotationTest
|
||||
has business meaning. You can address this issue by configuring an `ObjectNamingStrategy`
|
||||
as explained in xref:integration/jmx/naming.adoc[Controlling `ObjectName` Instances for
|
||||
Your Beans]. You can also see an example which uses the `MetadataNamingStrategy` in
|
||||
xref:integration/jmx/interface.adoc#jmx-interface-metadata[Using Source-level Metadata: Java Annotations].
|
||||
<<jmx-interface-metadata,Using Source-level Metadata: Java Annotations>>.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ If necessary, you can provide a reference to a particular MBean `server`, and th
|
||||
`defaultDomain` attribute (a property of `AnnotationMBeanExporter`) accepts an alternate
|
||||
value for the generated MBean `ObjectName` domains. This is used in place of the
|
||||
fully qualified package name as described in the previous section on
|
||||
xref:integration/jmx/naming.adoc#jmx-naming-metadata[MetadataNamingStrategy], as the following example shows:
|
||||
<<jmx-naming-metadata,MetadataNamingStrategy>>, as the following example shows:
|
||||
|
||||
include-code::./CustomJmxConfiguration[tag=snippet,indent=0]
|
||||
|
||||
|
||||
@@ -19,26 +19,26 @@ You can learn more about {spring-boot-docs-ref}/actuator/observability.html[conf
|
||||
== List of produced Observations
|
||||
|
||||
Spring Framework instruments various features for observability.
|
||||
As outlined xref:integration/observability.adoc[at the beginning of this section], observations can generate timer Metrics and/or Traces depending on the configuration.
|
||||
As outlined <<observability,at the beginning of this section>>, observations can generate timer Metrics and/or Traces depending on the configuration.
|
||||
|
||||
.Observations produced by Spring Framework
|
||||
[%autowidth]
|
||||
|===
|
||||
|Observation name |Description
|
||||
|
||||
|xref:integration/observability.adoc#observability.http-client[`"http.client.requests"`]
|
||||
|<<observability.http-client,`"http.client.requests"`>>
|
||||
|Time spent for HTTP client exchanges
|
||||
|
||||
|xref:integration/observability.adoc#observability.http-server[`"http.server.requests"`]
|
||||
|<<observability.http-server,`"http.server.requests"`>>
|
||||
|Processing time for HTTP server exchanges at the Framework level
|
||||
|
||||
|xref:integration/observability.adoc#observability.jms.publish[`"jms.message.publish"`]
|
||||
|<<observability.jms.publish,`"jms.message.publish"`>>
|
||||
|Time spent sending a JMS message to a destination by a message producer.
|
||||
|
||||
|xref:integration/observability.adoc#observability.jms.process[`"jms.message.process"`]
|
||||
|<<observability.jms.process,`"jms.message.process"`>>
|
||||
|Processing time for a JMS message that was previously received by a message consumer.
|
||||
|
||||
|xref:integration/observability.adoc#observability.tasks-scheduled[`"tasks.scheduled.execution"`]
|
||||
|<<observability.tasks-scheduled,`"tasks.scheduled.execution"`>>
|
||||
|Processing time for an execution of a `@Scheduled` task
|
||||
|===
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
The Spring Framework provides the following choices for making calls to REST endpoints:
|
||||
|
||||
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] -- synchronous client with a fluent API
|
||||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] -- non-blocking, reactive client with fluent API
|
||||
* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] -- synchronous client with template method API, now deprecated in favor of `RestClient`
|
||||
* xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service Clients] -- annotated interface backed by generated proxy
|
||||
* <<rest-restclient,`RestClient`>> -- synchronous client with a fluent API
|
||||
* <<rest-webclient,`WebClient`>> -- non-blocking, reactive client with fluent API
|
||||
* <<rest-resttemplate,`RestTemplate`>> -- synchronous client with template method API, now deprecated in favor of `RestClient`
|
||||
* <<rest-http-service-client,HTTP Service Clients>> -- annotated interface backed by generated proxy
|
||||
|
||||
|
||||
[[rest-restclient]]
|
||||
@@ -480,7 +480,7 @@ The `RestTemplate` provides a high-level API over HTTP client libraries in the f
|
||||
It exposes the following groups of overloaded methods:
|
||||
|
||||
WARNING: As of Spring Framework 7.0, `RestTemplate` is deprecated in favor of `RestClient` and will be removed in a future version,
|
||||
please use the xref:integration/rest-clients.adoc#migrating-to-restclient["Migrating to RestClient"] guide.
|
||||
please use the <<migrating-to-restclient,"Migrating to RestClient">> guide.
|
||||
For asynchronous and streaming scenarios, consider the reactive xref:web/webflux-webclient.adoc[WebClient].
|
||||
|
||||
[[rest-overview-of-resttemplate-methods-tbl]]
|
||||
@@ -560,7 +560,7 @@ You can consider the following steps:
|
||||
2. Once all client requests go through `RestClient` instances, you can now work on replicating your existing
|
||||
`RestTemplate` instance creations by using `RestClient.Builder`. Because `RestTemplate` and `RestClient`
|
||||
share the same infrastructure, you can reuse custom `ClientHttpRequestFactory` or `ClientHttpRequestInterceptor`
|
||||
in your setup. See xref:integration/rest-clients.adoc#rest-restclient[the `RestClient` builder API].
|
||||
in your setup. See <<rest-restclient,the `RestClient` builder API>>.
|
||||
|
||||
If no other library is available on the classpath, `RestClient` will choose the `JdkClientHttpRequestFactory`
|
||||
powered by the modern JDK `HttpClient`, whereas `RestTemplate` would pick the `SimpleClientHttpRequestFactory` that
|
||||
@@ -874,7 +874,7 @@ The following table shows `RestClient` equivalents for `RestTemplate` methods.
|
||||
`RestClient` and `RestTemplate` instances share the same behavior when it comes to throwing exceptions
|
||||
(with the `RestClientException` type being at the top of the hierarchy).
|
||||
When `RestTemplate` consistently throws `HttpClientErrorException` for "4xx" response statues,
|
||||
`RestClient` allows for more flexibility with custom xref:integration/rest-clients.adoc#rest-http-service-client-exceptions["status handlers"].
|
||||
`RestClient` allows for more flexibility with custom <<rest-http-service-client-exceptions,"status handlers">>.
|
||||
|
||||
|
||||
[[rest-http-service-client]]
|
||||
|
||||
@@ -183,7 +183,7 @@ default). The following listing shows the available methods for `Trigger` implem
|
||||
|
||||
Spring provides two implementations of the `Trigger` interface. The most interesting one
|
||||
is the `CronTrigger`. It enables the scheduling of tasks based on
|
||||
xref:integration/scheduling.adoc#scheduling-cron-expression[cron expressions].
|
||||
<<scheduling-cron-expression,cron expressions>>.
|
||||
For example, the following task is scheduled to run 15 minutes past each hour but only
|
||||
during the 9-to-5 "business hours" on weekdays:
|
||||
|
||||
@@ -335,7 +335,7 @@ of time to wait before the intended execution of the method:
|
||||
----
|
||||
|
||||
If simple periodic scheduling is not expressive enough, you can provide a
|
||||
xref:integration/scheduling.adoc#scheduling-cron-expression[cron expression].
|
||||
<<scheduling-cron-expression,cron expression>>.
|
||||
The following example runs only on weekdays:
|
||||
|
||||
[source,java,indent=0]
|
||||
@@ -578,7 +578,7 @@ in combination with a custom pointcut.
|
||||
=== Executor Qualification with `@Async`
|
||||
|
||||
By default, when specifying `@Async` on a method, the executor that is used is the
|
||||
one xref:integration/scheduling.adoc#scheduling-enable-annotation-support[configured when enabling async support],
|
||||
one <<scheduling-enable-annotation-support,configured when enabling async support>>,
|
||||
i.e. the "`annotation-driven`" element if you are using XML or your `AsyncConfigurer`
|
||||
implementation, if any. However, you can use the `value` attribute of the `@Async`
|
||||
annotation when you need to indicate that an executor other than the default should be
|
||||
@@ -658,7 +658,7 @@ The following creates a `ThreadPoolTaskExecutor` instance:
|
||||
<task:executor id="executor" pool-size="10"/>
|
||||
----
|
||||
|
||||
As with the scheduler shown in the xref:integration/scheduling.adoc#scheduling-task-namespace-scheduler[previous section],
|
||||
As with the scheduler shown in the <<scheduling-task-namespace-scheduler,previous section>>,
|
||||
the value provided for the `id` attribute is used as the prefix for thread names within
|
||||
the pool. As far as the pool size is concerned, the `executor` element supports more
|
||||
configuration options than the `scheduler` element. For one thing, the thread pool for
|
||||
@@ -770,7 +770,7 @@ any previous execution takes. Additionally, for both `fixed-delay` and `fixed-ra
|
||||
tasks, you can specify an 'initial-delay' parameter, indicating the number of
|
||||
milliseconds to wait before the first execution of the method. For more control,
|
||||
you can instead provide a `cron` attribute to provide a
|
||||
xref:integration/scheduling.adoc#scheduling-cron-expression[cron expression].
|
||||
<<scheduling-cron-expression,cron expression>>.
|
||||
The following example shows these other options:
|
||||
|
||||
[source,xml,indent=0]
|
||||
@@ -791,8 +791,8 @@ The following example shows these other options:
|
||||
== Cron Expressions
|
||||
|
||||
All Spring cron expressions have to conform to the same format, whether you are using them in
|
||||
xref:integration/scheduling.adoc#scheduling-annotation-support-scheduled[`@Scheduled` annotations],
|
||||
xref:integration/scheduling.adoc#scheduling-task-namespace-scheduled-tasks[`task:scheduled-tasks` elements],
|
||||
<<scheduling-annotation-support-scheduled,`@Scheduled` annotations>>,
|
||||
<<scheduling-task-namespace-scheduled-tasks,`task:scheduled-tasks` elements>>,
|
||||
or someplace else. A well-formed cron expression, such as `* * * * * *`, consists of six
|
||||
space-separated time and date fields, each with its own range of valid values:
|
||||
|
||||
|
||||
@@ -131,11 +131,11 @@ demonstrate its API and protocol features.
|
||||
|
||||
The `spring-messaging` module contains the following:
|
||||
|
||||
* xref:rsocket.adoc#rsocket-requester[RSocketRequester] -- fluent API to make requests
|
||||
* <<rsocket-requester,RSocketRequester>> -- fluent API to make requests
|
||||
through an `io.rsocket.RSocket` with data and metadata encoding/decoding.
|
||||
* xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders] -- `@MessageMapping`
|
||||
* <<rsocket-annot-responders,Annotated Responders>> -- `@MessageMapping`
|
||||
and `@RSocketExchange` annotated handler methods for responding.
|
||||
* xref:rsocket.adoc#rsocket-interface[RSocket Interface] -- RSocket service declaration
|
||||
* <<rsocket-interface,RSocket Interface>> -- RSocket service declaration
|
||||
as Java interface with `@RSocketExchange` methods, for use as requester or responder.
|
||||
|
||||
The `spring-web` module contains `Encoder` and `Decoder` implementations such as Jackson
|
||||
@@ -217,7 +217,7 @@ metadata, the default mime type is
|
||||
metadata value and mime type pairs per request. Typically both don't need to be changed.
|
||||
|
||||
Data and metadata in the `SETUP` frame is optional. On the server side,
|
||||
xref:rsocket.adoc#rsocket-annot-connectmapping[@ConnectMapping] methods can be used to
|
||||
<<rsocket-annot-connectmapping,@ConnectMapping>> methods can be used to
|
||||
handle the start of a connection and the content of the `SETUP` frame. Metadata may be
|
||||
used for connection level security.
|
||||
|
||||
@@ -355,7 +355,7 @@ annotation such as `@RSocketClientResponder` vs the default `@Controller`. This
|
||||
is necessary in scenarios with client and server, or multiple clients in the same
|
||||
application.
|
||||
|
||||
See also xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders], for more on the programming model.
|
||||
See also <<rsocket-annot-responders,Annotated Responders>>, for more on the programming model.
|
||||
|
||||
[[rsocket-requester-client-advanced]]
|
||||
==== Advanced
|
||||
@@ -396,7 +396,7 @@ Kotlin::
|
||||
To make requests from a server to connected clients is a matter of obtaining the
|
||||
requester for the connected client from the server.
|
||||
|
||||
In xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders], `@ConnectMapping` and `@MessageMapping` methods support an
|
||||
In <<rsocket-annot-responders,Annotated Responders>>, `@ConnectMapping` and `@MessageMapping` methods support an
|
||||
`RSocketRequester` argument. Use it to access the requester for the connection. Keep in
|
||||
mind that `@ConnectMapping` methods are essentially handlers of the `SETUP` frame which
|
||||
must be handled before requests can begin. Therefore, requests at the very start must be
|
||||
@@ -442,8 +442,8 @@ Kotlin::
|
||||
[[rsocket-requester-requests]]
|
||||
=== Requests
|
||||
|
||||
Once you have a xref:rsocket.adoc#rsocket-requester-client[client] or
|
||||
xref:rsocket.adoc#rsocket-requester-server[server] requester, you can make requests as follows:
|
||||
Once you have a <<rsocket-requester-client,client>> or
|
||||
<<rsocket-requester-server,server>> requester, you can make requests as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -647,7 +647,7 @@ Kotlin::
|
||||
`RSocketMessageHandler` supports
|
||||
{rsocket-protocol-extensions}/CompositeMetadata.md[composite] and
|
||||
{rsocket-protocol-extensions}/Routing.md[routing] metadata by default. You can set its
|
||||
xref:rsocket.adoc#rsocket-metadata-extractor[MetadataExtractor] if you need to switch to a
|
||||
<<rsocket-metadata-extractor,MetadataExtractor>> if you need to switch to a
|
||||
different mime type or register additional metadata mime types.
|
||||
|
||||
You'll need to set the `Encoder` and `Decoder` instances required for metadata and data
|
||||
@@ -716,13 +716,13 @@ Kotlin::
|
||||
|
||||
Annotated responders on the client side need to be configured in the
|
||||
`RSocketRequester.Builder`. For details, see
|
||||
xref:rsocket.adoc#rsocket-requester-client-responder[Client Responders].
|
||||
<<rsocket-requester-client-responder,Client Responders>>.
|
||||
|
||||
[[rsocket-annot-messagemapping]]
|
||||
=== @MessageMapping
|
||||
|
||||
Once xref:rsocket.adoc#rsocket-annot-responders-server[server] or
|
||||
xref:rsocket.adoc#rsocket-annot-responders-client[client] responder configuration is in place,
|
||||
Once <<rsocket-annot-responders-server,server>> or
|
||||
<<rsocket-annot-responders-client,client>> responder configuration is in place,
|
||||
`@MessageMapping` methods can be used as follows:
|
||||
|
||||
[tabs]
|
||||
@@ -780,10 +780,10 @@ use the following method arguments:
|
||||
pass:q[`@MessageMapping("find.radar.{id}")`].
|
||||
|
||||
| `@Header`
|
||||
| Metadata value registered for extraction as described in xref:rsocket.adoc#rsocket-metadata-extractor[MetadataExtractor].
|
||||
| Metadata value registered for extraction as described in <<rsocket-metadata-extractor,MetadataExtractor>>.
|
||||
|
||||
| `@Headers Map<String, Object>`
|
||||
| All metadata values registered for extraction as described in xref:rsocket.adoc#rsocket-metadata-extractor[MetadataExtractor].
|
||||
| All metadata values registered for extraction as described in <<rsocket-metadata-extractor,MetadataExtractor>>.
|
||||
|
||||
|===
|
||||
|
||||
@@ -846,7 +846,7 @@ interaction type(s):
|
||||
|
||||
As an alternative to `@MessageMapping`, you can also handle requests with
|
||||
`@RSocketExchange` methods. Such methods are declared on an
|
||||
xref:rsocket-interface[RSocket Interface] and can be used as a requester via
|
||||
<<rsocket-interface,RSocket Interface>> and can be used as a requester via
|
||||
`RSocketServiceProxyFactory` or implemented by a responder.
|
||||
|
||||
For example, to handle requests as a responder:
|
||||
@@ -897,8 +897,8 @@ former needs to remain suitable for requester and responder use. For example, wh
|
||||
`@MessageMapping` can be declared to handle any number of routes and each route can
|
||||
be a pattern, `@RSocketExchange` must be declared with a single, concrete route. There are
|
||||
also small differences in the supported method parameters related to metadata, see
|
||||
xref:rsocket-annot-messagemapping[@MessageMapping] and
|
||||
xref:rsocket-interface[RSocket Interface] for a list of supported parameters.
|
||||
<<rsocket-annot-messagemapping,@MessageMapping>> and
|
||||
<<rsocket-interface,RSocket Interface>> for a list of supported parameters.
|
||||
|
||||
`@RSocketExchange` can be used at the type level to specify a common prefix for all routes
|
||||
for a given RSocket service interface.
|
||||
@@ -911,7 +911,7 @@ any subsequent metadata push notifications through the `METADATA_PUSH` frame, i.
|
||||
`metadataPush(Payload)` in `io.rsocket.RSocket`.
|
||||
|
||||
`@ConnectMapping` methods support the same arguments as
|
||||
xref:rsocket.adoc#rsocket-annot-messagemapping[@MessageMapping] but based on metadata and data from the `SETUP` and
|
||||
<<rsocket-annot-messagemapping,@MessageMapping>> but based on metadata and data from the `SETUP` and
|
||||
`METADATA_PUSH` frames. `@ConnectMapping` can have a pattern to narrow handling to
|
||||
specific connections that have a route in the metadata, or if no patterns are declared
|
||||
then all connections match.
|
||||
@@ -920,7 +920,7 @@ then all connections match.
|
||||
`Mono<Void>` as the return value. If handling returns an error for a new
|
||||
connection then the connection is rejected. Handling must not be held up to make
|
||||
requests to the `RSocketRequester` for the connection. See
|
||||
xref:rsocket.adoc#rsocket-requester-server[Server Requester] for details.
|
||||
<<rsocket-requester-server,Server Requester>> for details.
|
||||
|
||||
|
||||
[[rsocket-metadata-extractor]]
|
||||
@@ -1036,7 +1036,7 @@ Kotlin::
|
||||
The Spring Framework lets you define an RSocket service as a Java interface with
|
||||
`@RSocketExchange` methods. You can pass such an interface to `RSocketServiceProxyFactory`
|
||||
to create a proxy which performs requests through an
|
||||
xref:rsocket.adoc#rsocket-requester[RSocketRequester]. You can also implement the
|
||||
<<rsocket-requester,RSocketRequester>>. You can also implement the
|
||||
interface as a responder that handles requests.
|
||||
|
||||
Start by creating the interface with `@RSocketExchange` methods:
|
||||
@@ -1064,7 +1064,7 @@ Now you can create a proxy that performs requests when methods are called:
|
||||
----
|
||||
|
||||
You can also implement the interface to handle requests as a responder.
|
||||
See xref:rsocket.adoc#rsocket-annot-rsocketexchange[Annotated Responders].
|
||||
See <<rsocket-annot-rsocketexchange,Annotated Responders>>.
|
||||
|
||||
[[rsocket-interface-method-parameters]]
|
||||
=== Method Parameters
|
||||
|
||||
+9
-9
@@ -5,13 +5,13 @@ The following annotations are supported when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and the JUnit Jupiter testing framework:
|
||||
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitwebconfig[`@SpringJUnitWebConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-enabledif[`@EnabledIf`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-disabledif[`@DisabledIf`]
|
||||
* <<integration-testing-annotations-springextensionconfig,`@SpringExtensionConfig`>>
|
||||
* <<integration-testing-annotations-junit-jupiter-springjunitconfig,`@SpringJUnitConfig`>>
|
||||
* <<integration-testing-annotations-junit-jupiter-springjunitwebconfig,`@SpringJUnitWebConfig`>>
|
||||
* <<integration-testing-annotations-testconstructor,`@TestConstructor`>>
|
||||
* <<integration-testing-annotations-nestedtestconfiguration,`@NestedTestConfiguration`>>
|
||||
* <<integration-testing-annotations-junit-jupiter-enabledif,`@EnabledIf`>>
|
||||
* <<integration-testing-annotations-junit-jupiter-disabledif,`@DisabledIf`>>
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Consequently, there is no need to declare this annotation on a test class that d
|
||||
contain `@Nested` test classes.
|
||||
|
||||
In addition,
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
|
||||
<<integration-testing-annotations-nestedtestconfiguration,`@NestedTestConfiguration`>>
|
||||
does not apply to this annotation. `@SpringExtensionConfig` will always be detected
|
||||
within a `@Nested` test class hierarchy, effectively disregarding any
|
||||
`@NestedTestConfiguration(OVERRIDE)` declarations.
|
||||
@@ -291,7 +291,7 @@ following annotations.
|
||||
* xref:testing/annotations/integration-spring/annotation-sql.adoc[`@Sql`]
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[`@SqlConfig`]
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
|
||||
* <<integration-testing-annotations-testconstructor,`@TestConstructor`>>
|
||||
|
||||
NOTE: The use of `@NestedTestConfiguration` typically only makes sense in conjunction
|
||||
with `@Nested` test classes in JUnit Jupiter; however, there may be other testing
|
||||
|
||||
@@ -13,10 +13,10 @@ xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runne
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules], or
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
|
||||
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-ifprofilevalue[`@IfProfileValue`]
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-profilevaluesourceconfiguration[`@ProfileValueSourceConfiguration`]
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-timed[`@Timed`]
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-repeat[`@Repeat`]
|
||||
* <<integration-testing-annotations-junit4-ifprofilevalue,`@IfProfileValue`>>
|
||||
* <<integration-testing-annotations-junit4-profilevaluesourceconfiguration,`@ProfileValueSourceConfiguration`>>
|
||||
* <<integration-testing-annotations-junit4-timed,`@Timed`>>
|
||||
* <<integration-testing-annotations-junit4-repeat,`@Repeat`>>
|
||||
|
||||
|
||||
[[integration-testing-annotations-junit4-ifprofilevalue]]
|
||||
|
||||
+295
-4
@@ -68,12 +68,22 @@ The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy for bean overrides].
|
||||
If a corresponding bean does not exist, a new bean will be created. However, you can
|
||||
switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to `true` –
|
||||
for example, `@MockitoBean(enforceOverride = true)`.
|
||||
for example, `@MockitoBean(enforceOverride = true)`. Because this strategy replaces the
|
||||
bean directly, bypassing the container's normal bean post-processing, the resulting mock
|
||||
is a bare object: it is never wrapped in a Spring AOP proxy, even if the original bean
|
||||
would have been — for example, due to `@Transactional`, `@Cacheable`, or `@Retryable`. See
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-aop-proxies[Bean
|
||||
Overrides and Spring AOP Proxies] for details.
|
||||
|
||||
The `@MockitoSpyBean` annotation uses the `WRAP`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy],
|
||||
and the original instance is wrapped in a Mockito spy. This strategy requires that
|
||||
exactly one candidate bean exists.
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy]:
|
||||
an early instance of the original bean is captured and used to create a Mockito spy.
|
||||
This strategy requires that exactly one candidate bean exists. In contrast to
|
||||
`@MockitoBean`, if the original bean would have been wrapped in a Spring AOP proxy, that
|
||||
proxy is still created — but it now wraps the spy instead of the original bean. See
|
||||
<<spring-testing-annotation-beanoverriding-mockitospybean-aop-proxies,`@MockitoSpyBean` and Spring AOP Proxies>>
|
||||
for a diagram and further details on the consequences this has for stubbing and
|
||||
verification.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -468,3 +478,284 @@ Kotlin::
|
||||
TIP: The spies can also be injected into `@Configuration` classes or other test-related
|
||||
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
|
||||
APIs.
|
||||
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-mockitospybean-aop-proxies]]
|
||||
== `@MockitoSpyBean` and Spring AOP Proxies
|
||||
|
||||
As explained in
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-aop-proxies[Bean
|
||||
Overrides and Spring AOP Proxies], if the bean being spied on would normally be wrapped in
|
||||
a Spring AOP proxy — for example, due to `@Transactional`, `@Cacheable`, or `@Retryable`
|
||||
— that proxy is still created, with the spy as its target. The bean injected into the
|
||||
test class and into other beans in the `ApplicationContext` is therefore the proxy, not
|
||||
the spy itself.
|
||||
|
||||
Verification via Mockito's `verify()` API is unaffected by this and works transparently,
|
||||
regardless of whether it is invoked on the proxy or on the underlying spy.
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-mockitospybean-aop-proxies-stubbing]]
|
||||
=== Stubbing Through the Proxy
|
||||
|
||||
Stubbing requires more care than verification, since `Mockito.doReturn(...).when(...)`,
|
||||
`Mockito.doThrow(...).when(...)`, and similar methods behave differently depending on the
|
||||
nature of the AOP advice involved when invoked on the proxy.
|
||||
|
||||
NOTE: Since `when` is a reserved keyword in Kotlin, the Kotlin examples below use the
|
||||
`given(...)`, `willReturn(...)`, and `willThrow(...)` methods from `BDDMockito` instead
|
||||
of `Mockito.doReturn(...).when(...)` and `Mockito.doThrow(...).when(...)`.
|
||||
|
||||
Advice that does not retain state between invocations — such as
|
||||
xref:core/resilience.adoc#resilience-annotations-retryable[`@Retryable`] — has no adverse
|
||||
effect on stubbing. The following stubbing sequence, invoked on the proxy, behaves exactly
|
||||
as it would on the underlying spy directly, including triggering a retry when the thrown
|
||||
exception is encountered.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
doReturn("ok")
|
||||
.doThrow(new RuntimeException("Message delivery failed"))
|
||||
.doReturn("ok again")
|
||||
.when(clientService).sendMessage(any()); // <1>
|
||||
----
|
||||
<1> `clientService` is the injected proxy. Since `@Retryable` advice is a stateless
|
||||
pass-through, each call — including the one that throws — reaches the spy directly.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
willReturn("ok")
|
||||
.willThrow(RuntimeException("Message delivery failed"))
|
||||
.willReturn("ok again")
|
||||
.given(clientService).sendMessage(any()) // <1>
|
||||
----
|
||||
<1> `clientService` is the injected proxy. Since `@Retryable` advice is a stateless
|
||||
pass-through, each call — including the one that throws — reaches the spy directly.
|
||||
======
|
||||
|
||||
Advice that caches or otherwise memoizes the outcome of an invocation — such as
|
||||
`@Cacheable` — does not behave the same way. While a `doReturn(...)`, `doThrow(...)`, or
|
||||
similar declaration is being recorded, Mockito does not invoke the spy's real or
|
||||
previously stubbed behavior; instead, the invocation used to declare the stubbing returns
|
||||
an empty value (for example, `null`). If that invocation is made on the proxy, the caching
|
||||
advice caches this empty value, which then permanently shadows the spy for that
|
||||
combination of arguments — including for the very invocation that was supposed to
|
||||
configure the stubbing.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
doReturn(1L).when(dateService).getDate(false); // <1>
|
||||
dateService.getDate(false); // <2>
|
||||
----
|
||||
<1> `dateService` is the injected proxy. This invocation is intercepted by Mockito's
|
||||
stubbing infrastructure before it reaches the spy, so the caching advice ends up
|
||||
caching an empty value for argument `false`.
|
||||
<2> Returns the empty value cached by the previous invocation — not `1L` — because the
|
||||
cache was already populated.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
willReturn(1L).given(dateService).getDate(false) // <1>
|
||||
dateService.getDate(false) // <2>
|
||||
----
|
||||
<1> `dateService` is the injected proxy. This invocation is intercepted by Mockito's
|
||||
stubbing infrastructure before it reaches the spy, so the caching advice ends up
|
||||
caching an empty value for argument `false`.
|
||||
<2> Returns the empty value cached by the previous invocation — not `1L` — because the
|
||||
cache was already populated.
|
||||
======
|
||||
|
||||
To avoid this, stub directly on the spy instead of on the proxy, by unwrapping the proxy
|
||||
with
|
||||
{spring-framework-api}/test/util/AopTestUtils.html#getUltimateTargetObject(java.lang.Object)[`AopTestUtils.getUltimateTargetObject(...)`].
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
DateService spy = AopTestUtils.getUltimateTargetObject(dateService);
|
||||
doReturn(1L).when(spy).getDate(false);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val spy = AopTestUtils.getUltimateTargetObject<DateService>(dateService)
|
||||
willReturn(1L).given(spy).getDate(false)
|
||||
----
|
||||
======
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-mockitospybean-aop-proxies-disabling]]
|
||||
=== Disabling AOP Advice for Tests
|
||||
|
||||
Rather than working around the proxy as shown above, you may instead prefer to disable
|
||||
the underlying AOP advice for the duration of the test, while keeping `@Retryable`,
|
||||
`@Cacheable`, or similar annotations in place in production code. Common reasons include
|
||||
avoiding retry delays that slow down the test suite, or avoiding caching altogether so
|
||||
that every invocation reaches the spy directly — which also sidesteps the stubbing
|
||||
pitfall described above, without having to unwrap the proxy at all.
|
||||
|
||||
The general technique is to externalize whatever controls the advice's effective behavior
|
||||
— for example, the number of retry attempts or the `CacheManager` backing `@Cacheable`
|
||||
— and override that configuration for tests only, typically by using a bean override or a
|
||||
test-specific property. The proxy and its advice are still created, but their behavior is
|
||||
simply made a no-op or pure pass-through for the test.
|
||||
|
||||
For `@Retryable`, bind the `maxRetriesString` attribute to a property placeholder with a
|
||||
sensible default (so that production configuration is unaffected if the property is not
|
||||
set), and override that property in the test with
|
||||
xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
|
||||
so that no retries are attempted.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(maxRetriesString = "${sendMessage.maxRetries:3}", delay = 10)
|
||||
public String sendMessage(String request) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(maxRetriesString = "\${sendMessage.maxRetries:3}", delay = 10)
|
||||
fun sendMessage(request: String): String {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig
|
||||
@TestPropertySource(properties = "sendMessage.maxRetries = 0") // <1>
|
||||
class ClientServiceTests {
|
||||
|
||||
@MockitoSpyBean
|
||||
ClientService clientService;
|
||||
|
||||
// test case body...
|
||||
}
|
||||
----
|
||||
<1> With no retries permitted, the first (and only) attempt is made, and a thrown
|
||||
exception propagates immediately, so the spy's stubbing chain behaves exactly as
|
||||
declared, including for `doThrow(...)` answers.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig
|
||||
@TestPropertySource(properties = ["sendMessage.maxRetries = 0"]) // <1>
|
||||
class ClientServiceTests {
|
||||
|
||||
@MockitoSpyBean
|
||||
lateinit var clientService: ClientService
|
||||
|
||||
// test case body...
|
||||
}
|
||||
----
|
||||
<1> With no retries permitted, the first (and only) attempt is made, and a thrown
|
||||
exception propagates immediately, so the spy's stubbing chain behaves exactly as
|
||||
declared, including for `doThrow(...)` answers.
|
||||
======
|
||||
|
||||
For `@Cacheable`, Spring provides
|
||||
{spring-framework-api}/cache/support/NoOpCacheManager.html[`NoOpCacheManager`] — a
|
||||
`CacheManager` that accepts cache entries but never actually stores them, so every
|
||||
invocation results in a cache miss and therefore an invocation of the target method.
|
||||
Overriding the `CacheManager` bean with a `NoOpCacheManager` — for example, with
|
||||
xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`] —
|
||||
effectively disables caching for the test without touching the `@Cacheable` annotation in
|
||||
production code.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig
|
||||
class DateServiceTests {
|
||||
|
||||
@MockitoSpyBean
|
||||
DateService dateService;
|
||||
|
||||
@TestBean // <1>
|
||||
CacheManager cacheManager;
|
||||
|
||||
static CacheManager cacheManager() { // <2>
|
||||
return new NoOpCacheManager();
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
doReturn(1L).when(dateService).getDate(false);
|
||||
assertThat(dateService.getDate(false)).isEqualTo(1L);
|
||||
|
||||
doReturn(2L).when(dateService).getDate(false);
|
||||
assertThat(dateService.getDate(false)).isEqualTo(2L); // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Override the `CacheManager` bean for this test.
|
||||
<2> Replace it with a `NoOpCacheManager`, so `@Cacheable` never actually caches anything.
|
||||
<3> No longer masked by a stale cache entry, since every call reaches the spy.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig
|
||||
class DateServiceTests {
|
||||
|
||||
@MockitoSpyBean
|
||||
lateinit var dateService: DateService
|
||||
|
||||
@TestBean // <1>
|
||||
lateinit var cacheManager: CacheManager
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun cacheManager(): CacheManager { // <2>
|
||||
return NoOpCacheManager()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
willReturn(1L).given(dateService).getDate(false)
|
||||
assertThat(dateService.getDate(false)).isEqualTo(1L)
|
||||
|
||||
willReturn(2L).given(dateService).getDate(false)
|
||||
assertThat(dateService.getDate(false)).isEqualTo(2L) // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Override the `CacheManager` bean for this test.
|
||||
<2> Replace it with a `NoOpCacheManager`, so `@Cacheable` never actually caches anything.
|
||||
<3> No longer masked by a stale cache entry, since every call reaches the spy.
|
||||
======
|
||||
|
||||
+12
@@ -171,3 +171,15 @@ Similarly, when overriding a bean created by a `FactoryBean`, the `FactoryBean`
|
||||
replaced with a singleton bean corresponding to the value returned from the `@TestBean`
|
||||
factory method.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`@TestBean` uses the `REPLACE` or `REPLACE_OR_CREATE`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy
|
||||
for bean overrides], which registers the value returned from the factory method directly
|
||||
as the bean, bypassing the container's normal bean post-processing. Consequently, none of
|
||||
the Spring AOP advice that would otherwise apply to the original bean (for example,
|
||||
`@Transactional`, `@Cacheable`, or `@Retryable`) is present on the override instance. See
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-aop-proxies[Bean
|
||||
Overrides and Spring AOP Proxies] for details.
|
||||
====
|
||||
|
||||
@@ -41,10 +41,10 @@ integration support, and the rest of this chapter then focuses on dedicated topi
|
||||
|
||||
Spring's integration testing support has the following primary goals:
|
||||
|
||||
* To manage xref:testing/integration.adoc#testing-ctx-management[Spring IoC container caching] between tests.
|
||||
* To provide xref:testing/integration.adoc#testing-fixture-di[Dependency Injection of test fixture instances].
|
||||
* To provide xref:testing/integration.adoc#testing-tx[transaction management] appropriate to integration testing.
|
||||
* To supply xref:testing/integration.adoc#testing-support-classes[Spring-specific base classes] that assist
|
||||
* To manage <<testing-ctx-management,Spring IoC container caching>> between tests.
|
||||
* To provide <<testing-fixture-di,Dependency Injection of test fixture instances>>.
|
||||
* To provide <<testing-tx,transaction management>> appropriate to integration testing.
|
||||
* To supply <<testing-support-classes,Spring-specific base classes>> that assist
|
||||
developers in writing integration tests.
|
||||
|
||||
The next few sections describe each goal and provide links to implementation and
|
||||
|
||||
@@ -139,8 +139,8 @@ xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] and `RestTestCl
|
||||
the same API up to the point of the call to `exchange()`. After that, `RestTestClient`
|
||||
provides two alternative ways to verify the response:
|
||||
|
||||
1. xref:resttestclient-workflow[Built-in Assertions] extend the request workflow with a chain of expectations
|
||||
2. xref:resttestclient-assertj[AssertJ Integration] to verify the response via `assertThat()` statements
|
||||
1. <<resttestclient.workflow,Built-in Assertions>> extend the request workflow with a chain of expectations
|
||||
2. <<resttestclient.assertj,AssertJ Integration>> to verify the response via `assertThat()` statements
|
||||
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ include-code::./RestClientWorkflowTests[tag=soft-assertions,indent=0]
|
||||
You can then choose to decode the response body through one of the following:
|
||||
|
||||
* `expectBody(Class<T>)`: Decode to single object.
|
||||
* `expectBody()`: Decode to `byte[]` for xref:testing/resttestclient.adoc#resttestclient-json[JSON Content] or an empty body.
|
||||
* `expectBody()`: Decode to `byte[]` for <<resttestclient.json,JSON Content>> or an empty body.
|
||||
|
||||
|
||||
If the built-in assertions are insufficient, you can consume the object instead and
|
||||
|
||||
@@ -90,3 +90,67 @@ Alternatively, the user can directly provide the bean name in the custom annotat
|
||||
`BeanOverrideProcessor` implementations may also internally compute a bean name based on
|
||||
a convention or some other method.
|
||||
====
|
||||
|
||||
[[testcontext-bean-overriding-aop-proxies]]
|
||||
== Bean Overrides and Spring AOP Proxies
|
||||
|
||||
Beans in a Spring `ApplicationContext` are frequently wrapped in an AOP proxy — for
|
||||
example, to support `@Transactional`, `@Cacheable`, or `@Retryable` semantics. Whether an
|
||||
overridden bean retains such a proxy depends on the `BeanOverrideStrategy` used to create
|
||||
the override.
|
||||
|
||||
* Overrides that use the `REPLACE` or `REPLACE_OR_CREATE` strategy (such as `@TestBean`
|
||||
and `@MockitoBean`) register their override instance directly as a manual singleton,
|
||||
which bypasses the container's normal bean post-processing. Consequently, the override
|
||||
instance is a bare object: none of the AOP advice that would otherwise apply to the
|
||||
original bean (`@Transactional`, `@Cacheable`, `@Retryable`, method security, and so
|
||||
on) is present.
|
||||
* Overrides that use the `WRAP` strategy (such as `@MockitoSpyBean`) capture an early
|
||||
reference to the original bean and use it to create the override instance, before the
|
||||
rest of the container's post-processors — including the one responsible for creating
|
||||
AOP proxies — have run. Consequently, if the original bean would have been proxied,
|
||||
that proxy is still created, but it now wraps the override instance instead of the
|
||||
original bean. The bean that ends up in the `ApplicationContext`, and that is injected
|
||||
into collaborating beans and test classes, is therefore the AOP proxy, with the
|
||||
override instance as its target — not the bare override instance itself.
|
||||
|
||||
The following diagrams illustrate the resulting shape of the bean for each strategy, from
|
||||
the perspective of a caller invoking a method on the injected bean.
|
||||
|
||||
With the `REPLACE` or `REPLACE_OR_CREATE` strategy, there is no AOP proxy at all: the
|
||||
caller invokes the override instance directly.
|
||||
|
||||
[source]
|
||||
----
|
||||
caller
|
||||
│
|
||||
▼
|
||||
[ override instance ]
|
||||
----
|
||||
|
||||
With the `WRAP` strategy, any AOP proxy that would normally have wrapped the original
|
||||
bean is still created, but now wraps the override instance instead:
|
||||
|
||||
[source]
|
||||
----
|
||||
caller
|
||||
│
|
||||
▼
|
||||
[ AOP proxy ] (for example, retry, caching, or transaction advice)
|
||||
│
|
||||
│ delegates to its target
|
||||
▼
|
||||
[ override instance ] (for example, a Mockito spy created by @MockitoSpyBean)
|
||||
----
|
||||
|
||||
For a `WRAP`-based override such as `@MockitoSpyBean`, the "wrapping" performed by the
|
||||
AOP proxy is unrelated to the manner in which the resulting Mockito spy itself "wraps"
|
||||
the original bean instance it was created from. The proxy shown above determines which
|
||||
object a caller actually invokes, whereas the spy's relationship to the original
|
||||
instance only determines what happens when an unstubbed method is invoked on the spy: it
|
||||
falls through to that instance's real behavior.
|
||||
|
||||
This distinction has practical consequences when combining bean overrides with Mockito's
|
||||
stubbing and verification APIs. See
|
||||
xref:testing/annotations/integration-spring/annotation-mockitobean.adoc#spring-testing-annotation-beanoverriding-mockitospybean-aop-proxies[`@MockitoSpyBean`
|
||||
and Spring AOP Proxies] for details.
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ implementations to the list of default factories in the same manner through thei
|
||||
|
||||
If a custom `ContextCustomizerFactory` is registered via `@ContextCustomizerFactories`, it
|
||||
will be _merged_ with the default factories that have been registered using the aforementioned
|
||||
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc#testcontext-context-customizers-automatic-discovery[automatic discovery mechanism].
|
||||
<<testcontext-context-customizers-automatic-discovery,automatic discovery mechanism>>.
|
||||
|
||||
The merging algorithm ensures that duplicates are removed from the list and that locally
|
||||
declared factories are appended to the list of default factories when merged.
|
||||
|
||||
+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#beans-factory-groovy[Groovy Bean Definition DSL], you can annotate
|
||||
xref:languages/groovy.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
|
||||
|
||||
@@ -105,7 +105,7 @@ by default.
|
||||
====
|
||||
Method-level `@Sql` declarations override class-level declarations by default, but this
|
||||
behavior may be configured per test class or per test method via `@SqlMergeMode`. See
|
||||
xref:testing/testcontext-framework/executing-sql.adoc#testcontext-executing-sql-declaratively-script-merging[Merging and Overriding Configuration with `@SqlMergeMode`]
|
||||
<<testcontext-executing-sql-declaratively-script-merging,Merging and Overriding Configuration with `@SqlMergeMode`>>
|
||||
for further details.
|
||||
|
||||
However, this does not apply to class-level declarations configured for the
|
||||
|
||||
+7
-7
@@ -23,8 +23,8 @@ following features above and beyond the feature set that Spring supports for JUn
|
||||
TestNG:
|
||||
|
||||
* Dependency injection for test constructors, test methods, and test lifecycle callback
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
|
||||
Injection with the `SpringExtension`] for further details.
|
||||
methods. See <<testcontext-junit-jupiter-di,Dependency Injection with the
|
||||
`SpringExtension`>> for further details.
|
||||
* Powerful support for link:https://docs.junit.org/current/extensions/conditional-test-execution.html[conditional
|
||||
test execution] based on SpEL expressions, environment variables, system properties,
|
||||
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
|
||||
@@ -499,7 +499,7 @@ Kotlin::
|
||||
====
|
||||
JUnit 4 is officially in maintenance mode, and JUnit 4 support in Spring is deprecated
|
||||
since Spring Framework 7.0 in favor of the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
<<testcontext-junit-jupiter-extension,`SpringExtension`>>
|
||||
and JUnit Jupiter.
|
||||
====
|
||||
|
||||
@@ -512,7 +512,7 @@ loading application contexts, dependency injection of test instances, transactio
|
||||
method execution, and so on. If you want to use the Spring TestContext Framework with an
|
||||
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
|
||||
(such as the `MockitoJUnitRunner`), you can, optionally, use
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules]
|
||||
<<testcontext-junit4-rules,Spring's support for JUnit rules>>
|
||||
instead.
|
||||
|
||||
The following code listing shows the minimal requirements for configuring a test class to
|
||||
@@ -562,7 +562,7 @@ be configured through `@ContextConfiguration`.
|
||||
====
|
||||
JUnit 4 is officially in maintenance mode, and JUnit 4 support in Spring is deprecated
|
||||
since Spring Framework 7.0 in favor of the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
<<testcontext-junit-jupiter-extension,`SpringExtension`>>
|
||||
and JUnit Jupiter.
|
||||
====
|
||||
|
||||
@@ -639,7 +639,7 @@ Kotlin::
|
||||
====
|
||||
JUnit 4 is officially in maintenance mode, and JUnit 4 support in Spring is deprecated
|
||||
since Spring Framework 7.0 in favor of the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
<<testcontext-junit-jupiter-extension,`SpringExtension`>>
|
||||
and JUnit Jupiter.
|
||||
====
|
||||
|
||||
@@ -675,7 +675,7 @@ Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
|
||||
TIP: These classes are a convenience for extension. If you do not want your test classes
|
||||
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
|
||||
classes by using `@RunWith(SpringRunner.class)` or
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules].
|
||||
<<testcontext-junit4-rules,Spring's JUnit rules>>.
|
||||
|
||||
|
||||
[[testcontext-support-classes-testng]]
|
||||
|
||||
@@ -99,7 +99,7 @@ manner through their own `spring.factories` files.
|
||||
== Ordering `TestExecutionListener` Implementations
|
||||
|
||||
When the TestContext framework discovers default `TestExecutionListener` implementations
|
||||
through the xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[aforementioned]
|
||||
through the <<testcontext-tel-config-automatic-discovery,aforementioned>>
|
||||
`SpringFactoriesLoader` mechanism, the instantiated listeners are sorted by using
|
||||
Spring's `AnnotationAwareOrderComparator`, which honors Spring's `Ordered` interface and
|
||||
`@Order` annotation for ordering. `AbstractTestExecutionListener` and all default
|
||||
@@ -167,7 +167,7 @@ introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionL
|
||||
was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
|
||||
Boot and Spring Security register their own default `TestExecutionListener`
|
||||
implementations by using the aforementioned
|
||||
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism].
|
||||
<<testcontext-tel-config-automatic-discovery,automatic discovery mechanism>>.
|
||||
|
||||
To avoid having to be aware of and re-declare all default listeners, you can set the
|
||||
`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`.
|
||||
@@ -175,7 +175,7 @@ To avoid having to be aware of and re-declare all default listeners, you can set
|
||||
default listeners. The merging algorithm ensures that duplicates are removed from the
|
||||
list and that the resulting set of merged listeners is sorted according to the semantics
|
||||
of `AnnotationAwareOrderComparator`, as described in
|
||||
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
|
||||
<<testcontext-tel-config-ordering,Ordering `TestExecutionListener` Implementations>>.
|
||||
If a listener implements `Ordered` or is annotated with `@Order`, it can influence the
|
||||
position in which it is merged with the defaults. Otherwise, locally declared listeners
|
||||
are appended to the list of default listeners when merged.
|
||||
|
||||
@@ -195,7 +195,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
As explained in xref:testing/testcontext-framework/tx.adoc#testcontext-tx-rollback-and-commit-behavior[Transaction Rollback and Commit Behavior],
|
||||
As explained in <<testcontext-tx-rollback-and-commit-behavior,Transaction Rollback and Commit Behavior>>,
|
||||
there is no need to clean up the database after the `createUser()` method runs,
|
||||
since any changes made to the database are automatically rolled back by the
|
||||
`TransactionalTestExecutionListener`.
|
||||
@@ -598,7 +598,7 @@ Kotlin::
|
||||
.Testing ORM entity lifecycle callbacks
|
||||
[NOTE]
|
||||
=====
|
||||
Similar to the note about avoiding xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives]
|
||||
Similar to the note about avoiding <<testcontext-tx-false-positives,false positives>>
|
||||
when testing ORM code, if your application makes use of entity lifecycle callbacks (also
|
||||
known as entity listeners), make sure to flush the underlying unit of work within test
|
||||
methods that run that code. Failing to _flush_ or _clear_ the underlying unit of work can
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user