mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f85d5bd84a |
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
!!! For Security Vulnerabilities, please go to https://spring.io/security-policy !!!
|
||||
-->
|
||||
**Affects:** \<Spring Framework version>
|
||||
|
||||
---
|
||||
<!--
|
||||
Thanks for taking the time to create an issue. Please read the following:
|
||||
|
||||
- Questions should be asked on Stack Overflow.
|
||||
- For bugs, specify affected versions and explain what you are trying to do.
|
||||
- For enhancements, provide context and describe the problem.
|
||||
|
||||
Issue or Pull Request? Create only one, not both. GitHub treats them as the same.
|
||||
If unsure, start with an issue, and if you submit a pull request later, the
|
||||
issue will be closed as superseded.
|
||||
-->
|
||||
@@ -1,6 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Community Support
|
||||
url: https://stackoverflow.com/tags/spring
|
||||
about: Please ask and answer questions on StackOverflow with the tag `spring`.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: General
|
||||
about: Bugs, enhancements, documentation, tasks.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Have you considered asking for help on stackoverflow.com?
|
||||
|
||||
** Bug Reports **
|
||||
Please submit issues against OSS supported versions, see https://spring.io/projects/spring-framework#support
|
||||
Please provide a minimal sample application that reproduces the problem for faster issue triage.
|
||||
|
||||
** Enhancements requests **
|
||||
Before explaining how you would like things to work,
|
||||
please describe a concrete use case for this feature and how you have tried to solve this so far.
|
||||
|
||||
-->
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Await HTTP Resource
|
||||
description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)'
|
||||
inputs:
|
||||
url:
|
||||
description: 'URL of the resource to await'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Await HTTP resource
|
||||
shell: bash
|
||||
run: |
|
||||
url=${{ inputs.url }}
|
||||
echo "Waiting for $url"
|
||||
until curl --fail --head --silent ${{ inputs.url }} > /dev/null
|
||||
do
|
||||
echo "."
|
||||
sleep 60
|
||||
done
|
||||
echo "$url is available"
|
||||
@@ -1,61 +0,0 @@
|
||||
name: 'Build'
|
||||
description: 'Builds the project, optionally publishing it to a local deployment repository'
|
||||
inputs:
|
||||
develocity-access-key:
|
||||
description: 'Access key for authentication with ge.spring.io'
|
||||
required: false
|
||||
java-distribution:
|
||||
description: 'Java distribution to use'
|
||||
required: false
|
||||
default: 'liberica'
|
||||
java-early-access:
|
||||
description: 'Whether the Java version is in early access'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-toolchain:
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-version:
|
||||
description: 'Java version to compile and test with'
|
||||
required: false
|
||||
default: '17'
|
||||
publish:
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
required: false
|
||||
default: 'false'
|
||||
outputs:
|
||||
build-scan-url:
|
||||
description: 'URL, if any, of the build scan produced by the build'
|
||||
value: ${{ (inputs.publish == 'true' && steps.publish.outputs.build-scan-url) || steps.build.outputs.build-scan-url }}
|
||||
version:
|
||||
description: 'Version that was built'
|
||||
value: ${{ steps.read-version.outputs.version }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Gradle Build
|
||||
uses: ./.github/actions/prepare-gradle-build
|
||||
with:
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
java-distribution: ${{ inputs.java-distribution }}
|
||||
java-early-access: ${{ inputs.java-early-access }}
|
||||
java-toolchain: ${{ inputs.java-toolchain }}
|
||||
java-version: ${{ inputs.java-version }}
|
||||
- name: Build
|
||||
id: build
|
||||
if: ${{ inputs.publish == 'false' }}
|
||||
shell: bash
|
||||
run: ./gradlew check antora
|
||||
- name: Publish
|
||||
id: publish
|
||||
if: ${{ inputs.publish == 'true' }}
|
||||
shell: bash
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Read Version From gradle.properties
|
||||
id: read-version
|
||||
shell: bash
|
||||
run: |
|
||||
version=$(sed -n 's/version=\(.*\)/\1/p' gradle.properties)
|
||||
echo "Version is $version"
|
||||
echo "version=$version" >> $GITHUB_OUTPUT
|
||||
@@ -1,27 +0,0 @@
|
||||
name: Create GitHub Release
|
||||
description: 'Create the release on GitHub with a changelog'
|
||||
inputs:
|
||||
milestone:
|
||||
description: 'Name of the GitHub milestone for which a release will be created'
|
||||
required: true
|
||||
pre-release:
|
||||
description: 'Whether the release is a pre-release (a milestone or release candidate)'
|
||||
required: false
|
||||
default: 'false'
|
||||
token:
|
||||
description: 'Token to use for authentication with GitHub'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
|
||||
with:
|
||||
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' || '' }}
|
||||
@@ -1,54 +0,0 @@
|
||||
name: Prepare Gradle Build
|
||||
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
|
||||
inputs:
|
||||
develocity-access-key:
|
||||
description: 'Access key for authentication with ge.spring.io'
|
||||
required: false
|
||||
java-distribution:
|
||||
description: 'Java distribution to use'
|
||||
required: false
|
||||
default: 'liberica'
|
||||
java-early-access:
|
||||
description: 'Whether the Java version is in early access. When true, forces java-distribution to temurin'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-toolchain:
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-version:
|
||||
description: 'Java version to use for the build'
|
||||
required: false
|
||||
default: '17'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
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' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
develocity-token-expiry: 4
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Configure Toolchain Properties
|
||||
if: ${{ inputs.java-toolchain == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ inputs.java-version }} >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $HOME/.gradle/gradle.properties
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Print JVM thread dumps
|
||||
description: 'Prints a thread dump for all running JVMs'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do
|
||||
jcmd $jvm_pid Thread.print
|
||||
done
|
||||
- if: ${{ runner.os == 'Windows' }}
|
||||
shell: powershell
|
||||
run: |
|
||||
foreach ($jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem)) {
|
||||
jcmd $jvm_pid Thread.print
|
||||
}
|
||||
@@ -1,39 +1,33 @@
|
||||
name: Send Notification
|
||||
description: 'Sends a Google Chat message as a notification of the job''s outcome'
|
||||
name: Send notification
|
||||
description: Sends a Google Chat message as a notification of the job's outcome
|
||||
inputs:
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
required: false
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
required: false
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Variables
|
||||
shell: bash
|
||||
- shell: bash
|
||||
run: |
|
||||
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
|
||||
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
|
||||
- name: Success Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'success' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
|
||||
- name: Failure Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'failure' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
|
||||
- name: Cancel Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'cancelled' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Sync to Maven Central
|
||||
description: 'Syncs a release to Maven Central and waits for it to be available for use'
|
||||
inputs:
|
||||
central-token-password:
|
||||
description: 'Password for authentication with central.sonatype.com'
|
||||
required: true
|
||||
central-token-username:
|
||||
description: 'Username for authentication with central.sonatype.com'
|
||||
required: true
|
||||
jfrog-cli-config-token:
|
||||
description: 'Config token for the JFrog CLI'
|
||||
required: true
|
||||
spring-framework-version:
|
||||
description: 'Version of Spring Framework that is being synced to Central'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@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) }}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"aql": {
|
||||
"items.find": {
|
||||
"$and": [
|
||||
{
|
||||
"@build.name": "${buildName}",
|
||||
"@build.number": "${buildNumber}",
|
||||
"path": {
|
||||
"$nmatch": "org/springframework/framework-api/*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": "nexus/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Backport Bot
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
@@ -28,6 +29,6 @@ jobs:
|
||||
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 }}
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
|
||||
|
||||
@@ -1,58 +1,62 @@
|
||||
name: Build and Deploy Snapshot
|
||||
name: Build and deploy snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
name: Build and Deploy Snapshot
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
name: Build and deploy snapshot
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Build and publish
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
uses: spring-io/artifactory-deploy-action@v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
|
||||
repository: 'libs-snapshot-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: 'spring-framework-6.2.x'
|
||||
folder: 'deployment-repository'
|
||||
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()
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
|
||||
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 }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-deploy-snapshot
|
||||
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:
|
||||
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Build Pull Request
|
||||
on: pull_request
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
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@v4
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
- name: Print JVM Thread Dumps When Cancelled
|
||||
if: cancelled()
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
+41
-19
@@ -2,15 +2,12 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
@@ -21,36 +18,61 @@ jobs:
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 23
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 17
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: |
|
||||
${{ matrix.java.version }}
|
||||
${{ matrix.java.toolchain && '17' || '' }}
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: |
|
||||
git config --global core.autocrlf true
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Configure toolchain properties
|
||||
if: ${{ matrix.java.toolchain }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
java-early-access: ${{ matrix.java.early-access || 'false' }}
|
||||
java-distribution: ${{ matrix.java.distribution }}
|
||||
java-toolchain: ${{ matrix.java.toolchain }}
|
||||
java-version: ${{ matrix.java.version }}
|
||||
- name: Send Notification
|
||||
if: always()
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew check antora
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
status: ${{ job.status }}
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
@@ -14,15 +14,14 @@ permissions:
|
||||
actions: write
|
||||
jobs:
|
||||
build:
|
||||
name: Dispatch docs deployment
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
- name: Dispatch (partial build)
|
||||
if: github.ref_type == 'branch'
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v2
|
||||
@@ -1,93 +0,0 @@
|
||||
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 }}
|
||||
@@ -1,37 +0,0 @@
|
||||
name: Update Antora UI Spring
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-antora-ui-spring:
|
||||
name: Update on Supported Branches
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.1.x' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: ${{ matrix.branch }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
antora-file-path: 'framework-docs/antora-playbook.yml'
|
||||
update-antora-ui-spring-docs-build:
|
||||
name: Update on docs-build
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: 'docs-build'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,77 +0,0 @@
|
||||
name: Verify
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
staging:
|
||||
description: 'Whether the release to verify is in the staging repository'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
version:
|
||||
description: 'Version to verify'
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
google-chat-webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
repository-password:
|
||||
description: 'Password for authentication with the repository'
|
||||
required: false
|
||||
repository-username:
|
||||
description: 'Username for authentication with the repository'
|
||||
required: false
|
||||
token:
|
||||
description: 'Token to use for authentication with GitHub'
|
||||
required: true
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'v0.0.2'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
- name: Run Release Verification Tests
|
||||
env:
|
||||
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
|
||||
RVT_RELEASE_TYPE: oss
|
||||
RVT_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@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
- name: Send Notification
|
||||
if: failure()
|
||||
uses: ./send-notification/.github/actions/send-notification
|
||||
with:
|
||||
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
|
||||
status: ${{ job.status }}
|
||||
webhook-url: ${{ secrets.google-chat-webhook-url }}
|
||||
+1
-2
@@ -42,8 +42,7 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/*
|
||||
!.idea/icon.svg
|
||||
.idea
|
||||
out
|
||||
test-output
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
Generated
-52
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 510 510" style="enable-background:new 0 0 510 510;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#6DB33F;}
|
||||
</style>
|
||||
<title>icon-framework</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_1-2">
|
||||
<path class="st0" d="M479.2,162.5c-5.6-34.3-20.5-64.4-43.9-87.8S382,36.5,347.6,30.9C320.1,11.2,288.5,0,255,0
|
||||
s-65.1,11.2-92.6,30.9C128,36.5,98,51.3,74.7,74.7C50.9,98.4,36.5,128.7,31,162.2C11.2,189.8,0,221.4,0,255s11.2,65.2,31,92.8
|
||||
c5.5,33.5,19.9,63.8,43.7,87.5c23.3,23.3,53.3,38.2,87.7,43.8C190,498.8,221.5,510,255,510s65-11.2,92.6-30.9
|
||||
c34.4-5.6,64.4-20.5,87.7-43.8c23.8-23.8,38.1-54.1,43.7-87.5c19.8-27.6,31-59.2,31-92.8S498.8,190,479.2,162.5z M473,224.2
|
||||
c-0.8,6-1.9,11.9-3.2,17.9c-7.2-22.4-17.1-43.8-29.4-63.8c-5.5-22.8-13.6-44.9-24.4-65.8c22.9,14.8,42.2,32.6,56.7,52.6
|
||||
C475.7,184.6,475.8,204.6,473,224.2z M444.7,199.6c9.3,17.7,16.6,36.3,21.9,55.6c-5.3,19.3-12.7,37.9-22,55.6
|
||||
C450.9,274,451,236.4,444.7,199.6L444.7,199.6z M175.4,341.1c6.5-4.5,13.7-7.8,21.3-9.8c32.2-7.5,46.7-9.9,66.3-18.4
|
||||
c36.8-15.9,74.8-53.3,84.6-94c-16.1,38.8-59.5,70.2-97.9,81.6c-26.2,7.8-79.8,13.6-80.1,13.6l-1.4-1.2
|
||||
c-30.6-17.4-27-85.8,31.8-104.1c25.7-8,55.9-0.7,83.5-5.4c29.4-5,64.4-24.5,79.9-52c12.3,46.4,26.1,118.5-9.7,160.2
|
||||
C309.4,363,211.8,318,175.4,341.1z M310.8,444.6c-17.7,9.4-36.5,16.8-55.8,22.1c-19.4-5.3-38.1-12.7-55.8-22.1
|
||||
C236.1,450.9,273.9,450.9,310.8,444.6z M117.6,392.4c13.1,13.1,27.3,25.1,42.3,35.8c-19-5.9-37.2-13.8-54.5-23.7
|
||||
c-9.9-17.3-17.8-35.7-23.7-54.7C92.4,365.1,104.5,379.3,117.6,392.4z M81.7,160.1c5.9-19,13.9-37.4,23.7-54.7
|
||||
c17.3-9.8,35.5-17.8,54.5-23.7c-15.1,10.8-29.2,22.7-42.3,35.8C104.5,130.7,92.4,144.9,81.7,160.1z M149.1,346.4
|
||||
c-8.5,0.2-15.5-6.5-15.7-15c-0.2-8.5,6.5-15.5,15-15.7c8.5-0.2,15.5,6.5,15.7,15c0,0,0,0.1,0,0.1
|
||||
C164.2,339.3,157.5,346.2,149.1,346.4L149.1,346.4z M199.2,65.4c17.7-9.4,36.5-16.8,55.8-22.1c19.4,5.3,38.1,12.7,55.8,22.1
|
||||
C273.9,59.1,236.1,59.1,199.2,65.4L199.2,65.4z M392.4,392.4c13.1-13.1,25.1-27.3,35.9-42.4c-5.9,19-13.9,37.3-23.7,54.5
|
||||
c-17.3,9.8-35.5,17.7-54.5,23.7C365.2,417.5,379.3,405.5,392.4,392.4L392.4,392.4z M392.4,117.6c-13.1-13.1-27.2-25.1-42.3-35.8
|
||||
c19,5.9,37.2,13.9,54.5,23.7c9.8,17.3,17.8,35.6,23.7,54.7C417.6,144.9,405.5,130.7,392.4,117.6L392.4,117.6z M430.5,79.5
|
||||
c19.4,19.3,32.6,43.6,39.3,71.1c-16.2-19.2-36.6-36.2-60.4-50c-13.8-23.8-30.8-44.2-50-60.4C387.1,46.9,411.2,60.1,430.5,79.5
|
||||
L430.5,79.5z M397.6,94c-20.8-10.7-42.8-18.8-65.6-24.2c-20-12.4-41.5-22.4-63.9-29.6c15.5-3.4,31.3-5.1,47.2-5.2
|
||||
c10,0,19.9,0.8,29.7,2.3C365.1,51.8,382.8,71.1,397.6,94z M255,6.8c27.6,0,53.8,8,77.4,22.2c-5.6-0.5-11.3-0.7-17.1-0.7
|
||||
c-20.4,0.1-40.6,2.8-60.3,8.1c-19.7-5.3-39.9-8-60.3-8.1c-5.8,0-11.5,0.2-17.1,0.7C201.2,14.7,227.4,6.8,255,6.8z M165,37.3
|
||||
c9.8-1.5,19.8-2.3,29.7-2.3c15.9,0.1,31.7,1.8,47.2,5.2C219.5,47.4,198,57.3,178,69.8c-22.7,5.4-44.8,13.6-65.6,24.2
|
||||
C127.2,71.1,144.9,51.8,165,37.3z M79.5,79.5c19.3-19.3,43.5-32.6,71.1-39.3c-19.2,16.2-36.2,36.6-50,60.4
|
||||
c-23.7,13.8-44,30.6-60.2,49.8C47,123.4,59.8,99.1,79.5,79.5z M37.4,164.9c14.6-20,33.8-37.7,56.6-52.4
|
||||
c-10.7,20.9-18.8,43-24.3,65.8c-12.3,20-22.2,41.4-29.4,63.8C34.6,215.4,33.5,189.3,37.4,164.9z M65.4,310.4
|
||||
c-9.3-17.6-16.6-36.2-21.9-55.4c5.3-19.2,12.6-37.8,21.9-55.4C59.1,236.3,59.1,273.7,65.4,310.4L65.4,310.4z M29.2,332.8
|
||||
c-14.4-23.7-22.4-50-22.4-77.8s8.1-54.1,22.4-77.8c-2.1,25,0.4,51.3,7.4,77.8C29.5,281.5,27,307.8,29.2,332.8z M40.3,268
|
||||
c7.2,22.3,17.1,43.7,29.4,63.7c5.5,22.8,13.6,44.9,24.3,65.8c-22.8-14.7-42-32.5-56.6-52.4C33.5,320.7,34.6,294.6,40.3,268z
|
||||
M79.5,430.5c-19.6-19.6-32.5-43.9-39.1-70.9c16.1,19.1,36.5,36,60.2,49.8c13.8,23.7,30.8,44.2,50,60.4
|
||||
C122.9,463.1,98.8,449.9,79.5,430.5z M112.5,416c20.8,10.7,42.8,18.8,65.5,24.2c20,12.4,41.5,22.4,63.9,29.6
|
||||
c-15.5,3.4-31.3,5.1-47.2,5.1c-10,0-19.9-0.8-29.7-2.3C144.9,458.1,127.2,438.9,112.5,416L112.5,416z M255,503.2
|
||||
c-27.6,0-53.8-8-77.4-22.2c5.6,0.5,11.3,0.7,17.1,0.7c20.4-0.1,40.6-2.8,60.3-8.1c19.7,5.3,39.9,8,60.3,8.1
|
||||
c5.8,0,11.5-0.2,17.1-0.7C308.8,495.3,282.6,503.2,255,503.2z M345,472.7c-9.8,1.5-19.8,2.3-29.7,2.3c-15.9-0.1-31.7-1.8-47.2-5.1
|
||||
c22.4-7.2,43.9-17.2,63.9-29.6c22.7-5.4,44.8-13.6,65.6-24.2C382.8,438.9,365.1,458.1,345,472.7z M430.5,430.5
|
||||
c-19.3,19.3-43.5,32.6-71.1,39.3c19.2-16.2,36.2-36.6,50-60.4c23.7-13.8,44-30.6,60.2-49.8C463.1,386.6,450.1,410.9,430.5,430.5
|
||||
L430.5,430.5z M472.6,345.1c-14.6,20-33.8,37.7-56.6,52.4c10.7-20.8,18.8-42.8,24.3-65.6c12.3-20,22.2-41.3,29.5-63.7
|
||||
C475.4,294.7,476.5,320.8,472.6,345.1L472.6,345.1z M473.5,255.3c2.6-9.9,4.7-20,6.1-30.1c2.2-15.7,2.7-31.7,1.4-47.5
|
||||
c14.2,23.6,22.2,49.8,22.2,77.4s-8.1,54.1-22.4,77.8C482.9,307.9,480.5,281.6,473.5,255.3L473.5,255.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB |
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.13-librca
|
||||
java=17.0.11-librca
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
||||
+7
-8
@@ -16,9 +16,9 @@ First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework#coc-ov-file).
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
@@ -65,6 +65,10 @@ follow-up reports will need to be created as new issues with a fresh description
|
||||
|
||||
#### Submit a Pull Request
|
||||
|
||||
1. If you have not previously done so, please sign the
|
||||
[Contributor License Agreement](https://cla.spring.io/sign/spring). You will be reminded
|
||||
automatically when you submit the PR.
|
||||
|
||||
1. Should you create an issue first? No, just create the pull request and use the
|
||||
description to provide context and motivation, as you would for an issue. If you want
|
||||
to start a discussion first or have already created an issue, once a pull request is
|
||||
@@ -81,13 +85,8 @@ multiple edits or corrections of the same logical change. See
|
||||
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
|
||||
for an overview of streamlining the commit history.
|
||||
|
||||
1. All commits must include a _Signed-off-by_ trailer at the end of each commit message
|
||||
to indicate that the contributor agrees to the Developer Certificate of Origin.
|
||||
For additional details, please refer to the blog post
|
||||
[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring).
|
||||
|
||||
1. Format commit messages using 55 characters for the subject line, 72 characters per line
|
||||
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
|
||||
for the description, followed by the issue fixed, e.g. `Closes gh-22276`. See the
|
||||
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
|
||||
for best practices around commit messages, and use `git log` to see some examples.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
@@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework/?tab=coc-ov-file#contributor-code-of-conduct). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
## Access to Binaries
|
||||
|
||||
|
||||
+11
-5
@@ -1,10 +1,16 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
Please, [open a draft security advisory](https://github.com/spring-projects/security-advisories/security/advisories/new) if you need to disclose and discuss a security issue in private with the Spring Framework team. Note that we only accept reports against [supported versions](https://spring.io/projects/spring-framework#support).
|
||||
|
||||
For more details, check out our [security policy](https://spring.io/security-policy).
|
||||
# Security Policy
|
||||
|
||||
## JAR signing
|
||||
|
||||
Spring Framework JARs released on Maven Central are signed.
|
||||
You'll find more information about the key here: https://spring.io/GPG-KEY-spring.txt
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see the
|
||||
[Spring Framework Versions](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions)
|
||||
wiki page.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please see https://spring.io/security-policy.
|
||||
|
||||
+15
-11
@@ -2,14 +2,13 @@ plugins {
|
||||
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' version '1.9.20'
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' 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 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -25,17 +24,17 @@ configure(allprojects) { project ->
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = "https://repo.spring.io/milestone"
|
||||
url "https://repo.spring.io/milestone"
|
||||
content {
|
||||
// Netty 5 optional support
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
if (version.contains('-')) {
|
||||
maven { url = "https://repo.spring.io/milestone" }
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
}
|
||||
if (version.endsWith('-SNAPSHOT')) {
|
||||
maven { url = "https://repo.spring.io/snapshot" }
|
||||
maven { url "https://repo.spring.io/snapshot" }
|
||||
}
|
||||
}
|
||||
configurations.all {
|
||||
@@ -68,13 +67,17 @@ configure([rootProject] + javaProjects) { project ->
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.junit.platform:junit-platform-suite")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params")
|
||||
testImplementation("org.junit.platform:junit-platform-suite-api")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
// Pull in the latest JUnit 5 Launcher API to ensure proper support in IDEs.
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-suite-engine")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl")
|
||||
@@ -87,17 +90,18 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://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://hc.apache.org/httpcomponents-client-5.2.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.3/api/",
|
||||
// "https://junit.org/junit5/docs/5.10.2/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/",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringHeaderCheck">
|
||||
<property name="fileExtensions" value="java"/>
|
||||
<property name="headerType" value="apache2"/>
|
||||
<property name="headerCopyrightPattern" value="20\d\d-present"/>
|
||||
<property name="headerCopyrightPattern" value="20\d\d-20\d\d"/>
|
||||
<property name="packageInfoHeaderType" value="none"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck"/>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.43
|
||||
javaFormatVersion=0.0.41
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.26.1");
|
||||
checkstyle.setToolVersion("10.16.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,8 +21,6 @@ import java.util.Map;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.api.tasks.testing.TestFrameworkOptions;
|
||||
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
|
||||
import org.gradle.testretry.TestRetryPlugin;
|
||||
import org.gradle.testretry.TestRetryTaskExtension;
|
||||
|
||||
@@ -36,7 +34,6 @@ import org.gradle.testretry.TestRetryTaskExtension;
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class TestConventions {
|
||||
|
||||
@@ -53,12 +50,7 @@ class TestConventions {
|
||||
}
|
||||
|
||||
private void configureTests(Project project, Test test) {
|
||||
TestFrameworkOptions existingOptions = test.getOptions();
|
||||
test.useJUnitPlatform(options -> {
|
||||
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
|
||||
options.copyFrom(junitPlatformOptions);
|
||||
}
|
||||
});
|
||||
test.useJUnitPlatform();
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
@@ -70,11 +62,9 @@ class TestConventions {
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
}
|
||||
test.jvmArgs(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
test.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"-Xshare:off"
|
||||
);
|
||||
"-Djava.locale.providers=COMPAT");
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,10 +26,7 @@ import org.gradle.api.attributes.LibraryElements;
|
||||
import org.gradle.api.attributes.Usage;
|
||||
import org.gradle.api.attributes.java.TargetJvmVersion;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.jvm.JvmTestSuite;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.testing.base.TestingExtension;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -50,21 +47,17 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
public void apply(Project project) {
|
||||
|
||||
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
|
||||
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
|
||||
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
|
||||
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
|
||||
TaskProvider<Test> agentTest = project.getTasks().register(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
test.useJUnitPlatform(options -> {
|
||||
options.includeTags("RuntimeHintsTests");
|
||||
});
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.systemProperty("java.awt.headless", "true");
|
||||
test.systemProperty("org.graalvm.nativeimage.imagecode", "runtime");
|
||||
test.setTestClassesDirs(jvmTestSuite.getSources().getOutput().getClassesDirs());
|
||||
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
|
||||
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
|
||||
});
|
||||
project.getTasks().named("check", task -> task.dependsOn(agentTest));
|
||||
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
|
||||
project.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
== Spring Framework Concourse pipeline
|
||||
|
||||
NOTE: CI is being migrated to GitHub Actions.
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
If you're part of the Spring Framework project on GitHub, you can get access to CI management features.
|
||||
First, you need to go to https://ci.spring.io and install the client CLI for your platform (see bottom right of the screen).
|
||||
|
||||
You can then login with the instance using:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring login -n spring-framework -c https://ci.spring.io
|
||||
----
|
||||
|
||||
Once logged in, you should get something like:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly ts
|
||||
name url team expiry
|
||||
spring https://ci.spring.io spring-framework Wed, 25 Mar 2020 17:45:26 UTC
|
||||
----
|
||||
|
||||
=== Pipeline configuration and structure
|
||||
|
||||
The build pipelines are described in `pipeline.yml` file.
|
||||
|
||||
This file is listing Concourse resources, i.e. build inputs and outputs such as container images, artifact repositories, source repositories, notification services, etc.
|
||||
|
||||
It also describes jobs (a job is a sequence of inputs, tasks and outputs); jobs are organized by groups.
|
||||
|
||||
The `pipeline.yml` definition contains `((parameters))` which are loaded from the `parameters.yml` file or from our https://docs.cloudfoundry.org/credhub/[credhub instance].
|
||||
|
||||
You'll find in this folder the following resources:
|
||||
|
||||
* `pipeline.yml` the build pipeline
|
||||
* `parameters.yml` the build parameters used for the pipeline
|
||||
* `images/` holds the container images definitions used in this pipeline
|
||||
* `scripts/` holds the build scripts that ship within the CI container images
|
||||
* `tasks` contains the task definitions used in the main `pipeline.yml`
|
||||
|
||||
=== Updating the build pipeline
|
||||
|
||||
Updating files on the repository is not enough to update the build pipeline, as changes need to be applied.
|
||||
|
||||
The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
+1
-9
@@ -17,12 +17,4 @@ changelog:
|
||||
- "type: dependency-upgrade"
|
||||
contributors:
|
||||
exclude:
|
||||
names:
|
||||
- "bclozel"
|
||||
- "jhoeller"
|
||||
- "poutsma"
|
||||
- "rstoyanchev"
|
||||
- "sbrannen"
|
||||
- "sdeleuze"
|
||||
- "simonbasle"
|
||||
- "snicoll"
|
||||
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
|
||||
@@ -0,0 +1,10 @@
|
||||
logging:
|
||||
level:
|
||||
io.spring.concourse: DEBUG
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
sonatype:
|
||||
exclude:
|
||||
- 'build-info\.json'
|
||||
- '.*\.zip'
|
||||
@@ -0,0 +1,21 @@
|
||||
== CI Images
|
||||
|
||||
These images are used by CI to run the actual builds.
|
||||
|
||||
To build the image locally run the following from this directory:
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f <image-folder>/Dockerfile .
|
||||
----
|
||||
|
||||
For example
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
|
||||
----
|
||||
|
||||
To test run:
|
||||
|
||||
----
|
||||
$ docker run -it --entrypoint /bin/bash <SHA>
|
||||
----
|
||||
@@ -0,0 +1,12 @@
|
||||
FROM ubuntu:jammy-20240125
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK21 /opt/openjdk/java21
|
||||
ENV JDK23 /opt/openjdk/java23
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
|
||||
;;
|
||||
java21)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
|
||||
;;
|
||||
java23)
|
||||
echo "https://download.java.net/java/early_access/jdk23/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
exit 1
|
||||
esac
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
###########################################################
|
||||
# UTILS
|
||||
###########################################################
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
|
||||
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
|
||||
dpkg-reconfigure --frontend noninteractive tzdata
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
|
||||
|
||||
###########################################################
|
||||
# JAVA
|
||||
###########################################################
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java21 java23
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
pushd $jdk > /dev/null
|
||||
curl -L ${JDK_URL} | tar zx --strip-components=1
|
||||
test -f bin/java
|
||||
test -f bin/javac
|
||||
popd > /dev/null
|
||||
done
|
||||
popd
|
||||
|
||||
###########################################################
|
||||
# GRADLE ENTERPRISE
|
||||
###########################################################
|
||||
cd /
|
||||
mkdir ~/.gradle
|
||||
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
|
||||
@@ -0,0 +1,10 @@
|
||||
github-repo: "https://github.com/spring-projects/spring-framework.git"
|
||||
github-repo-name: "spring-projects/spring-framework"
|
||||
sonatype-staging-profile: "org.springframework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "6.1.x"
|
||||
milestone: "6.1.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
anchors:
|
||||
git-repo-resource-source: &git-repo-resource-source
|
||||
uri: ((github-repo))
|
||||
username: ((github-username))
|
||||
password: ((github-ci-release-token))
|
||||
branch: ((branch))
|
||||
gradle-enterprise-task-params: &gradle-enterprise-task-params
|
||||
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USERNAME: ((sonatype-username))
|
||||
SONATYPE_PASSWORD: ((sonatype-password))
|
||||
SONATYPE_URL: ((sonatype-url))
|
||||
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
|
||||
artifactory-task-params: &artifactory-task-params
|
||||
ARTIFACTORY_SERVER: ((artifactory-server))
|
||||
ARTIFACTORY_USERNAME: ((artifactory-username))
|
||||
ARTIFACTORY_PASSWORD: ((artifactory-password))
|
||||
build-project-task-params: &build-project-task-params
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
changelog-task-params: &changelog-task-params
|
||||
name: generated-changelog/tag
|
||||
tag: generated-changelog/tag
|
||||
body: generated-changelog/changelog.md
|
||||
github-task-params: &github-task-params
|
||||
GITHUB_USERNAME: ((github-username))
|
||||
GITHUB_TOKEN: ((github-ci-release-token))
|
||||
|
||||
resource_types:
|
||||
- name: registry-image
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.8.0
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: springio/artifactory-resource
|
||||
tag: 0.0.18
|
||||
- name: github-release
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/github-release-resource
|
||||
tag: 1.8.0
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
resources:
|
||||
- name: git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
<<: *git-repo-resource-source
|
||||
- name: ci-images-git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
uri: ((github-repo))
|
||||
branch: ((branch))
|
||||
paths: ["ci/images/*"]
|
||||
- name: ci-image
|
||||
type: registry-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: artifactory-repo
|
||||
type: artifactory-resource
|
||||
icon: package-variant
|
||||
source:
|
||||
uri: ((artifactory-server))
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: github-pre-release
|
||||
type: github-release
|
||||
icon: briefcase-download-outline
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: true
|
||||
release: false
|
||||
- name: github-release
|
||||
type: github-release
|
||||
icon: briefcase-download
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: false
|
||||
jobs:
|
||||
- name: build-ci-images
|
||||
plan:
|
||||
- get: git-repo
|
||||
- get: ci-images-git-repo
|
||||
trigger: true
|
||||
- task: build-ci-image
|
||||
privileged: true
|
||||
file: git-repo/ci/tasks/build-ci-image.yml
|
||||
output_mapping:
|
||||
image: ci-image
|
||||
vars:
|
||||
ci-image-name: ci-image
|
||||
<<: *docker-resource-source
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params: &artifactory-params
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
repo: libs-staging-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-api-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-milestone]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *github-task-params
|
||||
<<: *docker-resource-source
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-rc]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *github-task-params
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-release]
|
||||
params:
|
||||
download_artifacts: true
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
<<: *sonatype-task-params
|
||||
- name: create-github-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: artifactory-repo
|
||||
trigger: true
|
||||
passed: [promote-release]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *github-task-params
|
||||
- put: github-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
|
||||
groups:
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
jobs: ["build-ci-images"]
|
||||
@@ -0,0 +1,2 @@
|
||||
source /opt/concourse-java.sh
|
||||
setup_symlinks
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
|
||||
java -jar /github-changelog-generator.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
|
||||
${version} generated-changelog/changelog.md
|
||||
|
||||
echo ${version} > generated-changelog/version
|
||||
echo v${version} > generated-changelog/tag
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
|
||||
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
|
||||
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
echo "Promotion complete"
|
||||
echo $version > version/version
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
git fetch --tags --all > /dev/null
|
||||
popd > /dev/null
|
||||
|
||||
git clone git-repo stage-git-repo > /dev/null
|
||||
|
||||
pushd stage-git-repo > /dev/null
|
||||
|
||||
snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
|
||||
if [[ $RELEASE_TYPE = "M" ]]; then
|
||||
stageVersion=$( get_next_milestone_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RC" ]]; then
|
||||
stageVersion=$( get_next_rc_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
|
||||
stageVersion=$( get_next_release $snapshotVersion)
|
||||
nextVersion=$( bump_version_number $snapshotVersion)
|
||||
else
|
||||
echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
|
||||
fi
|
||||
|
||||
echo "Staging $stageVersion (next version will be $nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
|
||||
|
||||
git config user.name "Spring Builds" > /dev/null
|
||||
git config user.email "spring-builds@users.noreply.github.com" > /dev/null
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Release v$stageVersion" > /dev/null
|
||||
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
|
||||
|
||||
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
build publishAllPublicationsToDeploymentRepository
|
||||
|
||||
git reset --hard HEAD^ > /dev/null
|
||||
if [[ $nextVersion != $snapshotVersion ]]; then
|
||||
echo "Setting next development version (v$nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Next development version (v$nextVersion)" > /dev/null
|
||||
fi;
|
||||
|
||||
echo "Staging Complete"
|
||||
|
||||
popd > /dev/null
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: concourse/oci-build-task
|
||||
tag: 0.10.0
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: ci-images-git-repo
|
||||
outputs:
|
||||
- name: image
|
||||
caches:
|
||||
- path: ci-image-cache
|
||||
params:
|
||||
CONTEXT: ci-images-git-repo/ci/images
|
||||
DOCKERFILE: ci-images-git-repo/ci/images/ci-image/Dockerfile
|
||||
DOCKER_HUB_AUTH: ((docker-hub-auth))
|
||||
run:
|
||||
path: /bin/sh
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
mkdir -p /root/.docker
|
||||
cat > /root/.docker/config.json <<EOF
|
||||
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
|
||||
EOF
|
||||
build
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.8'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: generated-changelog
|
||||
params:
|
||||
GITHUB_ORGANIZATION:
|
||||
GITHUB_REPO:
|
||||
GITHUB_USERNAME:
|
||||
GITHUB_TOKEN:
|
||||
RELEASE_TYPE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/generate-changelog.sh
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.4.0'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: version
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
ARTIFACTORY_SERVER:
|
||||
ARTIFACTORY_USERNAME:
|
||||
ARTIFACTORY_PASSWORD:
|
||||
SONATYPE_USER:
|
||||
SONATYPE_PASSWORD:
|
||||
SONATYPE_URL:
|
||||
SONATYPE_STAGING_PROFILE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/promote-version.sh
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: stage-git-repo
|
||||
- name: distribution-repository
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
caches:
|
||||
- path: gradle
|
||||
run:
|
||||
path: git-repo/ci/scripts/stage-version.sh
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'java-platform'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.3'
|
||||
}
|
||||
|
||||
description = "Spring Framework API Docs"
|
||||
@@ -9,7 +9,7 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://repo.spring.io/release"
|
||||
url "https://repo.spring.io/release"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ dependencies {
|
||||
|
||||
javadoc {
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
@@ -29,12 +28,11 @@ javadoc {
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
|
||||
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
|
||||
destinationDir = file("$project.docsDir/javadoc-api")
|
||||
splitIndex = true
|
||||
links(rootProject.ext.javadocLinks)
|
||||
// Check for 'syntax' and 'reference' during linting.
|
||||
addBooleanOption('Xdoclint:syntax,reference', true)
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
}
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
@@ -54,7 +52,7 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.java.docsDir.dir("kdoc-api").get().asFile)
|
||||
outputDirectory.set(file("$docsDir/kdoc-api"))
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
@@ -89,7 +87,7 @@ tasks.register('schemaZip', Zip) {
|
||||
archiveClassifier.set("schema")
|
||||
description = "Builds -${archiveClassifier} archive containing all " +
|
||||
"XSDs for deployment at https://springframework.org/schema."
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
duplicatesStrategy DuplicatesStrategy.EXCLUDE
|
||||
moduleProjects.each { module ->
|
||||
def Properties schemas = new Properties();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ content:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: ['main', '{6..9}.+({1..9}).x']
|
||||
branches: ['main', '{6..9}.+({0..9}).x']
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
@@ -36,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
|
||||
|
||||
@@ -6,7 +6,7 @@ nav:
|
||||
ext:
|
||||
collector:
|
||||
run:
|
||||
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g" :framework-docs:generateAntoraResources
|
||||
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :framework-docs:generateAntoraResources
|
||||
local: true
|
||||
scan:
|
||||
dir: ./build/generated-antora-resources
|
||||
@@ -20,8 +20,6 @@ asciidoc:
|
||||
fold: 'all'
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
|
||||
include-xml: 'example$docs-src/main/resources/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
spring-site-cve: "{spring-site}/security"
|
||||
@@ -42,8 +40,7 @@ asciidoc:
|
||||
spring-framework-reference: '{spring-framework-docs-root}/{spring-version}/reference'
|
||||
#
|
||||
# Other Spring portfolio projects
|
||||
spring-boot-docs: '{docs-site}/spring-boot'
|
||||
spring-boot-docs-ref: '{spring-boot-docs}/reference'
|
||||
spring-boot-docs: '{docs-site}/spring-boot/docs/current/reference/html'
|
||||
spring-boot-issues: '{spring-github-org}/spring-boot/issues'
|
||||
# TODO add more projects / links or just build up on {docs-site}?
|
||||
# TODO rename the below using new conventions
|
||||
@@ -73,9 +70,6 @@ asciidoc:
|
||||
kotlin-coroutines-api: '{kotlin-site}/api/kotlinx.coroutines'
|
||||
kotlin-github-org: 'https://github.com/Kotlin'
|
||||
kotlin-issues: 'https://youtrack.jetbrains.com/issue'
|
||||
micrometer-docs: 'https://docs.micrometer.io/micrometer/reference'
|
||||
micrometer-context-propagation-docs: 'https://docs.micrometer.io/context-propagation/reference'
|
||||
petclinic-github-org: 'https://github.com/spring-petclinic'
|
||||
reactive-streams-site: 'https://www.reactive-streams.org'
|
||||
reactive-streams-spec: 'https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md#specification'
|
||||
reactor-github-org: 'https://github.com/reactor'
|
||||
@@ -93,5 +87,4 @@ asciidoc:
|
||||
stackoverflow-questions: '{stackoverflow-site}/questions'
|
||||
stackoverflow-spring-tag: "{stackoverflow-questions}/tagged/spring"
|
||||
stackoverflow-spring-kotlin-tags: "{stackoverflow-spring-tag}+kotlin"
|
||||
testcontainers-site: 'https://www.testcontainers.org'
|
||||
vavr-docs: 'https://vavr-io.github.io/vavr-docs'
|
||||
testcontainers-site: 'https://www.testcontainers.org'
|
||||
@@ -37,39 +37,17 @@ javadoc {
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://repo.spring.io/release"
|
||||
url "https://repo.spring.io/release"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-aspects"))
|
||||
implementation(project(":spring-context"))
|
||||
implementation(project(":spring-context-support"))
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation(project(":spring-jdbc"))
|
||||
implementation(project(":spring-jms"))
|
||||
implementation(project(":spring-test"))
|
||||
implementation(project(":spring-web"))
|
||||
implementation(project(":spring-webflux"))
|
||||
implementation(project(":spring-webmvc"))
|
||||
implementation(project(":spring-websocket"))
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-web"))
|
||||
api("jakarta.jms:jakarta.jms-api")
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
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.projectreactor.netty:reactor-netty-http")
|
||||
implementation("jakarta.jms:jakarta.jms-api")
|
||||
implementation("jakarta.servlet:jakarta.servlet-api")
|
||||
implementation("jakarta.resource:jakarta.resource-api")
|
||||
implementation("jakarta.validation:jakarta.validation-api")
|
||||
implementation("jakarta.websocket:jakarta.websocket-client-api")
|
||||
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.aspectj:aspectjweaver")
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
**** xref:core/expressions/language-ref/constructors.adoc[]
|
||||
**** xref:core/expressions/language-ref/variables.adoc[]
|
||||
**** xref:core/expressions/language-ref/functions.adoc[]
|
||||
**** xref:core/expressions/language-ref/varargs.adoc[]
|
||||
**** xref:core/expressions/language-ref/bean-references.adoc[]
|
||||
**** xref:core/expressions/language-ref/operator-ternary.adoc[]
|
||||
**** xref:core/expressions/language-ref/operator-elvis.adoc[]
|
||||
@@ -107,6 +106,87 @@
|
||||
*** xref:core/appendix/xsd-schemas.adoc[]
|
||||
*** xref:core/appendix/xml-custom.adoc[]
|
||||
*** xref:core/appendix/application-startup-steps.adoc[]
|
||||
* xref:testing.adoc[]
|
||||
** xref:testing/introduction.adoc[]
|
||||
** xref:testing/unit.adoc[]
|
||||
** xref:testing/integration.adoc[]
|
||||
** xref:testing/support-jdbc.adoc[]
|
||||
** xref:testing/testcontext-framework.adoc[]
|
||||
*** xref:testing/testcontext-framework/key-abstractions.adoc[]
|
||||
*** xref:testing/testcontext-framework/bootstrapping.adoc[]
|
||||
*** xref:testing/testcontext-framework/tel-config.adoc[]
|
||||
*** 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/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.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[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[]
|
||||
**** 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/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
*** xref:testing/testcontext-framework/tx.adoc[]
|
||||
*** xref:testing/testcontext-framework/executing-sql.adoc[]
|
||||
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/spring-mvc-test-framework.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-static-imports.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-setup-options.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-setup-steps.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-performing-requests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-defining-expectations.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/async-requests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/vs-streaming-response.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-filters.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-resources.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-htmlunit.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/why.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/geb.adoc[]
|
||||
** xref:testing/spring-mvc-test-client.adoc[]
|
||||
** xref:testing/appendix.adoc[]
|
||||
*** xref:testing/annotations.adoc[]
|
||||
**** xref:testing/annotations/integration-standard.adoc[]
|
||||
**** xref:testing/annotations/integration-spring.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-bootstrapwith.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-commit.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-rollback.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-beforetransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-aftertransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sql.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
*** xref:testing/resources.adoc[]
|
||||
* xref:data-access.adoc[]
|
||||
** xref:data-access/transaction.adoc[]
|
||||
*** xref:data-access/transaction/motivation.adoc[]
|
||||
@@ -165,7 +245,6 @@
|
||||
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
|
||||
*** xref:web/webmvc/filters.adoc[]
|
||||
*** xref:web/webmvc/message-converters.adoc[]
|
||||
*** xref:web/webmvc/mvc-controller.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-requestmapping.adoc[]
|
||||
@@ -206,7 +285,6 @@
|
||||
**** xref:web/webmvc-view/mvc-freemarker.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-groovymarkup.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-script.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-fragments.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-jsp.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-feeds.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-document.adoc[]
|
||||
@@ -314,97 +392,6 @@
|
||||
** xref:web/webflux-test.adoc[]
|
||||
** xref:rsocket.adoc[]
|
||||
** xref:web/webflux-reactive-libraries.adoc[]
|
||||
* xref:testing.adoc[]
|
||||
** xref:testing/introduction.adoc[]
|
||||
** xref:testing/unit.adoc[]
|
||||
** xref:testing/integration.adoc[]
|
||||
** xref:testing/support-jdbc.adoc[]
|
||||
** xref:testing/testcontext-framework.adoc[]
|
||||
*** xref:testing/testcontext-framework/key-abstractions.adoc[]
|
||||
*** xref:testing/testcontext-framework/bootstrapping.adoc[]
|
||||
*** xref:testing/testcontext-framework/tel-config.adoc[]
|
||||
*** 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/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.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[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[]
|
||||
**** 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/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
*** xref:testing/testcontext-framework/tx.adoc[]
|
||||
*** xref:testing/testcontext-framework/executing-sql.adoc[]
|
||||
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/mockmvc.adoc[]
|
||||
*** xref:testing/mockmvc/overview.adoc[]
|
||||
*** xref:testing/mockmvc/setup-options.adoc[]
|
||||
*** xref:testing/mockmvc/hamcrest.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/static-imports.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/setup.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/setup-steps.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/requests.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/expectations.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/async-requests.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/vs-streaming-response.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/filters.adoc[]
|
||||
*** xref:testing/mockmvc/assertj.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/setup.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/requests.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/assertions.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/integration.adoc[]
|
||||
*** xref:testing/mockmvc/htmlunit.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/why.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/mah.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/webdriver.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/geb.adoc[]
|
||||
*** xref:testing/mockmvc/vs-end-to-end-integration-tests.adoc[]
|
||||
*** xref:testing/mockmvc/resources.adoc[]
|
||||
** xref:testing/spring-mvc-test-client.adoc[]
|
||||
** xref:testing/appendix.adoc[]
|
||||
*** xref:testing/annotations.adoc[]
|
||||
**** xref:testing/annotations/integration-standard.adoc[]
|
||||
**** xref:testing/annotations/integration-spring.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-bootstrapwith.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-commit.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-rollback.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-beforetransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-aftertransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sql.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
*** xref:testing/resources.adoc[]
|
||||
* xref:integration.adoc[]
|
||||
** xref:integration/rest-clients.adoc[]
|
||||
** xref:integration/jms.adoc[]
|
||||
@@ -452,6 +439,5 @@
|
||||
** 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]
|
||||
* {spring-framework-wiki}[Wiki, window=_blank, role=link-external]
|
||||
* {spring-framework-wiki}[Wiki]
|
||||
|
||||
|
||||
@@ -23,12 +23,6 @@ The following table lists all currently supported Spring properties.
|
||||
|===
|
||||
| Name | Description
|
||||
|
||||
| `spring.aop.ajc.ignore`
|
||||
| Instructs Spring to ignore ajc-compiled aspects for Spring AOP proxying, restoring traditional
|
||||
Spring behavior for scenarios where both weaving and AspectJ auto-proxying are enabled. See
|
||||
{spring-framework-api}++/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.html#IGNORE_AJC_PROPERTY_NAME++[`AbstractAspectJAdvisorFactory`]
|
||||
for details.
|
||||
|
||||
| `spring.aot.enabled`
|
||||
| Indicates the application should run with AOT generated artifacts. See
|
||||
xref:core/aot.adoc[Ahead of Time Optimizations] and
|
||||
@@ -38,7 +32,7 @@ for details.
|
||||
| `spring.beaninfo.ignore`
|
||||
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
|
||||
JavaBeans `Introspector`. See
|
||||
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`StandardBeanInfoFactory`]
|
||||
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
|
||||
for details.
|
||||
|
||||
| `spring.cache.reactivestreams.ignore`
|
||||
@@ -55,13 +49,15 @@ for details.
|
||||
|
||||
| `spring.context.checkpoint`
|
||||
| Property that specifies a common context checkpoint. See
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
|
||||
checkpoint/restore at startup] and
|
||||
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#CHECKPOINT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
|
||||
for details.
|
||||
|
||||
| `spring.context.exit`
|
||||
| Property for terminating the JVM when the context reaches a specific phase. See
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
|
||||
checkpoint/restore at startup] and
|
||||
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
|
||||
for details.
|
||||
|
||||
@@ -92,25 +88,11 @@ the repeated JNDI lookup overhead. See
|
||||
{spring-framework-api}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
|
||||
for details.
|
||||
|
||||
| `spring.locking.strict`
|
||||
| Instructs Spring to enforce strict locking during bean creation, rather than the mix of
|
||||
strict and lenient locking that 6.2 applies by default. See
|
||||
{spring-framework-api}++/beans/factory/support/DefaultListableBeanFactory.html#STRICT_LOCKING_PROPERTY_NAME++[`DefaultListableBeanFactory`]
|
||||
for details.
|
||||
|
||||
| `spring.objenesis.ignore`
|
||||
| Instructs Spring to ignore Objenesis, not even attempting to use it. See
|
||||
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
||||
for details.
|
||||
|
||||
| `spring.placeholder.escapeCharacter.default`
|
||||
| The default escape character for property placeholder support. If not set, `'\'` will
|
||||
be used. Can be set to a custom escape character or an empty string to disable support
|
||||
for an escape character. The default escape character be explicitly overridden in
|
||||
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
|
||||
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
|
||||
for details.
|
||||
|
||||
| `spring.test.aot.processing.failOnError`
|
||||
| A boolean flag that controls whether errors encountered during AOT processing in the
|
||||
_Spring TestContext Framework_ should result in an exception that fails the overall process.
|
||||
|
||||
@@ -17,3 +17,14 @@ is also provided.
|
||||
|
||||
AOT processing can be used to optimize your application ahead-of-time. It is typically
|
||||
used for native image deployment using GraalVM.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,3 +6,7 @@ The previous chapter described the Spring's support for AOP with @AspectJ and sc
|
||||
aspect definitions. In this chapter, we discuss the lower-level Spring AOP APIs. For common
|
||||
applications, we recommend the use of Spring AOP with AspectJ pointcuts as described in the
|
||||
previous chapter.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Now we can examine how Spring AOP handles advice.
|
||||
|
||||
|
||||
|
||||
[[aop-api-advice-lifecycle]]
|
||||
== Advice Lifecycles
|
||||
|
||||
@@ -21,20 +22,22 @@ the advice adds state to the proxied object.
|
||||
You can use a mix of shared and per-instance advice in the same AOP proxy.
|
||||
|
||||
|
||||
|
||||
[[aop-api-advice-types]]
|
||||
== Advice Types in Spring
|
||||
|
||||
Spring provides several advice types and is extensible to support
|
||||
arbitrary advice types. This section describes the basic concepts and standard advice types.
|
||||
|
||||
|
||||
[[aop-api-advice-around]]
|
||||
=== Interception Around Advice
|
||||
|
||||
The most fundamental advice type in Spring is _interception around advice_.
|
||||
The most fundamental advice type in Spring is interception around advice.
|
||||
|
||||
Spring is compliant with the AOP Alliance interface for around advice that uses method
|
||||
interception. Classes that implement around advice should therefore implement the
|
||||
following `MethodInterceptor` interface from the `org.aopalliance.intercept` package:
|
||||
Spring is compliant with the AOP `Alliance` interface for around advice that uses method
|
||||
interception. Classes that implement `MethodInterceptor` and that implement around advice should also implement the
|
||||
following interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -46,8 +49,8 @@ following `MethodInterceptor` interface from the `org.aopalliance.intercept` pac
|
||||
|
||||
The `MethodInvocation` argument to the `invoke()` method exposes the method being
|
||||
invoked, the target join point, the AOP proxy, and the arguments to the method. The
|
||||
`invoke()` method should return the invocation's result: typically the return value of
|
||||
the join point.
|
||||
`invoke()` method should return the invocation's result: the return value of the join
|
||||
point.
|
||||
|
||||
The following example shows a simple `MethodInterceptor` implementation:
|
||||
|
||||
@@ -55,30 +58,30 @@ The following example shows a simple `MethodInterceptor` implementation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class DebugInterceptor implements MethodInterceptor {
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
System.out.println("Before: invocation=[" + invocation + "]");
|
||||
Object result = invocation.proceed();
|
||||
Object rval = invocation.proceed();
|
||||
System.out.println("Invocation returned");
|
||||
return result;
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class DebugInterceptor : MethodInterceptor {
|
||||
|
||||
override fun invoke(invocation: MethodInvocation): Any {
|
||||
println("Before: invocation=[$invocation]")
|
||||
val result = invocation.proceed()
|
||||
val rval = invocation.proceed()
|
||||
println("Invocation returned")
|
||||
return result
|
||||
return rval
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -98,10 +101,11 @@ you are likely to want to run the aspect in another AOP framework. Note that poi
|
||||
are not currently interoperable between frameworks, and the AOP Alliance does not
|
||||
currently define pointcut interfaces.
|
||||
|
||||
|
||||
[[aop-api-advice-before]]
|
||||
=== Before Advice
|
||||
|
||||
A simpler advice type is a _before advice_. This does not need a `MethodInvocation`
|
||||
A simpler advice type is a before advice. This does not need a `MethodInvocation`
|
||||
object, since it is called only before entering the method.
|
||||
|
||||
The main advantage of a before advice is that there is no need to invoke the `proceed()`
|
||||
@@ -118,6 +122,10 @@ The following listing shows the `MethodBeforeAdvice` interface:
|
||||
}
|
||||
----
|
||||
|
||||
(Spring's API design would allow for
|
||||
field before advice, although the usual objects apply to field interception and it is
|
||||
unlikely for Spring to ever implement it.)
|
||||
|
||||
Note that the return type is `void`. Before advice can insert custom behavior before the join
|
||||
point runs but cannot change the return value. If a before advice throws an
|
||||
exception, it stops further execution of the interceptor chain. The exception
|
||||
@@ -131,7 +139,7 @@ The following example shows a before advice in Spring, which counts all method i
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class CountingBeforeAdvice implements MethodBeforeAdvice {
|
||||
|
||||
@@ -149,7 +157,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CountingBeforeAdvice : MethodBeforeAdvice {
|
||||
|
||||
@@ -164,13 +172,14 @@ Kotlin::
|
||||
|
||||
TIP: Before advice can be used with any pointcut.
|
||||
|
||||
|
||||
[[aop-api-advice-throws]]
|
||||
=== Throws Advice
|
||||
|
||||
_Throws advice_ is invoked after the return of the join point if the join point threw
|
||||
Throws advice is invoked after the return of the join point if the join point threw
|
||||
an exception. Spring offers typed throws advice. Note that this means that the
|
||||
`org.springframework.aop.ThrowsAdvice` interface does not contain any methods. It is a
|
||||
marker interface identifying that the given object implements one or more typed throws
|
||||
tag interface identifying that the given object implements one or more typed throws
|
||||
advice methods. These should be in the following form:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
@@ -180,16 +189,15 @@ advice methods. These should be in the following form:
|
||||
|
||||
Only the last argument is required. The method signatures may have either one or four
|
||||
arguments, depending on whether the advice method is interested in the method and
|
||||
arguments. The next two listings show classes that are examples of throws advice.
|
||||
arguments. The next two listing show classes that are examples of throws advice.
|
||||
|
||||
The following advice is invoked if a `RemoteException` is thrown (including subclasses of
|
||||
`RemoteException`):
|
||||
The following advice is invoked if a `RemoteException` is thrown (including from subclasses):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class RemoteThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
@@ -201,7 +209,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class RemoteThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
@@ -212,15 +220,15 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Unlike the preceding advice, the next example declares four arguments, so that it has
|
||||
access to the invoked method, method arguments, and target object. The following advice
|
||||
is invoked if a `ServletException` is thrown:
|
||||
Unlike the preceding
|
||||
advice, the next example declares four arguments, so that it has access to the invoked method, method
|
||||
arguments, and target object. The following advice is invoked if a `ServletException` is thrown:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
|
||||
|
||||
@@ -232,7 +240,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
|
||||
|
||||
@@ -251,7 +259,7 @@ methods can be combined in a single class. The following listing shows the final
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static class CombinedThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
@@ -267,7 +275,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CombinedThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
@@ -292,10 +300,11 @@ exception that is incompatible with the target method's signature!_
|
||||
|
||||
TIP: Throws advice can be used with any pointcut.
|
||||
|
||||
|
||||
[[aop-api-advice-after-returning]]
|
||||
=== After Returning Advice
|
||||
|
||||
An _after returning advice_ in Spring must implement the
|
||||
An after returning advice in Spring must implement the
|
||||
`org.springframework.aop.AfterReturningAdvice` interface, which the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
@@ -317,7 +326,7 @@ not thrown exceptions:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
|
||||
|
||||
@@ -336,7 +345,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CountingAfterReturningAdvice : AfterReturningAdvice {
|
||||
|
||||
@@ -355,10 +364,11 @@ thrown up the interceptor chain instead of the return value.
|
||||
|
||||
TIP: After returning advice can be used with any pointcut.
|
||||
|
||||
|
||||
[[aop-api-advice-introduction]]
|
||||
=== Introduction Advice
|
||||
|
||||
Spring treats _introduction advice_ as a special kind of interception advice.
|
||||
Spring treats introduction advice as a special kind of interception advice.
|
||||
|
||||
Introduction requires an `IntroductionAdvisor` and an `IntroductionInterceptor` that
|
||||
implement the following interface:
|
||||
@@ -410,7 +420,7 @@ introduce the following interface to one or more objects:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public interface Lockable {
|
||||
void lock();
|
||||
@@ -421,7 +431,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
interface Lockable {
|
||||
fun lock()
|
||||
@@ -470,7 +480,7 @@ The following example shows the example `LockMixin` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
|
||||
|
||||
@@ -494,12 +504,13 @@ Java::
|
||||
}
|
||||
return super.invoke(invocation);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
|
||||
|
||||
@@ -523,6 +534,7 @@ Kotlin::
|
||||
}
|
||||
return super.invoke(invocation)
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -544,7 +556,7 @@ The following example shows our `LockMixinAdvisor` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
|
||||
|
||||
@@ -556,7 +568,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
|
||||
----
|
||||
@@ -573,3 +585,8 @@ We can apply this advisor programmatically by using the `Advised.addAdvisor()` m
|
||||
(the recommended way) in XML configuration, as any other advisor. All proxy creation
|
||||
choices discussed below, including "`auto proxy creators,`" correctly handle introductions
|
||||
and stateful mixins.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ following methods:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Advisor[] getAdvisors();
|
||||
|
||||
@@ -35,7 +35,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun getAdvisors(): Array<Advisor>
|
||||
|
||||
@@ -90,7 +90,7 @@ manipulating its advice:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Advised advised = (Advised) myObject;
|
||||
Advisor[] advisors = advised.getAdvisors();
|
||||
@@ -110,7 +110,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val advised = myObject as Advised
|
||||
val advisors = advised.advisors
|
||||
@@ -142,3 +142,7 @@ case, the `Advised` `isFrozen()` method returns `true`, and any attempts to modi
|
||||
advice through addition or removal results in an `AopConfigException`. The ability
|
||||
to freeze the state of an advised object is useful in some cases (for example, to
|
||||
prevent calling code removing a security interceptor).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,3 +14,7 @@ It is possible to mix advisor and advice types in Spring in the same AOP proxy.
|
||||
example, you could use an interception around advice, throws advice, and before advice in
|
||||
one proxy configuration. Spring automatically creates the necessary interceptor
|
||||
chain.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19,12 +19,14 @@ There are two ways to do this:
|
||||
auto-proxy creation driven by source-level metadata attributes.
|
||||
|
||||
|
||||
|
||||
[[aop-autoproxy-choices]]
|
||||
== Auto-proxy Bean Definitions
|
||||
|
||||
This section covers the auto-proxy creators provided by the
|
||||
`org.springframework.aop.framework.autoproxy` package.
|
||||
|
||||
|
||||
[[aop-api-autoproxy]]
|
||||
=== `BeanNameAutoProxyCreator`
|
||||
|
||||
@@ -59,6 +61,7 @@ automatically created by the `BeanNameAutoProxyCreator`. The same advice is appl
|
||||
to all matching beans. Note that, if advisors are used (rather than the interceptor in
|
||||
the preceding example), the pointcuts may apply differently to different beans.
|
||||
|
||||
|
||||
[[aop-api-autoproxy-default]]
|
||||
=== `DefaultAdvisorAutoProxyCreator`
|
||||
|
||||
@@ -122,3 +125,7 @@ differently configured, AdvisorAutoProxyCreators in the same factory) and orderi
|
||||
Advisors can implement the `org.springframework.core.Ordered` interface to ensure
|
||||
correct ordering if this is an issue. The `TransactionAttributeSourceAdvisor` used in the
|
||||
preceding example has a configurable order value. The default setting is unordered.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -65,3 +65,7 @@ that, if you have a (parent) bean definition that you intend to use only as a te
|
||||
and this definition specifies a class, you must make sure to set the `abstract`
|
||||
attribute to `true`. Otherwise, the application context actually tries to
|
||||
pre-instantiate it.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ the pointcuts, any advice that applies, and their ordering. However, there are s
|
||||
options that are preferable if you do not need such control.
|
||||
|
||||
|
||||
|
||||
[[aop-pfb-1]]
|
||||
== Basics
|
||||
|
||||
@@ -31,6 +32,7 @@ application objects (besides the target, which should be available in any AOP
|
||||
framework), benefiting from all the pluggability provided by Dependency Injection.
|
||||
|
||||
|
||||
|
||||
[[aop-pfb-2]]
|
||||
== JavaBean Properties
|
||||
|
||||
@@ -85,6 +87,7 @@ to be applied. You can find an example of using this feature in xref:core/aop-ap
|
||||
`false`.
|
||||
|
||||
|
||||
|
||||
[[aop-pfb-proxy-types]]
|
||||
== JDK- and CGLIB-based proxies
|
||||
|
||||
@@ -134,6 +137,7 @@ interface that the target class implements to the `proxyInterfaces` property. Ho
|
||||
it is significantly less work and less prone to typographical errors.
|
||||
|
||||
|
||||
|
||||
[[aop-api-proxying-intf]]
|
||||
== Proxying Interfaces
|
||||
|
||||
@@ -192,16 +196,16 @@ follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Person person = (Person) factory.getBean("person");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val person = factory.getBean("person") as Person
|
||||
val person = factory.getBean("person") as Person;
|
||||
----
|
||||
======
|
||||
|
||||
@@ -259,6 +263,7 @@ However, there are times when being able to obtain the un-advised target from th
|
||||
factory might actually be an advantage (for example, in certain test scenarios).
|
||||
|
||||
|
||||
|
||||
[[aop-api-proxying-class]]
|
||||
== Proxying Classes
|
||||
|
||||
@@ -297,6 +302,7 @@ There is little performance difference between CGLIB proxies and dynamic proxies
|
||||
Performance should not be a decisive consideration in this case.
|
||||
|
||||
|
||||
|
||||
[[aop-global-advisors]]
|
||||
== Using "`Global`" Advisors
|
||||
|
||||
@@ -319,3 +325,7 @@ two global advisors:
|
||||
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
|
||||
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
This section describes how Spring handles the crucial pointcut concept.
|
||||
|
||||
|
||||
|
||||
[[aop-api-concepts]]
|
||||
== Concepts
|
||||
|
||||
@@ -68,6 +69,7 @@ TIP: If possible, try to make pointcuts static, allowing the AOP framework to ca
|
||||
results of pointcut evaluation when an AOP proxy is created.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcut-ops]]
|
||||
== Operations on Pointcuts
|
||||
|
||||
@@ -82,6 +84,7 @@ You can compose pointcuts by using the static methods in the
|
||||
expressions is usually a simpler approach.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-aspectj]]
|
||||
== AspectJ Expression Pointcuts
|
||||
|
||||
@@ -92,12 +95,14 @@ uses an AspectJ-supplied library to parse an AspectJ pointcut expression string.
|
||||
See the xref:core/aop.adoc[previous chapter] for a discussion of supported AspectJ pointcut primitives.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-impls]]
|
||||
== Convenience Pointcut Implementations
|
||||
|
||||
Spring provides several convenient pointcut implementations. You can use some of them
|
||||
directly; others are intended to be subclassed in application-specific pointcuts.
|
||||
|
||||
|
||||
[[aop-api-pointcuts-static]]
|
||||
=== Static Pointcuts
|
||||
|
||||
@@ -123,7 +128,18 @@ the resulting pointcut is effectively the union of the specified patterns.)
|
||||
|
||||
The following example shows how to use `JdkRegexpMethodPointcut`:
|
||||
|
||||
include-code::./JdkRegexpConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulatePointcut"
|
||||
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
|
||||
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
|
||||
@@ -131,7 +147,21 @@ throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPoi
|
||||
Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both
|
||||
pointcut and advice, as the following example shows:
|
||||
|
||||
include-code::./RegexpConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulateAdvisor"
|
||||
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
|
||||
<property name="advice">
|
||||
<ref bean="beanNameOfAopAllianceInterceptor"/>
|
||||
</property>
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
|
||||
@@ -141,6 +171,7 @@ You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
An important type of static pointcut is a metadata-driven pointcut. This uses the
|
||||
values of metadata attributes (typically, source-level metadata).
|
||||
|
||||
|
||||
[[aop-api-pointcuts-dynamic]]
|
||||
=== Dynamic pointcuts
|
||||
|
||||
@@ -166,6 +197,7 @@ other dynamic pointcuts. In Java 1.4, the cost is about five times that of other
|
||||
pointcuts.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-superclasses]]
|
||||
== Pointcut Superclasses
|
||||
|
||||
@@ -180,7 +212,7 @@ following example shows how to subclass `StaticMethodMatcherPointcut`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class TestStaticPointcut extends StaticMethodMatcherPointcut {
|
||||
|
||||
@@ -192,7 +224,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TestStaticPointcut : StaticMethodMatcherPointcut() {
|
||||
|
||||
@@ -207,6 +239,7 @@ There are also superclasses for dynamic pointcuts.
|
||||
You can use custom pointcuts with any advice type.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-custom]]
|
||||
== Custom Pointcuts
|
||||
|
||||
@@ -217,3 +250,7 @@ expression language, if you can.
|
||||
|
||||
NOTE: Later versions of Spring may offer support for "`semantic pointcuts`" as offered by JAC --
|
||||
for example, "`all methods that change instance variables in the target object.`"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ interceptor and one advisor:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
|
||||
factory.addAdvice(myMethodInterceptor);
|
||||
@@ -22,7 +22,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val factory = ProxyFactory(myBusinessInterfaceImpl)
|
||||
factory.addAdvice(myMethodInterceptor)
|
||||
|
||||
@@ -22,6 +22,7 @@ rather than a singleton bean definition. This allows Spring to create a new targ
|
||||
instance when required.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-swap]]
|
||||
== Hot-swappable Target Sources
|
||||
|
||||
@@ -37,7 +38,7 @@ You can change the target by using the `swap()` method on HotSwappableTargetSour
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
|
||||
Object oldTarget = swapper.swap(newTarget);
|
||||
@@ -45,7 +46,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
|
||||
val oldTarget = swapper.swap(newTarget)
|
||||
@@ -76,6 +77,7 @@ use a `TargetSource`), any `TargetSource` can be used in conjunction with
|
||||
arbitrary advice.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-pool]]
|
||||
== Pooling Target Sources
|
||||
|
||||
@@ -150,7 +152,7 @@ The cast is defined as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
|
||||
System.out.println("Max pool size is " + conf.getMaxSize());
|
||||
@@ -158,7 +160,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val conf = beanFactory.getBean("businessObject") as PoolingConfig
|
||||
println("Max pool size is " + conf.maxSize)
|
||||
@@ -173,6 +175,7 @@ Simpler pooling is available by using auto-proxying. You can set the `TargetSour
|
||||
used by any auto-proxy creator.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-prototype]]
|
||||
== Prototype Target Sources
|
||||
|
||||
@@ -197,6 +200,7 @@ The only property is the name of the target bean. Inheritance is used in the
|
||||
source, the target bean must be a prototype bean definition.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-threadlocal]]
|
||||
== `ThreadLocal` Target Sources
|
||||
|
||||
@@ -217,8 +221,12 @@ NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in
|
||||
incorrectly using them in multi-threaded and multi-classloader environments. You
|
||||
should always consider wrapping a `ThreadLocal` in some other class and never directly use
|
||||
the `ThreadLocal` itself (except in the wrapper class). Also, you should
|
||||
always remember to correctly set and unset (where the latter involves a call to
|
||||
`ThreadLocal.remove()`) the resource local to the thread. Unsetting should be done in
|
||||
always remember to correctly set and unset (where the latter simply involves a call to
|
||||
`ThreadLocal.set(null)`) the resource local to the thread. Unsetting should be done in
|
||||
any case, since not unsetting it might result in problematic behavior. Spring's
|
||||
`ThreadLocal` support does this for you and should always be considered in favor of using
|
||||
`ThreadLocal` instances without other proper handling code.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,3 +32,7 @@ AOP is used in the Spring Framework to:
|
||||
NOTE: If you are interested only in generic declarative services or other pre-packaged
|
||||
declarative middleware services such as pooling, you do not need to work directly with
|
||||
Spring AOP, and can skip most of this chapter.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ The basic usage for this class is very simple, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
|
||||
@@ -34,7 +34,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
val factory = AspectJProxyFactory(targetObject)
|
||||
@@ -53,3 +53,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
See the {spring-framework-api}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,3 +11,6 @@ there is no dependency on the AspectJ compiler or weaver.
|
||||
|
||||
NOTE: Using the AspectJ compiler and weaver enables use of the full AspectJ language and
|
||||
is discussed in xref:core/aop/using-aspectj.adoc[Using AspectJ with Spring Applications].
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ The following example uses an inline pointcut expression.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
@@ -34,7 +34,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
@@ -57,7 +57,7 @@ as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
@@ -74,7 +74,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
@@ -101,7 +101,7 @@ You can declare it by using the `@AfterReturning` annotation.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
@@ -118,7 +118,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
@@ -146,7 +146,7 @@ access, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
@@ -165,7 +165,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
@@ -204,7 +204,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
@@ -221,7 +221,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
@@ -247,7 +247,7 @@ The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
@@ -266,7 +266,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
@@ -311,7 +311,7 @@ purposes. The following example shows how to use after finally advice:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
@@ -328,7 +328,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.After
|
||||
@@ -417,7 +417,7 @@ The following example shows how to use around advice:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
@@ -438,7 +438,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Around
|
||||
@@ -500,7 +500,7 @@ You could write the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
public void validateAccount(Account account) {
|
||||
@@ -510,7 +510,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
fun validateAccount(account: Account) {
|
||||
@@ -533,7 +533,7 @@ from the advice. This would look as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private void accountDataAccessOperation(Account account) {}
|
||||
@@ -546,7 +546,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private fun accountDataAccessOperation(account: Account) {
|
||||
@@ -572,7 +572,7 @@ The following shows the definition of the `@Auditable` annotation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@@ -583,7 +583,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@@ -597,7 +597,7 @@ The following shows the advice that matches the execution of `@Auditable` method
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
public void audit(Auditable auditable) {
|
||||
@@ -609,7 +609,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
fun audit(auditable: Auditable) {
|
||||
@@ -630,7 +630,7 @@ you have a generic type like the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public interface Sample<T> {
|
||||
void sampleGenericMethod(T param);
|
||||
@@ -640,7 +640,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
interface Sample<T> {
|
||||
fun sampleGenericMethod(param: T)
|
||||
@@ -656,7 +656,7 @@ tying the advice parameter to the parameter type for which you want to intercept
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(MyType param) {
|
||||
@@ -666,7 +666,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: MyType) {
|
||||
@@ -682,7 +682,7 @@ pointcut as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(Collection<MyType> param) {
|
||||
@@ -692,7 +692,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: Collection<MyType>) {
|
||||
@@ -756,7 +756,7 @@ The following example shows how to use the `argNames` attribute:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -771,7 +771,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -794,7 +794,7 @@ point object, the `argNames` attribute does not need to include it:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -809,7 +809,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -833,7 +833,7 @@ the `argNames` attribute:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
public void audit(JoinPoint jp) {
|
||||
@@ -844,7 +844,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
fun audit(jp: JoinPoint) {
|
||||
@@ -867,7 +867,7 @@ The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
@@ -882,7 +882,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
@@ -923,7 +923,7 @@ Each of the distinct advice types of a particular aspect is conceptually meant t
|
||||
to the join point directly. As a consequence, an `@AfterThrowing` advice method is not
|
||||
supposed to receive an exception from an accompanying `@After`/`@AfterReturning` method.
|
||||
|
||||
Advice methods defined in the same `@Aspect` class that
|
||||
As of Spring Framework 5.2.7, advice methods defined in the same `@Aspect` class that
|
||||
need to run at the same join point are assigned precedence based on their advice type in
|
||||
the following order, from highest to lowest precedence: `@Around`, `@Before`, `@After`,
|
||||
`@AfterReturning`, `@AfterThrowing`. Note, however, that an `@After` advice method will
|
||||
@@ -937,3 +937,5 @@ reflection for javac-compiled classes). Consider collapsing such advice methods
|
||||
advice method per join point in each `@Aspect` class or refactor the pieces of advice into
|
||||
separate `@Aspect` classes that you can order at the aspect level via `Ordered` or `@Order`.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,54 @@ determines that a bean is advised by one or more aspects, it automatically gener
|
||||
a proxy for that bean to intercept method invocations and ensures that advice is run
|
||||
as needed.
|
||||
|
||||
The @AspectJ support can be enabled with programmatic or XML configuration. In either
|
||||
case, you also need to ensure that AspectJ's `org.aspectj:aspectjweaver` library is on the
|
||||
classpath of your application (version 1.9 or later).
|
||||
The @AspectJ support can be enabled with XML- or Java-style configuration. In either
|
||||
case, you also need to ensure that AspectJ's `aspectjweaver.jar` library is on the
|
||||
classpath of your application (version 1.9 or later). This library is available in the
|
||||
`lib` directory of an AspectJ distribution or from the Maven Central repository.
|
||||
|
||||
|
||||
[[aop-enable-aspectj-java]]
|
||||
== Enabling @AspectJ Support with Java Configuration
|
||||
|
||||
To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy`
|
||||
annotation, as the following example shows:
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
class AppConfig
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-enable-aspectj-xml]]
|
||||
== Enabling @AspectJ Support with XML Configuration
|
||||
|
||||
To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
----
|
||||
|
||||
This assumes that you use schema support as described in
|
||||
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration].
|
||||
See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema] for how to
|
||||
import the tags in the `aop` namespace.
|
||||
|
||||
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -9,12 +9,43 @@ minimal steps required for a not-very-useful aspect.
|
||||
The first of the two examples shows a regular bean definition in the application context
|
||||
that points to a bean class that is annotated with `@Aspect`:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
|
||||
<!-- configure properties of the aspect here -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
|
||||
annotated with `@Aspect`:
|
||||
|
||||
include-code::./NotVeryUsefulAspect[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
@Aspect
|
||||
public class NotVeryUsefulAspect {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
|
||||
@Aspect
|
||||
class NotVeryUsefulAspect
|
||||
----
|
||||
======
|
||||
|
||||
Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any
|
||||
other class. They can also contain pointcut, advice, and introduction (inter-type)
|
||||
@@ -32,3 +63,6 @@ stereotype annotation that qualifies, as per the rules of Spring's component sca
|
||||
NOTE: In Spring AOP, aspects themselves cannot be the targets of advice from other
|
||||
aspects. The `@Aspect` annotation on a class marks it as an aspect and, hence, excludes
|
||||
it from auto-proxying.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,10 +16,93 @@ aspect.
|
||||
Because we want to retry the operation, we need to use around advice so that we can
|
||||
call `proceed` multiple times. The following listing shows the basic aspect implementation:
|
||||
|
||||
include-code::./ConcurrentOperationExecutor[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
`@Around("com.xyz.CommonPointcuts.businessService()")` references the `businessService` named pointcut defined in
|
||||
xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
private static final int DEFAULT_MAX_RETRIES = 2;
|
||||
|
||||
private int maxRetries = DEFAULT_MAX_RETRIES;
|
||||
private int order = 1;
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
int numAttempts = 0;
|
||||
PessimisticLockingFailureException lockFailureException;
|
||||
do {
|
||||
numAttempts++;
|
||||
try {
|
||||
return pjp.proceed();
|
||||
}
|
||||
catch(PessimisticLockingFailureException ex) {
|
||||
lockFailureException = ex;
|
||||
}
|
||||
} while(numAttempts <= this.maxRetries);
|
||||
throw lockFailureException;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
private val DEFAULT_MAX_RETRIES = 2
|
||||
private var maxRetries = DEFAULT_MAX_RETRIES
|
||||
private var order = 1
|
||||
|
||||
fun setMaxRetries(maxRetries: Int) {
|
||||
this.maxRetries = maxRetries
|
||||
}
|
||||
|
||||
override fun getOrder(): Int {
|
||||
return this.order
|
||||
}
|
||||
|
||||
fun setOrder(order: Int) {
|
||||
this.order = order
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
numAttempts++
|
||||
try {
|
||||
return pjp.proceed()
|
||||
} catch (ex: PessimisticLockingFailureException) {
|
||||
lockFailureException = ex
|
||||
}
|
||||
|
||||
} while (numAttempts <= this.maxRetries)
|
||||
throw lockFailureException
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
|
||||
the aspect higher than the transaction advice (we want a fresh transaction each time we
|
||||
@@ -31,15 +114,70 @@ we have exhausted all of our retry attempts.
|
||||
|
||||
The corresponding Spring configuration follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<bean id="concurrentOperationExecutor"
|
||||
class="com.xyz.service.impl.ConcurrentOperationExecutor">
|
||||
<property name="maxRetries" value="3"/>
|
||||
<property name="order" value="100"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
To refine the aspect so that it retries only idempotent operations, we might define the following
|
||||
`Idempotent` annotation:
|
||||
|
||||
include-code::./service/Idempotent[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
annotation class Idempotent
|
||||
----
|
||||
======
|
||||
|
||||
We can then use the annotation to annotate the implementation of service operations. The change
|
||||
to the aspect to retry only idempotent operations involves refining the pointcut
|
||||
expression so that only `@Idempotent` operations match, as follows:
|
||||
|
||||
include-code::./service/SampleService[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ annotation. Consider the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
public class MyAspect {
|
||||
@@ -33,7 +33,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
class MyAspect {
|
||||
@@ -60,3 +60,6 @@ Programming Guide for more information on `per` clauses.
|
||||
|
||||
The `pertarget` instantiation model works in exactly the same way as `perthis`, but it
|
||||
creates one aspect instance for each unique target object at matched join points.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ You can make an introduction by using the `@DeclareParents` annotation. This ann
|
||||
is used to declare that matching types have a new parent (hence the name). For example,
|
||||
given an interface named `UsageTracked` and an implementation of that interface named
|
||||
`DefaultUsageTracked`, the following aspect declares that all implementors of service
|
||||
interfaces also implement the `UsageTracked` interface (for example, for statistics via JMX):
|
||||
interfaces also implement the `UsageTracked` interface (e.g. for statistics via JMX):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class UsageTracking {
|
||||
@@ -33,7 +33,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class UsageTracking {
|
||||
@@ -63,15 +63,17 @@ you would write the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean<UsageTracked>("myService")
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ matches the execution of any method named `transfer`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private void anyOldTransfer() {} // the pointcut signature
|
||||
@@ -27,7 +27,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private fun anyOldTransfer() {} // the pointcut signature
|
||||
@@ -150,7 +150,7 @@ pointcut expressions by name. The following example shows three pointcut express
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -174,7 +174,7 @@ trading module.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -217,7 +217,7 @@ expressions for this purpose. Such a class typically resembles the following
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages",fold="none"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -279,7 +279,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages",fold="none"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -581,3 +581,6 @@ performance (time and memory used), due to extra processing and analysis. Scopin
|
||||
designators are very fast to match, and using them means AspectJ can very quickly
|
||||
dismiss groups of join points that should not be further processed. A good
|
||||
pointcut should always include one if possible.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ decisions are influenced by a number of factors including application requiremen
|
||||
development tools, and team familiarity with AOP.
|
||||
|
||||
|
||||
|
||||
[[aop-spring-or-aspectj]]
|
||||
== Spring AOP or Full AspectJ?
|
||||
|
||||
@@ -30,6 +31,7 @@ the @AspectJ style, sticking with regular Java compilation in your IDE, and addi
|
||||
an aspect weaving phase to your build script.
|
||||
|
||||
|
||||
|
||||
[[aop-ataspectj-or-xml]]
|
||||
== @AspectJ or XML for Spring AOP?
|
||||
|
||||
@@ -57,7 +59,7 @@ For example, in the @AspectJ style you can write something like the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
public void propertyAccess() {}
|
||||
@@ -71,7 +73,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
fun propertyAccess() {}
|
||||
@@ -105,3 +107,7 @@ Spring AOP and by AspectJ. So, if you later decide you need the capabilities of
|
||||
to implement additional requirements, you can easily migrate to a classic AspectJ setup.
|
||||
In general, the Spring team prefers the @AspectJ style for custom aspects beyond simple
|
||||
configuration of enterprise services.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ with less potential for errors. For example, you do not need to invoke the `proc
|
||||
method on the `JoinPoint` used for around advice, and, hence, you cannot fail to invoke it.
|
||||
|
||||
All advice parameters are statically typed so that you work with advice parameters of
|
||||
the appropriate type (for example, the type of the return value from a method execution) rather
|
||||
the appropriate type (e.g. the type of the return value from a method execution) rather
|
||||
than `Object` arrays.
|
||||
|
||||
The concept of join points matched by pointcuts is the key to AOP, which distinguishes
|
||||
@@ -73,3 +73,7 @@ it from older technologies offering only interception. Pointcuts enable advice t
|
||||
targeted independently of the object-oriented hierarchy. For example, you can apply an
|
||||
around advice providing declarative transaction management to a set of methods that span
|
||||
multiple objects (such as all business operations in the service layer).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,5 +14,9 @@ need to advise a method that is not declared on an interface or where you need t
|
||||
pass a proxied object to a method as a concrete type.
|
||||
|
||||
It is important to grasp the fact that Spring AOP is proxy-based. See
|
||||
xref:core/aop/proxying.adoc#aop-understanding-aop-proxies[Understanding AOP Proxies]
|
||||
for a thorough examination of exactly what this implementation detail actually means.
|
||||
xref:core/aop/proxying.adoc#aop-understanding-aop-proxies[Understanding AOP Proxies] for a thorough examination of exactly what this
|
||||
implementation detail actually means.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@ configuration-style approach. The fact that this chapter chooses to introduce th
|
||||
@AspectJ-style approach first should not be taken as an indication that the Spring team
|
||||
favors the @AspectJ annotation-style approach over the Spring XML configuration-style.
|
||||
|
||||
See xref:core/aop/choosing.adoc[Choosing which AOP Declaration Style to Use] for a more
|
||||
complete discussion of the advantages and disadvantages of each style.
|
||||
See xref:core/aop/choosing.adoc[Choosing which AOP Declaration Style to Use] for a more complete discussion of the advantages and disadvantages of
|
||||
each style.
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,3 +6,7 @@ It is perfectly possible to mix @AspectJ style aspects by using the auto-proxyin
|
||||
schema-defined `<aop:aspect>` aspects, `<aop:advisor>` declared advisors, and even proxies
|
||||
and interceptors in other styles in the same configuration. All of these are implemented
|
||||
by using the same underlying support mechanism and can co-exist without any difficulty.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,26 +6,22 @@ target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a co
|
||||
open-source class definition library (repackaged into `spring-core`).
|
||||
|
||||
If the target object to be proxied implements at least one interface, a JDK dynamic
|
||||
proxy is used, and all of the interfaces implemented by the target type are proxied.
|
||||
If the target object does not implement any interfaces, a CGLIB proxy is created which
|
||||
is a runtime-generated subclass of the target type.
|
||||
proxy is used. All of the interfaces implemented by the target type are proxied.
|
||||
If the target object does not implement any interfaces, a CGLIB proxy is created.
|
||||
|
||||
If you want to force the use of CGLIB proxying (for example, to proxy every method
|
||||
defined for the target object, not only those implemented by its interfaces),
|
||||
you can do so. However, you should consider the following issues:
|
||||
|
||||
* `final` classes cannot be proxied, because they cannot be extended.
|
||||
* `final` methods cannot be advised, because they cannot be overridden.
|
||||
* `private` methods cannot be advised, because they cannot be overridden.
|
||||
* Methods that are not visible – for example, package-private methods in a parent class
|
||||
from a different package – cannot be advised because they are effectively private.
|
||||
* The constructor of your proxied object will not be called twice, since the CGLIB proxy
|
||||
instance is created through Objenesis. However, if your JVM does not allow for
|
||||
constructor bypassing, you might see double invocations and corresponding debug log
|
||||
entries from Spring's AOP support.
|
||||
* Your CGLIB proxy usage may face limitations with the Java Module System. As a typical
|
||||
case, you cannot create a CGLIB proxy for a class from the `java.lang` package when
|
||||
deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
* With CGLIB, `final` methods cannot be advised, as they cannot be overridden in
|
||||
runtime-generated subclasses.
|
||||
* As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore,
|
||||
since the CGLIB proxy instance is created through Objenesis. Only if your JVM does
|
||||
not allow for constructor bypassing, you might see double invocations and
|
||||
corresponding debug log entries from Spring's AOP support.
|
||||
* Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system.
|
||||
As a typical case, you cannot create a CGLIB proxy for a class from the `java.lang`
|
||||
package when deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
@@ -61,6 +57,7 @@ proxies _for all three of them_.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[aop-understanding-aop-proxies]]
|
||||
== Understanding AOP Proxies
|
||||
|
||||
@@ -68,14 +65,15 @@ Spring AOP is proxy-based. It is vitally important that you grasp the semantics
|
||||
what that last statement actually means before you write your own aspects or use any of
|
||||
the Spring AOP-based aspects supplied with the Spring Framework.
|
||||
|
||||
Consider first the scenario where you have a plain-vanilla, un-proxied object reference,
|
||||
as the following code snippet shows:
|
||||
Consider first the scenario where you have a plain-vanilla, un-proxied,
|
||||
nothing-special-about-it, straight object reference, as the following
|
||||
code snippet shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class SimplePojo implements Pojo {
|
||||
|
||||
@@ -92,7 +90,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
class SimplePojo : Pojo {
|
||||
|
||||
@@ -117,7 +115,7 @@ image::aop-proxy-plain-pojo-call.png[]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
@@ -131,7 +129,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val pojo = SimplePojo()
|
||||
@@ -150,7 +148,7 @@ image::aop-proxy-call.png[]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
@@ -168,7 +166,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val factory = ProxyFactory(SimplePojo())
|
||||
@@ -189,35 +187,26 @@ the interceptors (advice) that are relevant to that particular method call. Howe
|
||||
once the call has finally reached the target object (the `SimplePojo` reference in
|
||||
this case), any method calls that it may make on itself, such as `this.bar()` or
|
||||
`this.foo()`, are going to be invoked against the `this` reference, and not the proxy.
|
||||
This has important implications. It means that self invocation is not going to result
|
||||
in the advice associated with a method invocation getting a chance to run. In other words,
|
||||
self invocation via an explicit or implicit `this` reference will bypass the advice.
|
||||
This has important implications. It means that self-invocation is not going to result
|
||||
in the advice associated with a method invocation getting a chance to run.
|
||||
|
||||
To address that, you have the following options.
|
||||
|
||||
Avoid self invocation ::
|
||||
The best approach (the term "best" is used loosely here) is to refactor your code such
|
||||
that the self invocation does not happen. This does entail some work on your part, but
|
||||
it is the best, least-invasive approach.
|
||||
Inject a self reference ::
|
||||
An alternative approach is to make use of
|
||||
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-self-injection[self injection],
|
||||
and invoke methods on the proxy via the self reference instead of via `this`.
|
||||
Use `AopContext.currentProxy()` ::
|
||||
This last approach is highly discouraged, and we hesitate to point it out, in favor of
|
||||
the previous options. However, as a last resort you can choose to tie the logic within
|
||||
your class to Spring AOP, as the following example shows.
|
||||
Okay, so what is to be done about this? The best approach (the term "best" is used
|
||||
loosely here) is to refactor your code such that the self-invocation does not happen.
|
||||
This does entail some work on your part, but it is the best, least-invasive approach.
|
||||
The next approach is absolutely horrendous, and we hesitate to point it out, precisely
|
||||
because it is so horrendous. You can (painful as it is to us) totally tie the logic
|
||||
within your class to Spring AOP, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class SimplePojo implements Pojo {
|
||||
|
||||
public void foo() {
|
||||
// This works, but it should be avoided if possible.
|
||||
// this works, but... gah!
|
||||
((Pojo) AopContext.currentProxy()).bar();
|
||||
}
|
||||
|
||||
@@ -229,12 +218,12 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
class SimplePojo : Pojo {
|
||||
|
||||
fun foo() {
|
||||
// This works, but it should be avoided if possible.
|
||||
// this works, but... gah!
|
||||
(AopContext.currentProxy() as Pojo).bar()
|
||||
}
|
||||
|
||||
@@ -245,16 +234,16 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The use of `AopContext.currentProxy()` totally couples your code to Spring AOP, and it
|
||||
makes the class itself aware of the fact that it is being used in an AOP context, which
|
||||
reduces some of the benefits of AOP. It also requires that the `ProxyFactory` is
|
||||
configured to expose the proxy, as the following example shows:
|
||||
This totally couples your code to Spring AOP, and it makes the class itself aware of
|
||||
the fact that it is being used in an AOP context, which flies in the face of AOP. It
|
||||
also requires some additional configuration when the proxy is being created, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
@@ -273,7 +262,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val factory = ProxyFactory(SimplePojo())
|
||||
@@ -288,5 +277,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: AspectJ compile-time weaving and load-time weaving do not have this self-invocation
|
||||
issue because they apply advice within the bytecode instead of via a proxy.
|
||||
Finally, it must be noted that AspectJ does not have this self-invocation issue because
|
||||
it is not a proxy-based AOP framework.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use either only the `<aop:config>` style or only the `AutoProxyCreator` style an
|
||||
never mix them.
|
||||
|
||||
|
||||
|
||||
[[aop-schema-declaring-an-aspect]]
|
||||
== Declaring an Aspect
|
||||
|
||||
@@ -53,6 +54,7 @@ The bean that backs the aspect (`aBean` in this case) can of course be configure
|
||||
dependency injected just like any other Spring bean.
|
||||
|
||||
|
||||
|
||||
[[aop-schema-pointcuts]]
|
||||
== Declaring a Pointcut
|
||||
|
||||
@@ -134,7 +136,7 @@ parameters of the matching names, as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void monitor(Object service) {
|
||||
// ...
|
||||
@@ -143,7 +145,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun monitor(service: Any) {
|
||||
// ...
|
||||
@@ -175,7 +177,9 @@ follows:
|
||||
|
||||
Note that pointcuts defined in this way are referred to by their XML `id` and cannot be
|
||||
used as named pointcuts to form composite pointcuts. The named pointcut support in the
|
||||
schema-based definition style is thus more limited than that offered by the @AspectJ style.
|
||||
schema-based definition style is thus more limited than that offered by the @AspectJ
|
||||
style.
|
||||
|
||||
|
||||
|
||||
[[aop-schema-advice]]
|
||||
@@ -184,6 +188,7 @@ schema-based definition style is thus more limited than that offered by the @Asp
|
||||
The schema-based AOP support uses the same five kinds of advice as the @AspectJ style, and they have
|
||||
exactly the same semantics.
|
||||
|
||||
|
||||
[[aop-schema-advice-before]]
|
||||
=== Before Advice
|
||||
|
||||
@@ -232,6 +237,7 @@ that contains the advice. Before a data access operation is performed (a method
|
||||
join point matched by the pointcut expression), the `doAccessCheck` method on the aspect
|
||||
bean is invoked.
|
||||
|
||||
|
||||
[[aop-schema-advice-after-returning]]
|
||||
=== After Returning Advice
|
||||
|
||||
@@ -276,19 +282,20 @@ example, you can declare the method signature as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void doAccessCheck(Object retVal) {...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun doAccessCheck(retVal: Any) {...
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[aop-schema-advice-after-throwing]]
|
||||
=== After Throwing Advice
|
||||
|
||||
@@ -333,19 +340,20 @@ The type of this parameter constrains matching in the same way as described for
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void doRecoveryActions(DataAccessException dataAccessEx) {...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[aop-schema-advice-after-finally]]
|
||||
=== After (Finally) Advice
|
||||
|
||||
@@ -364,6 +372,7 @@ You can declare it by using the `after` element, as the following example shows:
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
|
||||
[[aop-schema-advice-around]]
|
||||
=== Around Advice
|
||||
|
||||
@@ -412,7 +421,7 @@ The implementation of the `doBasicProfiling` advice can be exactly the same as i
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// start stopwatch
|
||||
@@ -424,7 +433,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
|
||||
// start stopwatch
|
||||
@@ -435,18 +444,17 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[aop-schema-params]]
|
||||
=== Advice Parameters
|
||||
|
||||
The schema-based declaration style supports fully typed advice in the same way as
|
||||
described for the @AspectJ support -- by matching pointcut parameters by name against
|
||||
advice method parameters. See
|
||||
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params[Advice Parameters] for details.
|
||||
If you wish to explicitly specify argument names for the advice methods (not relying on the
|
||||
advice method parameters. See xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params[Advice Parameters] for details. If you wish
|
||||
to explicitly specify argument names for the advice methods (not relying on the
|
||||
detection strategies previously described), you can do so by using the `arg-names`
|
||||
attribute of the advice element, which is treated in the same manner as the `argNames`
|
||||
attribute in an advice annotation (as described in
|
||||
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names[Determining Argument Names]).
|
||||
attribute in an advice annotation (as described in xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names[Determining Argument Names]).
|
||||
The following example shows how to specify an argument name in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
@@ -456,8 +464,7 @@ The following example shows how to specify an argument name in XML:
|
||||
method="audit"
|
||||
arg-names="auditable" />
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in
|
||||
xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
|
||||
The `arg-names` attribute accepts a comma-delimited list of parameter names.
|
||||
|
||||
@@ -468,7 +475,7 @@ some around advice used in conjunction with a number of strongly typed parameter
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.service;
|
||||
|
||||
@@ -487,7 +494,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.service
|
||||
|
||||
@@ -514,7 +521,7 @@ proceed with the method call. The presence of this parameter is an indication th
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -538,7 +545,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -603,7 +610,7 @@ Consider the following driver script:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Boot {
|
||||
|
||||
@@ -617,7 +624,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val ctx = ClassPathXmlApplicationContext("beans.xml")
|
||||
@@ -638,15 +645,15 @@ ms % Task name
|
||||
00000 ? execution(getFoo)
|
||||
----
|
||||
|
||||
|
||||
[[aop-ordering]]
|
||||
=== Advice Ordering
|
||||
|
||||
When multiple pieces of advice need to run at the same join point (executing method)
|
||||
the ordering rules are as described in
|
||||
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-ordering[Advice Ordering]. The
|
||||
precedence between aspects is determined via the `order` attribute in the `<aop:aspect>`
|
||||
element or by either adding the `@Order` annotation to the bean that backs the aspect
|
||||
or by having the bean implement the `Ordered` interface.
|
||||
the ordering rules are as described in xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-ordering[Advice Ordering]. The precedence
|
||||
between aspects is determined via the `order` attribute in the `<aop:aspect>` element or
|
||||
by either adding the `@Order` annotation to the bean that backs the aspect or by having
|
||||
the bean implement the `Ordered` interface.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -669,6 +676,7 @@ at the aspect level.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[aop-schema-introductions]]
|
||||
== Introductions
|
||||
|
||||
@@ -706,7 +714,7 @@ The class that backs the `usageTracking` bean would then contain the following m
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void recordUsage(UsageTracked usageTracked) {
|
||||
usageTracked.incrementUseCount();
|
||||
@@ -715,7 +723,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun recordUsage(usageTracked: UsageTracked) {
|
||||
usageTracked.incrementUseCount()
|
||||
@@ -734,20 +742,21 @@ following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
[[aop-schema-instantiation-models]]
|
||||
== Aspect Instantiation Models
|
||||
|
||||
@@ -755,6 +764,7 @@ The only supported instantiation model for schema-defined aspects is the singlet
|
||||
model. Other instantiation models may be supported in future releases.
|
||||
|
||||
|
||||
|
||||
[[aop-schema-advisors]]
|
||||
== Advisors
|
||||
|
||||
@@ -762,8 +772,7 @@ The concept of "advisors" comes from the AOP support defined in Spring
|
||||
and does not have a direct equivalent in AspectJ. An advisor is like a small
|
||||
self-contained aspect that has a single piece of advice. The advice itself is
|
||||
represented by a bean and must implement one of the advice interfaces described in
|
||||
xref:core/aop-api/advice.adoc#aop-api-advice-types[Advice Types in Spring].
|
||||
Advisors can take advantage of AspectJ pointcut expressions.
|
||||
xref:core/aop-api/advice.adoc#aop-api-advice-types[Advice Types in Spring]. Advisors can take advantage of AspectJ pointcut expressions.
|
||||
|
||||
Spring supports the advisor concept with the `<aop:advisor>` element. You most
|
||||
commonly see it used in conjunction with transactional advice, which also has its own
|
||||
@@ -796,6 +805,7 @@ To define the precedence of an advisor so that the advice can participate in ord
|
||||
use the `order` attribute to define the `Ordered` value of the advisor.
|
||||
|
||||
|
||||
|
||||
[[aop-schema-example]]
|
||||
== An AOP Schema Example
|
||||
|
||||
@@ -819,7 +829,7 @@ call `proceed` multiple times. The following listing shows the basic aspect impl
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
@@ -859,7 +869,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
@@ -943,7 +953,7 @@ to annotate the implementation of service operations, as the following example s
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
@@ -953,7 +963,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
@@ -971,3 +981,7 @@ pointcut expression so that only `@Idempotent` operations match, as follows:
|
||||
expression="execution(* com.xyz.service.*.*(..)) and
|
||||
@annotation(com.xyz.service.Idempotent)"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,14 +8,12 @@ alone.
|
||||
|
||||
Spring ships with a small AspectJ aspect library, which is available stand-alone in your
|
||||
distribution as `spring-aspects.jar`. You need to add this to your classpath in order
|
||||
to use the aspects in it.
|
||||
xref:core/aop/using-aspectj.adoc#aop-atconfigurable[Using AspectJ to Dependency Inject Domain Objects with Spring]
|
||||
and xref:core/aop/using-aspectj.adoc#aop-ajlib-other[Other Spring aspects for AspectJ]
|
||||
discuss the content of this library and how you can use it.
|
||||
xref:core/aop/using-aspectj.adoc#aop-aj-configure[Configuring AspectJ Aspects by Using Spring IoC]
|
||||
discusses how to dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally,
|
||||
xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]
|
||||
provides an introduction to load-time weaving for Spring applications that use AspectJ.
|
||||
to use the aspects in it. xref:core/aop/using-aspectj.adoc#aop-atconfigurable[Using AspectJ to Dependency Inject Domain Objects with Spring] and xref:core/aop/using-aspectj.adoc#aop-ajlib-other[Other Spring aspects for AspectJ] discuss the
|
||||
content of this library and how you can use it. xref:core/aop/using-aspectj.adoc#aop-aj-configure[Configuring AspectJ Aspects by Using Spring IoC] discusses how to
|
||||
dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally,
|
||||
xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework] provides an introduction to load-time weaving for Spring applications
|
||||
that use AspectJ.
|
||||
|
||||
|
||||
|
||||
[[aop-atconfigurable]]
|
||||
@@ -38,7 +36,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain;
|
||||
|
||||
@@ -52,7 +50,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain
|
||||
|
||||
@@ -68,7 +66,7 @@ Kotlin::
|
||||
When used as a marker interface in this way, Spring configures new instances of the
|
||||
annotated type (`Account`, in this case) by using a bean definition (typically
|
||||
prototype-scoped) with the same name as the fully-qualified type name
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean defined via XML is the
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean is the
|
||||
fully-qualified name of its type, a convenient way to declare the prototype definition
|
||||
is to omit the `id` attribute, as the following example shows:
|
||||
|
||||
@@ -86,7 +84,7 @@ can do so directly in the annotation, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain;
|
||||
|
||||
@@ -100,7 +98,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain
|
||||
|
||||
@@ -155,14 +153,14 @@ available for use in the body of the constructors, you need to define this on th
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configurable(preConstruction = true)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configurable(preConstruction = true)
|
||||
----
|
||||
@@ -179,10 +177,41 @@ either use a build-time Ant or Maven task to do this (see, for example, the
|
||||
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
|
||||
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
|
||||
`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:
|
||||
a reference to the bean factory that is to be used to configure new objects). If you
|
||||
use Java-based configuration, you can add `@EnableSpringConfigured` to any
|
||||
`@Configuration` class, as follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML based configuration, the Spring
|
||||
xref:core/appendix/xsd-schemas.adoc#context[`context` namespace]
|
||||
defines a convenient `context:spring-configured` element, which you can use as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<context:spring-configured/>
|
||||
----
|
||||
|
||||
Instances of `@Configurable` objects created before the aspect has been configured
|
||||
result in a message being issued to the debug log and no configuration of the
|
||||
@@ -208,6 +237,7 @@ not use `@Configurable` on bean classes that are registered as regular Spring be
|
||||
with the container. Doing so results in double initialization, once through the
|
||||
container and once through the aspect.
|
||||
|
||||
|
||||
[[aop-configurable-testing]]
|
||||
=== Unit Testing `@Configurable` Objects
|
||||
|
||||
@@ -220,6 +250,7 @@ you can still unit test outside of the container as normal, but you see a warnin
|
||||
message each time that you construct a `@Configurable` object indicating that it has
|
||||
not been configured by Spring.
|
||||
|
||||
|
||||
[[aop-configurable-container]]
|
||||
=== Working with Multiple Application Contexts
|
||||
|
||||
@@ -249,6 +280,7 @@ is added only to the container-wide classpath (and hence loaded by the shared pa
|
||||
not what you want).
|
||||
|
||||
|
||||
|
||||
[[aop-ajlib-other]]
|
||||
== Other Spring aspects for AspectJ
|
||||
|
||||
@@ -301,6 +333,7 @@ fully qualified class names:
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[aop-aj-configure]]
|
||||
== Configuring AspectJ Aspects by Using Spring IoC
|
||||
|
||||
@@ -355,6 +388,7 @@ results in the creation of Spring AOP proxies. The @AspectJ style of aspect
|
||||
declaration is being used here, but the AspectJ runtime is not involved.
|
||||
|
||||
|
||||
|
||||
[[aop-aj-ltw]]
|
||||
== Load-time Weaving with AspectJ in the Spring Framework
|
||||
|
||||
@@ -386,7 +420,8 @@ who typically are in charge of the deployment configuration, such as the launch
|
||||
Now that the sales pitch is over, let us first walk through a quick example of AspectJ
|
||||
LTW that uses Spring, followed by detailed specifics about elements introduced in the
|
||||
example. For a complete example, see the
|
||||
{petclinic-github-org}/spring-framework-petclinic[Petclinic sample application based on Spring Framework].
|
||||
{spring-github-org}/spring-petclinic[Petclinic sample application].
|
||||
|
||||
|
||||
[[aop-aj-ltw-first-example]]
|
||||
=== A First Example
|
||||
@@ -409,7 +444,7 @@ It is a time-based profiler that uses the @AspectJ-style of aspect declaration:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -442,7 +477,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -540,7 +575,7 @@ driver class with a `main(..)` method to demonstrate the LTW in action:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -562,7 +597,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -621,7 +656,7 @@ result:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -643,7 +678,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -673,6 +708,7 @@ nice example of a development-time aspect that developers can use during develop
|
||||
and then easily exclude from builds of the application being deployed
|
||||
into UAT or production.
|
||||
|
||||
|
||||
[[aop-aj-ltw-the-aspects]]
|
||||
=== Aspects
|
||||
|
||||
@@ -681,6 +717,7 @@ either the AspectJ language itself, or you can write your aspects in the @Aspect
|
||||
Your aspects are then both valid AspectJ and Spring AOP aspects.
|
||||
Furthermore, the compiled aspect classes need to be available on the classpath.
|
||||
|
||||
|
||||
[[aop-aj-ltw-aop_dot_xml]]
|
||||
=== `META-INF/aop.xml`
|
||||
|
||||
@@ -710,6 +747,7 @@ The structure and contents of this file is detailed in the LTW part of the
|
||||
{aspectj-docs-devguide}/ltw-configuration.html[AspectJ reference
|
||||
documentation]. Because the `aop.xml` file is 100% AspectJ, we do not describe it further here.
|
||||
|
||||
|
||||
[[aop-aj-ltw-libraries]]
|
||||
=== Required libraries (JARS)
|
||||
|
||||
@@ -724,6 +762,7 @@ If you use the xref:core/aop/using-aspectj.adoc#aop-aj-ltw-environments-generic[
|
||||
|
||||
* `spring-instrument.jar`
|
||||
|
||||
|
||||
[[aop-aj-ltw-spring]]
|
||||
=== Spring Configuration
|
||||
|
||||
@@ -744,9 +783,52 @@ adding one line. (Note that you almost certainly need to use an
|
||||
`ApplicationContext` as your Spring container -- typically, a `BeanFactory` is not
|
||||
enough because the LTW support uses `BeanFactoryPostProcessors`.)
|
||||
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver` as follows:
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver`,
|
||||
which typically is done by using the `@EnableLoadTimeWeaving` annotation, as follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, if you prefer XML-based configuration, use the
|
||||
`<context:load-time-weaver/>` element. Note that the element is defined in the
|
||||
`context` namespace. The following example shows how to use `<context:load-time-weaver/>`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The preceding configuration automatically defines and registers a number of LTW-specific
|
||||
infrastructure beans, such as a `LoadTimeWeaver` and an `AspectJWeavingEnabler`, for you.
|
||||
@@ -782,12 +864,63 @@ Note that the table lists only the `LoadTimeWeavers` that are autodetected when
|
||||
use the `DefaultContextLoadTimeWeaver`. You can specify exactly which `LoadTimeWeaver`
|
||||
implementation to use.
|
||||
|
||||
To configure a specific `LoadTimeWeaver`, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method
|
||||
(or use the XML equivalent).
|
||||
To specify a specific `LoadTimeWeaver` with Java configuration, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method.
|
||||
The following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
include-code::./CustomWeaverConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return new ReflectiveLoadTimeWeaver();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig : LoadTimeWeavingConfigurer {
|
||||
|
||||
override fun getLoadTimeWeaver(): LoadTimeWeaver {
|
||||
return ReflectiveLoadTimeWeaver()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you use XML-based configuration, you can specify the fully qualified class name
|
||||
as the value of the `weaver-class` attribute on the `<context:load-time-weaver/>`
|
||||
element. Again, the following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver
|
||||
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The `LoadTimeWeaver` that is defined and registered by the configuration can be later
|
||||
retrieved from the Spring container by using the well known name, `loadTimeWeaver`.
|
||||
@@ -823,6 +956,7 @@ possible values:
|
||||
then AspectJ weaving is on. Otherwise, it is off. This is the default value.
|
||||
|===
|
||||
|
||||
|
||||
[[aop-aj-ltw-environments]]
|
||||
=== Environment-specific Configuration
|
||||
|
||||
@@ -871,3 +1005,7 @@ Note that this requires modification of the JVM launch script, which may prevent
|
||||
from using this in application server environments (depending on your server and your
|
||||
operation policies). That said, for one-app-per-JVM deployments such as standalone
|
||||
Spring Boot applications, you typically control the entire JVM setup in any case.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ This chapter covers Spring's Ahead of Time (AOT) optimizations.
|
||||
|
||||
For AOT support specific to integration tests, see xref:testing/testcontext-framework/aot.adoc[Ahead of Time Support for Tests].
|
||||
|
||||
|
||||
[[aot.introduction]]
|
||||
== Introduction to Ahead of Time Optimizations
|
||||
|
||||
@@ -18,9 +17,9 @@ Applying such optimizations early implies the following restrictions:
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration, needs to be chosen at build time and is automatically enabled at runtime when AOT is enabled.
|
||||
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead of time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time.
|
||||
* Beans registered as singletons (using `registerSingleton`, typically from
|
||||
`ConfigurableListableBeanFactory`) cannot be transformed ahead of time either.
|
||||
`ConfigurableListableBeanFactory`) cannot be transformed ahead-of-time either.
|
||||
* As we cannot rely on the instance, make sure that the bean type is as precise as
|
||||
possible.
|
||||
|
||||
@@ -36,9 +35,8 @@ A Spring AOT processed application typically generates:
|
||||
NOTE: At the moment, AOT is focused on allowing Spring applications to be deployed as native images using GraalVM.
|
||||
We intend to support more JVM-based use cases in future generations.
|
||||
|
||||
|
||||
[[aot.basics]]
|
||||
== AOT Engine Overview
|
||||
== AOT engine overview
|
||||
|
||||
The entry point of the AOT engine for processing an `ApplicationContext` is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
|
||||
|
||||
@@ -53,7 +51,6 @@ The `RuntimeHints` instance can also be used to generate the relevant GraalVM na
|
||||
|
||||
Those steps are covered in greater detail in the sections below.
|
||||
|
||||
|
||||
[[aot.refresh]]
|
||||
== Refresh for AOT Processing
|
||||
|
||||
@@ -91,7 +88,6 @@ This makes sure to create any proxy that will be required at runtime.
|
||||
|
||||
Once this part completes, the `BeanFactory` contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiation but allows the AOT engine to inspect the beans that will be created at runtime.
|
||||
|
||||
|
||||
[[aot.bean-factory-initialization-contributions]]
|
||||
== Bean Factory Initialization AOT Contributions
|
||||
|
||||
@@ -110,10 +106,11 @@ Consequently, such a bean is automatically excluded from the AOT-optimized conte
|
||||
[NOTE]
|
||||
====
|
||||
If a bean implements the `BeanFactoryInitializationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing.
|
||||
We generally recommend that this interface is only implemented by infrastructure beans, such as a `BeanFactoryPostProcessor`, which have limited dependencies and are already initialized early in the bean factory lifecycle.
|
||||
We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle.
|
||||
If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized.
|
||||
====
|
||||
|
||||
|
||||
[[aot.bean-registration-contributions]]
|
||||
=== Bean Registration AOT Contributions
|
||||
|
||||
@@ -130,7 +127,7 @@ Typically used when the bean definition needs to be tuned for specific features
|
||||
[NOTE]
|
||||
====
|
||||
If a bean implements the `BeanRegistrationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing.
|
||||
We generally recommend that this interface is only implemented by infrastructure beans, such as a `BeanFactoryPostProcessor`, which have limited dependencies and are already initialized early in the bean factory lifecycle.
|
||||
We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle.
|
||||
If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized.
|
||||
====
|
||||
|
||||
@@ -143,7 +140,7 @@ Taking our previous example, let's assume that `DataSourceConfiguration` is as f
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class DataSourceConfiguration {
|
||||
@@ -158,7 +155,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DataSourceConfiguration {
|
||||
@@ -179,7 +176,7 @@ The AOT engine will convert the configuration class above to code similar to the
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0]
|
||||
[source,java,indent=0,role="primary"]
|
||||
----
|
||||
/**
|
||||
* Bean definitions for {@link DataSourceConfiguration}
|
||||
@@ -222,23 +219,21 @@ NOTE: The exact code generated may differ depending on the exact nature of your
|
||||
TIP: Each generated class is annotated with `org.springframework.aot.generate.Generated` to
|
||||
identify them if they need to be excluded, for instance by static analysis tools.
|
||||
|
||||
The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection at all if possible.
|
||||
The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible.
|
||||
There is a bean definition for `dataSourceConfiguration` and one for `dataSourceBean`.
|
||||
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
|
||||
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
|
||||
|
||||
|
||||
[[aot.running]]
|
||||
== Running with AOT Optimizations
|
||||
== Running with AOT optimizations
|
||||
|
||||
AOT is a mandatory step to transform a Spring application to a native executable, so it
|
||||
is automatically enabled when running within a native image. However it is also possible to use AOT optimizations
|
||||
is automatically enabled when running in this mode. It is possible to use those optimizations
|
||||
on the JVM by setting the `spring.aot.enabled` System property to `true`.
|
||||
|
||||
NOTE: When AOT optimizations are included, some decisions that have been made at build time
|
||||
are hard coded in the application setup. For instance, profiles that have been enabled at
|
||||
build time are automatically enabled at runtime as well.
|
||||
|
||||
NOTE: When AOT optimizations are included, some decisions that have been taken at build-time
|
||||
are hard-coded in the application setup. For instance, profiles that have been enabled at
|
||||
build-time are automatically enabled at runtime as well.
|
||||
|
||||
[[aot.bestpractices]]
|
||||
== Best Practices
|
||||
@@ -249,7 +244,7 @@ However, keep in mind that some optimizations are made at build time based on a
|
||||
This section lists the best practices that make sure your application is ready for AOT.
|
||||
|
||||
[[aot.bestpractices.bean-registration]]
|
||||
=== Programmatic Bean Registration
|
||||
== Programmatic bean registration
|
||||
|
||||
The AOT engine takes care of the `@Configuration` model and any callback that might be
|
||||
invoked as part of processing your configuration. If you need to register additional
|
||||
@@ -271,19 +266,19 @@ notion of a classpath. For cases like this, it is crucial that the scanning happ
|
||||
build time.
|
||||
|
||||
[[aot.bestpractices.bean-type]]
|
||||
=== Expose the Most Precise Bean Type
|
||||
=== Expose The Most Precise Bean Type
|
||||
|
||||
While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type.
|
||||
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods.
|
||||
|
||||
For `@Configuration` classes, make sure that the return type of an `@Bean` factory method is as precise as possible.
|
||||
For `@Configuration` classes, make sure that the return type of the factory `@Bean` method is as precise as possible.
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
@@ -295,32 +290,19 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
fun myInterface(): MyInterface = MyImplementation()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the example above, the declared type for the `myInterface` bean is `MyInterface`.
|
||||
During AOT processing, none of the usual post-processing will take `MyImplementation` into account.
|
||||
For instance, if there is an annotated handler method on `MyImplementation` that the context should register, it will not be detected during AOT processing.
|
||||
In the example above, the declared type for the `myInterface` bean is `MyInterface`.
|
||||
None of the usual post-processing will take `MyImplementation` into account.
|
||||
For instance, if there is an annotated handler method on `MyImplementation` that the context should register, it won’t be detected upfront.
|
||||
|
||||
The example above should therefore be rewritten as follows:
|
||||
The example above should be rewritten as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
@@ -332,19 +314,6 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
fun myInterface() = MyImplementation()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you are registering bean definitions programmatically, consider using `RootBeanBefinition` as it allows to specify a `ResolvableType` that handles generics.
|
||||
@@ -353,28 +322,15 @@ If you are registering bean definitions programmatically, consider using `RootBe
|
||||
=== Avoid Multiple Constructors
|
||||
|
||||
The container is able to choose the most appropriate constructor to use based on several candidates.
|
||||
However, relying on that is not a best practice, and flagging the preferred constructor with `@Autowired` if necessary is preferred.
|
||||
However, this is not a best practice and flagging the preferred constructor with `@Autowired` if necessary is preferred.
|
||||
|
||||
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
|
||||
[[aot.bestpractices.complex-data-structures]]
|
||||
=== Avoid Complex Data Structures for Constructor Parameters and Properties
|
||||
|
||||
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
|
||||
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
|
||||
|
||||
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
|
||||
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
|
||||
Rather than using such structures, decomposing to simple types or referring to a bean that is built as such is recommended.
|
||||
|
||||
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
|
||||
To use it, register its fully-qualified name in `META-INF/spring/aot.factories` using `org.springframework.aot.generate.ValueCodeGenerator$Delegate` as the key.
|
||||
|
||||
[[aot.bestpractices.custom-arguments]]
|
||||
=== Avoid Creating Beans with Custom Arguments
|
||||
=== Avoid Creating Bean with Custom Arguments
|
||||
|
||||
Spring AOT detects what needs to be done to create a bean and translates that into generated code that uses an instance supplier.
|
||||
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] which can lead to several issues with AOT:
|
||||
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
|
||||
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
@@ -383,25 +339,11 @@ For instance, autowiring on fields and methods will be skipped as they are handl
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
[[aot.bestpractices.circular-dependencies]]
|
||||
=== Avoid Circular Dependencies
|
||||
|
||||
Certain use cases can result in circular dependencies between one or more beans. With the
|
||||
regular runtime, it may be possible to wire those circular dependencies via `@Autowired`
|
||||
on setter methods or fields. However, an AOT-optimized context will fail to start with
|
||||
explicit circular dependencies.
|
||||
|
||||
In an AOT-optimized application, you should therefore strive to avoid circular
|
||||
dependencies. If that is not possible, you can use `@Lazy` injection points or
|
||||
`ObjectProvider` to lazily access or retrieve the necessary collaborating beans. See
|
||||
xref:core/beans/classpath-scanning.adoc#beans-factorybeans-annotations-lazy-injection-points[this tip]
|
||||
for further information.
|
||||
|
||||
[[aot.bestpractices.factory-bean]]
|
||||
=== FactoryBean
|
||||
|
||||
`FactoryBean` should be used with care as it introduces an intermediate layer in terms of bean type resolution that may not be conceptually necessary.
|
||||
As a rule of thumb, if a `FactoryBean` instance does not hold long-term state and is not needed at a later point at runtime, it should be replaced by a regular `@Bean` factory method, possibly with a `FactoryBean` adapter layer on top (for declarative configuration purposes).
|
||||
As a rule of thumb, if the `FactoryBean` instance does not hold long-term state and is not needed at a later point in time at runtime, it should be replaced by a regular factory method, possibly with a `FactoryBean` adapter layer on top (for declarative configuration purposes).
|
||||
|
||||
If your `FactoryBean` implementation does not resolve the object type (i.e. `T`), extra care is necessary.
|
||||
Consider the following example:
|
||||
@@ -410,21 +352,12 @@ Consider the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class ClientFactoryBean<T : AbstractClient> : FactoryBean<T> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
A concrete client declaration should provide a resolved generic for the client, as shown in the following example:
|
||||
@@ -433,7 +366,7 @@ A concrete client declaration should provide a resolved generic for the client,
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
@@ -445,22 +378,9 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
fun myClient() = ClientFactoryBean<MyClient>(...)
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If a `FactoryBean` bean definition is registered programmatically, make sure to follow these steps:
|
||||
If the `FactoryBean` bean definition is registered programmatically, make sure to follow these steps:
|
||||
|
||||
1. Use `RootBeanDefinition`.
|
||||
2. Set the `beanClass` to the `FactoryBean` class so that AOT knows that it is an intermediate layer.
|
||||
@@ -472,22 +392,12 @@ The following example showcases a basic definition:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition)
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition);
|
||||
----
|
||||
======
|
||||
|
||||
@@ -500,7 +410,7 @@ The JPA persistence unit has to be known upfront for certain optimizations to ap
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
|
||||
@@ -510,29 +420,16 @@ Java::
|
||||
return factoryBean;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
fun customDBEntityManagerFactory(dataSource: DataSource): LocalContainerEntityManagerFactoryBean {
|
||||
val factoryBean = LocalContainerEntityManagerFactoryBean()
|
||||
factoryBean.dataSource = dataSource
|
||||
factoryBean.setPackagesToScan("com.example.app")
|
||||
return factoryBean
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To ensure that entity scanning occurs ahead of time, a `PersistenceManagedTypes` bean must be declared and used by the
|
||||
To make sure the scanning occurs ahead of time, a `PersistenceManagedTypes` bean must be declared and used by the
|
||||
factory bean definition, as shown by the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
|
||||
@@ -548,28 +445,8 @@ Java::
|
||||
return factoryBean;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
fun persistenceManagedTypes(resourceLoader: ResourceLoader): PersistenceManagedTypes {
|
||||
return PersistenceManagedTypesScanner(resourceLoader)
|
||||
.scan("com.example.app")
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun customDBEntityManagerFactory(dataSource: DataSource, managedTypes: PersistenceManagedTypes): LocalContainerEntityManagerFactoryBean {
|
||||
val factoryBean = LocalContainerEntityManagerFactoryBean()
|
||||
factoryBean.dataSource = dataSource
|
||||
factoryBean.setManagedTypes(managedTypes)
|
||||
return factoryBean
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[aot.hints]]
|
||||
== Runtime Hints
|
||||
|
||||
@@ -585,17 +462,10 @@ The following example makes sure that `config/app.properties` can be loaded from
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
runtimeHints.resources().registerPattern("config/app.properties");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
runtimeHints.resources().registerPattern("config/app.properties")
|
||||
----
|
||||
======
|
||||
|
||||
A number of contracts are handled automatically during AOT processing.
|
||||
@@ -604,130 +474,68 @@ For instance, the return type of a `@Controller` method is inspected, and releva
|
||||
For cases that the core container cannot infer, you can register such hints programmatically.
|
||||
A number of convenient annotations are also provided for common use cases.
|
||||
|
||||
|
||||
[[aot.hints.import-runtime-hints]]
|
||||
=== `@ImportRuntimeHints`
|
||||
|
||||
{spring-framework-api}/aot/hint/RuntimeHintsRegistrar.html[`RuntimeHintsRegistrar`]
|
||||
implementations allow you to get a callback to the `RuntimeHints` instance managed by the
|
||||
AOT engine. Implementations of this interface can be registered using
|
||||
{spring-framework-api}/context/annotation/ImportRuntimeHints.html[`@ImportRuntimeHints`]
|
||||
on any Spring bean or `@Bean` factory method. `RuntimeHintsRegistrar` implementations are
|
||||
detected and invoked at build time.
|
||||
`RuntimeHintsRegistrar` implementations allow you to get a callback to the `RuntimeHints` instance managed by the AOT engine.
|
||||
Implementations of this interface can be registered using `@ImportRuntimeHints` on any Spring bean or `@Bean` factory method.
|
||||
`RuntimeHintsRegistrar` implementations are detected and invoked at build time.
|
||||
|
||||
include-code::./SpellCheckService[]
|
||||
|
||||
If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints.
|
||||
This way, if the component is not contributed to the `BeanFactory`, the hints will not be contributed either.
|
||||
This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either.
|
||||
|
||||
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the `RuntimeHintsRegistrar` interface.
|
||||
|
||||
|
||||
[[aot.hints.reflective]]
|
||||
=== `@Reflective`
|
||||
|
||||
{spring-framework-api}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element.
|
||||
For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection.
|
||||
|
||||
Out-of-the-box, only Spring beans are considered, but you can opt-in for scanning using
|
||||
{spring-framework-api}/context/annotation/ReflectiveScan.html[`@ReflectiveScan`]. In the
|
||||
example below, all types in the `com.example.app` package and its subpackages are
|
||||
considered:
|
||||
|
||||
include-code::./MyConfiguration[]
|
||||
|
||||
Scanning happens during AOT processing, and the types in the target packages do not need to have a class-level annotation to be considered.
|
||||
This performs a _deep scan_, and the presence of `@Reflective`, either directly or as a meta-annotation, is checked on types, fields, constructors, methods, and enclosed elements.
|
||||
|
||||
By default, `@Reflective` registers an invocation hint for the annotated element.
|
||||
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the `@Reflective` annotation.
|
||||
By default, only Spring beans are considered, and an invocation hint is registered for the annotated element.
|
||||
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the
|
||||
`@Reflective` annotation.
|
||||
|
||||
Library authors can reuse this annotation for their own purposes.
|
||||
An example of such customization is covered in the next section.
|
||||
If components other than Spring beans need to be processed, a `BeanFactoryInitializationAotProcessor` can detect the relevant types and use `ReflectiveRuntimeHintsRegistrar` to process them.
|
||||
|
||||
[[aot.hints.register-reflection]]
|
||||
=== `@RegisterReflection`
|
||||
|
||||
{spring-framework-api}/aot/hint/annotation/RegisterReflection.html[`@RegisterReflection`] is a specialization of `@Reflective` that provides a declarative way to register reflection for arbitrary types.
|
||||
[[aot.hints.register-reflection-for-binding]]
|
||||
=== `@RegisterReflectionForBinding`
|
||||
|
||||
NOTE: As a specialization of `@Reflective`, `@RegisterReflection` is also detected if you are using `@ReflectiveScan`.
|
||||
|
||||
In the following example, public constructors and public methods can be invoked via reflection on `AccountService`:
|
||||
|
||||
include-code::./MyConfiguration[tag=snippet,indent=0]
|
||||
|
||||
`@RegisterReflection` can be applied to any target type at the class level, but it can also be applied directly to a method to better indicate where the hints are actually required.
|
||||
|
||||
`@RegisterReflection` can be used as a meta-annotation to support more specific needs.
|
||||
{spring-framework-api}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a composed annotation that is meta-annotated with `@RegisterReflection` and registers the need for serializing arbitrary types.
|
||||
{spring-framework-api}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a specialization of `@Reflective` that registers the need for serializing arbitrary types.
|
||||
A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body.
|
||||
|
||||
The following example registers `Order` for serialization.
|
||||
|
||||
include-code::./OrderService[tag=snippet,indent=0]
|
||||
|
||||
This registers hints for constructors, fields, properties, and record components of `Order`.
|
||||
Hints are also registered for types transitively used on properties and record components.
|
||||
In other words, if `Order` exposes others types, hints are registered for those as well.
|
||||
|
||||
[[aot.hints.convention-based-conversion]]
|
||||
=== Runtime Hints for Convention-based Conversion
|
||||
|
||||
Although the core container provides built-in support for automatic conversion of many
|
||||
common types (see xref:core/validation/convert.adoc[Spring Type Conversion]), some
|
||||
conversions are supported via a convention-based algorithm that relies on reflection.
|
||||
|
||||
Specifically, if there is no explicit `Converter` registered with the `ConversionService`
|
||||
for a particular source → target type pair, the internal `ObjectToObjectConverter`
|
||||
will attempt to use conventions to convert a source object to a target type by delegating
|
||||
to a method on the source object or to a static factory method or constructor on the
|
||||
target type. Since this convention-based algorithm can be applied to arbitrary types at
|
||||
runtime, the core container is not able to infer the runtime hints necessary to support
|
||||
such reflection.
|
||||
|
||||
If you encounter convention-based conversion issues within a native image resulting from
|
||||
lacking runtime hints, you can register the necessary hints programmatically. For
|
||||
example, if your application requires a conversion from `java.time.Instant` to
|
||||
`java.sql.Timestamp` and relies on `ObjectToObjectConverter` to invoke
|
||||
`java.sql.Timestamp.from(Instant)` using reflection, you could implement a custom
|
||||
`RuntimeHintsRegitrar` to support this use case within a native image, as demonstrated in
|
||||
the following example.
|
||||
`@RegisterReflectionForBinding` can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required.
|
||||
The following example registers `Account` for serialization.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class TimestampConversionRuntimeHints implements RuntimeHintsRegistrar {
|
||||
@Component
|
||||
public class OrderService {
|
||||
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
ReflectionHints reflectionHints = hints.reflection();
|
||||
@RegisterReflectionForBinding(Account.class)
|
||||
public void process(Order order) {
|
||||
// ...
|
||||
}
|
||||
|
||||
reflectionHints.registerTypeIfPresent(classLoader, "java.sql.Timestamp", hint -> hint
|
||||
.withMethod("from", List.of(TypeReference.of(Instant.class)), ExecutableMode.INVOKE)
|
||||
.onReachableType(TypeReference.of("java.sql.Timestamp")));
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
`TimestampConversionRuntimeHints` can then be registered declaratively via
|
||||
<<aot.hints.import-runtime-hints>> or statically via a `META-INF/spring/aot.factories`
|
||||
configuration file.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The above `TimestampConversionRuntimeHints` class is a simplified version of the
|
||||
`ObjectToObjectConverterRuntimeHints` class that is included in the framework and
|
||||
registered by default.
|
||||
|
||||
Thus, this specific `Instant`-to-`Timestamp` use case is already handled by the framework.
|
||||
====
|
||||
|
||||
[[aot.hints.testing]]
|
||||
=== Testing Runtime Hints
|
||||
|
||||
Spring Core also ships `RuntimeHintsPredicates`, a utility for checking that existing hints match a particular use case.
|
||||
This can be used in your own tests to validate that a `RuntimeHintsRegistrar` produces the expected results.
|
||||
This can be used in your own tests to validate that a `RuntimeHintsRegistrar` contains the expected results.
|
||||
We can write a test for our `SpellCheckService` and ensure that we will be able to load a dictionary at runtime:
|
||||
|
||||
include-code::./SpellCheckServiceTests[tag=hintspredicates]
|
||||
@@ -753,7 +561,7 @@ If you forgot to contribute a hint, the test will fail and provide some details
|
||||
[source,txt,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
|
||||
INFO: Spring version: 6.2.0
|
||||
INFO: Spring version:6.0.0-SNAPSHOT
|
||||
|
||||
Missing <"ReflectionHints"> for invocation <java.lang.Class#forName>
|
||||
with arguments ["org.springframework.core.SpringVersion",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Spring's extensible XML configuration mechanism is based on XML Schema. If you a
|
||||
familiar with Spring's current XML configuration extensions that come with the standard
|
||||
Spring distribution, you should first read the previous section on xref:core/appendix/xsd-schemas.adoc[XML Schemas].
|
||||
|
||||
|
||||
To create new XML configuration extensions:
|
||||
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-schema[Author] an XML schema to describe your custom element(s).
|
||||
@@ -37,6 +38,7 @@ examples follow later in this appendix. The intent of this first simple example
|
||||
through the basic steps of making a custom extension.)
|
||||
|
||||
|
||||
|
||||
[[xsd-custom-schema]]
|
||||
== Authoring the Schema
|
||||
|
||||
@@ -108,6 +110,7 @@ can use autocompletion to let a user choose between several configuration option
|
||||
defined in the enumeration.
|
||||
|
||||
|
||||
|
||||
[[xsd-custom-namespacehandler]]
|
||||
== Coding a `NamespaceHandler`
|
||||
|
||||
@@ -142,7 +145,7 @@ use the `NamespaceHandlerSupport` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
|
||||
@@ -158,7 +161,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
|
||||
@@ -184,6 +187,7 @@ means that each `BeanDefinitionParser` contains only the logic for parsing a sin
|
||||
custom element, as we can see in the next step.
|
||||
|
||||
|
||||
|
||||
[[xsd-custom-parser]]
|
||||
== Using `BeanDefinitionParser`
|
||||
|
||||
@@ -198,7 +202,7 @@ we can parse our custom XML content, as you can see in the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
|
||||
@@ -236,7 +240,7 @@ single `BeanDefinition` represents.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
|
||||
@@ -272,11 +276,13 @@ the basic grunt work of creating a single `BeanDefinition`.
|
||||
single `BeanDefinition` represents.
|
||||
======
|
||||
|
||||
|
||||
In this simple case, this is all that we need to do. The creation of our single
|
||||
`BeanDefinition` is handled by the `AbstractSingleBeanDefinitionParser` superclass, as
|
||||
is the extraction and setting of the bean definition's unique identifier.
|
||||
|
||||
|
||||
|
||||
[[xsd-custom-registration]]
|
||||
== Registering the Handler and the Schema
|
||||
|
||||
@@ -288,6 +294,7 @@ can, for example, be distributed alongside your binary classes in a JAR file. Th
|
||||
XML parsing infrastructure automatically picks up your new extension by consuming
|
||||
these special properties files, the formats of which are detailed in the next two sections.
|
||||
|
||||
|
||||
[[xsd-custom-registration-spring-handlers]]
|
||||
=== Writing `META-INF/spring.handlers`
|
||||
|
||||
@@ -306,6 +313,7 @@ The first part (the key) of the key-value pair is the URI associated with your c
|
||||
namespace extension and needs to exactly match exactly the value of the `targetNamespace`
|
||||
attribute, as specified in your custom XSD schema.
|
||||
|
||||
|
||||
[[xsd-custom-registration-spring-schemas]]
|
||||
=== Writing 'META-INF/spring.schemas'
|
||||
|
||||
@@ -329,6 +337,7 @@ You are encouraged to deploy your XSD file (or files) right alongside
|
||||
the `NamespaceHandler` and `BeanDefinitionParser` classes on the classpath.
|
||||
|
||||
|
||||
|
||||
[[xsd-custom-using]]
|
||||
== Using a Custom Extension in Your Spring XML Configuration
|
||||
|
||||
@@ -362,11 +371,13 @@ in a Spring XML configuration file:
|
||||
<1> Our custom bean.
|
||||
|
||||
|
||||
|
||||
[[xsd-custom-meat]]
|
||||
== More Detailed Examples
|
||||
|
||||
This section presents some more detailed examples of custom XML extensions.
|
||||
|
||||
|
||||
[[xsd-custom-custom-nested]]
|
||||
=== Nesting Custom Elements within Custom Elements
|
||||
|
||||
@@ -405,7 +416,7 @@ The following listing shows the `Component` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -438,7 +449,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -469,7 +480,7 @@ setter property for the `components` property. The following listing shows such
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -511,7 +522,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -587,7 +598,7 @@ we then create a custom `NamespaceHandler`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -603,7 +614,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -626,7 +637,7 @@ listing shows our custom `BeanDefinitionParser` implementation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -677,7 +688,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -742,6 +753,7 @@ http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
|
||||
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
|
||||
----
|
||||
|
||||
|
||||
[[xsd-custom-custom-just-attributes]]
|
||||
=== Custom Attributes on "`Normal`" Elements
|
||||
|
||||
@@ -775,7 +787,7 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -795,7 +807,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -831,7 +843,7 @@ Next, we need to create the associated `NamespaceHandler`, as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -849,7 +861,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -874,7 +886,7 @@ The following listing shows our `BeanDefinitionDecorator` implementation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -930,7 +942,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -995,3 +1007,5 @@ http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
|
||||
# in 'META-INF/spring.schemas'
|
||||
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
This part of the appendix lists XML schemas related to the core container.
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-util]]
|
||||
== The `util` Schema
|
||||
|
||||
@@ -28,6 +29,7 @@ correct schema so that the tags in the `util` namespace are available to you):
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[xsd-schemas-util-constant]]
|
||||
=== Using `<util:constant/>`
|
||||
|
||||
@@ -119,7 +121,7 @@ The following example enumeration shows how easy injecting an enum value is:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package jakarta.persistence;
|
||||
|
||||
@@ -132,7 +134,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package jakarta.persistence
|
||||
|
||||
@@ -150,7 +152,7 @@ Now consider the following setter of type `PersistenceContextType` and the corre
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package example;
|
||||
|
||||
@@ -166,7 +168,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package example
|
||||
|
||||
@@ -184,6 +186,7 @@ Kotlin::
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
[[xsd-schemas-util-property-path]]
|
||||
=== Using `<util:property-path/>`
|
||||
|
||||
@@ -305,6 +308,7 @@ You can specifically set the result type in the actual definition. This is not n
|
||||
for most use cases, but it can sometimes be useful. See the javadoc for more info on
|
||||
this feature.
|
||||
|
||||
|
||||
[[xsd-schemas-util-properties]]
|
||||
=== Using `<util:properties/>`
|
||||
|
||||
@@ -330,6 +334,7 @@ The following example uses a `util:properties` element to make a more concise re
|
||||
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
|
||||
----
|
||||
|
||||
|
||||
[[xsd-schemas-util-list]]
|
||||
=== Using `<util:list/>`
|
||||
|
||||
@@ -384,6 +389,7 @@ following configuration:
|
||||
|
||||
If no `list-class` attribute is supplied, the container chooses a `List` implementation.
|
||||
|
||||
|
||||
[[xsd-schemas-util-map]]
|
||||
=== Using `<util:map/>`
|
||||
|
||||
@@ -438,6 +444,7 @@ following configuration:
|
||||
|
||||
If no `'map-class'` attribute is supplied, the container chooses a `Map` implementation.
|
||||
|
||||
|
||||
[[xsd-schemas-util-set]]
|
||||
=== Using `<util:set/>`
|
||||
|
||||
@@ -493,6 +500,7 @@ following configuration:
|
||||
If no `set-class` attribute is supplied, the container chooses a `Set` implementation.
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-aop]]
|
||||
== The `aop` Schema
|
||||
|
||||
@@ -522,6 +530,7 @@ are available to you):
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-context]]
|
||||
== The `context` Schema
|
||||
|
||||
@@ -546,6 +555,7 @@ available to you:
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[xsd-schemas-context-pphc]]
|
||||
=== Using `<property-placeholder/>`
|
||||
|
||||
@@ -589,25 +599,34 @@ element for that purpose. Similarly, Spring's
|
||||
xref:integration/cache/annotations.adoc[caching annotations] need to be explicitly
|
||||
xref:integration/cache/annotations.adoc#cache-annotation-enable[enabled] as well.
|
||||
|
||||
|
||||
[[xsd-schemas-context-component-scan]]
|
||||
=== Using `<component-scan/>`
|
||||
|
||||
This element is detailed in the section on xref:core/beans/annotation-config.adoc[annotation-based container configuration].
|
||||
This element is detailed in the section on xref:core/beans/annotation-config.adoc[annotation-based container configuration]
|
||||
.
|
||||
|
||||
|
||||
[[xsd-schemas-context-ltw]]
|
||||
=== Using `<load-time-weaver/>`
|
||||
|
||||
This element is detailed in the section on xref:core/aop/using-aspectj.adoc#aop-aj-ltw[load-time weaving with AspectJ in the Spring Framework].
|
||||
This element is detailed in the section on xref:core/aop/using-aspectj.adoc#aop-aj-ltw[load-time weaving with AspectJ in the Spring Framework]
|
||||
.
|
||||
|
||||
|
||||
[[xsd-schemas-context-sc]]
|
||||
=== Using `<spring-configured/>`
|
||||
|
||||
This element is detailed in the section on xref:core/aop/using-aspectj.adoc#aop-atconfigurable[using AspectJ to dependency inject domain objects with Spring].
|
||||
This element is detailed in the section on xref:core/aop/using-aspectj.adoc#aop-atconfigurable[using AspectJ to dependency inject domain objects with Spring]
|
||||
.
|
||||
|
||||
|
||||
[[xsd-schemas-context-mbe]]
|
||||
=== Using `<mbean-export/>`
|
||||
|
||||
This element is detailed in the section on xref:integration/jmx/naming.adoc#jmx-context-mbeanexport[configuring annotation-based MBean export].
|
||||
This element is detailed in the section on xref:integration/jmx/naming.adoc#jmx-context-mbeanexport[configuring annotation-based MBean export]
|
||||
.
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-beans]]
|
||||
@@ -647,3 +666,7 @@ as it stands).
|
||||
|
||||
In the case of the preceding example, you could assume that there is some logic that consumes
|
||||
the bean definition and sets up some caching infrastructure that uses the supplied metadata.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
This chapter covers Spring's Inversion of Control (IoC) container.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ can be found in the xref:core/beans/standard-annotations.adoc[relevant section].
|
||||
[NOTE]
|
||||
====
|
||||
Annotation injection is performed before external property injection. Thus, external
|
||||
configuration (for example, XML-specified bean properties) effectively overrides the annotations
|
||||
configuration (e.g. XML-specified bean properties) effectively overrides the annotations
|
||||
for properties when wired through mixed approaches.
|
||||
====
|
||||
|
||||
@@ -62,3 +62,6 @@ application context in which it is defined. This means that, if you put
|
||||
it only checks for `@Autowired` beans in your controllers, and not your services. See
|
||||
xref:web/webmvc/mvc-servlet.adoc[The DispatcherServlet] for more information.
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
+10
-50
@@ -1,5 +1,5 @@
|
||||
[[beans-autowired-annotation-primary]]
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary` or `@Fallback`
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary`
|
||||
|
||||
Because autowiring by type may lead to multiple candidates, it is often necessary to have
|
||||
more control over the selection process. One way to accomplish this is with Spring's
|
||||
@@ -15,7 +15,7 @@ primary `MovieCatalog`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class MovieConfiguration {
|
||||
@@ -33,7 +33,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MovieConfiguration {
|
||||
@@ -50,57 +50,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, as of 6.2, there is a `@Fallback` annotation for demarcating
|
||||
any beans other than the regular ones to be injected. If only one regular
|
||||
bean is left, it is effectively primary as well:
|
||||
With the preceding configuration, the following `MovieRecommender` is autowired with the
|
||||
`firstMovieCatalog`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
public class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
public MovieCatalog firstMovieCatalog() { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
public MovieCatalog secondMovieCatalog() { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
fun firstMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
fun secondMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
With both variants of the preceding configuration, the following
|
||||
`MovieRecommender` is autowired with the `firstMovieCatalog`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -113,7 +70,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -152,3 +109,6 @@ The corresponding bean definitions follow:
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user