mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-18 09:59:03 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f64480c9f |
@@ -0,0 +1,20 @@
|
||||
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,18 +1,6 @@
|
||||
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
|
||||
@@ -31,7 +19,7 @@ inputs:
|
||||
java-version:
|
||||
description: 'Java version to compile and test with'
|
||||
required: false
|
||||
default: '25'
|
||||
default: '17'
|
||||
publish:
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
required: false
|
||||
@@ -58,21 +46,11 @@ 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,13 +1,6 @@
|
||||
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
|
||||
@@ -22,13 +15,13 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@f7d7a87a3e7c627ecb8c26cf086c38ac5a939721 #v0.0.14
|
||||
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
|
||||
with:
|
||||
config-file: ${{ inputs.commercial && '.github/actions/create-github-release/changelog-generator-commercial.yml' || '.github/actions/create-github-release/changelog-generator-oss.yml' }}
|
||||
config-file: .github/actions/create-github-release/changelog-generator.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' || format('--latest={0}', inputs.latest) }}
|
||||
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md ${{ inputs.pre-release == 'true' && '--prerelease' || '' }}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
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
-7
@@ -1,13 +1,6 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":warning: Attention Required"
|
||||
labels:
|
||||
- "for: upgrade-attention"
|
||||
summary:
|
||||
mode: "member-comment"
|
||||
config:
|
||||
prefix: "Attention Required:"
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
@@ -27,7 +20,9 @@ changelog:
|
||||
names:
|
||||
- "bclozel"
|
||||
- "jhoeller"
|
||||
- "poutsma"
|
||||
- "rstoyanchev"
|
||||
- "sbrannen"
|
||||
- "sdeleuze"
|
||||
- "simonbasle"
|
||||
- "snicoll"
|
||||
@@ -19,35 +19,36 @@ inputs:
|
||||
java-version:
|
||||
description: 'Java version to use for the build'
|
||||
required: false
|
||||
default: '25'
|
||||
default: '17'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
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' && '25' || '' }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # 6.2.0
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
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: |
|
||||
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
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Configure Toolchain Properties
|
||||
if: ${{ inputs.java-toolchain == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
name: Build Release
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build Release
|
||||
shell: bash
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository publishAllPublicationsToDeploymentRepository
|
||||
@@ -1,16 +0,0 @@
|
||||
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/**"
|
||||
@@ -1,7 +0,0 @@
|
||||
name: Test Release
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Test Release
|
||||
shell: bash
|
||||
run: ./gradlew check
|
||||
@@ -0,0 +1,34 @@
|
||||
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@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
|
||||
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@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.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) }}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"aql": {
|
||||
"items.find": {
|
||||
"$and": [
|
||||
{
|
||||
"@build.name": "${buildName}",
|
||||
"@build.number": "${buildNumber}",
|
||||
"path": {
|
||||
"$nmatch": "org/springframework/framework-api/*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": "nexus/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
require:
|
||||
members: false
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
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,15 +7,27 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
backport-issue:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create Backport Issue
|
||||
uses: spring-io/backport-bot@v0.0.3
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
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"
|
||||
|
||||
@@ -2,76 +2,57 @@ name: Build and Deploy Snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 7.0.x
|
||||
- 6.2.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
name: Build and Deploy Snapshot
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' || github.repository == 'spring-projects/spring-framework-commercial' }}
|
||||
runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }}
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v4
|
||||
- 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@aba148f1541e09adcf5735af90029fac9a6d3083 # v0.0.5
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
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: ${{ vars.COMMERCIAL && format('spring-framework-commercial-{0}', '7.0.x') || format('spring-framework-{0}', '7.0.x') }}
|
||||
build-name: 'spring-framework-6.2.x'
|
||||
folder: 'deployment-repository'
|
||||
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 }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
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 25', github.ref_name) }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', 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 }}
|
||||
opensource-repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
opensource-repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
|
||||
|
||||
@@ -7,9 +7,10 @@ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v4
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
@@ -18,7 +19,7 @@ jobs:
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
path: '**/build/reports/'
|
||||
|
||||
+11
-16
@@ -1,34 +1,33 @@
|
||||
name: CI
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
permissions:
|
||||
contents: read
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' || github.repository == 'spring-projects/spring-framework-commercial' }}
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- id: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }}
|
||||
- id: ubuntu-latest
|
||||
name: Linux
|
||||
java:
|
||||
- version: 17
|
||||
toolchain: true
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 25
|
||||
toolchain: false
|
||||
- version: 26
|
||||
- version: 24
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 25
|
||||
version: 17
|
||||
steps:
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
@@ -37,15 +36,11 @@ jobs:
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v4
|
||||
- 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 }}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
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@v4
|
||||
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)
|
||||
@@ -1,39 +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:
|
||||
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 }}
|
||||
@@ -1,38 +0,0 @@
|
||||
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,93 +0,0 @@
|
||||
# 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 }}
|
||||
@@ -1,55 +0,0 @@
|
||||
# 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
|
||||
@@ -1,46 +0,0 @@
|
||||
# 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
|
||||
@@ -1,47 +0,0 @@
|
||||
# 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
|
||||
@@ -1,34 +0,0 @@
|
||||
# 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 }}
|
||||
@@ -1,84 +0,0 @@
|
||||
# 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"
|
||||
@@ -0,0 +1,93 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v6.2.[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@v4
|
||||
- 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@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- 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@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- 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 }}
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.2.x', '7.0.x', 'main' ]
|
||||
branch: [ '6.1.x' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
|
||||
@@ -12,51 +12,42 @@ 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
|
||||
opensource-repository-password:
|
||||
description: 'Password for authentication with the open-source repository'
|
||||
repository-password:
|
||||
description: 'Password for authentication with the repository'
|
||||
required: false
|
||||
opensource-repository-username:
|
||||
description: 'Username for authentication with the open-source repository'
|
||||
repository-username:
|
||||
description: 'Username for authentication with the repository'
|
||||
required: false
|
||||
token:
|
||||
description: 'Token to use for authentication with GitHub'
|
||||
required: true
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify
|
||||
runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'v0.0.3'
|
||||
ref: 'v0.0.2'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-provider: basic
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
@@ -65,17 +56,15 @@ jobs:
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
- name: Run Release Verification Tests
|
||||
env:
|
||||
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_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
|
||||
RVT_RELEASE_TYPE: 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@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
+1
-6
@@ -9,7 +9,6 @@ ivy-cache
|
||||
argfile*
|
||||
activemq-data/
|
||||
classes/
|
||||
.cursor/
|
||||
|
||||
# Log files
|
||||
jxl.log
|
||||
@@ -39,7 +38,7 @@ bin
|
||||
.springBeans
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
|
||||
# IntelliJ IDEA artifacts and output dirs
|
||||
# IDEA artifacts and output dirs
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
@@ -52,10 +51,6 @@ atlassian-ide-plugin.xml
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Claude artifacts
|
||||
.claude/*
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
/.kotlin/
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=25-librca
|
||||
java=17.0.13-librca
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ source code into your IDE.
|
||||
The wiki pages
|
||||
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
|
||||
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
|
||||
define the source file coding standards we use along with some IntelliJ editor settings we customize.
|
||||
define the source file coding standards we use along with some IDEA editor settings we customize.
|
||||
|
||||
### Reference Docs
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
|
||||
|
||||
## Continuous Integration Builds
|
||||
|
||||
CI builds are defined with [GitHub Actions workflows](.github/workflows).
|
||||
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
|
||||
+51
-20
@@ -1,12 +1,15 @@
|
||||
plugins {
|
||||
id 'io.freefair.aspectj' version '8.13.1' apply false
|
||||
id 'io.freefair.aspectj' version '8.4' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka'
|
||||
id 'org.jetbrains.dokka' version '1.9.20'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'com.gradleup.shadow' version "9.2.2" apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'io.spring.nullability' version '0.0.15' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -18,8 +21,23 @@ description = "Spring Framework"
|
||||
|
||||
configure(allprojects) { project ->
|
||||
apply plugin: "org.springframework.build.localdev"
|
||||
apply plugin: "org.springframework.build.repositories"
|
||||
group = "org.springframework"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = "https://repo.spring.io/milestone"
|
||||
content {
|
||||
// Netty 5 optional support
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
if (version.contains('-')) {
|
||||
maven { url = "https://repo.spring.io/milestone" }
|
||||
}
|
||||
if (version.endsWith('-SNAPSHOT')) {
|
||||
maven { url = "https://repo.spring.io/snapshot" }
|
||||
}
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
cacheChangingModulesFor 0, "seconds"
|
||||
@@ -46,6 +64,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
apply plugin: "java"
|
||||
apply plugin: "java-test-fixtures"
|
||||
apply plugin: 'org.springframework.build.conventions'
|
||||
apply from: "${rootDir}/gradle/toolchains.gradle"
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
|
||||
dependencies {
|
||||
@@ -53,27 +72,39 @@ configure([rootProject] + javaProjects) { project ->
|
||||
testImplementation("org.junit.platform:junit-platform-suite")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk") {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl")
|
||||
// JSR-305 only used for non-required meta-annotations
|
||||
compileOnly("com.google.code.findbugs:jsr305")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/7.2/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/5.6/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/core/release/api/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.0.3/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.13.4/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
|
||||
// JakartaEE equivalents in the jakarta.annotation package.
|
||||
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
||||
+1
-31
@@ -9,18 +9,7 @@ The `org.springframework.build.conventions` plugin applies all conventions to th
|
||||
|
||||
* Configuring the Java compiler, see `JavaConventions`
|
||||
* Configuring the Kotlin compiler, see `KotlinConventions`
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
* Configuring the ArchUnit rules for the project, see `org.springframework.build.architecture.ArchitectureRules`
|
||||
|
||||
This plugin also provides a DSL extension to optionally enable Java preview features for
|
||||
compiling and testing sources in a module. This can be applied with the following in a
|
||||
module build file:
|
||||
|
||||
```groovy
|
||||
springFramework {
|
||||
enableJavaPreviewFeatures = true
|
||||
}
|
||||
```
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
|
||||
|
||||
## Build Plugins
|
||||
@@ -33,25 +22,6 @@ but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
### MultiRelease Jar
|
||||
|
||||
The `org.springframework.build.multiReleaseJar` plugin configures the project with MultiRelease JAR support.
|
||||
It creates a new SourceSet and dedicated tasks for each Java variant considered.
|
||||
This can be configured with the DSL, by setting a list of Java variants to configure:
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
|
||||
multiRelease {
|
||||
releaseVersions 21, 24
|
||||
}
|
||||
```
|
||||
|
||||
Note, Java classes will be compiled with the toolchain pre-configured by the project, assuming that its
|
||||
Java language version is equal or higher than all variants we consider. Each compilation task will only
|
||||
set the "-release" compilation option accordingly to produce the expected bytecode version.
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
|
||||
+2
-26
@@ -20,24 +20,14 @@ ext {
|
||||
dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.1.0"
|
||||
implementation "com.tngtech.archunit:archunit:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
|
||||
testImplementation("org.assertj:assertj-core:${assertjVersion}")
|
||||
testImplementation(platform("org.junit:junit-bom:${junitVersion}"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
architecturePlugin {
|
||||
id = "org.springframework.architecture"
|
||||
implementationClass = "org.springframework.build.architecture.ArchitecturePlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
@@ -46,14 +36,6 @@ gradlePlugin {
|
||||
id = "org.springframework.build.localdev"
|
||||
implementationClass = "org.springframework.build.dev.LocalDevelopmentPlugin"
|
||||
}
|
||||
multiReleasePlugin {
|
||||
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"
|
||||
@@ -64,9 +46,3 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jar.dependsOn check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<module name="com.puppycrawl.tools.checkstyle.Checker">
|
||||
|
||||
<!-- Root Checks -->
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringHeaderCheck">
|
||||
@@ -12,15 +12,16 @@
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck"/>
|
||||
|
||||
<!-- TreeWalker Checks -->
|
||||
<module name="TreeWalker">
|
||||
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
|
||||
|
||||
<!-- Imports -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
<module name="RedundantImport"/>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck">
|
||||
<property name="processJavadoc" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Modifiers -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck"/>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
</module>
|
||||
@@ -1,4 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
assertjVersion=3.27.3
|
||||
javaFormatVersion=0.0.43
|
||||
junitVersion=5.12.2
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
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;
|
||||
|
||||
@@ -38,7 +35,6 @@ import org.gradle.api.plugins.quality.CheckstylePlugin;
|
||||
* {@link Plugin} that applies conventions for checkstyle.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class CheckstyleConventions {
|
||||
|
||||
@@ -52,10 +48,9 @@ public class CheckstyleConventions {
|
||||
configureNoHttpPlugin(project);
|
||||
}
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize()
|
||||
.set("checkstyleNohttp".equals(checkstyle.getName()) ? "1536m" : "1g"));
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("14.1.0");
|
||||
checkstyle.setToolVersion("10.26.1");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -68,10 +63,8 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(NoHttpPlugin.class);
|
||||
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/**",
|
||||
".claude/**");
|
||||
excludeGitIgnoredPaths(project, noHttp);
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
@@ -83,48 +76,4 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,15 +21,12 @@ import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
|
||||
import org.springframework.build.architecture.ArchitecturePlugin;
|
||||
|
||||
/**
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
* Conventions are applied in response to various plugins being applied.
|
||||
*
|
||||
* <p>When the {@link JavaBasePlugin} is applied, the conventions in {@link CheckstyleConventions},
|
||||
* {@link TestConventions} and {@link JavaConventions} are applied.
|
||||
* The {@link ArchitecturePlugin} plugin is also applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* are applied.
|
||||
*
|
||||
@@ -39,8 +36,6 @@ public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getExtensions().create("springFramework", SpringFrameworkExtension.class);
|
||||
new ArchitecturePlugin().apply(project);
|
||||
new CheckstyleConventions().apply(project);
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
@@ -26,6 +27,7 @@ import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JvmVendorSpec;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
|
||||
@@ -40,21 +42,8 @@ public class JavaConventions {
|
||||
|
||||
private static final List<String> TEST_COMPILER_ARGS;
|
||||
|
||||
/**
|
||||
* The Java version we should use as the JVM baseline for building the project.
|
||||
* <p>NOTE: If you update this value, you should also update the value used in
|
||||
* the {@code javadoc} task in {@code framework-api.gradle}.
|
||||
*/
|
||||
private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(25);
|
||||
|
||||
/**
|
||||
* The Java version we should use as the baseline for the compiled bytecode
|
||||
* (the "-release" compiler argument).
|
||||
*/
|
||||
private static final JavaLanguageVersion DEFAULT_RELEASE_VERSION = JavaLanguageVersion.of(17);
|
||||
|
||||
static {
|
||||
List<String> commonCompilerArgs = List.of(
|
||||
List<String> commonCompilerArgs = Arrays.asList(
|
||||
"-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann",
|
||||
"-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides",
|
||||
"-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options",
|
||||
@@ -62,74 +51,43 @@ public class JavaConventions {
|
||||
);
|
||||
COMPILER_ARGS = new ArrayList<>();
|
||||
COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
COMPILER_ARGS.addAll(List.of(
|
||||
COMPILER_ARGS.addAll(Arrays.asList(
|
||||
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
|
||||
"-Xlint:unchecked", "-Werror"
|
||||
));
|
||||
TEST_COMPILER_ARGS = new ArrayList<>();
|
||||
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
TEST_COMPILER_ARGS.addAll(List.of("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||
TEST_COMPILER_ARGS.addAll(Arrays.asList("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||
"-Xlint:-deprecation", "-Xlint:-unchecked"));
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> {
|
||||
applyToolchainConventions(project);
|
||||
applyJavaCompileConventions(project);
|
||||
});
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> applyJavaCompileConventions(project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the Toolchain support for the project.
|
||||
* @param project the current project
|
||||
*/
|
||||
private static void applyToolchainConventions(Project project) {
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getLanguageVersion().set(DEFAULT_LANGUAGE_VERSION);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the common Java compiler options for main sources, test fixture sources, and
|
||||
* Applies the common Java compiler options for main sources, test fixture sources, and
|
||||
* test sources.
|
||||
* @param project the current project
|
||||
*/
|
||||
private void applyJavaCompileConventions(Project project) {
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
|
||||
toolchain.getLanguageVersion().set(JavaLanguageVersion.of(17));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* We should pick the {@link #DEFAULT_RELEASE_VERSION} for all compiled classes,
|
||||
* unless the current task is compiling multi-release JAR code with a higher version.
|
||||
*/
|
||||
private void setJavaRelease(JavaCompile task) {
|
||||
int defaultVersion = DEFAULT_RELEASE_VERSION.asInt();
|
||||
int releaseVersion = defaultVersion;
|
||||
int compilerVersion = task.getJavaCompiler().get().getMetadata().getLanguageVersion().asInt();
|
||||
for (int version = defaultVersion ; version <= compilerVersion ; version++) {
|
||||
if (task.getName().contains("Java" + version)) {
|
||||
releaseVersion = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
task.getOptions().getRelease().set(releaseVersion);
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,78 +16,33 @@
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.jetbrains.dokka.gradle.DokkaExtension;
|
||||
import org.jetbrains.dokka.gradle.DokkaPlugin;
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class KotlinConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm", plugin -> {
|
||||
project.getTasks().withType(KotlinCompile.class, this::configure);
|
||||
if (project.getLayout().getProjectDirectory().dir("src/main/kotlin").getAsFile().exists()) {
|
||||
project.getPlugins().apply(DokkaPlugin.class);
|
||||
project.getExtensions().configure(DokkaExtension.class, dokka -> configure(project, dokka));
|
||||
project.project(":framework-api").getDependencies().add("dokka", project);
|
||||
}
|
||||
});
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm",
|
||||
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
|
||||
}
|
||||
|
||||
private void configure(KotlinCompile compile) {
|
||||
compile.compilerOptions(options -> {
|
||||
options.getApiVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getLanguageVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getJvmTarget().set(JvmTarget.JVM_17);
|
||||
options.getJavaParameters().set(true);
|
||||
options.getAllWarningsAsErrors().set(true);
|
||||
options.getFreeCompilerArgs().addAll(
|
||||
"-Xsuppress-version-warnings",
|
||||
"-Xjsr305=strict", // For dependencies using JSR 305
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void configure(Project project, DokkaExtension dokka) {
|
||||
dokka.getDokkaSourceSets().forEach(sourceSet -> {
|
||||
sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin"));
|
||||
sourceSet.getClasspath()
|
||||
.from(project.getExtensions()
|
||||
.getByType(SourceSetContainer.class)
|
||||
.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
|
||||
.getOutput());
|
||||
var externalDocumentationLinks = sourceSet.getExternalDocumentationLinks();
|
||||
var springVersion = project.getVersion();
|
||||
externalDocumentationLinks.register("spring-framework", spec -> {
|
||||
spec.url("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/");
|
||||
spec.packageListUrl("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/element-list");
|
||||
});
|
||||
externalDocumentationLinks.register("reactor-core", spec ->
|
||||
spec.url("https://projectreactor.io/docs/core/release/api/"));
|
||||
externalDocumentationLinks.register("reactive-streams", spec ->
|
||||
spec.url("https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/"));
|
||||
externalDocumentationLinks.register("kotlinx-coroutines", spec ->
|
||||
spec.url("https://kotlinlang.org/api/kotlinx.coroutines/"));
|
||||
externalDocumentationLinks.register("hamcrest", spec ->
|
||||
spec.url("https://javadoc.io/doc/org.hamcrest/hamcrest/2.1/"));
|
||||
externalDocumentationLinks.register("jakarta-servlet", spec -> {
|
||||
spec.url("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/");
|
||||
spec.packageListUrl("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list");
|
||||
});
|
||||
externalDocumentationLinks.register("rsocket-core", spec ->
|
||||
spec.url("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"));
|
||||
});
|
||||
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
|
||||
kotlinOptions.setApiVersion("1.7");
|
||||
kotlinOptions.setLanguageVersion("1.7");
|
||||
kotlinOptions.setJvmTarget("17");
|
||||
kotlinOptions.setJavaParameters(true);
|
||||
kotlinOptions.setAllWarningsAsErrors(true);
|
||||
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
|
||||
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
|
||||
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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 java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.process.CommandLineArgumentProvider;
|
||||
|
||||
public class SpringFrameworkExtension {
|
||||
|
||||
private final Property<Boolean> enableJavaPreviewFeatures;
|
||||
|
||||
public SpringFrameworkExtension(Project project) {
|
||||
this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class);
|
||||
project.getTasks().withType(JavaCompile.class).configureEach(javaCompile ->
|
||||
javaCompile.getOptions().getCompilerArgumentProviders().add(asArgumentProvider()));
|
||||
project.getTasks().withType(Test.class).configureEach(test ->
|
||||
test.getJvmArgumentProviders().add(asArgumentProvider()));
|
||||
|
||||
}
|
||||
|
||||
public Property<Boolean> getEnableJavaPreviewFeatures() {
|
||||
return this.enableJavaPreviewFeatures;
|
||||
}
|
||||
|
||||
private CommandLineArgumentProvider asArgumentProvider() {
|
||||
return () -> {
|
||||
if (getEnableJavaPreviewFeatures().getOrElse(false)) {
|
||||
return List.of("--enable-preview");
|
||||
}
|
||||
return Collections.emptyList();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.api.tasks.testing.TestFrameworkOptions;
|
||||
@@ -26,16 +26,12 @@ import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
|
||||
import org.gradle.testretry.TestRetryPlugin;
|
||||
import org.gradle.testretry.TestRetryTaskExtension;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
|
||||
* plugin is applied:
|
||||
* <ul>
|
||||
* <li>The {@link TestRetryPlugin Test Retry} plugin is applied so that flaky tests
|
||||
* are retried 3 times when running on the CI server.
|
||||
* <li>Common test properties are configured
|
||||
* <li>The ByteBuddy Java agent is configured on test tasks.
|
||||
* </ul>
|
||||
*
|
||||
* @author Brian Clozel
|
||||
@@ -49,7 +45,6 @@ class TestConventions {
|
||||
}
|
||||
|
||||
private void configureTestConventions(Project project) {
|
||||
configureByteBuddyAgent(project);
|
||||
project.getTasks().withType(Test.class,
|
||||
test -> {
|
||||
configureTests(project, test);
|
||||
@@ -68,10 +63,12 @@ class TestConventions {
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
"io.netty.leakDetection.level", "paranoid",
|
||||
"junit.platform.discovery.issue.severity.critical", "INFO"
|
||||
"io.netty5.leakDetectionLevel", "paranoid",
|
||||
"io.netty5.leakDetection.targetRecords", "32",
|
||||
"io.netty5.buffer.lifecycleTracingEnabled", "true"
|
||||
));
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.findProperty("testGroups"));
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
}
|
||||
test.jvmArgs(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
@@ -80,20 +77,6 @@ class TestConventions {
|
||||
);
|
||||
}
|
||||
|
||||
private void configureByteBuddyAgent(Project project) {
|
||||
if (project.hasProperty("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);
|
||||
byteBuddyAgentConfig.getDependencies().add(byteBuddyAgent);
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().withType(Test.class, test -> test
|
||||
.jvmArgs("-javaagent:" + byteBuddyAgentConfig.getAsPath()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* 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.architecture;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.EvaluationResult;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.SkipWhenEmpty;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import static org.springframework.build.architecture.ArchitectureRules.allPackagesShouldBeFreeOfTangles;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.classesShouldNotImportForbiddenTypes;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.javaClassesShouldNotImportKotlinAnnotations;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToLowerCaseWithoutLocale;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToUpperCaseWithoutLocale;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks for architecture problems.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public abstract class ArchitectureCheck extends DefaultTask {
|
||||
|
||||
private FileCollection classes;
|
||||
|
||||
public ArchitectureCheck() {
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
getProhibitObjectsRequireNonNull().convention(true);
|
||||
getRules().addAll(classesShouldNotImportForbiddenTypes(),
|
||||
javaClassesShouldNotImportKotlinAnnotations(),
|
||||
allPackagesShouldBeFreeOfTangles(),
|
||||
noClassesShouldCallStringToLowerCaseWithoutLocale(),
|
||||
noClassesShouldCallStringToUpperCaseWithoutLocale());
|
||||
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void checkArchitecture() throws IOException {
|
||||
JavaClasses javaClasses = new ClassFileImporter()
|
||||
.importPaths(this.classes.getFiles().stream().map(File::toPath).toList());
|
||||
List<EvaluationResult> violations = getRules().get()
|
||||
.stream()
|
||||
.map((rule) -> rule.evaluate(javaClasses))
|
||||
.filter(EvaluationResult::hasViolation)
|
||||
.toList();
|
||||
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
||||
outputFile.getParentFile().mkdirs();
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder report = new StringBuilder();
|
||||
for (EvaluationResult violation : violations) {
|
||||
report.append(violation.getFailureReport());
|
||||
report.append(String.format("%n"));
|
||||
}
|
||||
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
throw new GradleException("Architecture check failed. See '" + outputFile + "' for details.");
|
||||
}
|
||||
else {
|
||||
outputFile.createNewFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void setClasses(FileCollection classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public FileCollection getClasses() {
|
||||
return this.classes;
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
@SkipWhenEmpty
|
||||
@IgnoreEmptyDirectories
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
final FileTree getInputClasses() {
|
||||
return this.classes.getAsFileTree();
|
||||
}
|
||||
|
||||
@Optional
|
||||
@InputFiles
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract DirectoryProperty getResourcesDirectory();
|
||||
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDirectory();
|
||||
|
||||
@Internal
|
||||
public abstract ListProperty<ArchRule> getRules();
|
||||
|
||||
@Internal
|
||||
public abstract Property<Boolean> getProhibitObjectsRequireNonNull();
|
||||
|
||||
@Input
|
||||
// The rules themselves can't be an input as they aren't serializable so we use
|
||||
// their descriptions instead
|
||||
abstract ListProperty<String> getRuleDescriptions();
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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.architecture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
/**
|
||||
* {@link Plugin} for verifying a project's architecture.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ArchitecturePlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerTasks(project));
|
||||
}
|
||||
|
||||
private void registerTasks(Project project) {
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
List<TaskProvider<ArchitectureCheck>> architectureChecks = new ArrayList<>();
|
||||
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
|
||||
if (sourceSet.getName().contains("test")) {
|
||||
// skip test source sets.
|
||||
continue;
|
||||
}
|
||||
TaskProvider<ArchitectureCheck> checkArchitecture = project.getTasks()
|
||||
.register(taskName(sourceSet), ArchitectureCheck.class,
|
||||
(task) -> {
|
||||
task.setClasses(sourceSet.getOutput().getClassesDirs());
|
||||
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
|
||||
task.dependsOn(sourceSet.getProcessResourcesTaskName());
|
||||
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
|
||||
+ " source set.");
|
||||
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
});
|
||||
architectureChecks.add(checkArchitecture);
|
||||
}
|
||||
if (!architectureChecks.isEmpty()) {
|
||||
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
|
||||
checkTask.configure((check) -> check.dependsOn(architectureChecks));
|
||||
}
|
||||
}
|
||||
|
||||
private static String taskName(SourceSet sourceSet) {
|
||||
return "checkArchitecture"
|
||||
+ sourceSet.getName().substring(0, 1).toUpperCase()
|
||||
+ sourceSet.getName().substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* 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.architecture;
|
||||
|
||||
import com.tngtech.archunit.base.DescribedPredicate;
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
|
||||
import com.tngtech.archunit.library.dependencies.SliceAssignment;
|
||||
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
|
||||
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||
import java.util.List;
|
||||
|
||||
abstract class ArchitectureRules {
|
||||
|
||||
static ArchRule allPackagesShouldBeFreeOfTangles() {
|
||||
return SlicesRuleDefinition.slices()
|
||||
.assignedFrom(new SpringSlices()).should().beFreeOfCycles();
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToLowerCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toLowerCase")
|
||||
.because("String.toLowerCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToUpperCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toUpperCase")
|
||||
.because("String.toUpperCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule classesShouldNotImportForbiddenTypes() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("reactor.core.support.Assert")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.slf4j.LoggerFactory")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.NonNull")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.Nullable");
|
||||
}
|
||||
|
||||
static ArchRule javaClassesShouldNotImportKotlinAnnotations() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.that(new DescribedPredicate<JavaClass>("is not a Kotlin class") {
|
||||
@Override
|
||||
public boolean test(JavaClass javaClass) {
|
||||
return javaClass.getSourceCodeLocation()
|
||||
.getSourceFileName().endsWith(".java");
|
||||
}
|
||||
}
|
||||
)
|
||||
.should().dependOnClassesThat()
|
||||
.resideInAnyPackage("org.jetbrains.annotations..")
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
static class SpringSlices implements SliceAssignment {
|
||||
|
||||
private final List<String> ignoredPackages = List.of("org.springframework.asm",
|
||||
"org.springframework.cglib",
|
||||
"org.springframework.javapoet",
|
||||
"org.springframework.objenesis");
|
||||
|
||||
@Override
|
||||
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
|
||||
|
||||
String packageName = javaClass.getPackageName();
|
||||
for (String ignoredPackage : ignoredPackages) {
|
||||
if (packageName.startsWith(ignoredPackage)) {
|
||||
return SliceIdentifier.ignore();
|
||||
}
|
||||
}
|
||||
return SliceIdentifier.of("spring framework");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Spring Framework Slices";
|
||||
}
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* 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.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.attributes.LibraryElements;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
/**
|
||||
* @author Cedric Champeau
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public abstract class MultiReleaseExtension {
|
||||
private final TaskContainer tasks;
|
||||
private final SourceSetContainer sourceSets;
|
||||
private final DependencyHandler dependencies;
|
||||
private final ObjectFactory objects;
|
||||
private final ConfigurationContainer configurations;
|
||||
|
||||
@Inject
|
||||
public MultiReleaseExtension(SourceSetContainer sourceSets,
|
||||
ConfigurationContainer configurations,
|
||||
TaskContainer tasks,
|
||||
DependencyHandler dependencies,
|
||||
ObjectFactory objectFactory) {
|
||||
this.sourceSets = sourceSets;
|
||||
this.configurations = configurations;
|
||||
this.tasks = tasks;
|
||||
this.dependencies = dependencies;
|
||||
this.objects = objectFactory;
|
||||
}
|
||||
|
||||
public void releaseVersions(int... javaVersions) {
|
||||
releaseVersions("src/main/", "src/test/", javaVersions);
|
||||
}
|
||||
|
||||
private void releaseVersions(String mainSourceDirectory, String testSourceDirectory, int... javaVersions) {
|
||||
for (int javaVersion : javaVersions) {
|
||||
addLanguageVersion(javaVersion, mainSourceDirectory, testSourceDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private void addLanguageVersion(int javaVersion, String mainSourceDirectory, String testSourceDirectory) {
|
||||
String javaN = "java" + javaVersion;
|
||||
|
||||
SourceSet langSourceSet = sourceSets.create(javaN, srcSet -> srcSet.getJava().srcDir(mainSourceDirectory + javaN));
|
||||
SourceSet testSourceSet = sourceSets.create(javaN + "Test", srcSet -> srcSet.getJava().srcDir(testSourceDirectory + javaN));
|
||||
SourceSet sharedSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||
SourceSet sharedTestSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME);
|
||||
|
||||
FileCollection mainClasses = objects.fileCollection().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs());
|
||||
dependencies.add(javaN + "Implementation", mainClasses);
|
||||
|
||||
tasks.named(langSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||
task.getOptions().getRelease().set(javaVersion)
|
||||
);
|
||||
tasks.named(testSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||
task.getOptions().getRelease().set(javaVersion)
|
||||
);
|
||||
|
||||
TaskProvider<Test> testTask = createTestTask(javaVersion, testSourceSet, sharedTestSourceSet, langSourceSet, sharedSourceSet);
|
||||
tasks.named("check", task -> task.dependsOn(testTask));
|
||||
|
||||
configureMultiReleaseJar(javaVersion, langSourceSet);
|
||||
}
|
||||
|
||||
private TaskProvider<Test> createTestTask(int javaVersion, SourceSet testSourceSet, SourceSet sharedTestSourceSet, SourceSet langSourceSet, SourceSet sharedSourceSet) {
|
||||
Configuration testImplementation = configurations.getByName(testSourceSet.getImplementationConfigurationName());
|
||||
testImplementation.extendsFrom(configurations.getByName(sharedTestSourceSet.getImplementationConfigurationName()));
|
||||
Configuration testCompileOnly = configurations.getByName(testSourceSet.getCompileOnlyConfigurationName());
|
||||
testCompileOnly.extendsFrom(configurations.getByName(sharedTestSourceSet.getCompileOnlyConfigurationName()));
|
||||
testCompileOnly.getDependencies().add(dependencies.create(langSourceSet.getOutput().getClassesDirs()));
|
||||
testCompileOnly.getDependencies().add(dependencies.create(sharedSourceSet.getOutput().getClassesDirs()));
|
||||
|
||||
Configuration testRuntimeClasspath = configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName());
|
||||
// so here's the deal. MRjars are JARs! Which means that to execute tests, we need
|
||||
// the JAR on classpath, not just classes + resources as Gradle usually does
|
||||
testRuntimeClasspath.getAttributes()
|
||||
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
|
||||
|
||||
TaskProvider<Test> testTask = tasks.register("java" + javaVersion + "Test", Test.class, test -> {
|
||||
test.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
|
||||
ConfigurableFileCollection testClassesDirs = objects.fileCollection();
|
||||
testClassesDirs.from(testSourceSet.getOutput());
|
||||
testClassesDirs.from(sharedTestSourceSet.getOutput());
|
||||
test.setTestClassesDirs(testClassesDirs);
|
||||
ConfigurableFileCollection classpath = objects.fileCollection();
|
||||
// must put the MRJar first on classpath
|
||||
classpath.from(tasks.named("jar"));
|
||||
// then we put the specific test sourceset tests, so that we can override
|
||||
// the shared versions
|
||||
classpath.from(testSourceSet.getOutput());
|
||||
|
||||
// then we add the shared tests
|
||||
classpath.from(sharedTestSourceSet.getRuntimeClasspath());
|
||||
test.setClasspath(classpath);
|
||||
});
|
||||
return testTask;
|
||||
}
|
||||
|
||||
private void configureMultiReleaseJar(int version, SourceSet languageSourceSet) {
|
||||
tasks.named("jar", Jar.class, jar -> {
|
||||
jar.into("META-INF/versions/" + version, s -> s.from(languageSourceSet.getOutput()));
|
||||
Attributes attributes = jar.getManifest().getAttributes();
|
||||
attributes.put("Multi-Release", "true");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.plugins.ExtensionContainer;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
/**
|
||||
* A plugin which adds support for building multi-release jars
|
||||
* with Gradle.
|
||||
* @author Cedric Champeau
|
||||
* @author Brian Clozel
|
||||
* @see <a href="https://github.com/melix/mrjar-gradle-plugin">original project</a>
|
||||
*/
|
||||
public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||
|
||||
public static String VALIDATE_JAR_TASK_NAME = "validateMultiReleaseJar";
|
||||
|
||||
@Inject
|
||||
protected JavaToolchainService getToolchains() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply(JavaPlugin.class);
|
||||
ExtensionContainer extensions = project.getExtensions();
|
||||
JavaPluginExtension javaPluginExtension = extensions.getByType(JavaPluginExtension.class);
|
||||
ConfigurationContainer configurations = project.getConfigurations();
|
||||
TaskContainer tasks = project.getTasks();
|
||||
DependencyHandler dependencies = project.getDependencies();
|
||||
ObjectFactory objects = project.getObjects();
|
||||
extensions.create("multiRelease", MultiReleaseExtension.class,
|
||||
javaPluginExtension.getSourceSets(),
|
||||
configurations,
|
||||
tasks,
|
||||
dependencies,
|
||||
objects);
|
||||
|
||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
}
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.multirelease;
|
||||
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.CacheableTask;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@CacheableTask
|
||||
public abstract class MultiReleaseJarValidateTask extends JavaExec {
|
||||
|
||||
|
||||
public MultiReleaseJarValidateTask() {
|
||||
getMainModule().set("jdk.jartool");
|
||||
getArgumentProviders().add(() -> List.of("--validate", "--file", getJar().get().getAsFile().getAbsolutePath()));
|
||||
}
|
||||
|
||||
@Inject
|
||||
protected abstract JavaToolchainService getJavaToolchainService();
|
||||
|
||||
@InputFile
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract RegularFileProperty getJar();
|
||||
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class ShadowSource extends DefaultTask {
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
public DirectoryProperty getOutputDirectory() {
|
||||
DirectoryProperty getOutputDirectory() {
|
||||
return this.outputDirectory;
|
||||
}
|
||||
|
||||
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* 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.multirelease;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link MultiReleaseJarPlugin}
|
||||
*/
|
||||
public class MultiReleaseJarPluginTests {
|
||||
|
||||
private File projectDir;
|
||||
|
||||
private File buildFile;
|
||||
|
||||
private File propertiesFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
this.buildFile = new File(this.projectDir, "build.gradle");
|
||||
this.propertiesFile = new File(this.projectDir, "gradle.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureSourceSets() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
multiRelease { releaseVersions 21, 24 }
|
||||
task printSourceSets {
|
||||
doLast {
|
||||
sourceSets.all { println it.name }
|
||||
}
|
||||
}
|
||||
""");
|
||||
BuildResult buildResult = runGradle("printSourceSets");
|
||||
assertThat(buildResult.getOutput()).contains("main", "test", "java21", "java21Test", "java24", "java24Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureToolchainReleaseVersion() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
multiRelease { releaseVersions 21 }
|
||||
task printReleaseVersion {
|
||||
doLast {
|
||||
tasks.all { println it.name }
|
||||
tasks.named("compileJava21Java") {
|
||||
println "compileJava21Java releaseVersion: ${it.options.release.get()}"
|
||||
}
|
||||
tasks.named("compileJava21TestJava") {
|
||||
println "compileJava21TestJava releaseVersion: ${it.options.release.get()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
BuildResult buildResult = runGradle("printReleaseVersion");
|
||||
assertThat(buildResult.getOutput()).contains("compileJava21Java releaseVersion: 21")
|
||||
.contains("compileJava21TestJava releaseVersion: 21");
|
||||
}
|
||||
|
||||
@Test
|
||||
void packageInJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
version = '1.2.3'
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {}
|
||||
""");
|
||||
BuildResult buildResult = runGradle("assemble");
|
||||
File file = new File(this.projectDir, "/build/libs/" + this.projectDir.getName() + "-1.2.3.jar");
|
||||
assertThat(file).exists();
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
Attributes mainAttributes = jar.getManifest().getMainAttributes();
|
||||
assertThat(mainAttributes.getValue("Multi-Release")).isEqualTo("true");
|
||||
|
||||
assertThat(jar.entries().asIterator()).toIterable()
|
||||
.anyMatch(entry -> entry.getName().equals("META-INF/versions/17/Main.class"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
|
||||
void validateJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
version = '1.2.3'
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.release = 11
|
||||
}
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeGradleProperties("""
|
||||
org.gradle.jvmargs=-Duser.language=en
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {
|
||||
|
||||
public void method() {}
|
||||
|
||||
}
|
||||
""");
|
||||
writeClass("src/main/java", "Main.java", """
|
||||
public class Main {}
|
||||
""");
|
||||
assertThatThrownBy(() ->runGradle("validateMultiReleaseJar"))
|
||||
.isInstanceOf(UnexpectedBuildFailure.class)
|
||||
.hasMessageContaining("entry: META-INF/versions/17/Main.class, contains a class with different api from earlier version");
|
||||
}
|
||||
|
||||
private void writeBuildFile(String buildContent) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
|
||||
out.print(buildContent);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGradleProperties(String properties) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
|
||||
out.print(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClass(String path, String fileName, String fileContent) throws IOException {
|
||||
Path folder = this.projectDir.toPath().resolve(path);
|
||||
Files.createDirectories(folder);
|
||||
Path filePath = folder.resolve(fileName);
|
||||
Files.createFile(filePath);
|
||||
Files.writeString(filePath, fileContent);
|
||||
}
|
||||
|
||||
private BuildResult runGradle(String... args) {
|
||||
return GradleRunner.create().withProjectDir(this.projectDir).withArguments(args).withPluginClasspath().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
plugins {
|
||||
id 'java-platform'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
id 'org.jetbrains.dokka'
|
||||
}
|
||||
|
||||
description = "Spring Framework API Docs"
|
||||
@@ -15,17 +14,12 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
rootProject.ext.moduleProjects.each { moduleProject ->
|
||||
javadoc project(moduleProject.path)
|
||||
moduleProjects.each { moduleProject ->
|
||||
javadoc moduleProject
|
||||
}
|
||||
}
|
||||
|
||||
def springAspectsOutput = project(":spring-aspects").sourceSets.main.output
|
||||
javadoc {
|
||||
javadocTool.set(javaToolchains.javadocToolFor({
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}))
|
||||
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
@@ -40,35 +34,35 @@ javadoc {
|
||||
links(rootProject.ext.javadocLinks)
|
||||
// Check for 'syntax' and 'reference' during linting.
|
||||
addBooleanOption('Xdoclint:syntax,reference', true)
|
||||
// Change modularity mismatch from warn to info.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
addStringOption("-link-modularity-mismatch", "info")
|
||||
// Fail build on Javadoc warnings.
|
||||
addBooleanOption('Werror', true)
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
}
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
classpath += files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
project(":spring-aspects").sourceSets.main.output
|
||||
)
|
||||
classpath += files(rootProject.ext.moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
}
|
||||
|
||||
dokka {
|
||||
moduleName = "spring-framework"
|
||||
dokkaPublications.html {
|
||||
outputDirectory = project.java.docsDir.dir("kdoc-api")
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
/**
|
||||
* Produce KDoc for all Spring Framework modules in "build/docs/kdoc"
|
||||
*/
|
||||
rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
dependsOn {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.java.docsDir.dir("kdoc-api").get().asFile)
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all Java docs (javadoc & kdoc) into a single archive
|
||||
*/
|
||||
tasks.register('docsZip', Zip) {
|
||||
dependsOn = ['javadoc', 'dokkaGenerate']
|
||||
dependsOn = ['javadoc', rootProject.tasks.dokkaHtmlMultiModule]
|
||||
group = "distribution"
|
||||
description = "Builds -${archiveClassifier} archive containing api and reference " +
|
||||
"for deployment at https://docs.spring.io/spring-framework/docs/."
|
||||
@@ -81,7 +75,7 @@ tasks.register('docsZip', Zip) {
|
||||
from(javadoc) {
|
||||
into "javadoc-api"
|
||||
}
|
||||
from(project.java.docsDir.dir("kdoc-api")) {
|
||||
from(rootProject.tasks.dokkaHtmlMultiModule.outputDirectory) {
|
||||
into "kdoc-api"
|
||||
}
|
||||
}
|
||||
@@ -96,7 +90,7 @@ tasks.register('schemaZip', Zip) {
|
||||
description = "Builds -${archiveClassifier} archive containing all " +
|
||||
"XSDs for deployment at https://springframework.org/schema."
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
rootProject.ext.moduleProjects.each { module ->
|
||||
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 project(it.path)
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,8 @@ content:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: include all releases from 6.2.0 to 9.*.*.
|
||||
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
|
||||
branches: ['main', '{6..9}.+({1..9}).x']
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
@@ -38,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
|
||||
@@ -31,7 +31,7 @@ asciidoc:
|
||||
spring-org: 'spring-projects'
|
||||
spring-github-org: "https://github.com/{spring-org}"
|
||||
spring-framework-github: "https://github.com/{spring-org}/spring-framework"
|
||||
spring-framework-code: '{spring-framework-github}/tree/7.0.x'
|
||||
spring-framework-code: '{spring-framework-github}/tree/main'
|
||||
spring-framework-issues: '{spring-framework-github}/issues'
|
||||
spring-framework-wiki: '{spring-framework-github}/wiki'
|
||||
# Docs
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
|
||||
|
||||
plugins {
|
||||
id 'kotlin'
|
||||
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
|
||||
@@ -15,15 +12,11 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
node {
|
||||
version = '24.15.0'
|
||||
}
|
||||
|
||||
tasks.named("generateAntoraYml") {
|
||||
asciidocAttributes = project.provider( {
|
||||
return ["spring-version": project.version ]
|
||||
@@ -48,16 +41,6 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
// To avoid a redeclaration error with Kotlin compiler and set the JVM target
|
||||
tasks.withType(KotlinCompilationTask.class).configureEach {
|
||||
javaSources.from = []
|
||||
compilerOptions.jvmTarget = JvmTarget.JVM_17
|
||||
compilerOptions.freeCompilerArgs.addAll(
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-aspects"))
|
||||
implementation(project(":spring-context"))
|
||||
@@ -71,10 +54,10 @@ dependencies {
|
||||
implementation(project(":spring-webmvc"))
|
||||
implementation(project(":spring-websocket"))
|
||||
|
||||
implementation("com.github.ben-manes.caffeine:caffeine")
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
implementation("com.mchange:c3p0:0.9.5.5")
|
||||
implementation("com.oracle.database.jdbc:ojdbc11")
|
||||
implementation("io.micrometer:context-propagation")
|
||||
implementation("io.projectreactor.netty:reactor-netty-http")
|
||||
implementation("jakarta.jms:jakarta.jms-api")
|
||||
implementation("jakarta.servlet:jakarta.servlet-api")
|
||||
@@ -84,15 +67,9 @@ dependencies {
|
||||
implementation("javax.cache:cache-api")
|
||||
implementation("org.apache.activemq:activemq-ra:6.1.2")
|
||||
implementation("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
implementation("org.apache.groovy:groovy-templates")
|
||||
implementation("org.aspectj:aspectjweaver")
|
||||
implementation("org.assertj:assertj-core")
|
||||
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
implementation("org.freemarker:freemarker")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
implementation("tools.jackson.core:jackson-databind")
|
||||
implementation("tools.jackson.dataformat:jackson-dataformat-xml")
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 63 KiB |
@@ -17,7 +17,6 @@
|
||||
class="st5"
|
||||
id="svg5499"
|
||||
version="1.1"
|
||||
font-family="Helvetica, Arial, sans-serif"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mvc-splitted-contexts.svg"
|
||||
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
|
||||
@@ -37,7 +36,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path9164" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -48,7 +47,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8836" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -59,7 +58,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8520" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -70,7 +69,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8216" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -81,7 +80,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7924" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -92,7 +91,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7644" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -103,7 +102,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7375" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -114,7 +113,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7119" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -125,7 +124,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6875" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -136,7 +135,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6643" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -147,7 +146,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6423" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -158,7 +157,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6215" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -169,7 +168,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6019" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -180,7 +179,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5835" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -191,7 +190,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5663" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -202,7 +201,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5503" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -213,7 +212,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5355" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -224,7 +223,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5219" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -235,7 +234,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5095" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -246,7 +245,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path4983" /></marker><marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
@@ -257,7 +256,7 @@
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"><path
|
||||
id="path7394"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
@@ -268,7 +267,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8123"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
@@ -280,7 +279,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path8035"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -291,7 +290,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7959"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -302,7 +301,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7376"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
@@ -312,7 +311,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7388"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
@@ -324,7 +323,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7370"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
@@ -353,9 +352,9 @@
|
||||
v:viewMarkup="false" /><style
|
||||
type="text/css"
|
||||
id="style5501"><![CDATA[
|
||||
.st1 {fill:#969696;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#F8F9FA;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#333333;font-family:Helvetica, Arial, sans-serif;font-size:2.50001em;font-weight:bold}
|
||||
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
|
||||
.st4 {font-size:0.333333em;font-weight:normal}
|
||||
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]></style><g
|
||||
@@ -364,22 +363,20 @@
|
||||
v:groupContext="foregroundPage"
|
||||
id="g5503"
|
||||
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6599"
|
||||
width="382.68423"
|
||||
height="146.09897"
|
||||
x="87.884865"
|
||||
y="148.26482"
|
||||
rx="8" /><v:userDefs><v:ud
|
||||
y="148.26482" /><v:userDefs><v:ud
|
||||
v:nameU="SchemeName"
|
||||
v:val="VT4(Default)" /></v:userDefs><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5725"
|
||||
width="464.31128"
|
||||
height="374.11411"
|
||||
x="41.684383"
|
||||
y="112.3262"
|
||||
rx="8" /><title
|
||||
y="112.3262" /><title
|
||||
id="title5505">Page-1</title><v:pageProperties
|
||||
v:drawingScale="0.0393701"
|
||||
v:pageScale="0.0393701"
|
||||
@@ -388,16 +385,15 @@
|
||||
v:shadowOffsetY="-8.50394" /><v:layer
|
||||
v:name="Connector"
|
||||
v:index="0" /><rect
|
||||
style="fill:#F8F9FA;fill-opacity:1;fill-rule:evenodd;stroke:#6db33f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5715"
|
||||
width="322.8194"
|
||||
height="43.63184"
|
||||
x="119.95335"
|
||||
y="-135.66222"
|
||||
transform="scale(1,-1)"
|
||||
rx="8" /><text
|
||||
transform="scale(1,-1)" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
id="text5717"
|
||||
@@ -405,12 +401,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5719"
|
||||
x="172.843"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">DispatcherServlet</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
id="text6589"
|
||||
@@ -418,12 +414,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6591"
|
||||
x="106.770851"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">Servlet WebApplicationContext</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
id="text6593"
|
||||
@@ -433,39 +429,36 @@
|
||||
id="tspan6595"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555">(containing controllers, view resolvers,</tspan><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="260.00443"
|
||||
y="212.83057"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan6597">and other web-related beans)</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6620"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="114.52653"
|
||||
y="-259.43161"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="scale(1,-1)" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6622"
|
||||
width="87.843979"
|
||||
height="36.697304"
|
||||
x="223.39864"
|
||||
y="-287.19809"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-264.81918"
|
||||
x="117.92834"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6614"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
id="text6616"
|
||||
@@ -473,20 +466,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6618"
|
||||
x="124.24728"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
style="font-size:11.53426838px;fill:#333333">Controllers</tspan></text>
|
||||
style="font-size:11.53426838px">Controllers</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
style="font-size:11.53426838px"
|
||||
y="282.70709"
|
||||
x="221.61203"
|
||||
x="219.61203"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">ViewResolver</tspan></text>
|
||||
<rect
|
||||
@@ -496,10 +489,9 @@
|
||||
height="36.577778"
|
||||
width="114.4539"
|
||||
id="rect6628"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
id="text6630"
|
||||
@@ -507,27 +499,26 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6632"
|
||||
x="332.51276"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
style="font-size:11.53426838px;fill:#333333">HandlerMapping</tspan></text>
|
||||
style="font-size:11.53426838px">HandlerMapping</tspan></text>
|
||||
<rect
|
||||
y="338.69724"
|
||||
x="87.803261"
|
||||
height="121.5683"
|
||||
width="382.84744"
|
||||
id="rect6634"
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="8" /><text
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6636"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
|
||||
y="376.61673"
|
||||
x="113.61351"
|
||||
x="108.61351"
|
||||
id="tspan6638"
|
||||
sodipodi:role="line">Root WebApplicationContext</tspan></text>
|
||||
<text
|
||||
@@ -536,10 +527,10 @@
|
||||
id="text6640"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan6644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
|
||||
@@ -550,47 +541,43 @@
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6648"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6650"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="167.73116"
|
||||
y="-445.45563"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><text
|
||||
transform="scale(1,-1)" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6652"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
style="font-size:11.53426838px"
|
||||
y="448.55054"
|
||||
x="178.87148"
|
||||
x="175.87148"
|
||||
id="tspan6654"
|
||||
sodipodi:role="line">Services</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6656"
|
||||
width="86.393044"
|
||||
height="36.704323"
|
||||
x="306.86328"
|
||||
y="-439.60837"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-444.99475"
|
||||
x="310.26624"
|
||||
height="36.701977"
|
||||
width="86.876686"
|
||||
id="rect6658"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
id="text6660"
|
||||
@@ -598,20 +585,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6662"
|
||||
x="308.30771"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
style="font-size:11.53426838px;fill:#333333">Repositories</tspan></text>
|
||||
style="font-size:11.53426838px">Repositories</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
|
||||
id="path7643"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
style="fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
|
||||
id="path7645"
|
||||
inkscape:connector-curvature="0" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
id="text4963"
|
||||
@@ -620,6 +607,6 @@
|
||||
sodipodi:role="line"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan4965">Delegates if no bean found</tspan></text>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
@@ -32,7 +32,6 @@
|
||||
**** xref:core/beans/java/bean-annotation.adoc[]
|
||||
**** xref:core/beans/java/configuration-annotation.adoc[]
|
||||
**** xref:core/beans/java/composing-configuration-classes.adoc[]
|
||||
**** xref:core/beans/java/programmatic-bean-registration.adoc[]
|
||||
*** xref:core/beans/environment.adoc[]
|
||||
*** xref:core/beans/context-load-time-weaver.adoc[]
|
||||
*** xref:core/beans/context-introduction.adoc[]
|
||||
@@ -40,8 +39,8 @@
|
||||
** xref:core/resources.adoc[]
|
||||
** xref:core/validation.adoc[]
|
||||
*** xref:core/validation/validator.adoc[]
|
||||
*** xref:core/validation/data-binding.adoc[]
|
||||
*** xref:core/validation/error-code-resolution.adoc[]
|
||||
*** xref:core/validation/beans-beans.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/convert.adoc[]
|
||||
*** xref:core/validation/format.adoc[]
|
||||
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
|
||||
@@ -100,9 +99,9 @@
|
||||
*** xref:core/aop-api/autoproxy.adoc[]
|
||||
*** xref:core/aop-api/targetsource.adoc[]
|
||||
*** xref:core/aop-api/extensibility.adoc[]
|
||||
** xref:core/resilience.adoc[]
|
||||
** xref:core/null-safety.adoc[]
|
||||
** xref:core/databuffer-codec.adoc[]
|
||||
** xref:core/spring-jcl.adoc[]
|
||||
** xref:core/aot.adoc[]
|
||||
** xref:core/appendix.adoc[]
|
||||
*** xref:core/appendix/xsd-schemas.adoc[]
|
||||
@@ -162,6 +161,7 @@
|
||||
**** xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/viewresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/localeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/themeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
|
||||
*** xref:web/webmvc/filters.adoc[]
|
||||
@@ -197,10 +197,7 @@
|
||||
*** xref:web/webmvc-functional.adoc[]
|
||||
*** xref:web/webmvc/mvc-uri-building.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-async.adoc[]
|
||||
*** xref:web/webmvc/mvc-range.adoc[]
|
||||
*** xref:web/webmvc/mvc-data-binding.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc-versioning.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webmvc/mvc-security.adoc[]
|
||||
*** xref:web/webmvc/mvc-caching.adoc[]
|
||||
@@ -229,7 +226,6 @@
|
||||
**** xref:web/webmvc/mvc-config/static-resources.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/default-servlet-handler.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/path-matching.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/api-version.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-java.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-xml.adoc[]
|
||||
*** xref:web/webmvc/mvc-http2.adoc[]
|
||||
@@ -262,6 +258,7 @@
|
||||
**** xref:web/websocket/stomp/configuration-performance.adoc[]
|
||||
**** xref:web/websocket/stomp/stats.adoc[]
|
||||
**** xref:web/websocket/stomp/testing.adoc[]
|
||||
** xref:web/integration.adoc[]
|
||||
* xref:web-reactive.adoc[]
|
||||
** xref:web/webflux.adoc[]
|
||||
*** xref:web/webflux/new-framework.adoc[]
|
||||
@@ -295,10 +292,7 @@
|
||||
**** xref:web/webflux/controller/ann-advice.adoc[]
|
||||
*** xref:web/webflux-functional.adoc[]
|
||||
*** xref:web/webflux/uri-building.adoc[]
|
||||
*** xref:web/webflux/range.adoc[]
|
||||
*** xref:web/webflux/data-binding.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux-versioning.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webflux/security.adoc[]
|
||||
*** xref:web/webflux/caching.adoc[]
|
||||
@@ -315,7 +309,7 @@
|
||||
*** xref:web/webflux-webclient/client-context.adoc[]
|
||||
*** xref:web/webflux-webclient/client-synchronous.adoc[]
|
||||
*** xref:web/webflux-webclient/client-testing.adoc[]
|
||||
** xref:web/webflux-http-service-client.adoc[]
|
||||
** xref:web/webflux-http-interface-client.adoc[]
|
||||
** xref:web/webflux-websocket.adoc[]
|
||||
** xref:web/webflux-test.adoc[]
|
||||
** xref:rsocket.adoc[]
|
||||
@@ -332,10 +326,9 @@
|
||||
*** xref:testing/testcontext-framework/application-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/ctx-management.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
@@ -346,7 +339,6 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
@@ -358,7 +350,6 @@
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/resttestclient.adoc[]
|
||||
** xref:testing/mockmvc.adoc[]
|
||||
*** xref:testing/mockmvc/overview.adoc[]
|
||||
*** xref:testing/mockmvc/setup-options.adoc[]
|
||||
@@ -442,8 +433,8 @@
|
||||
*** xref:integration/cache/plug.adoc[]
|
||||
*** xref:integration/cache/specific-config.adoc[]
|
||||
** xref:integration/observability.adoc[]
|
||||
** xref:integration/aot-cache.adoc[]
|
||||
** xref:integration/checkpoint-restore.adoc[]
|
||||
** xref:integration/cds.adoc[]
|
||||
** xref:integration/appendix.adoc[]
|
||||
* xref:languages.adoc[]
|
||||
** xref:languages/kotlin.adoc[]
|
||||
@@ -452,13 +443,14 @@
|
||||
*** xref:languages/kotlin/null-safety.adoc[]
|
||||
*** xref:languages/kotlin/classes-interfaces.adoc[]
|
||||
*** xref:languages/kotlin/annotations.adoc[]
|
||||
*** xref:languages/kotlin/bean-registration-dsl.adoc[]
|
||||
*** xref:languages/kotlin/bean-definition-dsl.adoc[]
|
||||
*** xref:languages/kotlin/web.adoc[]
|
||||
*** xref:languages/kotlin/coroutines.adoc[]
|
||||
*** xref:languages/kotlin/spring-projects-in.adoc[]
|
||||
*** xref:languages/kotlin/getting-started.adoc[]
|
||||
*** xref:languages/kotlin/resources.adoc[]
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
|
||||
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
|
||||
|
||||
@@ -74,17 +74,6 @@ 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]
|
||||
expression evaluation.
|
||||
|
||||
| `spring.getenv.ignore`
|
||||
| Instructs Spring to ignore operating system environment variables if a Spring
|
||||
`Environment` property -- for example, a placeholder in a configuration String -- isn't
|
||||
@@ -92,11 +81,6 @@ resolvable otherwise. See
|
||||
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.http.response.flush.enabled`
|
||||
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
|
||||
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
|
||||
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
@@ -140,20 +124,11 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int
|
||||
| The maximum size of the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
|
||||
|
||||
| `spring.test.context.cache.pause`
|
||||
| The pause mode for the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context Pausing].
|
||||
|
||||
| `spring.test.context.failure.threshold`
|
||||
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
|
||||
in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
|
||||
|
||||
| `spring.test.extension.context.scope`
|
||||
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
|
||||
class hierarchies. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
|
||||
@@ -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 <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
|
||||
* 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]).
|
||||
|
||||
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 <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
|
||||
are created (but see also xref:core/aop-api/pfb.adoc#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 <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
|
||||
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]).
|
||||
* `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 <<aop-global-advisors,Using "`Global`" Advisors>>.
|
||||
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].
|
||||
|
||||
* singleton: Whether or not the factory should return a single object, no matter how
|
||||
often the `getObject()` method is called. Several `FactoryBean` implementations offer
|
||||
|
||||
@@ -87,12 +87,14 @@ A crucial difference between Spring pooling and SLSB pooling is that Spring pool
|
||||
be applied to any POJO. As with Spring in general, this service can be applied in a
|
||||
non-invasive way.
|
||||
|
||||
Spring provides support for Commons Pool 2, which provides a
|
||||
Spring provides support for Commons Pool 2.2, which provides a
|
||||
fairly efficient pooling implementation. You need the `commons-pool` Jar on your
|
||||
application's classpath to use this feature. You can also subclass
|
||||
`org.springframework.aop.target.AbstractPoolingTargetSource` to support any other
|
||||
pooling API.
|
||||
|
||||
NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.
|
||||
|
||||
The following listing shows an example configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
|
||||
@@ -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
|
||||
<<aop-ataspectj-advice-proceeding-with-the-call,following section on advice parameters>>.
|
||||
xref:core/aop/ataspectj/advice.adoc#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,12 +722,12 @@ 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 <<aop-ataspectj-advice-params-names-explicit,Explicit Argument Names>> for details.
|
||||
pointcut annotation. See xref:core/aop/ataspectj/advice.adoc#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`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach.
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{spring-framework-api}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
|
||||
@@ -104,7 +104,7 @@ Note that pointcut definitions are generally matched against any intercepted met
|
||||
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
|
||||
potential non-public interactions through proxies, it needs to be defined accordingly.
|
||||
|
||||
If your interception needs to include method calls or even constructors within the target
|
||||
If your interception needs include method calls or even constructors within the target
|
||||
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
|
||||
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
|
||||
with different characteristics, so be sure to make yourself familiar with weaving
|
||||
|
||||
@@ -28,10 +28,6 @@ you can do so. However, you should consider the following issues:
|
||||
deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
|
||||
[[aop-forcing-proxy-types]]
|
||||
== Forcing Specific AOP Proxy Types
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
of the `<aop:config>` element to true, as follows:
|
||||
|
||||
@@ -64,24 +60,6 @@ To be clear, using `proxy-target-class="true"` on `<tx:annotation-driven/>`,
|
||||
proxies _for all three of them_.
|
||||
====
|
||||
|
||||
`@EnableAspectJAutoProxy`, `@EnableTransactionManagement` and related configuration
|
||||
annotations offer a corresponding `proxyTargetClass` attribute. These are collapsed
|
||||
into a single unified auto-proxy creator too, effectively applying the _strongest_
|
||||
proxy settings at runtime. As of 7.0, this applies to individual proxy processors
|
||||
as well, for example `@EnableAsync`, consistently participating in unified global
|
||||
default settings for all auto-proxying attempts in a given application.
|
||||
|
||||
The global default proxy type may differ between setups. While the core framework
|
||||
suggests interface-based proxies by default, Spring Boot may - depending on
|
||||
configuration properties - enable class-based proxies by default.
|
||||
|
||||
As of 7.0, forcing a specific proxy type for individual beans is possible through
|
||||
the `@Proxyable` annotation on a given `@Bean` method or `@Component` class, with
|
||||
`@Proxyable(INTERFACES)` or `@Proxyable(TARGET_CLASS)` overriding any globally
|
||||
configured default. For very specific purposes, you may even specify the proxy
|
||||
interface(s) to use through `@Proxyable(interfaces=...)`, limiting the exposure
|
||||
to selected interfaces rather than all interfaces that the target bean implements.
|
||||
|
||||
|
||||
[[aop-understanding-aop-proxies]]
|
||||
== Understanding AOP Proxies
|
||||
|
||||
@@ -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#xsd-schemas-aop[the AOP schema]
|
||||
. See xref:core/appendix/xsd-schemas.adoc#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 <<aop-schema-pointcuts,Declaring a Pointcut>>).
|
||||
the top (`<aop:config>`) level (see xref:core/aop/schema.adoc#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.
|
||||
<<aop-atconfigurable,Using AspectJ to Dependency Inject Domain Objects with Spring>>
|
||||
and <<aop-ajlib-other,Other Spring aspects for AspectJ>>
|
||||
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]
|
||||
discuss the content of this library and how you can use it.
|
||||
<<aop-aj-configure,Configuring AspectJ Aspects by Using Spring IoC>>
|
||||
xref:core/aop/using-aspectj.adoc#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,
|
||||
<<aop-aj-ltw,Load-time Weaving with AspectJ in the Spring Framework>>
|
||||
xref:core/aop/using-aspectj.adoc#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 <<aop-aj-ltw,Load-time Weaving with AspectJ in the Spring Framework>>). The
|
||||
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
|
||||
`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, <<aop-aj-ltw-environments,in certain environments>>, this support enables
|
||||
Further, xref:core/aop/using-aspectj.adoc#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 <<aop-aj-ltw-spring,below>> for details).
|
||||
(see xref:core/aop/using-aspectj.adoc#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 <<aop-aj-ltw-environments-generic,Spring-provided agent to enable instrumentation>>
|
||||
If you use the xref:core/aop/using-aspectj.adoc#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 <<aop-using-aspectj,described earlier>>.
|
||||
You can simply enable load-time weaving, as xref:core/aop/using-aspectj.adoc[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 <<aot.bestpractices>> section.
|
||||
TIP: See also the xref:core/aot.adoc#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:
|
||||
@@ -378,7 +378,7 @@ The container also supports creating a bean with {spring-framework-api}++/beans/
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
. Bypassing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
@@ -19,6 +19,10 @@ its behavior changes.
|
||||
| Initialization of `SmartInitializingSingleton` beans.
|
||||
| `beanName` the name of the bean.
|
||||
|
||||
| `spring.context.annotated-bean-reader.create`
|
||||
| Creation of the `AnnotatedBeanDefinitionReader`.
|
||||
|
|
||||
|
||||
| `spring.context.base-packages.scan`
|
||||
| Scanning of base packages.
|
||||
| `packages` array of base packages for scanning.
|
||||
|
||||
@@ -14,11 +14,11 @@ Spring distribution, you should first read the previous section on xref:core/app
|
||||
|
||||
To create new XML configuration extensions:
|
||||
|
||||
. <<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
|
||||
. 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
|
||||
(this is where the real work is done).
|
||||
. <<xsd-custom-registration,Register>> your new artifacts with Spring.
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.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 <<xsd-custom-introduction,the steps described previously>>, we start off
|
||||
If we stick to xref:core/appendix/xml-custom.adoc#core.appendix.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 <<xsd-custom-introduction,the process described earlier>>,
|
||||
Again following xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-introduction[the process described earlier],
|
||||
we then create a custom `NamespaceHandler`:
|
||||
|
||||
[tabs]
|
||||
|
||||
+11
-11
@@ -151,17 +151,17 @@ injected into a `Set<MovieCatalog>` annotated with `@Qualifier("action")`.
|
||||
[TIP]
|
||||
====
|
||||
Letting qualifier values select against target bean names, within the type-matching
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point. If there
|
||||
is no other resolution indicator (such as a qualifier, a primary marker, or a fallback
|
||||
marker), for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses the
|
||||
same-named candidate, if any (either by bean name or by associated alias).
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point.
|
||||
If there is no other resolution indicator (such as a qualifier or a primary marker),
|
||||
for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses
|
||||
the same-named candidate, if any (either by bean name or by associated alias).
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present. As
|
||||
of 6.2, the container applies fast shortcut resolution for bean name matches, bypassing
|
||||
the full type matching algorithm when the parameter name matches the bean name and no
|
||||
type, qualifier, primary, or fallback conditions override the match. It is therefore
|
||||
recommendable for your parameter names to match the target bean names.
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
As of 6.2, the container applies fast shortcut resolution for bean name matches,
|
||||
bypassing the full type matching algorithm when the parameter name matches the
|
||||
bean name and no type, qualifier or primary conditions override the match. It is
|
||||
therefore recommendable for your parameter names to match the target bean names.
|
||||
====
|
||||
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
@@ -274,7 +274,7 @@ Kotlin::
|
||||
Next, you can provide the information for the candidate bean definitions. You can add
|
||||
`<qualifier/>` tags as sub-elements of the `<bean/>` tag and then specify the `type` and
|
||||
`value` to match your custom qualifier annotations. The type is matched against the
|
||||
fully-qualified class name of the annotation. Alternatively, as a convenience if no risk of
|
||||
fully-qualified class name of the annotation. Alternately, as a convenience if no risk of
|
||||
conflicting names exists, you can use the short class name. The following example
|
||||
demonstrates both approaches:
|
||||
|
||||
|
||||
@@ -37,18 +37,18 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
[NOTE]
|
||||
====
|
||||
An `@Autowired` annotation on such a constructor is not necessary if the target bean
|
||||
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 <<beans-autowired-annotation-constructor-resolution,constructor resolution>>
|
||||
for details.
|
||||
As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is no longer
|
||||
necessary if the target bean defines only one constructor to begin with. However, if
|
||||
several constructors are available and there is no primary/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] for details.
|
||||
====
|
||||
|
||||
You can apply the `@Autowired` annotation to _traditional_ setter methods, as the
|
||||
following example shows:
|
||||
You can also apply the `@Autowired` annotation to _traditional_ setter methods,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -84,8 +84,8 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can apply `@Autowired` to methods with arbitrary names and multiple arguments, as the
|
||||
following example shows:
|
||||
You can also apply the annotation to methods with arbitrary names and multiple
|
||||
arguments, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -176,15 +176,14 @@ Kotlin::
|
||||
====
|
||||
Make sure that your target components (for example, `MovieCatalog` or `CustomerPreferenceDao`)
|
||||
are consistently declared by the type that you use for your `@Autowired`-annotated
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at
|
||||
runtime.
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at runtime.
|
||||
|
||||
For XML-defined beans or component classes found via classpath scanning, the container
|
||||
usually knows the concrete type up front. However, for `@Bean` factory methods, you need
|
||||
to make sure that the declared return type is sufficiently expressive. For components
|
||||
that implement several interfaces or for components potentially referred to by their
|
||||
implementation type, declare the most specific return type on your factory method (at
|
||||
least as specific as required by the injection points referring to your bean).
|
||||
implementation type, consider declaring the most specific return type on your factory
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
.[[beans-autowired-annotation-self-injection]]Self Injection
|
||||
@@ -309,13 +308,12 @@ set of multiple matches for the specific bean type (as returned by the factory m
|
||||
|
||||
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
|
||||
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
|
||||
through `@Order` values in combination with `@Primary` or `@Fallback` on a single bean
|
||||
for each type.
|
||||
through `@Order` values in combination with `@Primary` on a single bean for each type.
|
||||
====
|
||||
|
||||
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
|
||||
The map values are all beans of the expected type, and the keys are the corresponding
|
||||
bean names, as the following example shows:
|
||||
The map values contain all beans of the expected type, and the keys contain the
|
||||
corresponding bean names, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -433,7 +431,7 @@ annotated constructor does not have to be public.
|
||||
====
|
||||
|
||||
Alternatively, you can express the non-required nature of a particular dependency
|
||||
through Java's `java.util.Optional`, as the following example shows:
|
||||
through Java 8's `java.util.Optional`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -447,8 +445,8 @@ through Java's `java.util.Optional`, as the following example shows:
|
||||
----
|
||||
|
||||
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
|
||||
for example, `org.jspecify.annotations.Nullable` from JSpecify) or just leverage Kotlin's
|
||||
built-in null-safety support:
|
||||
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
|
||||
null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -479,6 +477,13 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
|
||||
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
|
||||
detects type-level annotations and consistently declares JSpecify in its own codebase.
|
||||
====
|
||||
|
||||
You can also use `@Autowired` for interfaces that are well-known resolvable
|
||||
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
|
||||
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
|
||||
@@ -523,6 +528,5 @@ class MovieRecommender {
|
||||
The `@Autowired`, `@Inject`, `@Value`, and `@Resource` annotations are handled by Spring
|
||||
`BeanPostProcessor` implementations. This means that you cannot apply these annotations
|
||||
within your own `BeanPostProcessor` or `BeanFactoryPostProcessor` types (if any).
|
||||
|
||||
These types must be 'wired up' explicitly by using XML or a Spring `@Bean` method.
|
||||
====
|
||||
|
||||
+1
-2
@@ -27,5 +27,4 @@ with the `CustomAutowireConfigurer`
|
||||
|
||||
When multiple beans qualify as autowire candidates, the determination of a "`primary`" is
|
||||
as follows: If exactly one bean definition among the candidates has a `primary`
|
||||
attribute set to `true`, it is selected. For annotation-based configuration, see
|
||||
xref:core/beans/annotation-config/autowired-primary.adoc[Fine-tuning with `@Primary` or `@Fallback`].
|
||||
attribute set to `true`, it is selected.
|
||||
|
||||
@@ -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
|
||||
<<beans-factory-xml,previous section>>. Alternatively,
|
||||
xref:core/beans/basics.adoc#beans-factory-xml[previous section]. Alternatively,
|
||||
use one or more occurrences of the `<import/>` element to load bean definitions from
|
||||
another file or files. The following example shows how to do so:
|
||||
|
||||
@@ -202,17 +202,18 @@ another file or files. The following example shows how to do so:
|
||||
<beans>
|
||||
<import resource="services.xml"/>
|
||||
<import resource="resources/messageSource.xml"/>
|
||||
<import resource="/resources/themeSource.xml"/>
|
||||
|
||||
<bean id="bean1" class="..."/>
|
||||
<bean id="bean2" class="..."/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
In the preceding example, external bean definitions are loaded from the files
|
||||
`services.xml` and `messageSource.xml`. All location paths are
|
||||
In the preceding example, external bean definitions are loaded from three files:
|
||||
`services.xml`, `messageSource.xml`, and `themeSource.xml`. All location paths are
|
||||
relative to the definition file doing the importing, so `services.xml` must be in the
|
||||
same directory or classpath location as the file doing the importing, while
|
||||
`messageSource.xml` must be in a `resources` location below the
|
||||
`messageSource.xml` and `themeSource.xml` must be in a `resources` location below the
|
||||
location of the importing file. As you can see, a leading slash is ignored. However, given
|
||||
that these paths are relative, it is better form not to use the slash at all. The
|
||||
contents of the files being imported, including the top level `<beans/>` element, must
|
||||
@@ -239,6 +240,40 @@ The namespace itself provides the import directive feature. Further
|
||||
configuration features beyond plain bean definitions are available in a selection
|
||||
of XML namespaces provided by Spring -- for example, the `context` and `util` namespaces.
|
||||
|
||||
[[beans-factory-groovy]]
|
||||
=== The Groovy Bean Definition DSL
|
||||
|
||||
As a further example for externalized configuration metadata, bean definitions can also
|
||||
be expressed in Spring's Groovy Bean Definition DSL, as known from the Grails framework.
|
||||
Typically, such configuration live in a ".groovy" file with the structure shown in the
|
||||
following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
beans {
|
||||
dataSource(BasicDataSource) {
|
||||
driverClassName = "org.hsqldb.jdbcDriver"
|
||||
url = "jdbc:hsqldb:mem:grailsDB"
|
||||
username = "sa"
|
||||
password = ""
|
||||
settings = [mynew:"setting"]
|
||||
}
|
||||
sessionFactory(SessionFactory) {
|
||||
dataSource = dataSource
|
||||
}
|
||||
myService(MyService) {
|
||||
nestedBean = { AnotherBean bean ->
|
||||
dataSource = dataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This configuration style is largely equivalent to XML bean definitions and even
|
||||
supports Spring's XML configuration namespaces. It also allows for importing XML
|
||||
bean definition files through an `importBeans` directive.
|
||||
|
||||
|
||||
[[beans-factory-client]]
|
||||
== Using the Container
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ annotations. Even in those examples, however, the "base" bean definitions are ex
|
||||
defined in the XML file, while the annotations drive only the dependency injection.
|
||||
|
||||
This section describes an option for implicitly detecting the candidate components by
|
||||
scanning the classpath. Candidate components are classes that match against filter
|
||||
scanning the classpath. Candidate components are classes that match against a filter
|
||||
criteria and have a corresponding bean definition registered with the container.
|
||||
This removes the need to use XML to perform bean registration. Instead, you can use
|
||||
annotations (for example, `@Component`), AspectJ type expressions, or your own
|
||||
@@ -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 <<beans-stereotype-annotations,earlier>>
|
||||
For example, the `@Service` annotation mentioned xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[earlier]
|
||||
is meta-annotated with `@Component`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
@@ -70,7 +70,7 @@ Java::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -85,7 +85,7 @@ Kotlin::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
======
|
||||
|
||||
You can also combine meta-annotations to create "`composed annotations`". For example,
|
||||
@@ -97,7 +97,7 @@ meta-annotations to allow customization. This can be particularly useful when yo
|
||||
want to only expose a subset of the meta-annotation's attributes. For example, Spring's
|
||||
`@SessionScope` annotation hard codes the scope name to `session` but still allows
|
||||
customization of the `proxyMode`. The following listing shows the definition of the
|
||||
`@SessionScope` annotation:
|
||||
`SessionScope` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -211,7 +211,7 @@ Java::
|
||||
@Service
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private final MovieFinder movieFinder;
|
||||
private MovieFinder movieFinder;
|
||||
|
||||
public SimpleMovieLister(MovieFinder movieFinder) {
|
||||
this.movieFinder = movieFinder;
|
||||
@@ -251,11 +251,11 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
To autodetect these classes and register the corresponding beans, you need to add
|
||||
`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute is
|
||||
configured with a common parent package for the two classes. Alternatively, you can
|
||||
specify a comma-, semicolon-, or space-separated list that includes the parent package
|
||||
of each class.
|
||||
`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute
|
||||
is a common parent package for the two classes. (Alternatively, you can specify a
|
||||
comma- or semicolon- or space-separated list that includes the parent package of each class.)
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -282,10 +282,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
TIP: For brevity, the preceding example could have used the implicit `value` attribute of
|
||||
the annotation instead: `@ComponentScan("org.example")`
|
||||
NOTE: For brevity, the preceding example could have used the `value` attribute of the
|
||||
annotation (that is, `@ComponentScan("org.example")`).
|
||||
|
||||
The following example uses XML configuration:
|
||||
The following alternative uses XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -325,68 +325,16 @@ sure that they are 'opened' (that is, that they use an `opens` declaration inste
|
||||
|
||||
Furthermore, the `AutowiredAnnotationBeanPostProcessor` and
|
||||
`CommonAnnotationBeanPostProcessor` are both implicitly included when you use the
|
||||
`<context:component-scan>` element. That means that the two components are autodetected
|
||||
and wired together -- all without any bean configuration metadata provided in XML.
|
||||
component-scan element. That means that the two components are autodetected and
|
||||
wired together -- all without any bean configuration metadata provided in XML.
|
||||
|
||||
NOTE: You can disable the registration of `AutowiredAnnotationBeanPostProcessor` and
|
||||
`CommonAnnotationBeanPostProcessor` by including the `annotation-config` attribute
|
||||
with a value of `false`.
|
||||
|
||||
|
||||
[[beans-scanning-placeholders-and-patterns]]
|
||||
=== Property Placeholders and Ant-style Patterns
|
||||
|
||||
The `basePackages` and `value` attributes in `@ComponentScan` support `${...}` property
|
||||
placeholders which are resolved against the `Environment` as well as Ant-style package
|
||||
patterns such as `"org.example.+++**+++"`.
|
||||
|
||||
In addition, multiple packages or patterns may be specified, either separately or within
|
||||
a single String — for example, `{"org.example.config", "org.example.service.+++**+++"}`
|
||||
or `"org.example.config, org.example.service.+++**+++"`.
|
||||
|
||||
The following example specifies the `app.scan.packages` property placeholder for the
|
||||
implicit `value` attribute in `@ComponentScan`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("${app.scan.packages}") // <1>
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(["\${app.scan.packages}"]) // <1>
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
|
||||
======
|
||||
|
||||
The following listing represents a properties file which defines the `app.scan.packages`
|
||||
property. In the preceding example, it is assumed that this properties file has been
|
||||
registered with the `Environment` – for example, via `@PropertySource` or a similar
|
||||
mechanism.
|
||||
|
||||
[source,properties,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
app.scan.packages=org.example.config, org.example.service.**
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-filters]]
|
||||
=== Using Filters to Customize Scanning
|
||||
== Using Filters to Customize Scanning
|
||||
|
||||
By default, classes annotated with `@Component`, `@Repository`, `@Service`, `@Controller`,
|
||||
`@Configuration`, or a custom annotation that itself is annotated with `@Component` are
|
||||
@@ -423,8 +371,8 @@ The following table describes the filtering options:
|
||||
| A custom implementation of the `org.springframework.core.type.TypeFilter` interface.
|
||||
|===
|
||||
|
||||
The following example shows `@ComponentScan` configuration that excludes all
|
||||
`@Repository` annotations and includes "`Stub`" repositories instead:
|
||||
The following example shows the configuration ignoring all `@Repository` annotations
|
||||
and using "`stub`" repositories instead:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -476,367 +424,6 @@ annotated or meta-annotated with `@Component`, `@Repository`, `@Service`, `@Cont
|
||||
`@RestController`, or `@Configuration`.
|
||||
|
||||
|
||||
[[beans-scanning-name-generator]]
|
||||
=== Naming Autodetected Components
|
||||
|
||||
When a component is autodetected as part of the scanning process, its bean name is
|
||||
generated by the `BeanNameGenerator` strategy known to that scanner.
|
||||
|
||||
By default, the `AnnotationBeanNameGenerator` is used. For Spring
|
||||
<<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.
|
||||
|
||||
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
|
||||
the bean name is no longer required to be `value`. Custom stereotype annotations can
|
||||
declare an attribute with a different name (such as `name`) and annotate that attribute
|
||||
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
|
||||
declaration of `ControllerAdvice#name()` for a concrete example.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
|
||||
and will be removed in a future version of the framework. Consequently, custom stereotype
|
||||
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
|
||||
in `@Component`. See the source code declaration of `Repository#value()` and
|
||||
`ControllerAdvice#name()` for concrete examples.
|
||||
====
|
||||
|
||||
If an explicit bean name cannot be derived from such an annotation or for any other
|
||||
detected component (such as those discovered by custom filters), the default bean name
|
||||
generator returns the uncapitalized non-qualified class name. For example, if the
|
||||
following component classes were detected, the names would be `myMovieLister` and
|
||||
`movieFinderImpl`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
public class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement either the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`] or
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following examples show.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example"
|
||||
name-generator="org.example.MyNameGenerator" />
|
||||
</beans>
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you run into naming conflicts due to multiple autodetected components having the same
|
||||
non-qualified class name (for example, classes with identical names but residing in
|
||||
different packages), you can configure a `BeanNameGenerator` that defaults to the
|
||||
fully-qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` can be used for such purposes.
|
||||
|
||||
As of Spring Framework 7.0, if you encounter naming conflicts among `@Bean` methods in
|
||||
`@Configuration` classes, you can alternatively configure a
|
||||
`ConfigurationBeanNameGenerator` that generates unique bean names for `@Bean` methods.
|
||||
The `FullyQualifiedConfigurationBeanNameGenerator` can be used to generate
|
||||
fully-qualified default bean names for `@Bean` methods without an explicit `name`
|
||||
attribute — for example, `com.example.MyConfig.myBean` for an `@Bean` method named
|
||||
`myBean()` declared in `@Configuration` class `com.example.MyConfig`.
|
||||
|
||||
The `FullyQualifiedAnnotationBeanNameGenerator` and
|
||||
`FullyQualifiedConfigurationBeanNameGenerator` both reside in the
|
||||
`org.springframework.context.annotation` package.
|
||||
====
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
|
||||
[[beans-scanning-scope-resolver]]
|
||||
=== Providing a Scope for Autodetected Components
|
||||
|
||||
As with Spring-managed components in general, the default and most common scope for
|
||||
autodetected components is `singleton`. However, sometimes you need a different scope
|
||||
that can be specified by the `@Scope` annotation. You can provide the name of the
|
||||
scope within the annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Scope` annotations are only introspected on the concrete bean class (for annotated
|
||||
components) or the factory method (for `@Bean` methods). In contrast to XML bean
|
||||
definitions, there is no notion of bean definition inheritance, and inheritance
|
||||
hierarchies at the class level are irrelevant for metadata purposes.
|
||||
|
||||
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes].
|
||||
As with the pre-built annotations for those scopes, you may also compose your own scoping
|
||||
annotations by using Spring's meta-annotation approach: for example, a custom annotation
|
||||
meta-annotated with `@Scope("prototype")`, possibly also declaring a custom scoped-proxy mode.
|
||||
|
||||
NOTE: To provide a custom strategy for scope resolution rather than relying on the
|
||||
annotation-based approach, you can implement the
|
||||
{spring-framework-api}/context/annotation/ScopeMetadataResolver.html[`ScopeMetadataResolver`]
|
||||
interface. Be sure to include a default no-arg constructor. Then you can provide the
|
||||
fully qualified class name when configuring the scanner, as the following example of both
|
||||
an annotation and a bean definition shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
When using certain non-singleton scopes, it may be necessary to generate proxies for the
|
||||
scoped objects. The reasoning is described in
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
For this purpose, a scoped-proxy attribute is available on the component-scan
|
||||
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
|
||||
the following configuration results in standard JDK dynamic proxies:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-qualifiers]]
|
||||
=== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
metadata was provided on the candidate bean definitions by using the `qualifier` or `meta`
|
||||
child elements of the `bean` element in the XML. When relying upon classpath scanning for
|
||||
auto-detection of components, you can provide the qualifier metadata with type-level
|
||||
annotations on the candidate class. The following three examples demonstrate this
|
||||
technique:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
class ActionMovieCatalog : MovieCatalog
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
class ActionMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
public class CachingMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
class CachingMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As with most annotation-based alternatives, keep in mind that the annotation metadata is
|
||||
bound to the class definition itself, while the use of XML allows for multiple beans
|
||||
of the same type to provide variations in their qualifier metadata, because that
|
||||
metadata is provided per-instance rather than per-class.
|
||||
|
||||
|
||||
[[beans-factorybeans-annotations]]
|
||||
== Defining Bean Metadata within Components
|
||||
|
||||
@@ -1059,10 +646,10 @@ methods anywhere. However, regular `@Bean` methods in `@Configuration` classes n
|
||||
to be overridable -- that is, they must not be declared as `private` or `final`.
|
||||
|
||||
`@Bean` methods are also discovered on base classes of a given component or
|
||||
configuration class, as well as on Java default methods declared in interfaces
|
||||
configuration class, as well as on Java 8 default methods declared in interfaces
|
||||
implemented by the component or configuration class. This allows for a lot of
|
||||
flexibility in composing complex configuration arrangements, with even multiple
|
||||
inheritance being possible through Java default methods.
|
||||
inheritance being possible through Java 8 default methods as of Spring 4.2.
|
||||
|
||||
Finally, a single class may hold multiple `@Bean` methods for the same
|
||||
bean, as an arrangement of multiple factory methods to use depending on available
|
||||
@@ -1071,3 +658,352 @@ constructor or factory method in other configuration scenarios: The variant with
|
||||
the largest number of satisfiable dependencies is picked at construction time,
|
||||
analogous to how the container selects between multiple `@Autowired` constructors.
|
||||
====
|
||||
|
||||
|
||||
[[beans-scanning-name-generator]]
|
||||
== Naming Autodetected Components
|
||||
|
||||
When a component is autodetected as part of the scanning process, its bean name is
|
||||
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],
|
||||
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
|
||||
following JSR-250 and JSR-330 annotations are used instead of Spring stereotype
|
||||
annotations: `@jakarta.annotation.ManagedBean`, `@javax.annotation.ManagedBean`,
|
||||
`@jakarta.inject.Named`, and `@javax.inject.Named`.
|
||||
|
||||
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
|
||||
the bean name is no longer required to be `value`. Custom stereotype annotations can
|
||||
declare an attribute with a different name (such as `name`) and annotate that attribute
|
||||
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
|
||||
declaration of `ControllerAdvice#name()` for a concrete example.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
|
||||
and will be removed in a future version of the framework. Consequently, custom stereotype
|
||||
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
|
||||
in `@Component`. See the source code declaration of `Repository#value()` and
|
||||
`ControllerAdvice#name()` for concrete examples.
|
||||
====
|
||||
|
||||
If an explicit bean name cannot be derived from such an annotation or for any other
|
||||
detected component (such as those discovered by custom filters), the default bean name
|
||||
generator returns the uncapitalized non-qualified class name. For example, if the
|
||||
following component classes were detected, the names would be `myMovieLister` and
|
||||
`movieFinderImpl`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
public class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following example annotation
|
||||
and bean definition show.
|
||||
|
||||
TIP: If you run into naming conflicts due to multiple autodetected components having the
|
||||
same non-qualified class name (i.e., classes with identical names but residing in
|
||||
different packages), you may need to configure a `BeanNameGenerator` that defaults to the
|
||||
fully qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` located in package
|
||||
`org.springframework.context.annotation` can be used for such purposes.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example"
|
||||
name-generator="org.example.MyNameGenerator" />
|
||||
</beans>
|
||||
----
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
|
||||
[[beans-scanning-scope-resolver]]
|
||||
== Providing a Scope for Autodetected Components
|
||||
|
||||
As with Spring-managed components in general, the default and most common scope for
|
||||
autodetected components is `singleton`. However, sometimes you need a different scope
|
||||
that can be specified by the `@Scope` annotation. You can provide the name of the
|
||||
scope within the annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Scope` annotations are only introspected on the concrete bean class (for annotated
|
||||
components) or the factory method (for `@Bean` methods). In contrast to XML bean
|
||||
definitions, there is no notion of bean definition inheritance, and inheritance
|
||||
hierarchies at the class level are irrelevant for metadata purposes.
|
||||
|
||||
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes].
|
||||
As with the pre-built annotations for those scopes, you may also compose your own scoping
|
||||
annotations by using Spring's meta-annotation approach: for example, a custom annotation
|
||||
meta-annotated with `@Scope("prototype")`, possibly also declaring a custom scoped-proxy mode.
|
||||
|
||||
NOTE: To provide a custom strategy for scope resolution rather than relying on the
|
||||
annotation-based approach, you can implement the
|
||||
{spring-framework-api}/context/annotation/ScopeMetadataResolver.html[`ScopeMetadataResolver`]
|
||||
interface. Be sure to include a default no-arg constructor. Then you can provide the
|
||||
fully qualified class name when configuring the scanner, as the following example of both
|
||||
an annotation and a bean definition shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
When using certain non-singleton scopes, it may be necessary to generate proxies for the
|
||||
scoped objects. The reasoning is described in
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
For this purpose, a scoped-proxy attribute is available on the component-scan
|
||||
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
|
||||
the following configuration results in standard JDK dynamic proxies:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-qualifiers]]
|
||||
== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
metadata was provided on the candidate bean definitions by using the `qualifier` or `meta`
|
||||
child elements of the `bean` element in the XML. When relying upon classpath scanning for
|
||||
auto-detection of components, you can provide the qualifier metadata with type-level
|
||||
annotations on the candidate class. The following three examples demonstrate this
|
||||
technique:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
class ActionMovieCatalog : MovieCatalog
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
class ActionMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
public class CachingMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
class CachingMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As with most annotation-based alternatives, keep in mind that the annotation metadata is
|
||||
bound to the class definition itself, while the use of XML allows for multiple beans
|
||||
of the same type to provide variations in their qualifier metadata, because that
|
||||
metadata is provided per-instance rather than per-class.
|
||||
|
||||
@@ -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 <<context-functionality-events-annotation,annotation-based model>> as well as the
|
||||
an xref:core/beans/context-introduction.adoc#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.
|
||||
@@ -575,7 +575,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honor that and will not register the method to listen to events.
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
|
||||
|
||||
The method signature once again declares the event type to which it listens,
|
||||
but, this time, with a flexible name and without implementing a specific listener interface.
|
||||
@@ -698,7 +698,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This feature is not supported for
|
||||
<<context-functionality-events-async,asynchronous listeners>>.
|
||||
xref:core/beans/context-introduction.adoc#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
|
||||
@@ -933,12 +933,13 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
try (StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
}
|
||||
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
// end the current step
|
||||
scanPackages.end();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -946,12 +947,13 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
try (val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
}
|
||||
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages)
|
||||
// end the current step
|
||||
scanPackages.end()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ The following table describes these properties:
|
||||
| Property| Explained in...
|
||||
|
||||
| Class
|
||||
| <<beans-factory-class,Instantiating Beans>>
|
||||
| xref:core/beans/definition.adoc#beans-factory-class[Instantiating Beans]
|
||||
|
||||
| Name
|
||||
| <<beans-beanname,Naming Beans>>
|
||||
| xref:core/beans/definition.adoc#beans-beanname[Naming Beans]
|
||||
|
||||
| Scope
|
||||
| xref:core/beans/factory-scopes.adoc[Bean Scopes]
|
||||
@@ -99,7 +99,7 @@ the `@Bean` factory method in favor of any pre-declared constructor on the bean
|
||||
****
|
||||
|
||||
NOTE: We acknowledge that overriding beans in test scenarios is convenient, and there is
|
||||
explicit support for this. Please refer to
|
||||
explicit support for this as of Spring Framework 6.2. Please refer to
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc[this section] for more details.
|
||||
|
||||
|
||||
@@ -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
|
||||
<<beans-factory-class-instance-factory-method,Instantiation by Using an Instance Factory Method>>
|
||||
xref:core/beans/definition.adoc#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 <<beans-factory-class-static-factory-method,static factory method>>
|
||||
Similar to instantiation through a xref:core/beans/definition.adoc#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
|
||||
<<beans-factory-class-instance-factory-method,instance>> or
|
||||
<<beans-factory-class-static-factory-method,static>> factory method. By contrast,
|
||||
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,
|
||||
`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
|
||||
<<beans-factory-autowire-candidate,next section>>.
|
||||
xref:core/beans/dependencies/factory-autowire.adoc#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:
|
||||
<<beans-constructor-injection,Constructor-based dependency injection>>
|
||||
and <<beans-setter-injection,Setter-based dependency injection>>.
|
||||
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]]
|
||||
|
||||
+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 <<beans-factory-method-injection,the preceding section>>. The Spring Framework
|
||||
in xref:core/beans/dependencies/factory-method-injection.adoc[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.
|
||||
|
||||
|
||||
+23
-18
@@ -27,7 +27,7 @@ The following example shows various values being set:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example uses the <<beans-p-namespace,p-namespace>> for even more succinct
|
||||
The following example uses the xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-p-namespace[p-namespace] for even more succinct
|
||||
XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -50,8 +50,9 @@ XML configuration:
|
||||
|
||||
The preceding XML is more succinct. However, typos are discovered at runtime rather than
|
||||
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
|
||||
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
|
||||
completion when you create bean definitions. Such IDE assistance is highly recommended.
|
||||
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
|
||||
that supports automatic property completion when you create bean definitions. Such IDE
|
||||
assistance is highly recommended.
|
||||
|
||||
You can also configure a `java.util.Properties` instance, as follows:
|
||||
|
||||
@@ -84,11 +85,11 @@ element. The following example shows how to use it:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="collaborator" class="..." />
|
||||
<bean id="theTargetBean" class="..."/>
|
||||
|
||||
<bean id="client" class="...">
|
||||
<bean id="theClientBean" class="...">
|
||||
<property name="targetName">
|
||||
<idref bean="collaborator" />
|
||||
<idref bean="theTargetBean"/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
@@ -98,24 +99,28 @@ following snippet:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="collaborator" class="..." />
|
||||
<bean id="theTargetBean" class="..." />
|
||||
|
||||
<bean id="client" class="...">
|
||||
<property name="targetName" value="collaborator" />
|
||||
<bean id="theClientBean" class="...">
|
||||
<property name="targetName" ref="theTargetBean"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The first form is preferable to the second, because using the `idref` tag lets the
|
||||
container validate at deployment time that the referenced, named bean actually exists. In
|
||||
the second variation, no validation is performed on the value that is passed to the
|
||||
`targetName` property of the `client` bean. Typos are therefore only discovered (with most
|
||||
container validate at deployment time that the referenced, named bean actually
|
||||
exists. In the second variation, no validation is performed on the value that is passed
|
||||
to the `targetName` property of the `client` bean. Typos are only discovered (with most
|
||||
likely fatal results) when the `client` bean is actually instantiated. If the `client`
|
||||
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting
|
||||
exception may only be discovered long after the container is deployed.
|
||||
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting exception
|
||||
may only be discovered long after the container is deployed.
|
||||
|
||||
NOTE: A common place (at least in versions earlier than Spring 2.0) where the `<idref/>`
|
||||
element brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors]
|
||||
in a `ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
|
||||
NOTE: The `local` attribute on the `idref` element is no longer supported in the 4.0 beans
|
||||
XSD, since it does not provide value over a regular `bean` reference any more. Change
|
||||
your existing `idref local` references to `idref bean` when upgrading to the 4.0 schema.
|
||||
|
||||
A common place (at least in versions earlier than Spring 2.0) where the `<idref/>` element
|
||||
brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors] in a
|
||||
`ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
|
||||
interceptor names prevents you from misspelling an interceptor ID.
|
||||
|
||||
|
||||
@@ -539,7 +544,7 @@ three approaches at the same time.
|
||||
== XML Shortcut with the c-namespace
|
||||
|
||||
Similar to the
|
||||
<<beans-p-namespace,XML Shortcut with the p-namespace>>,
|
||||
xref:core/beans/dependencies/factory-properties-detailed.adoc#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: <<beans-definition-profiles,profiles>>
|
||||
and <<beans-property-source-abstraction,properties>>.
|
||||
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].
|
||||
|
||||
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 <<beans-property-source-abstraction,`PropertySource` Abstraction>>). In integration tests, active
|
||||
entry in JNDI (see xref:core/beans/environment.adoc#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 <<beans-definition-profiles-enable,no profile is active>>, the `dataSource` is
|
||||
If xref:#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
|
||||
<<beans-factory-programmatically-registering-beanpostprocessors,programmatic registration of `BeanPostProcessor` instances>>.
|
||||
xref:core/beans/factory-extension.adoc#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
|
||||
<<beans-factory-extension-factory-postprocessors,Customizing Configuration Metadata with a `BeanFactoryPostProcessor`>>.
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-extension-factory-postprocessors[Customizing Configuration Metadata with a `BeanFactoryPostProcessor`].
|
||||
====
|
||||
|
||||
The `org.springframework.beans.factory.config.BeanPostProcessor` interface consists of
|
||||
@@ -67,13 +67,6 @@ interface, clearly indicating the post-processor nature of that bean. Otherwise,
|
||||
Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the
|
||||
initialization of other beans in the context, this early type detection is critical.
|
||||
|
||||
Furthermore, when registering a `BeanPostProcessor` via an `@Bean` factory method,
|
||||
declare the method as `static` and ideally with no dependencies. Doing so avoids eager
|
||||
initialization of the configuration class and other beans, which would make them
|
||||
ineligible for full post-processing (such as auto-proxying). See the
|
||||
"BeanPostProcessor-returning `@Bean` methods" section in the
|
||||
{spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc for details.
|
||||
|
||||
[[beans-factory-programmatically-registering-beanpostprocessors]]
|
||||
.Programmatically registering `BeanPostProcessor` instances
|
||||
NOTE: While the recommended approach for `BeanPostProcessor` registration is through
|
||||
@@ -87,7 +80,7 @@ of execution. Note also that `BeanPostProcessor` instances registered programmat
|
||||
are always processed before those registered through auto-detection, regardless of any
|
||||
explicit ordering.
|
||||
|
||||
.`BeanPostProcessor` instances and early initialization
|
||||
.`BeanPostProcessor` instances and AOP auto-proxying
|
||||
[NOTE]
|
||||
====
|
||||
Classes that implement the `BeanPostProcessor` interface are special and are treated
|
||||
@@ -97,23 +90,17 @@ of the `ApplicationContext`. Next, all `BeanPostProcessor` instances are registe
|
||||
in a sorted fashion and applied to all further beans in the container. Because AOP
|
||||
auto-proxying is implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessor`
|
||||
instances nor the beans they directly reference are eligible for auto-proxying and,
|
||||
thus, do not have aspects woven into them. More generally, any bean that is instantiated
|
||||
during this early phase is not eligible for full post-processing by all
|
||||
`BeanPostProcessor` instances.
|
||||
thus, do not have aspects woven into them.
|
||||
|
||||
For any such bean, you should see a WARN-level log message similar to the following.
|
||||
For any such bean, you should see an informational log message: `Bean someBean is not
|
||||
eligible for getting processed by all BeanPostProcessor interfaces (for example: not
|
||||
eligible for auto-proxying)`.
|
||||
|
||||
[quote]
|
||||
Bean 'someBean' of type [org.example.SomeType] is not eligible for getting processed by
|
||||
all BeanPostProcessors (for example: not eligible for auto-proxying).
|
||||
|
||||
To minimize the number of beans affected, register a `BeanPostProcessor` with a
|
||||
`static` `@Bean` method that has no dependencies (see the note above). If you have
|
||||
beans wired into your `BeanPostProcessor` by using autowiring or `@Resource` (which
|
||||
may fall back to autowiring), Spring might access unexpected beans when searching
|
||||
for type-matching dependency candidates and, therefore, make them ineligible for
|
||||
auto-proxying or other kinds of bean post-processing. For example, if you have a
|
||||
dependency annotated with `@Resource` where the field or setter name does not
|
||||
If you have beans wired into your `BeanPostProcessor` by using autowiring or
|
||||
`@Resource` (which may fall back to autowiring), Spring might access unexpected beans
|
||||
when searching for type-matching dependency candidates and, therefore, make them
|
||||
ineligible for auto-proxying or other kinds of bean post-processing. For example, if you
|
||||
have a dependency annotated with `@Resource` where the field or setter name does not
|
||||
directly correspond to the declared name of a bean and no name attribute is used,
|
||||
Spring accesses other beans for matching them by type.
|
||||
====
|
||||
@@ -148,7 +135,7 @@ Java::
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean);
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -177,48 +164,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can register the `InstantiationTracingBeanPostProcessor` with Java configuration
|
||||
by using a `static` `@Bean` method (recommended to avoid eager initialization of the
|
||||
configuration class and other beans):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public static InstantiationTracingBeanPostProcessor instantiationTracingBeanPostProcessor() {
|
||||
return new InstantiationTracingBeanPostProcessor();
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun instantiationTracingBeanPostProcessor() = InstantiationTracingBeanPostProcessor()
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via the
|
||||
`bean` element with XML configuration:
|
||||
The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -246,9 +192,10 @@ Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via
|
||||
----
|
||||
|
||||
Notice how the `InstantiationTracingBeanPostProcessor` is merely defined. It does not
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as with any
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as you would any
|
||||
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
|
||||
script.)
|
||||
script. The Spring dynamic language support is detailed in the chapter entitled
|
||||
xref:languages/dynamic.adoc[Dynamic Language Support].)
|
||||
|
||||
The following Java application runs the preceding code and configuration:
|
||||
|
||||
@@ -329,7 +276,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
|
||||
<<beans-factory-extension-bpp,Customizing Beans by Using a `BeanPostProcessor`>>).
|
||||
xref:core/beans/factory-extension.adoc#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
|
||||
@@ -354,23 +301,6 @@ implement the `BeanFactoryPostProcessor` interface. It uses these beans as bean
|
||||
post-processors, at the appropriate time. You can deploy these post-processor beans as
|
||||
you would any other bean.
|
||||
|
||||
When registering a `BeanFactoryPostProcessor` via an `@Bean` factory method in a
|
||||
`@Configuration` class, declare the method as `static` to avoid lifecycle conflicts
|
||||
with annotation processing (such as `@Autowired`, `@Value`, and `@PostConstruct`) in the
|
||||
configuration class. See the "BeanFactoryPostProcessor-returning `@Bean` methods"
|
||||
section in the {spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc
|
||||
for details and an example.
|
||||
|
||||
For any non-static `@Bean` factory method with a `BeanFactoryPostProcessor` return type,
|
||||
you should see an INFO-level log message similar to the following.
|
||||
|
||||
[quote]
|
||||
@Bean method MyConfig.myBfpp is non-static and returns an object assignable to Spring's
|
||||
BeanFactoryPostProcessor interface. This will result in a failure to process annotations
|
||||
such as @Autowired, @Resource, and @PostConstruct within the method's declaring
|
||||
@Configuration class. Add the 'static' modifier to this method to avoid these container
|
||||
lifecycle issues; see @Bean javadoc for complete details.
|
||||
|
||||
NOTE: As with ``BeanPostProcessor``s , you typically do not want to configure
|
||||
``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a
|
||||
`Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all.
|
||||
|
||||
@@ -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:
|
||||
|
||||
* <<beans-factory-lifecycle,Lifecycle Callbacks>>
|
||||
* <<beans-factory-aware,`ApplicationContextAware` and `BeanNameAware`>>
|
||||
* <<aware-list,Other `Aware` Interfaces>>
|
||||
* 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]]
|
||||
@@ -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
|
||||
<<beans-factory-lifecycle-default-init-destroy-methods,Default Initialization and Destroy Methods>>).
|
||||
xref:core/beans/factory-nature.adoc#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 <<beans-factory-lifecycle,described previously>>).
|
||||
with the standard lifecycle callback contract xref:core/beans/factory-nature.adoc#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 <<beans-factory-lifecycle-initializingbean,`InitializingBean`>> and
|
||||
<<beans-factory-lifecycle-disposablebean,`DisposableBean`>> callback interfaces
|
||||
* 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
|
||||
* 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
|
||||
<<beans-factory-lifecycle-default-init-destroy-methods,preceding section>>.
|
||||
xref:core/beans/factory-nature.adoc#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 <<beans-factory-aware,earlier>>),
|
||||
Besides `ApplicationContextAware` and `BeanNameAware` (discussed xref:core/beans/factory-nature.adoc#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`.
|
||||
| <<beans-factory-aware,`ApplicationContextAware` and `BeanNameAware`>>
|
||||
| xref:core/beans/factory-nature.adoc#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.
|
||||
| <<beans-factory-aware,`ApplicationContextAware` and `BeanNameAware`>>
|
||||
| xref:core/beans/factory-nature.adoc#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
|
||||
<<beans-factory-scopes-custom,a custom scope.>>
|
||||
xref:core/beans/factory-scopes.adoc#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
|
||||
|
||||
| <<beans-factory-scopes-singleton,singleton>>
|
||||
| xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton]
|
||||
| (Default) Scopes a single bean definition to a single object instance for each Spring IoC
|
||||
container.
|
||||
|
||||
| <<beans-factory-scopes-prototype,prototype>>
|
||||
| xref:core/beans/factory-scopes.adoc#beans-factory-scopes-prototype[prototype]
|
||||
| Scopes a single bean definition to any number of object instances.
|
||||
|
||||
| <<beans-factory-scopes-request,request>>
|
||||
| xref:core/beans/factory-scopes.adoc#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`.
|
||||
|
||||
| <<beans-factory-scopes-session,session>>
|
||||
| xref:core/beans/factory-scopes.adoc#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`.
|
||||
|
||||
| <<beans-factory-scopes-application,application>>
|
||||
| xref:core/beans/factory-scopes.adoc#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
|
||||
<<beans-factory-scopes-custom-using,Using a Custom Scope>>.
|
||||
xref:core/beans/factory-scopes.adoc#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
|
||||
<<beans-factory-scopes-other-injection-proxies,Choosing the Type of Proxy to Create>>
|
||||
xref:core/beans/factory-scopes.adoc#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
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
`@Bean` is a method-level annotation and a direct analog of the XML `<bean/>` element.
|
||||
The annotation supports some of the attributes offered by `<bean/>`, such as:
|
||||
|
||||
* xref:core/beans/definition.adoc#beans-beanname[name]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-initializingbean[init-method]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-disposablebean[destroy-method]
|
||||
* xref:core/beans/dependencies/factory-autowire.adoc[autowiring]
|
||||
* `name`.
|
||||
|
||||
You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
`@Component`-annotated class.
|
||||
@@ -17,10 +17,9 @@ You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
== Declaring a Bean
|
||||
|
||||
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 <<beans-java-customizing-bean-naming,bean name generator>> is
|
||||
configured). The following example shows a `@Bean` method declaration:
|
||||
method to register a bean definition within an `ApplicationContext` of the type
|
||||
specified as the method's return value. By default, the bean name is the same as
|
||||
the method name. The following example shows a `@Bean` method declaration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -127,7 +126,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
However, this limits the visibility for advanced type prediction to the specified
|
||||
However, this limits the visibility for advance type prediction to the specified
|
||||
interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`)
|
||||
known to the container only once the affected singleton bean has been instantiated.
|
||||
Non-lazy singleton beans get instantiated according to their declaration order,
|
||||
@@ -474,15 +473,8 @@ Kotlin::
|
||||
== Customizing Bean Naming
|
||||
|
||||
By default, configuration classes use a `@Bean` method's name as the name of the
|
||||
resulting bean. However, as of Spring Framework 7.0, you can change this default strategy
|
||||
by configuring a custom
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
when bootstrapping the context or configuring component scanning. For example,
|
||||
{spring-framework-api}/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.html[`FullyQualifiedConfigurationBeanNameGenerator`]
|
||||
can be used to generate fully-qualified default bean names for `@Bean` methods without an
|
||||
explicit `name` attribute. For individual `@Bean` methods, the default or
|
||||
generator-derived name can be overridden with the `name` attribute, as the following
|
||||
example shows:
|
||||
resulting bean. This functionality can be overridden, however, with the `name` attribute,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -513,7 +505,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Bean("myThing")` is equivalent to `@Bean(name = "myThing")`.
|
||||
|
||||
[[beans-java-bean-aliasing]]
|
||||
== Bean Aliasing
|
||||
|
||||
+8
-20
@@ -327,8 +327,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
TIP: Note that there is no need to specify `@Autowired` if the target bean defines
|
||||
only one constructor.
|
||||
TIP: Constructor injection in `@Configuration` classes is only supported as of Spring
|
||||
Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
|
||||
bean defines only one constructor.
|
||||
|
||||
[discrete]
|
||||
[[beans-java-injecting-imported-beans-fq]]
|
||||
@@ -338,11 +339,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
|
||||
modularity, but determining exactly where the autowired bean definitions are declared is
|
||||
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
|
||||
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
|
||||
explicit in the code, and this may be just fine. Note that the
|
||||
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
|
||||
showing how everything is wired, which may be all you need. Also, your Java IDE can
|
||||
easily find all declarations and uses of the `AccountRepository` type and quickly show
|
||||
you the location of `@Bean` methods that return that type.
|
||||
explicit in the code, and this may be just fine. Remember that the
|
||||
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
|
||||
can render graphs showing how everything is wired, which may be all you need. Also,
|
||||
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
|
||||
and quickly show you the location of `@Bean` methods that return that type.
|
||||
|
||||
In cases where this ambiguity is not acceptable and you wish to have direct navigation
|
||||
from within your IDE from one `@Configuration` class to another, consider autowiring the
|
||||
@@ -610,19 +611,6 @@ Kotlin::
|
||||
See the {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`]
|
||||
javadoc for more detail.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `@Conditional` annotation declared on an enclosing `@Configuration` class is only
|
||||
applied to the registration of a nested `@Configuration` class if the nested class is
|
||||
reached through the parser's recursion from its enclosing class, or via `@Import`. If a
|
||||
nested class is discovered independently of its enclosing class — for example, via
|
||||
`@ComponentScan` or by directly registering it against the application context — it is
|
||||
processed using only its own `@Conditional` annotations. Thus, if you wish to ensure that
|
||||
the same `@Conditional` annotations apply in such scenarios, you must redeclare the
|
||||
relevant annotations on the nested class, or extract them into a composed annotation
|
||||
which you apply to both the enclosing class and the nested class.
|
||||
====
|
||||
|
||||
|
||||
[[beans-java-combining]]
|
||||
== Combining Java and XML Configuration
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
[[beans-java-programmatic-registration]]
|
||||
= Programmatic Bean Registration
|
||||
|
||||
As of Spring Framework 7, a first-class support for programmatic bean registration is
|
||||
provided via the {spring-framework-api}/beans/factory/BeanRegistrar.html[`BeanRegistrar`]
|
||||
interface that can be implemented to register beans programmatically in a flexible and
|
||||
efficient way.
|
||||
|
||||
Those bean registrar implementations are typically imported with an `@Import` annotation
|
||||
on `@Configuration` classes.
|
||||
|
||||
include-code::./MyConfiguration[tag=snippet,indent=0]
|
||||
|
||||
NOTE: You can leverage type-level conditional annotations ({spring-framework-api}/context/annotation/Conditional.html[`@Conditional`],
|
||||
but also other variants) to conditionally import the related bean registrars.
|
||||
|
||||
The bean registrar implementation uses {spring-framework-api}/beans/factory/BeanRegistry.html[`BeanRegistry`] and
|
||||
{spring-framework-api}/core/env/Environment.html[`Environment`] APIs to register beans programmatically in a concise
|
||||
and flexible way. For example, it allows custom registration through an `if` expression, a
|
||||
`for` loop, etc.
|
||||
|
||||
include-code::./MyBeanRegistrar[tag=snippet,indent=0]
|
||||
|
||||
NOTE: Bean registrars are supported with xref:core/aot.adoc[Ahead of Time Optimizations],
|
||||
either on the JVM or with GraalVM native images, including when instance suppliers are used.
|
||||
@@ -1,17 +1,16 @@
|
||||
[[beans-standard-annotations]]
|
||||
= Using JSR-330 Standard Annotations
|
||||
= Using JSR 330 Standard Annotations
|
||||
|
||||
Spring offers support for JSR-330 standard _Dependency Injection_ annotations which are
|
||||
available in the `jakarta.inject` package. These annotations may optionally be used as
|
||||
alternatives to Spring annotations.
|
||||
|
||||
To use them, you need to have the relevant jar in your classpath. For example, the
|
||||
`jakarta.inject` artifact is available in the standard Maven repository
|
||||
(`https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/`),
|
||||
Spring offers support for JSR-330 standard annotations (Dependency Injection). Those
|
||||
annotations are scanned in the same way as the Spring annotations. To use them, you need
|
||||
to have the relevant jars in your classpath.
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
If you use Maven, you can add the following dependency to your `pom.xml` file.
|
||||
If you use Maven, the `jakarta.inject` artifact is available in the standard Maven
|
||||
repository (
|
||||
https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/[https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/]).
|
||||
You can add the following dependency to your file pom.xml:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -27,14 +26,13 @@ If you use Maven, you can add the following dependency to your `pom.xml` file.
|
||||
[[beans-inject-named]]
|
||||
== Dependency Injection with `@Inject` and `@Named`
|
||||
|
||||
Instead of using `@Autowired` for dependency injection, you may optionally choose to use
|
||||
`@jakarta.inject.Inject` as follows.
|
||||
Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@@ -56,7 +54,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
@@ -74,19 +72,17 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level, and
|
||||
constructor-argument level.
|
||||
|
||||
Furthermore, as an alternative to Spring's `ObjectProvider` mechanism, you may choose to
|
||||
declare your injection point as a `jakarta.inject.Provider`, allowing for on-demand
|
||||
access to beans of shorter scopes or lazy access to other beans through a
|
||||
`Provider.get()` call. The following example offers a variant of the preceding example.
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level
|
||||
and constructor-argument level. Furthermore, you may declare your injection point as a
|
||||
`Provider`, allowing for on-demand access to beans of shorter scopes or lazy access to
|
||||
other beans through a `Provider.get()` call. The following example offers a variant of the
|
||||
preceding example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
@@ -109,10 +105,9 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Provider
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -128,15 +123,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
If you would like to use a qualified name for the dependency that should be injected, you
|
||||
may choose to use the `@Named` annotation as an alternative to Spring's `@Qualifier`
|
||||
support, as the following example shows.
|
||||
If you would like to use a qualified name for the dependency that should be injected,
|
||||
you should use the `@Named` annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -156,7 +150,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -176,15 +170,12 @@ Kotlin::
|
||||
======
|
||||
|
||||
As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have a
|
||||
`required` attribute. The following examples show how to use `@Inject` with `Optional`,
|
||||
`@Nullable`, and Kotlin's built-in support for nullable types.
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have
|
||||
a `required` attribute. The following pair of examples show how to use `@Inject` and
|
||||
`@Nullable`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -198,11 +189,8 @@ As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -214,10 +202,8 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -228,21 +214,21 @@ Kotlin::
|
||||
|
||||
|
||||
[[beans-named]]
|
||||
== `@Named`: Standard Equivalent to the `@Component` Annotation
|
||||
== `@Named` and `@ManagedBean`: Standard Equivalents to the `@Component` Annotation
|
||||
|
||||
Instead of `@Component` or other Spring stereotype annotations, you may optionally choose
|
||||
to use `@jakarta.inject.Named`, as the following example shows.
|
||||
Instead of `@Component`, you can use `@jakarta.inject.Named` or `jakarta.annotation.ManagedBean`,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Named("movieListener")
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
@@ -258,12 +244,12 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
|
||||
@Named("movieListener")
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -274,15 +260,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
It is very common to use `@Component` or other Spring stereotype annotations without
|
||||
specifying an explicit name for the component, and `@Named` can be used in a similar
|
||||
fashion, as the following example shows.
|
||||
It is very common to use `@Component` without specifying a name for the component.
|
||||
`@Named` can be used in a similar fashion, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -303,7 +288,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -319,14 +304,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
When you use `@Named`, you can use component scanning in the exact same way as when you
|
||||
use Spring annotations, as the following example shows.
|
||||
When you use `@Named` or `@ManagedBean`, you can use component scanning in the
|
||||
exact same way as when you use Spring annotations, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example")
|
||||
@@ -337,7 +322,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"])
|
||||
@@ -347,106 +332,55 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` annotation is not composable. You
|
||||
should use Spring's stereotype model for building custom component annotations.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you work with legacy systems that still use `@javax.inject.Named` or
|
||||
`@javax.annotation.ManagedBean` for components (note the `javax` package namespace), you
|
||||
can explicitly configure component scanning to include those annotation types, as shown
|
||||
in the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = "org.example",
|
||||
includeFilters = @Filter({
|
||||
javax.inject.Named.class,
|
||||
javax.annotation.ManagedBean.class
|
||||
})
|
||||
)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = ["org.example"],
|
||||
includeFilters = [Filter([
|
||||
javax.inject.Named::class,
|
||||
javax.annotation.ManagedBean::class
|
||||
])]
|
||||
)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In addition, if you would like for the `value` attributes in `@javax.inject.Named` and
|
||||
`@javax.annotation.ManagedBean` to be used as component names, you need to override the
|
||||
`isStereotypeWithNameValue(...)` method in `AnnotationBeanNameGenerator` to add explicit
|
||||
support for `javax.annotation.ManagedBean` and `javax.inject.Named` and register your
|
||||
custom `AnnotationBeanNameGenerator` via the `nameGenerator` attribute in
|
||||
`@ComponentScan`.
|
||||
====
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `@ManagedBean`
|
||||
annotations are not composable. You should use Spring's stereotype model for building
|
||||
custom component annotations.
|
||||
|
||||
|
||||
[[beans-standard-annotations-limitations]]
|
||||
== Limitations of JSR-330 Standard Annotations
|
||||
|
||||
When you work with JSR-330 standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows.
|
||||
When you work with standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows:
|
||||
|
||||
[[annotations-comparison]]
|
||||
.Spring component model versus JSR-330 variants
|
||||
.Spring component model elements versus JSR-330 variants
|
||||
|===
|
||||
| Spring | JSR-330 | JSR-330 restrictions / comments
|
||||
| Spring| jakarta.inject.*| jakarta.inject restrictions / comments
|
||||
|
||||
| `@Autowired`
|
||||
| `@Inject`
|
||||
| `@Inject` has no `required` attribute. Can be used with Java's `Optional` instead.
|
||||
| @Autowired
|
||||
| @Inject
|
||||
| `@Inject` has no 'required' attribute. Can be used with Java 8's `Optional` instead.
|
||||
|
||||
| `@Component`
|
||||
| `@Named`
|
||||
| @Component
|
||||
| @Named / @ManagedBean
|
||||
| JSR-330 does not provide a composable model, only a way to identify named components.
|
||||
|
||||
| `@Scope("singleton")`
|
||||
| `@Singleton`
|
||||
| @Scope("singleton")
|
||||
| @Singleton
|
||||
| The JSR-330 default scope is like Spring's `prototype`. However, in order to keep it
|
||||
consistent with Spring's general defaults, a JSR-330 bean declared in the Spring
|
||||
container is a `singleton` by default. In order to use a scope other than `singleton`,
|
||||
you should use Spring's `@Scope` annotation. `jakarta.inject` also provides a
|
||||
`jakarta.inject.Scope` annotation; however, this one is only intended to be used
|
||||
`jakarta.inject.Scope` annotation: however, this one is only intended to be used
|
||||
for creating custom annotations.
|
||||
|
||||
| `@Qualifier`
|
||||
| `@Qualifier` / `@Named`
|
||||
| @Qualifier
|
||||
| @Qualifier / @Named
|
||||
| `jakarta.inject.Qualifier` is just a meta-annotation for building custom qualifiers.
|
||||
Concrete `String` qualifiers (like Spring's `@Qualifier` with a value) can be associated
|
||||
through `jakarta.inject.Named`.
|
||||
|
||||
| `@Value`
|
||||
| @Value
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| `@Lazy`
|
||||
| @Lazy
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| `ObjectFactory`
|
||||
| `Provider`
|
||||
| ObjectFactory
|
||||
| Provider
|
||||
| `jakarta.inject.Provider` is a direct alternative to Spring's `ObjectFactory`,
|
||||
only with a shorter `get()` method name. It can also be used in combination with
|
||||
Spring's `@Autowired` or with non-annotated constructors and setter methods.
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
|
||||
Java NIO provides `ByteBuffer` but many libraries build their own byte buffer API on top,
|
||||
especially for network operations where reusing buffers and/or using direct buffers is
|
||||
beneficial for performance. For example Netty has the `ByteBuf` hierarchy,
|
||||
Jetty uses pooled byte buffers with a callback to be released, and so on.
|
||||
beneficial for performance. For example Netty has the `ByteBuf` hierarchy, Undertow uses
|
||||
XNIO, 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:
|
||||
|
||||
* <<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.
|
||||
* 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.
|
||||
* <<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 <<databuffers-buffer-pooled,`PooledDataBuffer`>>.
|
||||
* Pooled buffers and reference counting via xref:core/databuffer-codec.adoc#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 <<databuffers-buffer-pooled,pooled>>. We'll use codecs to illustrate
|
||||
since they may be xref:core/databuffer-codec.adoc#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.
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
|
||||
Language] but offers additional features, most notably method invocation and basic string
|
||||
templating functionality.
|
||||
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and
|
||||
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
|
||||
Spring community with a single well supported expression language that can be used across
|
||||
all the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements for
|
||||
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
|
||||
is based on a technology-agnostic API that lets other expression language implementations
|
||||
be integrated, should the need arise.
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
|
||||
EL, to name a few -- the Spring Expression Language was created to provide the Spring
|
||||
community with a single well supported expression language that can be used across all
|
||||
the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements
|
||||
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
|
||||
That said, SpEL is based on a technology-agnostic API that lets other expression language
|
||||
implementations be integrated, should the need arise.
|
||||
|
||||
While SpEL serves as the foundation for expression evaluation within the Spring
|
||||
portfolio, it is not directly tied to Spring and can be used independently. To
|
||||
|
||||
@@ -253,175 +253,6 @@ 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
|
||||
|
||||
@@ -488,23 +319,17 @@ 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. 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
|
||||
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
|
||||
automatically grow a `List`.
|
||||
|
||||
[tabs]
|
||||
@@ -517,10 +342,10 @@ Java::
|
||||
public List<String> list;
|
||||
}
|
||||
|
||||
SpelParserConfiguration config = SpelParserConfiguration.builder()
|
||||
.autoGrowNullReferences()
|
||||
.autoGrowCollections()
|
||||
.build();
|
||||
// Turn on:
|
||||
// - auto null reference initialization
|
||||
// - auto collection growing
|
||||
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
|
||||
|
||||
ExpressionParser parser = new SpelExpressionParser(config);
|
||||
|
||||
@@ -542,10 +367,10 @@ Kotlin::
|
||||
var list: List<String>? = null
|
||||
}
|
||||
|
||||
val config = SpelParserConfiguration.builder()
|
||||
.autoGrowNullReferences()
|
||||
.autoGrowCollections()
|
||||
.build()
|
||||
// Turn on:
|
||||
// - auto null reference initialization
|
||||
// - auto collection growing
|
||||
val config = SpelParserConfiguration(true, true)
|
||||
|
||||
val parser = SpelExpressionParser(config)
|
||||
|
||||
@@ -562,8 +387,7 @@ 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` via
|
||||
`SpelParserConfiguration.builder().maximumExpressionLength(...)` when creating the
|
||||
programmatically, you can specify a custom `maxExpressionLength` 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
|
||||
@@ -571,32 +395,6 @@ set a JVM system property or Spring property named `spring.context.expression.ma
|
||||
to the maximum expression length needed by your application (see
|
||||
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 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
|
||||
@@ -635,9 +433,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
|
||||
(<<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.
|
||||
(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.
|
||||
|
||||
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.
|
||||
@@ -679,10 +477,8 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
SpelParserConfiguration config = SpelParserConfiguration.builder()
|
||||
.compilerMode(SpelCompilerMode.IMMEDIATE)
|
||||
.compilerClassLoader(getClass().getClassLoader())
|
||||
.build();
|
||||
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
|
||||
this.getClass().getClassLoader());
|
||||
|
||||
SpelExpressionParser parser = new SpelExpressionParser(config);
|
||||
|
||||
@@ -697,10 +493,8 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val config = SpelParserConfiguration.builder()
|
||||
.compilerMode(SpelCompilerMode.IMMEDIATE)
|
||||
.compilerClassLoader(javaClass.classLoader)
|
||||
.build()
|
||||
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
|
||||
this.javaClass.classLoader)
|
||||
|
||||
val parser = SpelExpressionParser(config)
|
||||
|
||||
@@ -737,7 +531,6 @@ following kinds of expressions cannot be compiled.
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using `Optional` with the null-safe or Elvis operator
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
* Expressions using bean references
|
||||
|
||||
@@ -2,12 +2,4 @@
|
||||
= Language Reference
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Expression Language (SpEL) expressions are composed of a sequence of tokens such
|
||||
as literals, operators, method invocations, and so forth.
|
||||
|
||||
Whitespace can be used freely between tokens to format and improve the readability of
|
||||
expressions. Specifically, the `\s` (space), `\t` (tab), `\r` (carriage return), and `\n`
|
||||
(newline) characters are all valid separators between tokens. However, whitespace is
|
||||
ignored by the expression parser unless it is part of a string literal.
|
||||
|
||||
The following sections describe the features and syntax of SpEL.
|
||||
This section describes how the Spring Expression Language works.
|
||||
|
||||
+19
-33
@@ -1,19 +1,19 @@
|
||||
[[expressions-operator-elvis]]
|
||||
= The Elvis Operator
|
||||
|
||||
The Elvis operator (`?:`) is a shortening of the ternary operator syntax and is used in
|
||||
the https://www.groovy-lang.org/operators.html#_elvis_operator[Groovy] language. With the
|
||||
ternary operator syntax, you often have to repeat a variable twice, as the following Java
|
||||
example shows:
|
||||
The Elvis operator is a shortening of the ternary operator syntax and is used in the
|
||||
https://www.groovy-lang.org/operators.html#_elvis_operator[Groovy] language.
|
||||
With the ternary operator syntax, you usually have to repeat a variable twice, as the
|
||||
following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String name = "Elvis Presley";
|
||||
String displayName = (name != null ? name : "Unknown");
|
||||
----
|
||||
|
||||
Instead, you can use the Elvis operator (named for the resemblance to Elvis' hair style).
|
||||
The following example shows how to use the Elvis operator in a SpEL expression:
|
||||
The following example shows how to use the Elvis operator:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -23,7 +23,7 @@ Java::
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
String name = parser.parseExpression("name ?: 'Unknown'").getValue(new Inventor(), String.class);
|
||||
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
|
||||
System.out.println(name); // 'Unknown'
|
||||
----
|
||||
|
||||
@@ -33,29 +33,14 @@ Kotlin::
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
val name = parser.parseExpression("name ?: 'Unknown'").getValue(Inventor(), String::class.java)
|
||||
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
|
||||
println(name) // 'Unknown'
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The SpEL Elvis operator also treats an _empty_ String like a `null` object. Thus, the
|
||||
original Java example is only close to emulating the semantics of the operator: it would
|
||||
need to use `name != null && !name.isEmpty()` as the predicate to be compatible with the
|
||||
semantics of the SpEL Elvis operator.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
As of Spring Framework 7.0, the SpEL Elvis operator supports `java.util.Optional` with
|
||||
transparent unwrapping semantics.
|
||||
|
||||
For example, given the expression `A ?: B`, if `A` is `null` or an _empty_ `Optional`,
|
||||
the expression evaluates to `B`. However, if `A` is a non-empty `Optional` the expression
|
||||
evaluates to the object contained in the `Optional`, thereby effectively unwrapping the
|
||||
`Optional` which correlates to `A.get()`.
|
||||
====
|
||||
NOTE: The SpEL Elvis operator also checks for _empty_ Strings in addition to `null` objects.
|
||||
The original snippet is thus only close to emulating the semantics of the operator (it would need an
|
||||
additional `!name.isEmpty()` check).
|
||||
|
||||
The following listing shows a more complex example:
|
||||
|
||||
@@ -69,11 +54,11 @@ Java::
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
|
||||
String name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
System.out.println(name); // Nikola Tesla
|
||||
|
||||
tesla.setName("");
|
||||
name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
System.out.println(name); // Elvis Presley
|
||||
----
|
||||
|
||||
@@ -85,16 +70,16 @@ Kotlin::
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
|
||||
val tesla = Inventor("Nikola Tesla", "Serbian")
|
||||
var name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
println(name) // Nikola Tesla
|
||||
|
||||
tesla.setName("")
|
||||
name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
println(name) // Elvis Presley
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
[NOTE]
|
||||
=====
|
||||
You can use the Elvis operator to apply default values in expressions. The following
|
||||
example shows how to use the Elvis operator in a `@Value` expression:
|
||||
@@ -104,6 +89,7 @@ example shows how to use the Elvis operator in a `@Value` expression:
|
||||
@Value("#{systemProperties['pop3.port'] ?: 25}")
|
||||
----
|
||||
|
||||
This will inject the value of the system property named `pop3.port` if it is defined or
|
||||
`25` if the property is not defined.
|
||||
This will inject a system property `pop3.port` if it is defined or 25 if not.
|
||||
=====
|
||||
|
||||
|
||||
|
||||
+1
-44
@@ -252,6 +252,7 @@ Kotlin::
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to use the "null-safe select last" operator for
|
||||
collections (`?.$`).
|
||||
|
||||
@@ -350,50 +351,6 @@ Kotlin::
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
======
|
||||
|
||||
[[expressions-operator-safe-navigation-optional]]
|
||||
== Null-safe Operations on `Optional`
|
||||
|
||||
As of Spring Framework 7.0, null-safe operations are supported on instances of
|
||||
`java.util.Optional` with transparent unwrapping semantics.
|
||||
|
||||
Specifically, when a null-safe operator is applied to an _empty_ `Optional`, it will be
|
||||
treated as if the `Optional` were `null`, and the subsequent operation will evaluate to
|
||||
`null`. However, if a null-safe operator is applied to a non-empty `Optional`, the
|
||||
subsequent operation will be applied to the object contained in the `Optional`, thereby
|
||||
effectively unwrapping the `Optional`.
|
||||
|
||||
For example, if `user` is of type `Optional<User>`, the expression `user?.name` will
|
||||
evaluate to `null` if `user` is either `null` or an _empty_ `Optional` and will otherwise
|
||||
evaluate to the `name` of the `user`, effectively `user.get().getName()` or
|
||||
`user.get().name` for property or field access, respectively.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Invocations of methods defined in the `Optional` API are still supported on an _empty_
|
||||
`Optional`. For example, if `name` is of type `Optional<String>`, the expression
|
||||
`name?.orElse('Unknown')` will evaluate to `"Unknown"` if `name` is an empty `Optional`
|
||||
and will otherwise evaluate to the `String` contained in the `Optional` if `name` is a
|
||||
non-empty `Optional`, effectively `name.get()`.
|
||||
====
|
||||
|
||||
// NOTE: ⁠ is the Unicode Character 'WORD JOINER', which prevents undesired line wraps.
|
||||
|
||||
Similarly, if `names` is of type `Optional<List<String>>`, the expression
|
||||
`names?.?⁠[#this.length > 5]` will evaluate to `null` if `names` is `null` or an _empty_
|
||||
`Optional` and will otherwise evaluate to a sequence containing the names whose lengths
|
||||
are greater than 5, effectively
|
||||
`names.get().stream().filter(s -> s.length() > 5).toList()`.
|
||||
|
||||
The same semantics apply to all of the null-safe operators mentioned previously in this
|
||||
chapter.
|
||||
|
||||
For further details and examples, consult the javadoc for the following operators.
|
||||
|
||||
* {spring-framework-api}/expression/spel/ast/PropertyOrFieldReference.html[`PropertyOrFieldReference`]
|
||||
* {spring-framework-api}/expression/spel/ast/MethodReference.html[`MethodReference`]
|
||||
* {spring-framework-api}/expression/spel/ast/Indexer.html[`Indexer`]
|
||||
* {spring-framework-api}/expression/spel/ast/Selection.html[`Selection`]
|
||||
* {spring-framework-api}/expression/spel/ast/Projection.html[`Projection`]
|
||||
|
||||
[[expressions-operator-safe-navigation-compound-expressions]]
|
||||
== Null-safe Operations in Compound Expressions
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
The Spring Expression Language supports the following kinds of 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>>
|
||||
* 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]
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ Kotlin::
|
||||
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
|
||||
|
||||
// uses CustomValue:::compareTo
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java)
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java);
|
||||
----
|
||||
======
|
||||
|
||||
@@ -167,7 +167,7 @@ Kotlin::
|
||||
[CAUTION]
|
||||
====
|
||||
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>`.
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
|
||||
|
||||
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
|
||||
to `false`.
|
||||
@@ -312,13 +312,13 @@ Kotlin::
|
||||
|
||||
// evaluates to 'a'
|
||||
val ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(Char::class.java)
|
||||
.getValue(Character::class.java);
|
||||
|
||||
// -- Repeat --
|
||||
|
||||
// evaluates to "abcabc"
|
||||
val repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String::class.java)
|
||||
.getValue(String::class.java);
|
||||
----
|
||||
======
|
||||
|
||||
@@ -485,7 +485,7 @@ Kotlin::
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
----
|
||||
======
|
||||
|
||||
@@ -541,7 +541,32 @@ For example, if we want to overload the `ADD` operator to allow two lists to be
|
||||
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
|
||||
follows.
|
||||
|
||||
include-code::./ListConcatenation[]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
pubic class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@Override
|
||||
public boolean overridesOperation(Operation operation, Object left, Object right) {
|
||||
return (operation == Operation.ADD &&
|
||||
left instanceof List && right instanceof List);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object operate(Operation operation, Object left, Object right) {
|
||||
if (operation == Operation.ADD &&
|
||||
left instanceof List list1 && right instanceof List list2) {
|
||||
|
||||
List result = new ArrayList(list1);
|
||||
result.addAll(list2);
|
||||
return result;
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"No overload for operation %s and operands [%s] and [%s]"
|
||||
.formatted(operation, left, right));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If we register `ListConcatenation` as the `OperatorOverloader` in a
|
||||
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
|
||||
@@ -564,8 +589,8 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val context = StandardEvaluationContext()
|
||||
context.operatorOverloader = ListConcatenation()
|
||||
StandardEvaluationContext context = StandardEvaluationContext()
|
||||
context.setOperatorOverloader(ListConcatenation())
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ is applicable for typical implementations of indexed structures.
|
||||
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
|
||||
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
|
||||
to bytecode for read access. Note, however, that the configured read-method must be
|
||||
invocable via a `public` class or `public` interface for compilation to succeed.
|
||||
invokable via a `public` class or `public` interface for compilation to succeed.
|
||||
|
||||
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
|
||||
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
|
||||
|
||||
@@ -6,7 +6,7 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Variable names must begin with a letter (as defined below), an underscore, or a dollar
|
||||
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
|
||||
sign.
|
||||
|
||||
Variable names must be composed of one or more of the following supported types of
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user