Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25db8f2ca7 |
@@ -1,10 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*.{adoc,bat,groovy,html,java,js,jsp,kt,kts,md,properties,py,rb,sh,sql,svg,txt,xml,xsd}]
|
||||
charset = utf-8
|
||||
|
||||
[*.{groovy,java,kt,kts,xml,xsd}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
continuation_indent_size = 8
|
||||
end_of_line = lf
|
||||
@@ -1,16 +0,0 @@
|
||||
# Normalize line endings to LF.
|
||||
* text eol=lf
|
||||
|
||||
# Ensure that line endings for multipart files in spring-web are not modified.
|
||||
*.multipart -text
|
||||
|
||||
# Ensure that line endings for DOS batch files are not modified.
|
||||
*.bat -text
|
||||
|
||||
# Ensure the following are treated as binary.
|
||||
*.gif binary
|
||||
*.jar binary
|
||||
*.jpeg binary
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.vsd binary
|
||||
@@ -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,28 +0,0 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
- title: ":lady_beetle: Bug Fixes"
|
||||
labels:
|
||||
- "type: bug"
|
||||
- "type: regression"
|
||||
- title: ":notebook_with_decorative_cover: Documentation"
|
||||
labels:
|
||||
- "type: documentation"
|
||||
- title: ":hammer: Dependency Upgrades"
|
||||
sort: "title"
|
||||
labels:
|
||||
- "type: dependency-upgrade"
|
||||
contributors:
|
||||
exclude:
|
||||
names:
|
||||
- "bclozel"
|
||||
- "jhoeller"
|
||||
- "poutsma"
|
||||
- "rstoyanchev"
|
||||
- "sbrannen"
|
||||
- "sdeleuze"
|
||||
- "simonbasle"
|
||||
- "snicoll"
|
||||
@@ -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@v5
|
||||
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@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
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 +0,0 @@
|
||||
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
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Variables
|
||||
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
|
||||
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
|
||||
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
|
||||
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@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
shell: bash
|
||||
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
|
||||
- name: Sync
|
||||
uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0
|
||||
with:
|
||||
token: ${{ inputs.central-token-password }}
|
||||
token-name: ${{ inputs.central-token-username }}
|
||||
- name: Await
|
||||
uses: ./.github/actions/await-http-resource
|
||||
with:
|
||||
url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/spring-context/{0}/spring-context-{0}.jar', inputs.spring-framework-version) }}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"aql": {
|
||||
"items.find": {
|
||||
"$and": [
|
||||
{
|
||||
"@build.name": "${buildName}",
|
||||
"@build.number": "${buildNumber}",
|
||||
"path": {
|
||||
"$nmatch": "org/springframework/framework-api/*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": "nexus/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Backport Bot
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Download BackportBot
|
||||
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
|
||||
- name: Backport
|
||||
env:
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
|
||||
@@ -1,58 +0,0 @@
|
||||
name: Build and Deploy Snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
name: Build and Deploy Snapshot
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: '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()
|
||||
uses: ./.github/actions/send-notification
|
||||
with:
|
||||
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
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 }}
|
||||
@@ -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@v6
|
||||
- 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@v6
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
@@ -1,56 +0,0 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- id: ubuntu-latest
|
||||
name: Linux
|
||||
java:
|
||||
- version: 17
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 25
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 17
|
||||
steps:
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: |
|
||||
git config --global core.autocrlf true
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- 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()
|
||||
uses: ./.github/actions/send-notification
|
||||
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 }}
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- '*.x'
|
||||
- '!gh-pages'
|
||||
tags:
|
||||
- 'v*'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
actions: write
|
||||
jobs:
|
||||
build:
|
||||
name: Dispatch docs deployment
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
- name: Dispatch (partial build)
|
||||
if: github.ref_type == 'branch'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
|
||||
- name: Dispatch (full build)
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
|
||||
@@ -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@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-staging-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
sync-to-maven-central:
|
||||
name: Sync to Maven Central
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
|
||||
central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
|
||||
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
promote-release:
|
||||
name: Promote Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- sync-to-maven-central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- promote-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
milestone: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -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@v6
|
||||
with:
|
||||
ref: 'v0.0.2'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
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@v6
|
||||
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,56 +1,27 @@
|
||||
# Miscellaneous
|
||||
*.java.hsp
|
||||
*.sonarj
|
||||
*.sw*
|
||||
.DS_Store
|
||||
.settings
|
||||
.springBeans
|
||||
target
|
||||
bin
|
||||
build.sh
|
||||
integration-repo
|
||||
ivy-cache
|
||||
argfile*
|
||||
activemq-data/
|
||||
classes/
|
||||
|
||||
# Log files
|
||||
jxl.log
|
||||
jmx.log
|
||||
derby.log
|
||||
|
||||
# Gradle artifacts
|
||||
.gradle
|
||||
.gradletasknamecache
|
||||
/build
|
||||
buildSrc/build
|
||||
/spring-*/build
|
||||
/framework-*/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
spring-jdbc/derby.log
|
||||
spring-test/test-output/
|
||||
|
||||
# Maven artifacts
|
||||
pom.xml
|
||||
/target/
|
||||
|
||||
# Eclipse artifacts, including WTP generated manifests
|
||||
bin
|
||||
.gradle
|
||||
build
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
argfile*
|
||||
pom.xml
|
||||
|
||||
# IDEA artifacts and output dirs
|
||||
# IDEA metadata and output dirs
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/*
|
||||
!.idea/icon.svg
|
||||
out
|
||||
test-output
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.13-librca
|
||||
@@ -0,0 +1,9 @@
|
||||
#com.springsource.sts.gradle.core.preferences.GradleImportPreferences
|
||||
#Thu Aug 9 11:34:43 CEST 2012
|
||||
enableAfterTasks=true
|
||||
afterTasks=afterEclipseImport;
|
||||
enableDependendencyManagement=false
|
||||
enableBeforeTasks=true
|
||||
projects=;spring-aop;spring-aspects;spring-beans;spring-context;spring-context-support;spring-core;spring-expression;spring-instrument;spring-instrument-tomcat;spring-jdbc;spring-jms;spring-orm;spring-oxm;spring-struts;spring-test;spring-tx;spring-web;spring-webmvc;spring-webmvc-portlet;
|
||||
enableDSLD=false
|
||||
beforeTasks=cleanEclipse;eclipse;\:spring-oxm\:compileTestJava;
|
||||
@@ -0,0 +1,5 @@
|
||||
#com.springsource.sts.gradle.core.preferences.GradleProjectPreferences
|
||||
#Tue Feb 21 14:38:31 CET 2012
|
||||
com.springsource.sts.gradle.classpath.enableSorting=false
|
||||
com.springsource.sts.gradle.rootprojectloc=
|
||||
com.springsource.sts.gradle.linkedresources=
|
||||
@@ -0,0 +1,9 @@
|
||||
#com.springsource.sts.gradle.core.actions.GradleRefreshPreferences
|
||||
#Thu Aug 9 11:34:43 CEST 2012
|
||||
enableAfterTasks=true
|
||||
afterTasks=afterEclipseImport;
|
||||
useHierarchicalNames=false
|
||||
enableBeforeTasks=true
|
||||
addResourceFilters=false
|
||||
enableDSLD=false
|
||||
beforeTasks=cleanEclipse;eclipse;\:spring-oxm\:compileTestJava;
|
||||
@@ -1,136 +1,307 @@
|
||||
# Contributing to the Spring Framework
|
||||
_Have something you'd like to contribute to the framework? We welcome pull
|
||||
requests, but ask that you carefully read this document first to understand how
|
||||
best to submit them; what kind of changes are likely to be accepted; and what
|
||||
to expect from the Spring team when evaluating your submission._
|
||||
|
||||
First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
_Please refer back to this document as a checklist before issuing any pull
|
||||
request; this will save time for everyone!_
|
||||
|
||||
### Table of Contents
|
||||
## Understand the basics
|
||||
|
||||
* [Code of Conduct](#code-of-conduct)
|
||||
* [How to Contribute](#how-to-contribute)
|
||||
* [Ask questions](#ask-questions)
|
||||
* [Create an Issue](#create-an-issue)
|
||||
* [Issue Lifecycle](#issue-lifecycle)
|
||||
* [Submit a Pull Request](#submit-a-pull-request)
|
||||
* [Build from Source](#build-from-source)
|
||||
* [Source Code Style](#source-code-style)
|
||||
* [Reference Docs](#reference-docs)
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework#coc-ov-file).
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
#### Ask questions
|
||||
|
||||
If you have a question, check Stack Overflow using
|
||||
[this list of tags](https://stackoverflow.com/questions/tagged/spring+or+spring-mvc+or+spring-aop+or+spring-jdbc+or+spring-transactions+or+spring-annotations+or+spring-jms+or+spring-el+or+spring-test+or+spring+or+spring-remoting+or+spring-orm+or+spring-jmx+or+spring-cache+or+spring-webflux?tab=Newest). Find an existing discussion, or start a new one if necessary.
|
||||
|
||||
If you believe there is an issue, search through
|
||||
[existing issues](https://github.com/spring-projects/spring-framework/issues) trying a
|
||||
few different ways to find discussions, past or current, that are related to the issue.
|
||||
Reading those discussions helps you to learn about the issue, and helps us to make a
|
||||
decision.
|
||||
Not sure what a pull request is, or how to submit one? Take a look at GitHub's
|
||||
excellent [help documentation][] first.
|
||||
|
||||
|
||||
#### Create an Issue
|
||||
## Search JIRA first; create an issue if necessary
|
||||
|
||||
Reporting an issue or making a feature request is a great way to contribute. Your feedback
|
||||
and the conversations that result from it provide a continuous flow of ideas. However,
|
||||
before creating a ticket, please take the time to [ask and research](#ask-questions) first.
|
||||
Is there already an issue that addresses your concern? Do a bit of searching
|
||||
in our [JIRA issue tracker][] to see if you can find something similar. If not,
|
||||
please create a new issue before submitting a pull request unless the change is
|
||||
truly trivial, e.g. typo fixes, removing compiler warnings, etc.
|
||||
|
||||
If you create an issue after a discussion on Stack Overflow, please provide a description
|
||||
in the issue instead of simply referring to Stack Overflow. The issue tracker is an
|
||||
important place of record for design discussions and should be self-sufficient.
|
||||
## Discuss non-trivial contribution ideas with committers
|
||||
|
||||
Once you're ready, create an issue on [GitHub](https://github.com/spring-projects/spring-framework/issues).
|
||||
If you're considering anything more than correcting a typo or fixing a minor
|
||||
bug, please discuss it on the [spring-framework-contrib][] mailing list before
|
||||
submitting a pull request. We're happy to provide guidance but please spend an
|
||||
hour or two researching the subject on your own including searching the mailing
|
||||
list for prior discussions.
|
||||
|
||||
Many issues are caused by subtle behavior, typos, and unintended configuration.
|
||||
Creating a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example)
|
||||
(starting with https://start.spring.io for example) of the problem helps the team
|
||||
quickly triage your issue and get to the core of the problem.
|
||||
## Sign the Contributor License Agreement
|
||||
|
||||
#### Issue Lifecycle
|
||||
If you have not previously done so, please fill out and submit the
|
||||
[SpringSource CLA form][]. You'll receive a token when this process is complete.
|
||||
Keep track of this, you may be asked for it later!
|
||||
|
||||
When an issue is first created, it is flagged `waiting-for-triage` waiting for a team
|
||||
member to triage it. Once the issue has been reviewed, the team may ask for further
|
||||
information if needed, and based on the findings, the issue is either assigned a target
|
||||
milestone or is closed with a specific status.
|
||||
Note that emailing/postal mailing a signed copy is _not_ necessary. Submission
|
||||
of the web form is all that is required.
|
||||
|
||||
When a fix is ready, the issue is closed and may still be re-opened until the fix is
|
||||
released. After that the issue will typically no longer be reopened. In rare cases if the
|
||||
issue was not at all fixed, the issue may be re-opened. In most cases however any
|
||||
follow-up reports will need to be created as new issues with a fresh description.
|
||||
When you've completed the web form, simply add the following in a comment on
|
||||
your pull request:
|
||||
|
||||
#### Submit a Pull Request
|
||||
I have signed and agree to the terms of the SpringSource Individual
|
||||
Contributor License Agreement.
|
||||
|
||||
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
|
||||
created, we will close the issue as superseded by the pull request, and the discussion
|
||||
about the issue will continue under the pull request.
|
||||
You do not need to include your token/id. Please add the statement above to all
|
||||
future pull requests as well, simply so the Spring Framework team knows
|
||||
immediately that this process is complete.
|
||||
|
||||
1. Always check out the `main` branch and submit pull requests against it
|
||||
(for target version see [settings.gradle](settings.gradle)).
|
||||
Backports to prior versions will be considered on a case-by-case basis and reflected as
|
||||
the fix version in the issue tracker.
|
||||
|
||||
1. Choose the granularity of your commits consciously and squash commits that represent
|
||||
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.
|
||||
## Create your branch from `master`
|
||||
|
||||
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).
|
||||
At any given time, Spring Framework's `master` branch represents the version
|
||||
currently under development. For example, if 3.1.1 was the latest Spring
|
||||
Framework release, `master` represents 3.2.0 development, and the `3.1.x`
|
||||
branch represents 3.1.2 development.
|
||||
|
||||
1. Format commit messages using 55 characters for the subject line, 72 characters per line
|
||||
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
|
||||
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
|
||||
for best practices around commit messages, and use `git log` to see some examples.
|
||||
Create your topic branch to be submitted as a pull request from `master`. The
|
||||
Spring team will consider your pull request for backporting to maintenance
|
||||
versions (e.g. 3.1.2) on a case-by-case basis; you don't need to worry about
|
||||
submitting anything for backporting.
|
||||
|
||||
1. If there is a prior issue, reference the GitHub issue number in the description of the
|
||||
pull request.
|
||||
|
||||
If accepted, your contribution may be heavily modified as needed prior to merging.
|
||||
You will likely retain author attribution for your Git commits granted that the bulk of
|
||||
your changes remain intact. You may also be asked to rework the submission.
|
||||
## Use short branch names
|
||||
|
||||
If asked to make corrections, simply push the changes against the same branch, and your
|
||||
pull request will be updated. In other words, you do not need to create a new pull request
|
||||
when asked to make changes.
|
||||
Branches used when submitting pull requests should preferably be named
|
||||
according to JIRA issues, e.g. 'SPR-1234'. Otherwise, use succinct, lower-case,
|
||||
dash (-) delimited names, such as 'fix-warnings', 'fix-typo', etc. In
|
||||
[fork-and-edit][] cases, the GitHub default 'patch-1' is fine as well. This is
|
||||
important, because branch names show up in the merge commits that result from
|
||||
accepting pull requests, and should be as expressive and concise as possible.
|
||||
|
||||
#### Participate in Reviews
|
||||
|
||||
Helping to review pull requests is another great way to contribute. Your feedback
|
||||
can help to shape the implementation of new features. When reviewing pull requests,
|
||||
however, please refrain from approving or rejecting a PR unless you are a core
|
||||
committer for the Spring Framework.
|
||||
## Mind the whitespace
|
||||
|
||||
### Build from Source
|
||||
Please carefully follow the whitespace and formatting conventions already
|
||||
present in the framework.
|
||||
|
||||
See the [Build from Source](https://github.com/spring-projects/spring-framework/wiki/Build-from-Source)
|
||||
wiki page for instructions on how to check out, build, and import the Spring Framework
|
||||
source code into your IDE.
|
||||
1. Tabs, not spaces
|
||||
1. Unix (LF), not dos (CRLF) line endings
|
||||
1. Eliminate all trailing whitespace
|
||||
1. Wrap Javadoc at 90 characters
|
||||
1. Aim to wrap code at 90 characters, but favor readability over wrapping
|
||||
1. Preserve existing formatting; i.e. do not reformat code for its own sake
|
||||
1. Search the codebase using `git grep` and other tools to discover common
|
||||
naming conventions, etc.
|
||||
1. Latin-1 (ISO-8859-1) encoding for Java sources; use `native2ascii` to convert
|
||||
if necessary
|
||||
|
||||
### Source Code Style
|
||||
|
||||
The wiki pages
|
||||
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
|
||||
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
|
||||
define the source file coding standards we use along with some IDEA editor settings we customize.
|
||||
## Add Apache license header to all new classes
|
||||
|
||||
### Reference Docs
|
||||
```java
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
The reference documentation is authored in [Asciidoctor](https://asciidoctor.org/) format
|
||||
using [Antora](https://docs.antora.org/antora/latest/). The source files for the documentation
|
||||
reside in the [framework-docs/modules/ROOT](framework-docs/modules/ROOT) directory. For
|
||||
trivial changes, you may be able to browse, edit source files, and submit directly from GitHub.
|
||||
package ...;
|
||||
```
|
||||
|
||||
When making changes locally, execute `./gradlew antora` and then browse the results under
|
||||
`framework-docs/build/site/index.html`.
|
||||
## Update Apache license header to modified files as necessary
|
||||
|
||||
Asciidoctor also supports live editing. For more details see
|
||||
[AsciiDoc Tooling](https://docs.asciidoctor.org/asciidoctor/latest/tooling/).
|
||||
Always check the date range in the license header. For example, if you've
|
||||
modified a file in 2012 whose header still reads
|
||||
|
||||
```java
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
```
|
||||
|
||||
then be sure to update it to 2012 appropriately
|
||||
|
||||
```java
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
```
|
||||
|
||||
## Use @since tags for newly-added public API types and methods
|
||||
|
||||
e.g.
|
||||
|
||||
```java
|
||||
/**
|
||||
* ...
|
||||
*
|
||||
* @author First Last
|
||||
* @since 3.2
|
||||
* @see ...
|
||||
*/
|
||||
```
|
||||
|
||||
## Submit JUnit test cases for all behavior changes
|
||||
|
||||
Search the codebase to find related unit tests and add additional @Test methods
|
||||
within. It is also acceptable to submit test cases on a per JIRA issue basis,
|
||||
e.g.
|
||||
|
||||
```java
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
/**
|
||||
* Unit tests for SPR-8954, in which a custom {@link InstantiationAwareBeanPostProcessor}
|
||||
* forces the predicted type of a FactoryBean, effectively preventing retrieval of the
|
||||
* bean from calls to #getBeansOfType(FactoryBean.class). The implementation of
|
||||
* {@link AbstractBeanFactory#isFactoryBean(String, RootBeanDefinition)} now ensures
|
||||
* that not only the predicted bean type is considered, but also the original bean
|
||||
* definition's beanClass.
|
||||
*
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class Spr8954Tests {
|
||||
|
||||
@Test
|
||||
public void cornerSpr8954() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Squash commits
|
||||
|
||||
Use `git rebase --interactive`, `git add --patch` and other tools to "squash"
|
||||
multiple commits into atomic changes. In addition to the man pages for git,
|
||||
there are many resources online to help you understand how these tools work.
|
||||
Here is one: http://book.git-scm.com/4_interactive_rebasing.html.
|
||||
|
||||
|
||||
## Use real name in git commits
|
||||
|
||||
Please configure git to use your real first and last name for any commits you
|
||||
intend to submit as pull requests. For example, this is not acceptable:
|
||||
|
||||
Author: Nickname <user@mail.com>
|
||||
|
||||
Rather, please include your first and last name, properly capitalized, as
|
||||
submitted against the SpringSource contributor license agreement:
|
||||
|
||||
Author: First Last <user@mail.com>
|
||||
|
||||
This helps ensure traceability against the CLA, and also goes a long way to
|
||||
ensuring useful output from tools like `git shortlog` and others.
|
||||
|
||||
You can configure this globally via the account admin area GitHub (useful for
|
||||
fork-and-edit cases); globally with
|
||||
|
||||
git config --global user.name "First Last"
|
||||
git config --global user.email user@mail.com
|
||||
|
||||
or locally for the spring-framework repository only by omitting the '--global'
|
||||
flag:
|
||||
|
||||
cd spring-framework
|
||||
git config user.name "First Last"
|
||||
git config user.email user@mail.com
|
||||
|
||||
|
||||
## Format commit messages
|
||||
|
||||
Please read and follow the [commit guidelines section of Pro Git][].
|
||||
|
||||
Most importantly, please format your commit messages in the following way
|
||||
(adapted from the commit template in the link above):
|
||||
|
||||
Short (50 chars or less) summary of changes
|
||||
|
||||
More detailed explanatory text, if necessary. Wrap it to about 72
|
||||
characters or so. In some contexts, the first line is treated as the
|
||||
subject of an email and the rest of the text as the body. The blank
|
||||
line separating the summary from the body is critical (unless you omit
|
||||
the body entirely); tools like rebase can get confused if you run the
|
||||
two together.
|
||||
|
||||
Further paragraphs come after blank lines.
|
||||
|
||||
- Bullet points are okay, too
|
||||
|
||||
- Typically a hyphen or asterisk is used for the bullet, preceded by a
|
||||
single space, with blank lines in between, but conventions vary here
|
||||
|
||||
Issue: SPR-1234, SPR-1235
|
||||
|
||||
|
||||
1. Use imperative statements in the subject line, e.g. "Fix broken Javadoc link"
|
||||
1. Begin the subject line sentence with a capitalized verb, e.g. "Add, Prune,
|
||||
Fix, Introduce, Avoid, etc"
|
||||
1. Do not end the subject line with a period
|
||||
1. Keep the subject line to 50 characters or less if possible
|
||||
1. Wrap lines in the body at 72 characters or less
|
||||
1. Mention associated jira issue(s) at the end of the commit comment, prefixed
|
||||
with "Issue: " as above
|
||||
1. In the body of the commit message, explain how things worked before this
|
||||
commit, what has changed, and how things work now
|
||||
|
||||
For examples of this style, issue a `git log --author=cbeams` in the
|
||||
spring-framework git repository. For convenience, here are several such commits:
|
||||
|
||||
https://github.com/SpringSource/spring-framework/commit/08e2669b84ec0faa2f7904441fe39ac70b65b078
|
||||
https://github.com/SpringSource/spring-framework/commit/1d9d3e6ff79ce9f0eca03b02cd1df705925575da
|
||||
https://github.com/SpringSource/spring-framework/commit/8e0b1c3a5f957af3049cfa0438317177e16d6de6
|
||||
https://github.com/SpringSource/spring-framework/commit/b787a68f2050df179f7036b209aa741230a02477
|
||||
|
||||
## Run all tests prior to submission
|
||||
|
||||
See the [building from source][] section of the README for instructions. Make
|
||||
sure that all tests pass prior to submitting your pull request.
|
||||
|
||||
|
||||
## Submit your pull request
|
||||
|
||||
Subject line:
|
||||
|
||||
Follow the same conventions for pull request subject lines as mentioned above
|
||||
for commit message subject lines.
|
||||
|
||||
In the body:
|
||||
|
||||
1. Explain your use case. What led you to submit this change? Why were existing
|
||||
mechanisms in the framework insufficient? Make a case that this is a
|
||||
general-purpose problem and that yours is a general-purpose solution, etc.
|
||||
1. Add any additional information and ask questions; start a conversation, or
|
||||
continue one from JIRA
|
||||
1. Mention the JIRA issue ID
|
||||
1. Also mention that you have submitted the CLA as described above
|
||||
|
||||
Note that for pull requests containing a single commit, GitHub will default the
|
||||
subject line and body of the pull request to match the subject line and body of
|
||||
the commit message. This is fine, but please also include the items above in the
|
||||
body of the request.
|
||||
|
||||
|
||||
## Mention your pull request on the associated JIRA issue
|
||||
|
||||
Add a comment to the associated JIRA issue(s) linking to your new pull request.
|
||||
|
||||
|
||||
## Expect discussion and rework
|
||||
|
||||
The Spring team takes a very conservative approach to accepting contributions to
|
||||
the framework. This is to keep code quality and stability as high as possible,
|
||||
and to keep complexity at a minimum. Your changes, if accepted, may be heavily
|
||||
modified prior to merging. You will retain "Author:" attribution for your Git
|
||||
commits granted that the bulk of your changes remain intact. You may be asked to
|
||||
rework the submission for style (as explained above) and/or substance. Again, we
|
||||
strongly recommend discussing any serious submissions with the Spring Framework
|
||||
team _prior_ to engaging in serious development work.
|
||||
|
||||
Note that you can always force push (`git push -f`) reworked / rebased commits
|
||||
against the branch used to submit your pull request. i.e. you do not need to
|
||||
issue a new pull request when asked to make changes.
|
||||
|
||||
[help documentation]: http://help.github.com/send-pull-requests
|
||||
[JIRA issue tracker]: https://jira.springsource.org/browse/SPR
|
||||
[spring-framework-contrib]: https://groups.google.com/forum/#!forum/spring-framework-contrib
|
||||
[SpringSource CLA form]: https://support.springsource.com/spring_committer_signup
|
||||
[fork-and-edit]: https://github.com/blog/844-forking-with-the-edit-button
|
||||
[commit guidelines section of Pro Git]: http://progit.org/book/ch5-2.html#commit_guidelines
|
||||
[building from source]: https://github.com/SpringSource/spring-framework#building-from-source
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,38 +1,96 @@
|
||||
# <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)
|
||||
## Spring Framework
|
||||
The Spring Framework provides a comprehensive programming and configuration
|
||||
model for modern Java-based enterprise applications - on any kind of deployment
|
||||
platform. A key element of Spring is infrastructural support at the application
|
||||
level: Spring focuses on the "plumbing" of enterprise applications so that teams
|
||||
can focus on application-level business logic, without unnecessary ties to
|
||||
specific deployment environments.
|
||||
|
||||
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".
|
||||
The framework also serves as the foundation for [Spring Integration][], [Spring
|
||||
Batch][] and the rest of the Spring [family of projects][]. Browse the
|
||||
repositories under the [SpringSource organization][] on GitHub for a full list.
|
||||
|
||||
Spring provides everything required beyond the Java programming language for creating enterprise applications for a wide range of scenarios and architectures. Please read the [Overview](https://docs.spring.io/spring-framework/reference/overview.html) section of the reference documentation for a more complete introduction.
|
||||
[.NET][] and [Python][] variants are available as well.
|
||||
|
||||
## 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.
|
||||
|
||||
## Access to Binaries
|
||||
|
||||
For access to artifacts or a distribution zip, see the [Spring Framework Artifacts](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts) wiki page.
|
||||
## Downloading artifacts
|
||||
See [downloading Spring artifacts][] for Maven repository information. Unable to
|
||||
use Maven or other transitive dependency management tools? See [building a
|
||||
distribution with dependencies][].
|
||||
|
||||
## Documentation
|
||||
See the current [Javadoc][] and [reference docs][].
|
||||
|
||||
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/reference/) and [source](framework-docs/modules/ROOT)), GitHub [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
|
||||
[API reference](https://docs.spring.io/spring-framework/docs/current/javadoc-api/). There are also [guides and tutorials](https://spring.io/guides) across Spring projects.
|
||||
## Getting support
|
||||
Check out the [Spring forums][] and the [spring][spring tag] and
|
||||
[spring-mvc][spring-mvc tag] tags on [Stack Overflow][]. [Commercial support][]
|
||||
is available too.
|
||||
|
||||
## Micro-Benchmarks
|
||||
## Issue Tracking
|
||||
Report issues via the [Spring Framework JIRA]. Understand our issue management
|
||||
process by reading about [the lifecycle of an issue][]. Think you've found a
|
||||
bug? Please consider submitting a reproduction project via the
|
||||
[spring-framework-issues][] GitHub repository. The [readme][] there provides
|
||||
simple step-by-step instructions.
|
||||
|
||||
See the [Micro-Benchmarks](https://github.com/spring-projects/spring-framework/wiki/Micro-Benchmarks) wiki page.
|
||||
## Building from source
|
||||
The Spring Framework uses a [Gradle][]-based build system. In the instructions
|
||||
below, [`./gradlew`][] is invoked from the root of the source tree and serves as
|
||||
a cross-platform, self-contained bootstrap mechanism for the build. The only
|
||||
prerequisites are [Git][] and JDK 1.7+.
|
||||
|
||||
## Build from Source
|
||||
### check out sources
|
||||
`git clone git://github.com/SpringSource/spring-framework.git`
|
||||
|
||||
See the [Build from Source](https://github.com/spring-projects/spring-framework/wiki/Build-from-Source) wiki page and the [CONTRIBUTING.md](CONTRIBUTING.md) file.
|
||||
### compile and test, build all jars, distribution zips and docs
|
||||
`./gradlew build`
|
||||
|
||||
## Continuous Integration Builds
|
||||
### install all spring-\* jars into your local Maven cache
|
||||
`./gradlew install`
|
||||
|
||||
CI builds are defined with [GitHub Actions workflows](.github/workflows).
|
||||
### import sources into your IDE
|
||||
Run `./import-into-eclipse.sh` or read `import-into-idea.md` as appropriate.
|
||||
|
||||
## Stay in Touch
|
||||
... and discover more commands with `./gradlew tasks`. See also the [Gradle
|
||||
build and release FAQ][].
|
||||
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on 𝕏. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
|
||||
## Contributing
|
||||
[Pull requests][] are welcome; see the [contributor guidelines][] for details.
|
||||
|
||||
## Staying in touch
|
||||
Follow [@springframework][] and its [team members][] on Twitter. In-depth
|
||||
articles can be found at the SpringSource [team blog][], and releases are
|
||||
announced via our [news feed][].
|
||||
|
||||
## License
|
||||
The Spring Framework is released under version 2.0 of the [Apache License][].
|
||||
|
||||
The Spring Framework is released under version 2.0 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
[Spring Integration]: https://github.com/SpringSource/spring-integration
|
||||
[Spring Batch]: https://github.com/SpringSource/spring-batch
|
||||
[family of projects]: http://springsource.org/projects
|
||||
[SpringSource organization]: https://github.com/SpringSource
|
||||
[.NET]: https://github.com/SpringSource/spring-net
|
||||
[Python]: https://github.com/SpringSource/spring-python
|
||||
[downloading Spring artifacts]: https://github.com/SpringSource/spring-framework/wiki/Downloading-Spring-artifacts
|
||||
[building a distribution with dependencies]: https://github.com/SpringSource/spring-framework/wiki/Building-a-distribution-with-dependencies
|
||||
[Javadoc]: http://static.springsource.org/spring-framework/docs/current/javadoc-api
|
||||
[reference docs]: http://static.springsource.org/spring-framework/docs/current/spring-framework-reference
|
||||
[Spring forums]: http://forum.springsource.org
|
||||
[spring tag]: http://stackoverflow.com/questions/tagged/spring
|
||||
[spring-mvc tag]: http://stackoverflow.com/questions/tagged/spring-mvc
|
||||
[Stack Overflow]: http://stackoverflow.com/faq
|
||||
[Commercial support]: http://springsource.com/support/springsupport
|
||||
[Spring Framework JIRA]: http://jira.springsource.org/browse/SPR
|
||||
[the lifecycle of an issue]: https://github.com/cbeams/spring-framework/wiki/The-Lifecycle-of-an-Issue
|
||||
[spring-framework-issues]: https://github.com/SpringSource/spring-framework-issues#readme
|
||||
[readme]: https://github.com/SpringSource/spring-framework-issues#readme
|
||||
[Gradle]: http://gradle.org
|
||||
[`./gradlew`]: http://vimeo.com/34436402
|
||||
[Git]: http://help.github.com/set-up-git-redirect
|
||||
[Gradle build and release FAQ]: https://github.com/SpringSource/spring-framework/wiki/Gradle-build-and-release-FAQ
|
||||
[Pull requests]: http://help.github.com/send-pull-requests
|
||||
[contributor guidelines]: https://github.com/SpringSource/spring-framework/blob/master/CONTRIBUTING.md
|
||||
[@springframework]: http://twitter.com/springframework
|
||||
[team members]: http://twitter.com/springframework/team/members
|
||||
[team blog]: http://blog.springsource.org
|
||||
[news feed]: http://www.springsource.org/news-events
|
||||
[Apache License]: http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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).
|
||||
|
||||
## 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
|
||||
@@ -1,113 +1,864 @@
|
||||
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 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url 'http://repo.springsource.org/plugins-release' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'org.springframework.build.gradle:docbook-reference-plugin:0.2.1'
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
moduleProjects = subprojects.findAll { it.name.startsWith("spring-") }
|
||||
javaProjects = subprojects.findAll { !it.name.startsWith("framework-") }
|
||||
configure(allprojects) {
|
||||
ext.aspectjVersion = '1.6.12'
|
||||
ext.hsqldbVersion='1.8.0.10'
|
||||
ext.junitVersion = '4.11.20120805.1225' // temporary use of snapshot; spring-test
|
||||
// still builds against on 4.10
|
||||
ext.gradleScriptDir = "${rootProject.projectDir}/gradle"
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'idea'
|
||||
apply from: "${gradleScriptDir}/ide.gradle"
|
||||
|
||||
group = 'org.springframework'
|
||||
|
||||
sourceCompatibility=1.5
|
||||
targetCompatibility=1.5
|
||||
|
||||
[compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none']
|
||||
|
||||
sourceSets.test.resources.srcDirs = ['src/test/resources', 'src/test/java']
|
||||
|
||||
test.systemProperty("java.awt.headless", "true")
|
||||
|
||||
repositories {
|
||||
maven { url "http://repo.springsource.org/libs-release" }
|
||||
maven { url "http://repo.springsource.org/ebr-maven-external" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile "org.hamcrest:hamcrest-all:1.3"
|
||||
testCompile "org.easymock:easymock:2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
description = "Spring Framework"
|
||||
|
||||
configure(allprojects) { project ->
|
||||
apply plugin: "org.springframework.build.localdev"
|
||||
group = "org.springframework"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = "https://repo.spring.io/milestone"
|
||||
content {
|
||||
// Netty 5 optional support
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
if (version.contains('-')) {
|
||||
maven { url = "https://repo.spring.io/milestone" }
|
||||
}
|
||||
if (version.endsWith('-SNAPSHOT')) {
|
||||
maven { url = "https://repo.spring.io/snapshot" }
|
||||
}
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
cacheChangingModulesFor 0, "seconds"
|
||||
cacheDynamicVersionsFor 0, "seconds"
|
||||
}
|
||||
}
|
||||
configure(subprojects - project(":spring-test")) {
|
||||
dependencies {
|
||||
testCompile ("junit:junit-dep:${junitVersion}") {
|
||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure(allprojects - project(":framework-platform")) {
|
||||
configurations {
|
||||
dependencyManagement {
|
||||
canBeConsumed = false
|
||||
canBeResolved = false
|
||||
visible = false
|
||||
}
|
||||
matching { it.name.endsWith("Classpath") }.all { it.extendsFrom(dependencyManagement) }
|
||||
}
|
||||
dependencies {
|
||||
dependencyManagement(enforcedPlatform(dependencies.project(path: ":framework-platform")))
|
||||
}
|
||||
configure(subprojects) { subproject ->
|
||||
apply from: "${gradleScriptDir}/publish-maven.gradle"
|
||||
|
||||
jar {
|
||||
manifest.attributes['Created-By'] =
|
||||
"${System.getProperty('java.version')} (${System.getProperty('java.specification.vendor')})"
|
||||
manifest.attributes['Implementation-Title'] = subproject.name
|
||||
manifest.attributes['Implementation-Version'] = subproject.version
|
||||
|
||||
from("${rootProject.projectDir}/src/dist") {
|
||||
include "license.txt"
|
||||
include "notice.txt"
|
||||
into "META-INF"
|
||||
expand(copyright: new Date().format('yyyy'), version: project.version)
|
||||
}
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = project.name
|
||||
//options.overview = "${projectDir}/src/main/java/overview.html"
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn:classes) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allJava.srcDirs
|
||||
include '**/*.java', '**/*.aj'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
}
|
||||
}
|
||||
|
||||
configure([rootProject] + javaProjects) { project ->
|
||||
apply plugin: "java"
|
||||
apply plugin: "java-test-fixtures"
|
||||
apply plugin: 'org.springframework.build.conventions'
|
||||
apply from: "${rootDir}/gradle/toolchains.gradle"
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.junit.platform:junit-platform-suite")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl")
|
||||
// JSR-305 only used for non-required meta-annotations
|
||||
compileOnly("com.google.code.findbugs:jsr305")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
}
|
||||
project('spring-core') {
|
||||
description = 'Spring Core'
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.hibernate.org/orm/5.6/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
|
||||
//"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.14.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
|
||||
// JakartaEE equivalents in the jakarta.annotation package.
|
||||
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
|
||||
] as String[]
|
||||
// As of Spring 3.2 spring-core repackages both asm 4.0 and cglib 3.0 and inlines both
|
||||
// into the spring-core jar. cglib 3.0 itself depends on asm 4.0, and is therefore
|
||||
// further transformed by the JarJar task to depend on org.springframework.asm; this
|
||||
// avoids including two different copies of asm unnecessarily. If however future cglib
|
||||
// versions drift from the version of asm used by Spring internally, this duplication
|
||||
// will become necessary.
|
||||
def asmVersion = '4.0'
|
||||
def cglibVersion = '3.0'
|
||||
|
||||
configurations {
|
||||
jarjar
|
||||
asm
|
||||
cglib
|
||||
}
|
||||
|
||||
task asmRepackJar(type: Jar) { repackJar ->
|
||||
repackJar.baseName = "spring-asm-repack"
|
||||
repackJar.version = asmVersion
|
||||
|
||||
doLast() {
|
||||
project.ant {
|
||||
taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask",
|
||||
classpath: configurations.jarjar.asPath
|
||||
jarjar(destfile: repackJar.archivePath) {
|
||||
configurations.asm.each { originalJar ->
|
||||
zipfileset(src: originalJar)
|
||||
}
|
||||
rule(pattern: 'org.objectweb.asm.**', result: 'org.springframework.asm.@1')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task cglibRepackJar(type: Jar) { repackJar ->
|
||||
repackJar.baseName = "spring-cglib-repack"
|
||||
repackJar.version = cglibVersion
|
||||
|
||||
doLast() {
|
||||
project.ant {
|
||||
taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask",
|
||||
classpath: configurations.jarjar.asPath
|
||||
jarjar(destfile: repackJar.archivePath) {
|
||||
configurations.cglib.each { originalJar ->
|
||||
zipfileset(src: originalJar)
|
||||
}
|
||||
// repackage net.sf.cglib => org.springframework.cglib
|
||||
rule(pattern: 'net.sf.cglib.**', result: 'org.springframework.cglib.@1')
|
||||
// as mentioned above, transform cglib's internal asm dependencies from
|
||||
// org.objectweb.asm => org.springframework.asm. Doing this counts on the
|
||||
// the fact that Spring and cglib depend on the same version of asm!
|
||||
rule(pattern: 'org.objectweb.asm.**', result: 'org.springframework.asm.@1')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asm "org.ow2.asm:asm:${asmVersion}@jar", "org.ow2.asm:asm-commons:${asmVersion}@jar"
|
||||
cglib "cglib:cglib:${cglibVersion}@jar"
|
||||
jarjar 'com.googlecode.jarjar:jarjar:1.3'
|
||||
|
||||
compile files(asmRepackJar)
|
||||
compile "commons-logging:commons-logging:1.1.1"
|
||||
compile("org.aspectj:aspectjweaver:${aspectjVersion}", optional)
|
||||
compile("net.sf.jopt-simple:jopt-simple:3.0") { dep ->
|
||||
optional dep
|
||||
exclude group: 'org.apache.ant', module: 'ant'
|
||||
}
|
||||
compile("log4j:log4j:1.2.15") { dep ->
|
||||
optional dep
|
||||
exclude group: 'javax.mail', module: 'mail'
|
||||
exclude group: 'javax.jms', module: 'jms'
|
||||
exclude group: 'com.sun.jdmk', module: 'jmxtools'
|
||||
exclude group: 'com.sun.jmx', module: 'jmxri'
|
||||
}
|
||||
testCompile "xmlunit:xmlunit:1.2"
|
||||
testCompile "org.codehaus.woodstox:wstx-asl:3.2.7"
|
||||
}
|
||||
|
||||
jar {
|
||||
// inline all repackaged asm and cglib classes directly into the spring-core jar
|
||||
dependsOn asmRepackJar
|
||||
from(zipTree(asmRepackJar.archivePath)) {
|
||||
include 'org/springframework/asm/**'
|
||||
}
|
||||
dependsOn cglibRepackJar
|
||||
from(zipTree(cglibRepackJar.archivePath)) {
|
||||
include 'org/springframework/cglib/**'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure(moduleProjects) { project ->
|
||||
apply from: "${rootDir}/gradle/spring-module.gradle"
|
||||
project('spring-beans') {
|
||||
description = 'Spring Beans'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile files(project(":spring-core").cglibRepackJar)
|
||||
compile("javax.el:el-api:1.0", provided)
|
||||
compile("javax.inject:javax.inject:1", provided)
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-aop') {
|
||||
description = 'Spring AOP'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile files(project(":spring-core").cglibRepackJar)
|
||||
compile project(":spring-beans")
|
||||
compile("aopalliance:aopalliance:1.0")
|
||||
compile("com.jamonapi:jamon:2.4", optional)
|
||||
compile("commons-pool:commons-pool:1.5.3", optional)
|
||||
compile("org.aspectj:aspectjweaver:${aspectjVersion}", optional)
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-expression') {
|
||||
description = 'Spring Expression Language (SpEL)'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-instrument') {
|
||||
description = 'Spring Instrument'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
}
|
||||
jar {
|
||||
manifest.attributes['Premain-Class'] =
|
||||
'org.springframework.instrument.InstrumentationSavingAgent'
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-instrument-tomcat') {
|
||||
description = 'Spring Instrument Tomcat'
|
||||
dependencies {
|
||||
compile("org.apache.tomcat:catalina:6.0.16", provided)
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-context') {
|
||||
description = 'Spring Context'
|
||||
dependencies {
|
||||
compile(project(":spring-instrument"), optional)
|
||||
compile project(":spring-aop")
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-expression")
|
||||
compile project(":spring-core")
|
||||
compile files(project(":spring-core").cglibRepackJar)
|
||||
compile("backport-util-concurrent:backport-util-concurrent:3.0", optional)
|
||||
compile("javax.annotation:jsr250-api:1.0", optional)
|
||||
compile("javax.ejb:ejb-api:3.0", optional)
|
||||
compile("javax.inject:javax.inject:1", optional)
|
||||
compile("org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1", optional)
|
||||
compile("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1", optional)
|
||||
compile("javax.persistence:persistence-api:1.0", optional)
|
||||
compile("javax.validation:validation-api:1.0.0.GA", optional)
|
||||
compile("javax.xml.ws:jaxws-api:2.1-1") { dep ->
|
||||
optional dep
|
||||
exclude group: 'javax.jws', module: 'jsr181'
|
||||
}
|
||||
compile("org.beanshell:bsh:2.0b4", optional)
|
||||
compile("org.codehaus.groovy:groovy-all:1.6.3", optional)
|
||||
compile("org.hibernate:hibernate-validator:4.2.0.Final") { dep ->
|
||||
optional dep
|
||||
exclude group: 'org.slf4j', module: 'slf4j-api'
|
||||
}
|
||||
compile("joda-time:joda-time:1.6", optional)
|
||||
compile("org.jruby:jruby:1.4.0", optional)
|
||||
compile("org.slf4j:slf4j-api:1.6.1", optional)
|
||||
compile("org.codehaus.jsr166-mirror:jsr166:1.7.0", provided)
|
||||
compile("org.aspectj:aspectjweaver:${aspectjVersion}", optional)
|
||||
testCompile "commons-dbcp:commons-dbcp:1.2.2"
|
||||
testCompile("javax.xml:jaxrpc-api:1.1")
|
||||
testCompile("javax.inject:com.springsource.org.atinject.tck:1.0.0")
|
||||
}
|
||||
|
||||
test {
|
||||
jvmArgs = ['-disableassertions:org.aspectj.weaver.UnresolvedType'] // SPR-7989
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-tx') {
|
||||
description = 'Spring Transaction'
|
||||
dependencies {
|
||||
compile(project(":spring-context"), optional) // for JCA, @EnableTransactionManagement
|
||||
compile(project(":spring-aop"), optional)
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-core")
|
||||
compile("aopalliance:aopalliance:1.0")
|
||||
compile("com.ibm.websphere:uow:6.0.2.17", provided)
|
||||
compile("javax.resource:connector-api:1.5", optional)
|
||||
compile("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1", optional)
|
||||
testCompile "org.easymock:easymockclassextension:2.3"
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-oxm') {
|
||||
description = 'Spring Object/XML Marshalling'
|
||||
apply from: 'oxm.gradle'
|
||||
dependencies {
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-core")
|
||||
compile(project(":spring-context"), optional) // for Jaxb2Marshaller
|
||||
compile "commons-lang:commons-lang:2.5"
|
||||
compile("com.thoughtworks.xstream:xstream:1.3.1", optional)
|
||||
compile("com.sun.xml.bind:jaxb-impl:2.1.7", optional)
|
||||
compile("org.jibx:jibx-run:1.2.3", optional)
|
||||
compile("org.apache.xmlbeans:xmlbeans:2.4.0", optional)
|
||||
compile("org.codehaus.castor:castor-xml:1.3.2", optional)
|
||||
testCompile "org.codehaus.jettison:jettison:1.0.1"
|
||||
testCompile "xmlunit:xmlunit:1.2"
|
||||
testCompile "xmlpull:xmlpull:1.1.3.4a"
|
||||
testCompile(files(genCastor.classesDir).builtBy(genCastor))
|
||||
testCompile(files(genJaxb.classesDir).builtBy(genJaxb))
|
||||
testCompile(files(genXmlbeans.classesDir).builtBy(genXmlbeans))
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-jms') {
|
||||
description = 'Spring JMS'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-aop")
|
||||
compile project(":spring-context")
|
||||
compile project(":spring-tx")
|
||||
compile(project(":spring-oxm"), optional)
|
||||
compile("aopalliance:aopalliance:1.0")
|
||||
compile("org.codehaus.jackson:jackson-mapper-asl:1.4.2", optional)
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-jdbc') {
|
||||
description = 'Spring JDBC'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-beans")
|
||||
compile(project(":spring-context"), optional) // for JndiDataSourceLookup
|
||||
compile project(":spring-tx")
|
||||
compile("c3p0:c3p0:0.9.1.2", optional)
|
||||
compile("hsqldb:hsqldb:${hsqldbVersion}", optional)
|
||||
compile("com.h2database:h2:1.0.71", optional)
|
||||
compile("org.apache.derby:derby:10.5.3.0_1", optional)
|
||||
compile("org.apache.derby:derbyclient:10.5.3.0_1", optional)
|
||||
compile("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1", optional)
|
||||
testCompile "org.easymock:easymock:2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-context-support') {
|
||||
description = 'Spring Context Support'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-context")
|
||||
compile(project(":spring-jdbc"), optional) // for Quartz support
|
||||
compile(project(":spring-tx"), optional) // for Quartz support
|
||||
compile("org.codehaus.fabric3.api:commonj:1.1.0", optional)
|
||||
compile("javax.cache:cache-api:0.5", optional)
|
||||
compile("net.sf.ehcache:ehcache-core:2.0.0", optional)
|
||||
compile("opensymphony:quartz:1.6.2", optional)
|
||||
compile("javax.mail:mail:1.4", optional)
|
||||
compile("velocity:velocity:1.5", optional)
|
||||
compile("commons-collections:commons-collections:3.2", optional)
|
||||
compile("org.freemarker:freemarker:2.3.15", optional)
|
||||
compile("jasperreports:jasperreports:2.0.5") { dep ->
|
||||
optional dep
|
||||
transitive = false
|
||||
}
|
||||
compile("commons-digester:commons-digester:1.8.1", optional)
|
||||
compile("commons-beanutils:commons-beanutils:1.8.0", optional)
|
||||
compile("com.lowagie:itext:2.0.8", optional)
|
||||
testCompile "hsqldb:hsqldb:${hsqldbVersion}"
|
||||
testCompile("org.apache.poi:poi:3.0.2-FINAL") {
|
||||
exclude group: 'log4j', module: 'log4j'
|
||||
}
|
||||
}
|
||||
|
||||
// pick up **/*.types files in src/main
|
||||
sourceSets.main.resources.srcDirs += 'src/main/java'
|
||||
}
|
||||
|
||||
project('spring-web') {
|
||||
description = 'Spring Web'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-beans") // for MultiPartFilter
|
||||
compile project(":spring-aop") // for JaxWsPortProxyFactoryBean
|
||||
compile project(":spring-context")
|
||||
compile(project(":spring-oxm"), optional) // for MarshallingHttpMessageConverter
|
||||
compile("aopalliance:aopalliance:1.0")
|
||||
compile("com.caucho:hessian:3.2.1", optional)
|
||||
compile("rome:rome:1.0", optional)
|
||||
compile("javax.el:el-api:1.0", optional)
|
||||
compile("javax.faces:jsf-api:1.2_08", optional)
|
||||
compile("javax.portlet:portlet-api:2.0", provided)
|
||||
compile("org.apache.tomcat:tomcat-servlet-api:7.0.32", provided) // servlet-api 3.0
|
||||
compile("javax.servlet.jsp:jsp-api:2.1", provided)
|
||||
compile("javax.xml.soap:saaj-api:1.3", provided)
|
||||
compile("axis:axis:1.4", optional)
|
||||
compile("commons-fileupload:commons-fileupload:1.2", optional)
|
||||
runtime("commons-io:commons-io:1.3", optional)
|
||||
compile("commons-httpclient:commons-httpclient:3.1", optional)
|
||||
compile("org.apache.httpcomponents:httpclient:4.2", optional)
|
||||
compile("org.codehaus.jackson:jackson-mapper-asl:1.4.2", optional)
|
||||
compile("com.fasterxml.jackson.core:jackson-databind:2.0.1", optional)
|
||||
compile("taglibs:standard:1.1.2", optional)
|
||||
compile("org.eclipse.jetty:jetty-servlet:8.1.5.v20120716") { dep ->
|
||||
optional dep
|
||||
exclude group: 'org.eclipse.jetty.orbit', module: 'javax.servlet'
|
||||
}
|
||||
compile("org.eclipse.jetty:jetty-server:8.1.5.v20120716") { dep ->
|
||||
optional dep
|
||||
exclude group: 'org.eclipse.jetty.orbit', module: 'javax.servlet'
|
||||
}
|
||||
testCompile project(":spring-context-support") // for JafMediaTypeFactory
|
||||
testCompile "xmlunit:xmlunit:1.2"
|
||||
}
|
||||
|
||||
// pick up ContextLoader.properties in src/main
|
||||
sourceSets.main.resources.srcDirs += 'src/main/java'
|
||||
}
|
||||
|
||||
project('spring-orm') {
|
||||
description = 'Spring Object/Relational Mapping'
|
||||
dependencies {
|
||||
// compiling against both hibernate 3 and 4 here in order to support
|
||||
// our respective orm.hibernate3 and orm.hibernate4 packages
|
||||
compile("aopalliance:aopalliance:1.0")
|
||||
compile("org.hibernate:com.springsource.org.hibernate:3.3.1.GA", optional)
|
||||
compile("org.hibernate:hibernate-cglib-repack:2.1_3", optional)
|
||||
compile("org.hibernate:hibernate-annotations:3.4.0.GA", optional)
|
||||
compile("org.hibernate:hibernate-entitymanager:3.4.0.GA", optional)
|
||||
compile("org.apache.openjpa:openjpa:1.1.0", optional)
|
||||
compile("org.eclipse.persistence:org.eclipse.persistence.core:1.0.1", optional)
|
||||
compile("org.eclipse.persistence:org.eclipse.persistence.jpa:1.0.1", optional)
|
||||
compile("toplink.essentials:toplink-essentials:2.0-41b", optional)
|
||||
compile("javax.jdo:jdo-api:3.0", optional)
|
||||
compile("org.apache.ibatis:ibatis-sqlmap:2.3.4.726", optional)
|
||||
testCompile "javax.servlet:servlet-api:2.5"
|
||||
testCompile "org.slf4j:slf4j-jcl:1.5.3"
|
||||
testCompile "commons-dbcp:commons-dbcp:1.2.2"
|
||||
testCompile "org.eclipse.persistence:org.eclipse.persistence.asm:1.0.1"
|
||||
testCompile "org.eclipse.persistence:org.eclipse.persistence.antlr:1.0.1"
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-beans")
|
||||
compile(project(":spring-aop"), optional)
|
||||
compile(project(":spring-context"), optional)
|
||||
compile project(":spring-tx")
|
||||
compile project(":spring-jdbc")
|
||||
compile(project(":spring-web")) { dep ->
|
||||
optional dep
|
||||
exclude group: 'javax.persistence', module: 'persistence-api'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-orm-hibernate4') {
|
||||
description = 'Spring Object/Relational Mapping - Hibernate 4 support'
|
||||
ext.mergeIntoProject = project(':spring-orm')
|
||||
apply from: "${gradleScriptDir}/merge-artifacts.gradle"
|
||||
dependencies {
|
||||
compile project(":spring-orm").sourceSets.main.output
|
||||
compile project(":spring-tx")
|
||||
compile project(":spring-jdbc")
|
||||
compile("org.hibernate:hibernate-core:4.1.0.Final", optional)
|
||||
compile("org.hibernate:hibernate-entitymanager:4.1.0.Final", optional)
|
||||
compile(project(":spring-web")) { dep ->
|
||||
optional dep
|
||||
exclude group: 'javax.persistence', module: 'persistence-api'
|
||||
}
|
||||
compile("javax.servlet:servlet-api:2.5", optional)
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-webmvc') {
|
||||
description = 'Spring Web MVC'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-expression")
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-web")
|
||||
compile project(":spring-context")
|
||||
compile(project(":spring-context-support"), optional) // for Velocity support
|
||||
compile(project(":spring-oxm"), optional) // for MarshallingView
|
||||
compile("org.apache.tiles:tiles-api:2.1.2", optional)
|
||||
compile("org.apache.tiles:tiles-core:2.1.2", optional)
|
||||
compile("org.apache.tiles:tiles-jsp:2.1.2", optional)
|
||||
compile("org.apache.tiles:tiles-servlet:2.1.2", optional)
|
||||
compile("velocity-tools:velocity-tools-view:1.4", optional)
|
||||
compile("net.sourceforge.jexcelapi:jxl:2.6.3") { dep ->
|
||||
optional dep
|
||||
exclude group: 'log4j', module: 'log4j'
|
||||
}
|
||||
compile("org.apache.poi:poi:3.0.2-FINAL") { dep ->
|
||||
optional dep
|
||||
exclude group: 'log4j', module: 'log4j'
|
||||
}
|
||||
compile("javax.servlet:jstl:1.1.2", provided)
|
||||
compile("org.apache.tomcat:tomcat-servlet-api:7.0.32", provided) // servlet-api 3.0
|
||||
testCompile project(":spring-aop")
|
||||
testCompile("org.slf4j:slf4j-log4j12:1.6.1") {
|
||||
exclude group: 'log4j', module: 'log4j'
|
||||
}
|
||||
testCompile "rhino:js:1.7R1"
|
||||
testCompile "xmlunit:xmlunit:1.2"
|
||||
testCompile("dom4j:dom4j:1.6.1") {
|
||||
exclude group: 'xml-apis', module: 'xml-apis'
|
||||
}
|
||||
testCompile("jaxen:jaxen:1.1.1") {
|
||||
exclude group: 'xml-apis', module: 'xml-apis'
|
||||
exclude group: 'xom', module: 'xom'
|
||||
exclude group: 'xerces', module: 'xercesImpl'
|
||||
}
|
||||
}
|
||||
|
||||
// pick up DispatcherServlet.properties in src/main
|
||||
sourceSets.main.resources.srcDirs += 'src/main/java'
|
||||
}
|
||||
|
||||
project('spring-webmvc-tiles3') {
|
||||
description = 'Spring Framework Tiles3 Integration'
|
||||
ext.mergeIntoProject = project(':spring-webmvc')
|
||||
apply from: "${gradleScriptDir}/merge-artifacts.gradle"
|
||||
dependencies {
|
||||
compile project(":spring-context")
|
||||
compile project(":spring-webmvc").sourceSets.main.output
|
||||
compile("javax.el:el-api:1.0", provided)
|
||||
compile("javax.servlet:jstl:1.1.2", provided)
|
||||
compile("javax.servlet.jsp:jsp-api:2.1", provided)
|
||||
compile("org.apache.tiles:tiles-request-api:1.0.1", optional)
|
||||
compile("org.apache.tiles:tiles-request-servlet-wildcard:1.0.1") { dep->
|
||||
optional dep
|
||||
exclude group: 'org.springframework', module: 'spring-web'
|
||||
}
|
||||
compile("org.apache.tiles:tiles-api:3.0.1", optional)
|
||||
compile("org.apache.tiles:tiles-core:3.0.1", optional)
|
||||
compile("org.apache.tiles:tiles-servlet:3.0.1", optional)
|
||||
compile("org.apache.tiles:tiles-jsp:3.0.1", optional)
|
||||
compile("org.apache.tiles:tiles-el:3.0.1", optional)
|
||||
compile("org.apache.tomcat:tomcat-servlet-api:7.0.32", provided) // servlet-api 3.0
|
||||
compile project(":spring-web").sourceSets*.output // mock request & response
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-webmvc-portlet') {
|
||||
description = 'Spring Web Portlet'
|
||||
dependencies {
|
||||
compile("javax.servlet:servlet-api:2.5", provided)
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-context")
|
||||
compile project(":spring-web")
|
||||
compile project(":spring-webmvc")
|
||||
}
|
||||
|
||||
// pick up DispatcherPortlet.properties in src/main
|
||||
sourceSets.main.resources.srcDirs += 'src/main/java'
|
||||
}
|
||||
|
||||
project('spring-test') {
|
||||
description = 'Spring TestContext Framework'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile(project(":spring-beans"), optional)
|
||||
compile(project(":spring-context"), optional)
|
||||
compile(project(":spring-jdbc"), optional)
|
||||
compile(project(":spring-tx"), optional)
|
||||
compile(project(":spring-orm"), optional)
|
||||
compile(project(":spring-web"), optional)
|
||||
compile(project(":spring-webmvc"), optional)
|
||||
compile(project(":spring-webmvc-portlet"), optional)
|
||||
compile("junit:junit-dep:4.10") { dep ->
|
||||
optional dep
|
||||
// We already have hamcrest-all as a global testCompile dependency.
|
||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||
}
|
||||
compile("org.testng:testng:6.5.2") { dep ->
|
||||
optional dep
|
||||
exclude group: 'junit', module: 'junit'
|
||||
// We already have hamcrest-all as a global testCompile dependency.
|
||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||
}
|
||||
compile("javax.servlet:servlet-api:2.5", optional)
|
||||
compile("javax.servlet.jsp:jsp-api:2.1", optional)
|
||||
compile("javax.portlet:portlet-api:2.0", optional)
|
||||
compile("javax.activation:activation:1.0", provided)
|
||||
testCompile "org.slf4j:slf4j-jcl:1.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-test-mvc') {
|
||||
description = 'Spring Test MVC Framework'
|
||||
ext.mergeIntoProject = project(':spring-test')
|
||||
apply from: "${gradleScriptDir}/merge-artifacts.gradle"
|
||||
apply from: "ide.gradle"
|
||||
dependencies {
|
||||
compile project(":spring-context")
|
||||
compile project(":spring-webmvc")
|
||||
compile project(":spring-test").sourceSets.main.output
|
||||
compile("org.apache.tomcat:tomcat-servlet-api:7.0.32", provided)
|
||||
compile("org.hamcrest:hamcrest-core:1.3", optional)
|
||||
compile("com.jayway.jsonpath:json-path:0.8.1", optional)
|
||||
compile("xmlunit:xmlunit:1.2", optional)
|
||||
testCompile("org.slf4j:jcl-over-slf4j:1.6.1")
|
||||
testCompile("org.slf4j:slf4j-log4j12:1.6.1") {
|
||||
exclude group: 'log4j', module: 'log4j'
|
||||
}
|
||||
testCompile("log4j:log4j:1.2.15") {
|
||||
exclude group: 'javax.mail', module: 'mail'
|
||||
exclude group: 'javax.jms', module: 'jms'
|
||||
exclude group: 'com.sun.jdmk', module: 'jmxtools'
|
||||
exclude group: 'com.sun.jmx', module: 'jmxri'
|
||||
}
|
||||
testCompile "javax.servlet:jstl:1.2"
|
||||
testCompile "org.hibernate:hibernate-validator:4.2.0.Final"
|
||||
testCompile "org.codehaus.jackson:jackson-mapper-asl:1.4.2"
|
||||
testCompile project(":spring-oxm")
|
||||
testCompile "com.thoughtworks.xstream:xstream:1.3.1"
|
||||
testCompile "cglib:cglib-nodep:2.2"
|
||||
testCompile "rome:rome:1.0"
|
||||
testCompile "javax.xml.bind:jaxb-api:2.2.6"
|
||||
testCompile "org.easymock:easymockclassextension:2.3"
|
||||
testCompile("org.springframework.security:spring-security-core:3.1.2.RELEASE") {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testCompile("org.springframework.security:spring-security-web:3.1.2.RELEASE") {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testCompile("org.springframework.security:spring-security-config:3.1.2.RELEASE") {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testCompile("org.springframework.hateoas:spring-hateoas:0.3.0.RELEASE")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-struts') {
|
||||
description = 'Spring Struts'
|
||||
dependencies {
|
||||
compile project(":spring-core")
|
||||
compile project(":spring-beans")
|
||||
compile project(":spring-context")
|
||||
compile project(":spring-web")
|
||||
compile project(":spring-webmvc")
|
||||
compile "struts:struts:1.2.9"
|
||||
compile "commons-beanutils:commons-beanutils:1.7.0"
|
||||
compile("javax.servlet:servlet-api:2.5", provided)
|
||||
testCompile project(":spring-test")
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-aspects') {
|
||||
description = 'Spring Aspects'
|
||||
apply from: 'aspects.gradle'
|
||||
dependencies {
|
||||
compile(project(":spring-beans"), optional) // for @Configurable support
|
||||
compile(project(":spring-aop"), optional) // for @Async support
|
||||
compile(project(":spring-context"), optional) // for @Enable* support
|
||||
compile(project(":spring-context-support"), optional) // for JavaMail support
|
||||
compile(project(":spring-tx"), optional) // for JPA, @Transactional support
|
||||
compile(project(":spring-orm"), optional) // for JPA exception translation support
|
||||
aspects project(":spring-orm")
|
||||
ajc "org.aspectj:aspectjtools:${aspectjVersion}"
|
||||
compile "org.aspectj:aspectjrt:${aspectjVersion}"
|
||||
testCompile project(":spring-core") // for CodeStyleAspect
|
||||
compile project(":spring-beans") // for 'p' namespace visibility
|
||||
testCompile project(":spring-test")
|
||||
}
|
||||
eclipse.project {
|
||||
natures += 'org.eclipse.ajdt.ui.ajnature'
|
||||
buildCommands = [new org.gradle.plugins.ide.eclipse.model.
|
||||
BuildCommand('org.eclipse.ajdt.core.ajbuilder')]
|
||||
}
|
||||
}
|
||||
|
||||
configure(rootProject) {
|
||||
description = 'Spring Framework'
|
||||
|
||||
apply plugin: 'docbook-reference'
|
||||
apply from: "${gradleScriptDir}/jdiff.gradle"
|
||||
|
||||
reference {
|
||||
sourceDir = file('src/reference/docbook')
|
||||
pdfFilename = 'spring-framework-reference.pdf'
|
||||
}
|
||||
|
||||
// don't publish the default jar for the root project
|
||||
configurations.archives.artifacts.clear()
|
||||
|
||||
dependencies { // for integration tests
|
||||
testCompile project(":spring-core")
|
||||
testCompile project(":spring-beans")
|
||||
testCompile project(":spring-aop")
|
||||
testCompile project(":spring-expression")
|
||||
testCompile project(":spring-context")
|
||||
testCompile project(":spring-tx")
|
||||
testCompile project(":spring-jdbc")
|
||||
testCompile project(":spring-test")
|
||||
testCompile project(":spring-web")
|
||||
testCompile project(":spring-webmvc-portlet")
|
||||
testCompile "org.hibernate:hibernate-core:4.1.0.Final"
|
||||
testCompile "javax.servlet:servlet-api:2.5"
|
||||
}
|
||||
|
||||
task api(type: Javadoc) {
|
||||
group = 'Documentation'
|
||||
description = 'Generates aggregated Javadoc API documentation.'
|
||||
title = "${rootProject.description} ${version} API"
|
||||
options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = rootProject.description
|
||||
options.overview = 'src/api/overview.html'
|
||||
options.splitIndex = true
|
||||
options.links(
|
||||
'http://docs.jboss.org/jbossas/javadoc/4.0.5/connector'
|
||||
)
|
||||
source subprojects.collect { project ->
|
||||
project.sourceSets.main.allJava
|
||||
}
|
||||
destinationDir = new File(buildDir, "api")
|
||||
classpath = files(subprojects.collect { project ->
|
||||
project.sourceSets.main.compileClasspath
|
||||
})
|
||||
maxMemory = '1024m'
|
||||
}
|
||||
|
||||
task docsZip(type: Zip) {
|
||||
group = 'Distribution'
|
||||
baseName = 'spring-framework'
|
||||
classifier = 'docs'
|
||||
description = "Builds -${classifier} archive containing api and reference " +
|
||||
"for deployment at http://static.springframework.org/spring-framework/docs."
|
||||
|
||||
from('src/dist') {
|
||||
include 'changelog.txt'
|
||||
}
|
||||
|
||||
from (api) {
|
||||
into 'api'
|
||||
}
|
||||
|
||||
from (reference) {
|
||||
into 'reference'
|
||||
}
|
||||
}
|
||||
|
||||
task schemaZip(type: Zip) {
|
||||
group = 'Distribution'
|
||||
baseName = 'spring-framework'
|
||||
classifier = 'schema'
|
||||
description = "Builds -${classifier} archive containing all " +
|
||||
"XSDs for deployment at http://springframework.org/schema."
|
||||
|
||||
subprojects.each { subproject ->
|
||||
def Properties schemas = new Properties();
|
||||
|
||||
subproject.sourceSets.main.resources.find {
|
||||
it.path.endsWith('META-INF/spring.schemas')
|
||||
}?.withInputStream { schemas.load(it) }
|
||||
|
||||
for (def key : schemas.keySet()) {
|
||||
def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
|
||||
assert shortName != key
|
||||
File xsdFile = subproject.sourceSets.main.resources.find {
|
||||
it.path.endsWith(schemas.get(key))
|
||||
}
|
||||
assert xsdFile != null
|
||||
into (shortName) {
|
||||
from xsdFile.path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) {
|
||||
group = 'Distribution'
|
||||
baseName = 'spring-framework'
|
||||
classifier = 'dist'
|
||||
description = "Builds -${classifier} archive, containing all jars and docs, " +
|
||||
"suitable for community download page."
|
||||
|
||||
ext.baseDir = "${baseName}-${project.version}";
|
||||
|
||||
from('src/dist') {
|
||||
include 'readme.txt'
|
||||
include 'license.txt'
|
||||
include 'notice.txt'
|
||||
into "${baseDir}"
|
||||
expand(copyright: new Date().format('yyyy'), version: project.version)
|
||||
}
|
||||
|
||||
from(zipTree(docsZip.archivePath)) {
|
||||
into "${baseDir}/docs"
|
||||
}
|
||||
|
||||
from(zipTree(schemaZip.archivePath)) {
|
||||
into "${baseDir}/schema"
|
||||
}
|
||||
|
||||
subprojects.each { subproject ->
|
||||
into ("${baseDir}/libs") {
|
||||
from subproject.jar
|
||||
if (subproject.tasks.findByPath('sourcesJar')) {
|
||||
from subproject.sourcesJar
|
||||
}
|
||||
if (subproject.tasks.findByPath('javadocJar')) {
|
||||
from subproject.javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an distribution that contains all dependencies (required and optional).
|
||||
// Not published by default; only for use when building from source.
|
||||
task depsZip(type: Zip, dependsOn: distZip) { zipTask ->
|
||||
group = 'Distribution'
|
||||
baseName = 'spring-framework'
|
||||
classifier = 'dist-with-deps'
|
||||
description = "Builds -${classifier} archive, containing everything " +
|
||||
"in the -${distZip.classifier} archive plus all runtime dependencies."
|
||||
|
||||
from zipTree(distZip.archivePath)
|
||||
|
||||
gradle.taskGraph.whenReady { taskGraph ->
|
||||
if (taskGraph.hasTask(":${zipTask.name}")) {
|
||||
def projectNames = rootProject.subprojects*.name
|
||||
def artifacts = new HashSet()
|
||||
subprojects.each { subproject ->
|
||||
subproject.configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { artifact ->
|
||||
def dependency = artifact.moduleVersion.id
|
||||
if (!projectNames.contains(dependency.name)) {
|
||||
artifacts << artifact.file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zipTask.from(artifacts) {
|
||||
into "${distZip.baseDir}/deps"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives docsZip
|
||||
archives schemaZip
|
||||
archives distZip
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
description = 'Generates gradlew[.bat] scripts'
|
||||
gradleVersion = '1.2'
|
||||
|
||||
doLast() {
|
||||
def gradleOpts = "-XX:MaxPermSize=1024m -Xmx1024m"
|
||||
def gradleBatOpts = "$gradleOpts -XX:MaxHeapSize=256"
|
||||
File wrapperFile = file('gradlew')
|
||||
wrapperFile.text = wrapperFile.text.replace("DEFAULT_JVM_OPTS=",
|
||||
"GRADLE_OPTS=\"$gradleOpts \$GRADLE_OPTS\"\nDEFAULT_JVM_OPTS=")
|
||||
File wrapperBatFile = file('gradlew.bat')
|
||||
wrapperBatFile.text = wrapperBatFile.text.replace("set DEFAULT_JVM_OPTS=",
|
||||
"set GRADLE_OPTS=$gradleBatOpts %GRADLE_OPTS%\nset DEFAULT_JVM_OPTS=")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Spring Framework Build
|
||||
|
||||
This folder contains the custom plugins and conventions for the Spring Framework build.
|
||||
They are declared in the `build.gradle` file in this folder.
|
||||
|
||||
## Build Conventions
|
||||
|
||||
The `org.springframework.build.conventions` plugin applies all conventions to the Framework build:
|
||||
|
||||
* Configuring the Java compiler, see `JavaConventions`
|
||||
* Configuring the Kotlin compiler, see `KotlinConventions`
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
|
||||
|
||||
## Build Plugins
|
||||
|
||||
### Optional dependencies
|
||||
|
||||
The `org.springframework.build.optional-dependencies` plugin creates a new `optional`
|
||||
Gradle configuration - it adds the dependencies to the project's compile and runtime classpath
|
||||
but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
The `spring-core-test` project module contributes the `RuntimeHintsAgent` Java agent.
|
||||
|
||||
The `RuntimeHintsAgentPlugin` Gradle plugin creates a dedicated `"runtimeHintsTest"` test task for each project.
|
||||
This task will detect and execute [tests tagged](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-gradle)
|
||||
with the `"RuntimeHintsTests"` [JUnit tag](https://junit.org/junit5/docs/current/user-guide/#running-tests-tags).
|
||||
In the Spring Framework test suite, those are usually annotated with the `@EnabledIfRuntimeHintsAgent` annotation.
|
||||
|
||||
By default, the agent will instrument all classes located in the `"org.springframework"` package, as they are loaded.
|
||||
The `RuntimeHintsAgentExtension` allows to customize this using a DSL:
|
||||
|
||||
```groovy
|
||||
// this applies the `RuntimeHintsAgentPlugin` to the project
|
||||
plugins {
|
||||
id 'org.springframework.build.runtimehints-agent'
|
||||
}
|
||||
|
||||
// You can configure the agent to include and exclude packages from the instrumentation process.
|
||||
runtimeHintsAgent {
|
||||
includedPackages = ["org.springframework", "io.spring"]
|
||||
excludedPackages = ["org.example"]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// to use the test infrastructure, the project should also depend on the "spring-core-test" module
|
||||
testImplementation(project(":spring-core-test"))
|
||||
}
|
||||
```
|
||||
|
||||
With this configuration, `./gradlew runtimeHintsTest` will run all tests instrumented by this java agent.
|
||||
The global `./gradlew check` task depends on `runtimeHintsTest`.
|
||||
|
||||
NOTE: the "spring-core-test" module doesn't shade "spring-core" by design, so the agent should never instrument
|
||||
code that doesn't have "spring-core" on its classpath.
|
||||
@@ -1,48 +0,0 @@
|
||||
plugins {
|
||||
id 'java-gradle-plugin'
|
||||
id 'checkstyle'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
ext {
|
||||
def propertiesFile = new File(new File("$projectDir").parentFile, "gradle.properties")
|
||||
propertiesFile.withInputStream {
|
||||
def properties = new Properties()
|
||||
properties.load(it)
|
||||
set("kotlinVersion", properties["kotlinVersion"])
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
}
|
||||
localDevPlugin {
|
||||
id = "org.springframework.build.localdev"
|
||||
implementationClass = "org.springframework.build.dev.LocalDevelopmentPlugin"
|
||||
}
|
||||
optionalDependenciesPlugin {
|
||||
id = "org.springframework.build.optional-dependencies"
|
||||
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
|
||||
}
|
||||
runtimeHintsAgentPlugin {
|
||||
id = "org.springframework.build.runtimehints-agent"
|
||||
implementationClass = "org.springframework.build.hint.RuntimeHintsAgentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="com.puppycrawl.tools.checkstyle.Checker">
|
||||
|
||||
<!-- Root Checks -->
|
||||
<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="packageInfoHeaderType" value="none"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck"/>
|
||||
|
||||
<!-- TreeWalker Checks -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
|
||||
|
||||
<!-- Imports -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck">
|
||||
<property name="processJavadoc" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Modifiers -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck"/>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -1,2 +0,0 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.43
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
|
||||
import io.spring.nohttp.gradle.NoHttpExtension;
|
||||
import io.spring.nohttp.gradle.NoHttpPlugin;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.quality.Checkstyle;
|
||||
import org.gradle.api.plugins.quality.CheckstyleExtension;
|
||||
import org.gradle.api.plugins.quality.CheckstylePlugin;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for checkstyle.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class CheckstyleConventions {
|
||||
|
||||
/**
|
||||
* Applies the Spring Java Format and Checkstyle plugins with the project conventions.
|
||||
* @param project the current project
|
||||
*/
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, (java) -> {
|
||||
if (project.getRootProject() == project) {
|
||||
configureNoHttpPlugin(project);
|
||||
}
|
||||
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.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
checkstyleDependencies.add(
|
||||
project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
|
||||
});
|
||||
}
|
||||
|
||||
private static void configureNoHttpPlugin(Project project) {
|
||||
project.getPlugins().apply(NoHttpPlugin.class);
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
Path projectPath = rootPath.relativize(subproject.getProjectDir().toPath());
|
||||
for (String buildFolder : buildFolders) {
|
||||
Path innerBuildDir = projectPath.resolve(buildFolder);
|
||||
noHttp.getSource().exclude(innerBuildDir + File.separator + "**");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
|
||||
/**
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
* Conventions are applied in response to various plugins being applied.
|
||||
*
|
||||
* <p>When the {@link JavaBasePlugin} is applied, the conventions in {@link CheckstyleConventions},
|
||||
* {@link TestConventions} and {@link JavaConventions} are applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* are applied.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
new CheckstyleConventions().apply(project);
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
new TestConventions().apply(project);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JvmVendorSpec;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class JavaConventions {
|
||||
|
||||
private static final List<String> COMPILER_ARGS;
|
||||
|
||||
private static final List<String> TEST_COMPILER_ARGS;
|
||||
|
||||
static {
|
||||
List<String> commonCompilerArgs = Arrays.asList(
|
||||
"-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann",
|
||||
"-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides",
|
||||
"-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options",
|
||||
"-parameters"
|
||||
);
|
||||
COMPILER_ARGS = new ArrayList<>();
|
||||
COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
COMPILER_ARGS.addAll(Arrays.asList(
|
||||
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
|
||||
"-Xlint:unchecked", "-Werror"
|
||||
));
|
||||
TEST_COMPILER_ARGS = new ArrayList<>();
|
||||
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
TEST_COMPILER_ARGS.addAll(Arrays.asList("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||
"-Xlint:-deprecation", "-Xlint:-unchecked"));
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> applyJavaCompileConventions(project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the common Java compiler options for main sources, test fixture sources, and
|
||||
* test sources.
|
||||
* @param project the current project
|
||||
*/
|
||||
private void applyJavaCompileConventions(Project project) {
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
|
||||
toolchain.getLanguageVersion().set(JavaLanguageVersion.of(17));
|
||||
});
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class KotlinConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm",
|
||||
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
|
||||
}
|
||||
|
||||
private void configure(KotlinCompile compile) {
|
||||
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
|
||||
kotlinOptions.setApiVersion("1.7");
|
||||
kotlinOptions.setLanguageVersion("1.7");
|
||||
kotlinOptions.setJvmTarget("17");
|
||||
kotlinOptions.setJavaParameters(true);
|
||||
kotlinOptions.setAllWarningsAsErrors(true);
|
||||
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
|
||||
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
|
||||
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.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;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
|
||||
* plugin is applied:
|
||||
* <ul>
|
||||
* <li>The {@link TestRetryPlugin Test Retry} plugin is applied so that flaky tests
|
||||
* are retried 3 times when running on the CI server.
|
||||
* </ul>
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class TestConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, (java) -> configureTestConventions(project));
|
||||
}
|
||||
|
||||
private void configureTestConventions(Project project) {
|
||||
project.getTasks().withType(Test.class,
|
||||
test -> {
|
||||
configureTests(project, test);
|
||||
configureTestRetryPlugin(project, test);
|
||||
});
|
||||
}
|
||||
|
||||
private void configureTests(Project project, Test test) {
|
||||
TestFrameworkOptions existingOptions = test.getOptions();
|
||||
test.useJUnitPlatform(options -> {
|
||||
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
|
||||
options.copyFrom(junitPlatformOptions);
|
||||
}
|
||||
});
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
"io.netty.leakDetection.level", "paranoid",
|
||||
"io.netty5.leakDetectionLevel", "paranoid",
|
||||
"io.netty5.leakDetection.targetRecords", "32",
|
||||
"io.netty5.buffer.lifecycleTracingEnabled", "true"
|
||||
));
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
}
|
||||
test.jvmArgs(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"-Xshare:off"
|
||||
);
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
testRetry.getFailOnPassedAfterRetry().set(true);
|
||||
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isCi() {
|
||||
return Boolean.parseBoolean(System.getenv("CI"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.dev;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that skips documentation tasks when the {@code "-PskipDocs"} property is defined.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class LocalDevelopmentPlugin implements Plugin<Project> {
|
||||
|
||||
private static final String SKIP_DOCS_PROPERTY = "skipDocs";
|
||||
|
||||
@Override
|
||||
public void apply(Project target) {
|
||||
if (target.hasProperty(SKIP_DOCS_PROPERTY)) {
|
||||
skipDocumentationTasks(target);
|
||||
target.subprojects(this::skipDocumentationTasks);
|
||||
}
|
||||
}
|
||||
|
||||
private void skipDocumentationTasks(Project project) {
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().matching(task -> {
|
||||
return JavaBasePlugin.DOCUMENTATION_GROUP.equals(task.getGroup())
|
||||
|| "distribution".equals(task.getGroup());
|
||||
})
|
||||
.forEach(task -> task.setEnabled(false));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.process.CommandLineArgumentProvider;
|
||||
|
||||
/**
|
||||
* Argument provider for registering the runtime hints agent with a Java process.
|
||||
*/
|
||||
public interface RuntimeHintsAgentArgumentProvider extends CommandLineArgumentProvider {
|
||||
|
||||
@Classpath
|
||||
ConfigurableFileCollection getAgentJar();
|
||||
|
||||
@Input
|
||||
SetProperty<String> getIncludedPackages();
|
||||
|
||||
@Input
|
||||
SetProperty<String> getExcludedPackages();
|
||||
|
||||
@Override
|
||||
default Iterable<String> asArguments() {
|
||||
StringBuilder packages = new StringBuilder();
|
||||
getIncludedPackages().get().forEach(packageName -> packages.append('+').append(packageName).append(','));
|
||||
getExcludedPackages().get().forEach(packageName -> packages.append('-').append(packageName).append(','));
|
||||
return Collections.singleton("-javaagent:" + getAgentJar().getSingleFile() + "=" + packages);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
|
||||
/**
|
||||
* Entry point to the DSL extension for the {@link RuntimeHintsAgentPlugin} Gradle plugin.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public interface RuntimeHintsAgentExtension {
|
||||
|
||||
SetProperty<String> getIncludedPackages();
|
||||
|
||||
SetProperty<String> getExcludedPackages();
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.attributes.Bundling;
|
||||
import org.gradle.api.attributes.Category;
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that configures the {@code RuntimeHints} Java agent to test tasks.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
|
||||
public static final String RUNTIMEHINTS_TEST_TASK = "runtimeHintsTest";
|
||||
private static final String EXTENSION_NAME = "runtimeHintsAgent";
|
||||
private static final String CONFIGURATION_NAME = "testRuntimeHintsAgentJar";
|
||||
|
||||
|
||||
@Override
|
||||
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.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.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
|
||||
});
|
||||
}
|
||||
|
||||
private static RuntimeHintsAgentExtension createRuntimeHintsAgentExtension(Project project) {
|
||||
RuntimeHintsAgentExtension agentExtension = project.getExtensions().create(EXTENSION_NAME, RuntimeHintsAgentExtension.class);
|
||||
agentExtension.getIncludedPackages().convention(Collections.singleton("org.springframework"));
|
||||
agentExtension.getExcludedPackages().convention(Collections.emptySet());
|
||||
return agentExtension;
|
||||
}
|
||||
|
||||
private static RuntimeHintsAgentArgumentProvider createRuntimeHintsAgentArgumentProvider(
|
||||
Project project, RuntimeHintsAgentExtension agentExtension) {
|
||||
RuntimeHintsAgentArgumentProvider agentArgumentProvider = project.getObjects().newInstance(RuntimeHintsAgentArgumentProvider.class);
|
||||
agentArgumentProvider.getAgentJar().from(createRuntimeHintsAgentConfiguration(project));
|
||||
agentArgumentProvider.getIncludedPackages().set(agentExtension.getIncludedPackages());
|
||||
agentArgumentProvider.getExcludedPackages().set(agentExtension.getExcludedPackages());
|
||||
return agentArgumentProvider;
|
||||
}
|
||||
|
||||
private static Configuration createRuntimeHintsAgentConfiguration(Project project) {
|
||||
return project.getConfigurations().create(CONFIGURATION_NAME, configuration -> {
|
||||
configuration.setCanBeConsumed(false);
|
||||
configuration.setTransitive(false); // Only the built artifact is required
|
||||
configuration.attributes(attributes -> {
|
||||
attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL));
|
||||
attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY));
|
||||
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR));
|
||||
attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, Integer.valueOf(JavaVersion.current().getMajorVersion()));
|
||||
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.optional;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
|
||||
/**
|
||||
* A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new
|
||||
* {@code optional} configuration. The {@code optional} configuration is part of the
|
||||
* project's compile and runtime classpaths but does not affect the classpath of
|
||||
* dependent projects.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class OptionalDependenciesPlugin implements Plugin<Project> {
|
||||
|
||||
/**
|
||||
* Name of the {@code optional} configuration.
|
||||
*/
|
||||
public static final String OPTIONAL_CONFIGURATION_NAME = "optional";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
Configuration optional = project.getConfigurations().create(OPTIONAL_CONFIGURATION_NAME);
|
||||
optional.setCanBeConsumed(false);
|
||||
optional.setCanBeResolved(false);
|
||||
project.getPlugins().withType(JavaBasePlugin.class, (javaBasePlugin) -> {
|
||||
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class)
|
||||
.getSourceSets();
|
||||
sourceSets.all((sourceSet) -> {
|
||||
project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(optional);
|
||||
project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(optional);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.shadow;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentSelector;
|
||||
import org.gradle.api.artifacts.query.ArtifactResolutionQuery;
|
||||
import org.gradle.api.artifacts.result.ArtifactResolutionResult;
|
||||
import org.gradle.api.artifacts.result.ComponentArtifactsResult;
|
||||
import org.gradle.api.artifacts.result.DependencyResult;
|
||||
import org.gradle.api.artifacts.result.ResolutionResult;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.jvm.JvmLibrary;
|
||||
import org.gradle.language.base.artifact.SourcesArtifact;
|
||||
|
||||
/**
|
||||
* Gradle task to add source from shadowed jars into our own source jars.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ShadowSource extends DefaultTask {
|
||||
|
||||
private final DirectoryProperty outputDirectory = getProject().getObjects().directoryProperty();
|
||||
|
||||
private List<Configuration> configurations = new ArrayList<>();
|
||||
|
||||
private final List<Relocation> relocations = new ArrayList<>();
|
||||
|
||||
|
||||
@Classpath
|
||||
@Optional
|
||||
public List<Configuration> getConfigurations() {
|
||||
return this.configurations;
|
||||
}
|
||||
|
||||
public void setConfigurations(List<Configuration> configurations) {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
|
||||
@Nested
|
||||
public List<Relocation> getRelocations() {
|
||||
return this.relocations;
|
||||
}
|
||||
|
||||
public void relocate(String pattern, String destination) {
|
||||
this.relocations.add(new Relocation(pattern, destination));
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
DirectoryProperty getOutputDirectory() {
|
||||
return this.outputDirectory;
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void syncSourceJarFiles() {
|
||||
sync(getSourceJarFiles());
|
||||
}
|
||||
|
||||
private List<File> getSourceJarFiles() {
|
||||
List<File> sourceJarFiles = new ArrayList<>();
|
||||
for (Configuration configuration : this.configurations) {
|
||||
ResolutionResult resolutionResult = configuration.getIncoming().getResolutionResult();
|
||||
resolutionResult.getRootComponent().get().getDependencies().forEach(dependency -> {
|
||||
Set<ComponentArtifactsResult> artifactsResults = resolveSourceArtifacts(dependency);
|
||||
for (ComponentArtifactsResult artifactResult : artifactsResults) {
|
||||
artifactResult.getArtifacts(SourcesArtifact.class).forEach(sourceArtifact -> {
|
||||
sourceJarFiles.add(((ResolvedArtifactResult) sourceArtifact).getFile());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return Collections.unmodifiableList(sourceJarFiles);
|
||||
}
|
||||
|
||||
private Set<ComponentArtifactsResult> resolveSourceArtifacts(DependencyResult dependency) {
|
||||
ModuleComponentSelector componentSelector = (ModuleComponentSelector) dependency.getRequested();
|
||||
ArtifactResolutionQuery query = getProject().getDependencies().createArtifactResolutionQuery()
|
||||
.forModule(componentSelector.getGroup(), componentSelector.getModule(), componentSelector.getVersion());
|
||||
return executeQuery(query).getResolvedComponents();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ArtifactResolutionResult executeQuery(ArtifactResolutionQuery query) {
|
||||
return query.withArtifacts(JvmLibrary.class, SourcesArtifact.class).execute();
|
||||
}
|
||||
|
||||
private void sync(List<File> sourceJarFiles) {
|
||||
getProject().sync(spec -> {
|
||||
spec.into(this.outputDirectory);
|
||||
spec.eachFile(this::relocateFile);
|
||||
spec.filter(this::transformContent);
|
||||
spec.exclude("META-INF/**");
|
||||
spec.setIncludeEmptyDirs(false);
|
||||
sourceJarFiles.forEach(sourceJar -> spec.from(zipTree(sourceJar)));
|
||||
});
|
||||
}
|
||||
|
||||
private void relocateFile(FileCopyDetails details) {
|
||||
String path = details.getPath();
|
||||
for (Relocation relocation : this.relocations) {
|
||||
path = relocation.relocatePath(path);
|
||||
}
|
||||
details.setPath(path);
|
||||
}
|
||||
|
||||
private String transformContent(String content) {
|
||||
for (Relocation relocation : this.relocations) {
|
||||
content = relocation.transformContent(content);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private FileTree zipTree(File sourceJar) {
|
||||
return getProject().zipTree(sourceJar);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A single relocation.
|
||||
*/
|
||||
static class Relocation {
|
||||
|
||||
private final String pattern;
|
||||
|
||||
private final String pathPattern;
|
||||
|
||||
private final String destination;
|
||||
|
||||
private final String pathDestination;
|
||||
|
||||
|
||||
Relocation(String pattern, String destination) {
|
||||
this.pattern = pattern;
|
||||
this.pathPattern = pattern.replace('.', '/');
|
||||
this.destination = destination;
|
||||
this.pathDestination = destination.replace('.', '/');
|
||||
}
|
||||
|
||||
|
||||
@Input
|
||||
public String getPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
@Input
|
||||
public String getDestination() {
|
||||
return this.destination;
|
||||
}
|
||||
|
||||
String relocatePath(String path) {
|
||||
return path.replace(this.pathPattern, this.pathDestination);
|
||||
}
|
||||
|
||||
public String transformContent(String content) {
|
||||
return content.replaceAll("\\b" + this.pattern, this.destination);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
plugins {
|
||||
id 'java-platform'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
}
|
||||
|
||||
description = "Spring Framework API Docs"
|
||||
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://repo.spring.io/release"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
moduleProjects.each { moduleProject ->
|
||||
javadoc moduleProject
|
||||
}
|
||||
}
|
||||
|
||||
javadoc {
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
author = true
|
||||
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
|
||||
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
|
||||
}
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
classpath += files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
project(":spring-aspects").sourceSets.main.output
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce KDoc for all Spring Framework modules in "build/docs/kdoc"
|
||||
*/
|
||||
rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
dependsOn {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.java.docsDir.dir("kdoc-api").get().asFile)
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all Java docs (javadoc & kdoc) into a single archive
|
||||
*/
|
||||
tasks.register('docsZip', Zip) {
|
||||
dependsOn = ['javadoc', rootProject.tasks.dokkaHtmlMultiModule]
|
||||
group = "distribution"
|
||||
description = "Builds -${archiveClassifier} archive containing api and reference " +
|
||||
"for deployment at https://docs.spring.io/spring-framework/docs/."
|
||||
|
||||
archiveBaseName.set("spring-framework")
|
||||
archiveClassifier.set("docs")
|
||||
from("src/dist") {
|
||||
include "changelog.txt"
|
||||
}
|
||||
from(javadoc) {
|
||||
into "javadoc-api"
|
||||
}
|
||||
from(rootProject.tasks.dokkaHtmlMultiModule.outputDirectory) {
|
||||
into "kdoc-api"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all Spring Framework schemas into a single archive
|
||||
*/
|
||||
tasks.register('schemaZip', Zip) {
|
||||
group = "distribution"
|
||||
archiveBaseName.set("spring-framework")
|
||||
archiveClassifier.set("schema")
|
||||
description = "Builds -${archiveClassifier} archive containing all " +
|
||||
"XSDs for deployment at https://springframework.org/schema."
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
moduleProjects.each { module ->
|
||||
def Properties schemas = new Properties();
|
||||
|
||||
module.sourceSets.main.resources.find {
|
||||
(it.path.endsWith("META-INF/spring.schemas") || it.path.endsWith("META-INF\\spring.schemas"))
|
||||
}?.withInputStream { schemas.load(it) }
|
||||
|
||||
for (def key : schemas.keySet()) {
|
||||
def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
|
||||
assert shortName != key
|
||||
File xsdFile = module.sourceSets.main.resources.find {
|
||||
(it.path.endsWith(schemas.get(key)) || it.path.endsWith(schemas.get(key).replaceAll('\\/', '\\\\')))
|
||||
}
|
||||
assert xsdFile != null
|
||||
into(shortName) {
|
||||
from xsdFile.path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifact docsZip
|
||||
artifact schemaZip
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
description = "Spring Framework (Bill of Materials)"
|
||||
|
||||
apply plugin: 'java-platform'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
group = "org.springframework"
|
||||
|
||||
dependencies {
|
||||
constraints {
|
||||
parent.moduleProjects.sort { "$it.name" }.each {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = 'spring-framework-bom'
|
||||
from components.javaPlatform
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
antora:
|
||||
extensions:
|
||||
- require: '@springio/antora-extensions'
|
||||
root_component_name: 'framework'
|
||||
site:
|
||||
title: Spring Framework
|
||||
url: https://docs.spring.io/spring-framework/reference
|
||||
robots: allow
|
||||
git:
|
||||
ensure_git_suffix: false
|
||||
content:
|
||||
sources:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
- '@springio/asciidoctor-extensions/include-code-extension'
|
||||
attributes:
|
||||
page-stackoverflow-url: https://stackoverflow.com/questions/tagged/spring
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
tabs-sync-option: '@'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
runtime:
|
||||
log:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
|
||||
@@ -1,97 +0,0 @@
|
||||
name: framework
|
||||
version: true
|
||||
title: Spring Framework
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
run:
|
||||
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g" :framework-docs:generateAntoraResources
|
||||
local: true
|
||||
scan:
|
||||
dir: ./build/generated-antora-resources
|
||||
|
||||
asciidoc:
|
||||
attributes:
|
||||
attribute-missing: 'warn'
|
||||
# FIXME: the copyright is not removed
|
||||
# FIXME: The package is not renamed
|
||||
chomp: 'all'
|
||||
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"
|
||||
spring-site-guides: '{spring-site}/guides'
|
||||
spring-site-projects: '{spring-site}/projects'
|
||||
spring-site-tools: "{spring-site}/tools"
|
||||
spring-org: 'spring-projects'
|
||||
spring-github-org: "https://github.com/{spring-org}"
|
||||
spring-framework-github: "https://github.com/{spring-org}/spring-framework"
|
||||
spring-framework-code: '{spring-framework-github}/tree/main'
|
||||
spring-framework-issues: '{spring-framework-github}/issues'
|
||||
spring-framework-wiki: '{spring-framework-github}/wiki'
|
||||
# Docs
|
||||
docs-site: 'https://docs.spring.io'
|
||||
spring-framework-docs-root: '{docs-site}/spring-framework/docs'
|
||||
spring-framework-api: '{spring-framework-docs-root}/{spring-version}/javadoc-api/org/springframework'
|
||||
spring-framework-api-kdoc: '{spring-framework-docs-root}/{spring-version}/kdoc-api'
|
||||
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-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
|
||||
docs-spring-gemfire: '{docs-site}/spring-gemfire/docs/current/reference'
|
||||
docs-spring-security: '{docs-site}/spring-security/reference'
|
||||
docs-spring-session: '{docs-site}/spring-session/reference'
|
||||
#
|
||||
# External projects URLs and related attributes
|
||||
aspectj-site: 'https://www.eclipse.org/aspectj'
|
||||
aspectj-docs: "{aspectj-site}/doc/released"
|
||||
aspectj-api: "{aspectj-docs}/runtime-api"
|
||||
aspectj-docs-devguide: "{aspectj-docs}/devguide"
|
||||
aspectj-docs-progguide: "{aspectj-docs}/progguide"
|
||||
assertj-docs: 'https://assertj.github.io/doc'
|
||||
baeldung-blog: 'https://www.baeldung.com'
|
||||
bean-validation-site: 'https://beanvalidation.org'
|
||||
graalvm-docs: 'https://www.graalvm.org/22.3/reference-manual'
|
||||
hibernate-validator-site: 'https://hibernate.org/validator/'
|
||||
jackson-docs: 'https://fasterxml.github.io'
|
||||
jackson-github-org: 'https://github.com/FasterXML'
|
||||
java-api: 'https://docs.oracle.com/en/java/javase/17/docs/api'
|
||||
java-tutorial: 'https://docs.oracle.com/javase/tutorial'
|
||||
JSR: 'https://www.jcp.org/en/jsr/detail?id='
|
||||
kotlin-site: 'https://kotlinlang.org'
|
||||
kotlin-docs: '{kotlin-site}/docs'
|
||||
kotlin-api: '{kotlin-site}/api/latest'
|
||||
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'
|
||||
reactor-site: 'https://projectreactor.io'
|
||||
rsocket-github-org: 'https://github.com/rsocket'
|
||||
rsocket-java: '{rsocket-github-org}/rsocket-java'
|
||||
rsocket-java-code: '{rsocket-java}/tree/master/'
|
||||
rsocket-protocol-extensions: '{rsocket-github-org}/rsocket/tree/master/Extensions'
|
||||
rsocket-site: 'https://rsocket.io'
|
||||
rfc-site: 'https://datatracker.ietf.org/doc/html'
|
||||
sockjs-client: 'https://github.com/sockjs/sockjs-client'
|
||||
sockjs-protocol: 'https://github.com/sockjs/sockjs-protocol'
|
||||
sockjs-protocol-site: "https://sockjs.github.io/sockjs-protocol"
|
||||
stackoverflow-site: 'https://stackoverflow.com'
|
||||
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'
|
||||
@@ -1,75 +0,0 @@
|
||||
plugins {
|
||||
id 'kotlin'
|
||||
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
|
||||
id 'org.antora' version '1.0.0'
|
||||
}
|
||||
|
||||
description = "Spring Framework Docs"
|
||||
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
tasks.named("generateAntoraYml") {
|
||||
asciidocAttributes = project.provider( {
|
||||
return ["spring-version": project.version ]
|
||||
} )
|
||||
}
|
||||
|
||||
tasks.register("generateAntoraResources") {
|
||||
dependsOn 'generateAntoraYml'
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
javadoc {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
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"))
|
||||
|
||||
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("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")
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 63 KiB |
@@ -1,612 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generated by Microsoft Visio 11.0, SVG Export, v1.0 spring-overview.svg Page-1 -->
|
||||
|
||||
<svg
|
||||
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="6.4728541in"
|
||||
height="5.9522467in"
|
||||
viewBox="0 0 466.04561 428.56238"
|
||||
xml:space="preserve"
|
||||
class="st5"
|
||||
id="svg5499"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mvc-splitted-contexts.svg"
|
||||
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
|
||||
inkscape:export-filename="/Users/seb/Workspace/spring-framework/src/asciidoc/images/mvc-splitted-contexts.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90"><metadata
|
||||
id="metadata5713"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="https://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs5711"><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker9162"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path9164" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker8834"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8836" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker8518"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8520" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker8214"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8216" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7922"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7924" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7641"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7644" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7373"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7375" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7117"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7119" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6873"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6875" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6641"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6643" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6421"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6423" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6213"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6215" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6017"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6019" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5833"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5835" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5661"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5663" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5501"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5503" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5353"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5355" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5217"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5219" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5093"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5095" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker4981"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path4983" /></marker><marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow2Mend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"><path
|
||||
id="path7394"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker8121"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8123"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker8033"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8035"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker7957"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7959"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow1Mend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7376"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow2Lend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7388"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow1Lend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7370"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="1005"
|
||||
id="namedview5709"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1640492"
|
||||
inkscape:cx="134.86698"
|
||||
inkscape:cy="203.59898"
|
||||
inkscape:window-x="84"
|
||||
inkscape:window-y="176"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g5503"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" /><v:documentProperties
|
||||
v:langID="1033"
|
||||
v:viewMarkup="false" /><style
|
||||
type="text/css"
|
||||
id="style5501"><![CDATA[
|
||||
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
|
||||
.st4 {font-size:0.333333em;font-weight:normal}
|
||||
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]></style><g
|
||||
v:mID="0"
|
||||
v:index="1"
|
||||
v:groupContext="foregroundPage"
|
||||
id="g5503"
|
||||
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6599"
|
||||
width="382.68423"
|
||||
height="146.09897"
|
||||
x="87.884865"
|
||||
y="148.26482" /><v:userDefs><v:ud
|
||||
v:nameU="SchemeName"
|
||||
v:val="VT4(Default)" /></v:userDefs><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5725"
|
||||
width="464.31128"
|
||||
height="374.11411"
|
||||
x="41.684383"
|
||||
y="112.3262" /><title
|
||||
id="title5505">Page-1</title><v:pageProperties
|
||||
v:drawingScale="0.0393701"
|
||||
v:pageScale="0.0393701"
|
||||
v:drawingUnits="24"
|
||||
v:shadowOffsetX="8.50394"
|
||||
v:shadowOffsetY="-8.50394" /><v:layer
|
||||
v:name="Connector"
|
||||
v:index="0" /><rect
|
||||
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5715"
|
||||
width="322.8194"
|
||||
height="43.63184"
|
||||
x="119.95335"
|
||||
y="-135.66222"
|
||||
transform="scale(1,-1)" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
id="text5717"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5719"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
id="text6589"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6591"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
id="text6593"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6595"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="260.00443"
|
||||
y="212.83057"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan6597">and other web-related beans)</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6620"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="114.52653"
|
||||
y="-259.43161"
|
||||
transform="scale(1,-1)" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6622"
|
||||
width="87.843979"
|
||||
height="36.697304"
|
||||
x="223.39864"
|
||||
y="-287.19809"
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-264.81918"
|
||||
x="117.92834"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6614"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
id="text6616"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6618"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
style="font-size:11.53426838px">Controllers</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">ViewResolver</tspan></text>
|
||||
<rect
|
||||
transform="scale(1,-1)"
|
||||
y="-259.30249"
|
||||
x="332.5405"
|
||||
height="36.577778"
|
||||
width="114.4539"
|
||||
id="rect6628"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
id="text6630"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6632"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
style="font-size:11.53426838px">HandlerMapping</tspan></text>
|
||||
<rect
|
||||
y="338.69724"
|
||||
x="87.803261"
|
||||
height="121.5683"
|
||||
width="382.84744"
|
||||
id="rect6634"
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6636"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
id="tspan6638"
|
||||
sodipodi:role="line">Root WebApplicationContext</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6640"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan6644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
|
||||
<rect
|
||||
transform="scale(1,-1)"
|
||||
y="-440.06805"
|
||||
x="164.32933"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6648"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6650"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="167.73116"
|
||||
y="-445.45563"
|
||||
transform="scale(1,-1)" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6652"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
id="tspan6654"
|
||||
sodipodi:role="line">Services</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6656"
|
||||
width="86.393044"
|
||||
height="36.704323"
|
||||
x="306.86328"
|
||||
y="-439.60837"
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-444.99475"
|
||||
x="310.26624"
|
||||
height="36.701977"
|
||||
width="86.876686"
|
||||
id="rect6658"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
id="text6660"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6662"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
style="font-size:11.53426838px">Repositories</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
|
||||
id="path7643"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
|
||||
id="path7645"
|
||||
inkscape:connector-curvature="0" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
id="text4963"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan4965">Delegates if no bean found</tspan></text>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 102 KiB |
@@ -1 +0,0 @@
|
||||
../../../src
|
||||
@@ -1,457 +0,0 @@
|
||||
* xref:overview.adoc[Overview]
|
||||
* xref:core.adoc[]
|
||||
** xref:core/beans.adoc[]
|
||||
*** xref:core/beans/introduction.adoc[]
|
||||
*** xref:core/beans/basics.adoc[]
|
||||
*** xref:core/beans/definition.adoc[]
|
||||
*** xref:core/beans/dependencies.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-collaborators.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-properties-detailed.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-dependson.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-lazy-init.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-autowire.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-method-injection.adoc[]
|
||||
*** xref:core/beans/factory-scopes.adoc[]
|
||||
*** xref:core/beans/factory-nature.adoc[]
|
||||
*** xref:core/beans/child-bean-definitions.adoc[]
|
||||
*** xref:core/beans/factory-extension.adoc[]
|
||||
*** xref:core/beans/annotation-config.adoc[]
|
||||
**** xref:core/beans/annotation-config/autowired.adoc[]
|
||||
**** xref:core/beans/annotation-config/autowired-primary.adoc[]
|
||||
**** xref:core/beans/annotation-config/autowired-qualifiers.adoc[]
|
||||
**** xref:core/beans/annotation-config/generics-as-qualifiers.adoc[]
|
||||
**** xref:core/beans/annotation-config/custom-autowire-configurer.adoc[]
|
||||
**** xref:core/beans/annotation-config/resource.adoc[]
|
||||
**** xref:core/beans/annotation-config/value-annotations.adoc[]
|
||||
**** xref:core/beans/annotation-config/postconstruct-and-predestroy-annotations.adoc[]
|
||||
*** xref:core/beans/classpath-scanning.adoc[]
|
||||
*** xref:core/beans/standard-annotations.adoc[]
|
||||
*** xref:core/beans/java.adoc[]
|
||||
**** xref:core/beans/java/basic-concepts.adoc[]
|
||||
**** xref:core/beans/java/instantiating-container.adoc[]
|
||||
**** xref:core/beans/java/bean-annotation.adoc[]
|
||||
**** xref:core/beans/java/configuration-annotation.adoc[]
|
||||
**** xref:core/beans/java/composing-configuration-classes.adoc[]
|
||||
*** xref:core/beans/environment.adoc[]
|
||||
*** xref:core/beans/context-load-time-weaver.adoc[]
|
||||
*** xref:core/beans/context-introduction.adoc[]
|
||||
*** xref:core/beans/beanfactory.adoc[]
|
||||
** xref:core/resources.adoc[]
|
||||
** xref:core/validation.adoc[]
|
||||
*** xref:core/validation/validator.adoc[]
|
||||
*** xref:core/validation/beans-beans.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/convert.adoc[]
|
||||
*** xref:core/validation/format.adoc[]
|
||||
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
|
||||
*** xref:core/validation/beanvalidation.adoc[]
|
||||
** xref:core/expressions.adoc[]
|
||||
*** xref:core/expressions/evaluation.adoc[]
|
||||
*** xref:core/expressions/beandef.adoc[]
|
||||
*** xref:core/expressions/language-ref.adoc[]
|
||||
**** xref:core/expressions/language-ref/literal.adoc[]
|
||||
**** xref:core/expressions/language-ref/properties-arrays.adoc[]
|
||||
**** xref:core/expressions/language-ref/inline-lists.adoc[]
|
||||
**** xref:core/expressions/language-ref/inline-maps.adoc[]
|
||||
**** xref:core/expressions/language-ref/array-construction.adoc[]
|
||||
**** xref:core/expressions/language-ref/methods.adoc[]
|
||||
**** xref:core/expressions/language-ref/operators.adoc[]
|
||||
**** xref:core/expressions/language-ref/types.adoc[]
|
||||
**** 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[]
|
||||
**** xref:core/expressions/language-ref/operator-safe-navigation.adoc[]
|
||||
**** xref:core/expressions/language-ref/collection-selection.adoc[]
|
||||
**** xref:core/expressions/language-ref/collection-projection.adoc[]
|
||||
**** xref:core/expressions/language-ref/templating.adoc[]
|
||||
*** xref:core/expressions/example-classes.adoc[]
|
||||
** xref:core/aop.adoc[]
|
||||
*** xref:core/aop/introduction-defn.adoc[]
|
||||
*** xref:core/aop/introduction-spring-defn.adoc[]
|
||||
*** xref:core/aop/introduction-proxies.adoc[]
|
||||
*** xref:core/aop/ataspectj.adoc[]
|
||||
**** xref:core/aop/ataspectj/aspectj-support.adoc[]
|
||||
**** xref:core/aop/ataspectj/at-aspectj.adoc[]
|
||||
**** xref:core/aop/ataspectj/pointcuts.adoc[]
|
||||
**** xref:core/aop/ataspectj/advice.adoc[]
|
||||
**** xref:core/aop/ataspectj/introductions.adoc[]
|
||||
**** xref:core/aop/ataspectj/instantiation-models.adoc[]
|
||||
**** xref:core/aop/ataspectj/example.adoc[]
|
||||
*** xref:core/aop/schema.adoc[]
|
||||
*** xref:core/aop/choosing.adoc[]
|
||||
*** xref:core/aop/mixing-styles.adoc[]
|
||||
*** xref:core/aop/proxying.adoc[]
|
||||
*** xref:core/aop/aspectj-programmatic.adoc[]
|
||||
*** xref:core/aop/using-aspectj.adoc[]
|
||||
*** xref:core/aop/resources.adoc[]
|
||||
** xref:core/aop-api.adoc[]
|
||||
*** xref:core/aop-api/pointcuts.adoc[]
|
||||
*** xref:core/aop-api/advice.adoc[]
|
||||
*** xref:core/aop-api/advisor.adoc[]
|
||||
*** xref:core/aop-api/pfb.adoc[]
|
||||
*** xref:core/aop-api/concise-proxy.adoc[]
|
||||
*** xref:core/aop-api/prog.adoc[]
|
||||
*** xref:core/aop-api/advised.adoc[]
|
||||
*** xref:core/aop-api/autoproxy.adoc[]
|
||||
*** xref:core/aop-api/targetsource.adoc[]
|
||||
*** xref:core/aop-api/extensibility.adoc[]
|
||||
** xref:core/null-safety.adoc[]
|
||||
** xref:core/databuffer-codec.adoc[]
|
||||
** xref:core/spring-jcl.adoc[]
|
||||
** xref:core/aot.adoc[]
|
||||
** xref:core/appendix.adoc[]
|
||||
*** xref:core/appendix/xsd-schemas.adoc[]
|
||||
*** xref:core/appendix/xml-custom.adoc[]
|
||||
*** xref:core/appendix/application-startup-steps.adoc[]
|
||||
* xref:data-access.adoc[]
|
||||
** xref:data-access/transaction.adoc[]
|
||||
*** xref:data-access/transaction/motivation.adoc[]
|
||||
*** xref:data-access/transaction/strategies.adoc[]
|
||||
*** xref:data-access/transaction/tx-resource-synchronization.adoc[]
|
||||
*** xref:data-access/transaction/declarative.adoc[]
|
||||
**** xref:data-access/transaction/declarative/tx-decl-explained.adoc[]
|
||||
**** xref:data-access/transaction/declarative/first-example.adoc[]
|
||||
**** xref:data-access/transaction/declarative/rolling-back.adoc[]
|
||||
**** xref:data-access/transaction/declarative/diff-tx.adoc[]
|
||||
**** xref:data-access/transaction/declarative/txadvice-settings.adoc[]
|
||||
**** xref:data-access/transaction/declarative/annotations.adoc[]
|
||||
**** xref:data-access/transaction/declarative/tx-propagation.adoc[]
|
||||
**** xref:data-access/transaction/declarative/applying-more-than-just-tx-advice.adoc[]
|
||||
**** xref:data-access/transaction/declarative/aspectj.adoc[]
|
||||
*** xref:data-access/transaction/programmatic.adoc[]
|
||||
*** xref:data-access/transaction/tx-decl-vs-prog.adoc[]
|
||||
*** xref:data-access/transaction/event.adoc[]
|
||||
*** xref:data-access/transaction/application-server-integration.adoc[]
|
||||
*** xref:data-access/transaction/solutions-to-common-problems.adoc[]
|
||||
*** xref:data-access/transaction/resources.adoc[]
|
||||
** xref:data-access/dao.adoc[]
|
||||
** xref:data-access/jdbc.adoc[]
|
||||
*** xref:data-access/jdbc/choose-style.adoc[]
|
||||
*** xref:data-access/jdbc/packages.adoc[]
|
||||
*** xref:data-access/jdbc/core.adoc[]
|
||||
*** xref:data-access/jdbc/connections.adoc[]
|
||||
*** xref:data-access/jdbc/advanced.adoc[]
|
||||
*** xref:data-access/jdbc/simple.adoc[]
|
||||
*** xref:data-access/jdbc/object.adoc[]
|
||||
*** xref:data-access/jdbc/parameter-handling.adoc[]
|
||||
*** xref:data-access/jdbc/embedded-database-support.adoc[]
|
||||
*** xref:data-access/jdbc/initializing-datasource.adoc[]
|
||||
** xref:data-access/r2dbc.adoc[]
|
||||
** xref:data-access/orm.adoc[]
|
||||
*** xref:data-access/orm/introduction.adoc[]
|
||||
*** xref:data-access/orm/general.adoc[]
|
||||
*** xref:data-access/orm/hibernate.adoc[]
|
||||
*** xref:data-access/orm/jpa.adoc[]
|
||||
** xref:data-access/oxm.adoc[]
|
||||
** xref:data-access/appendix.adoc[]
|
||||
* xref:web.adoc[]
|
||||
** xref:web/webmvc.adoc[]
|
||||
*** xref:web/webmvc/mvc-servlet.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/context-hierarchy.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/special-bean-types.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/config.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/container-config.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/sequence.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/handlermapping-path.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/handlermapping-interceptor.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/viewresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/localeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/themeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
|
||||
*** xref:web/webmvc/filters.adoc[]
|
||||
*** 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[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-methods.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/return-types.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/typeconversion.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/matrix-variables.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestparam.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestheader.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/cookievalue.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/sessionattributes.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/sessionattribute.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestattrib.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/redirecting-passing-data.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/flash-attributes.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/httpentity.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/responsebody.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/jackson.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-initbinder.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-validation.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-exceptionhandler.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-advice.adoc[]
|
||||
*** xref:web/webmvc-functional.adoc[]
|
||||
*** xref:web/webmvc/mvc-uri-building.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-async.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webmvc/mvc-security.adoc[]
|
||||
*** xref:web/webmvc/mvc-caching.adoc[]
|
||||
*** xref:web/webmvc-view.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-thymeleaf.adoc[]
|
||||
**** 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[]
|
||||
**** xref:web/webmvc-view/mvc-jackson.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-xml-marshalling.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-xslt.adoc[]
|
||||
*** xref:web/webmvc/mvc-config.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/enable.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/customize.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/conversion.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/validation.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/interceptors.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/content-negotiation.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/message-converters.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/view-controller.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/view-resolvers.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/static-resources.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/default-servlet-handler.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/path-matching.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-java.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-xml.adoc[]
|
||||
*** xref:web/webmvc/mvc-http2.adoc[]
|
||||
** xref:web/webmvc-client.adoc[]
|
||||
** xref:web/webmvc-test.adoc[]
|
||||
** xref:web/websocket.adoc[]
|
||||
*** xref:web/websocket/server.adoc[]
|
||||
*** xref:web/websocket/fallback.adoc[]
|
||||
*** xref:web/websocket/stomp.adoc[]
|
||||
**** xref:web/websocket/stomp/overview.adoc[]
|
||||
**** xref:web/websocket/stomp/benefits.adoc[]
|
||||
**** xref:web/websocket/stomp/enable.adoc[]
|
||||
**** xref:web/websocket/stomp/server-config.adoc[]
|
||||
**** xref:web/websocket/stomp/message-flow.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-annotations.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-send.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-simple-broker.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-broker-relay.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-broker-relay-configure.adoc[]
|
||||
**** xref:web/websocket/stomp/destination-separator.adoc[]
|
||||
**** xref:web/websocket/stomp/authentication.adoc[]
|
||||
**** xref:web/websocket/stomp/authentication-token-based.adoc[]
|
||||
**** xref:web/websocket/stomp/authorization.adoc[]
|
||||
**** xref:web/websocket/stomp/user-destination.adoc[]
|
||||
**** xref:web/websocket/stomp/ordered-messages.adoc[]
|
||||
**** xref:web/websocket/stomp/application-context-events.adoc[]
|
||||
**** xref:web/websocket/stomp/interceptors.adoc[]
|
||||
**** xref:web/websocket/stomp/client.adoc[]
|
||||
**** xref:web/websocket/stomp/scope.adoc[]
|
||||
**** xref:web/websocket/stomp/configuration-performance.adoc[]
|
||||
**** xref:web/websocket/stomp/stats.adoc[]
|
||||
**** xref:web/websocket/stomp/testing.adoc[]
|
||||
** xref:web/integration.adoc[]
|
||||
* xref:web-reactive.adoc[]
|
||||
** xref:web/webflux.adoc[]
|
||||
*** xref:web/webflux/new-framework.adoc[]
|
||||
*** xref:web/webflux/reactive-spring.adoc[]
|
||||
*** xref:web/webflux/dispatcher-handler.adoc[]
|
||||
*** xref:web/webflux/controller.adoc[]
|
||||
**** xref:web/webflux/controller/ann.adoc[]
|
||||
**** xref:web/webflux/controller/ann-requestmapping.adoc[]
|
||||
**** xref:web/webflux/controller/ann-methods.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/arguments.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/return-types.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/typeconversion.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/matrix-variables.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestparam.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestheader.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/cookievalue.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/sessionattributes.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/sessionattribute.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestattrib.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/multipart-forms.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestbody.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/httpentity.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/responsebody.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/responseentity.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/jackson.adoc[]
|
||||
**** xref:web/webflux/controller/ann-modelattrib-methods.adoc[]
|
||||
**** xref:web/webflux/controller/ann-initbinder.adoc[]
|
||||
**** xref:web/webflux/controller/ann-validation.adoc[]
|
||||
**** xref:web/webflux/controller/ann-exceptions.adoc[]
|
||||
**** xref:web/webflux/controller/ann-advice.adoc[]
|
||||
*** xref:web/webflux-functional.adoc[]
|
||||
*** xref:web/webflux/uri-building.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webflux/security.adoc[]
|
||||
*** xref:web/webflux/caching.adoc[]
|
||||
*** xref:web/webflux-view.adoc[]
|
||||
*** xref:web/webflux/config.adoc[]
|
||||
*** xref:web/webflux/http2.adoc[]
|
||||
** xref:web/webflux-webclient.adoc[]
|
||||
*** xref:web/webflux-webclient/client-builder.adoc[]
|
||||
*** xref:web/webflux-webclient/client-retrieve.adoc[]
|
||||
*** xref:web/webflux-webclient/client-exchange.adoc[]
|
||||
*** xref:web/webflux-webclient/client-body.adoc[]
|
||||
*** xref:web/webflux-webclient/client-filter.adoc[]
|
||||
*** xref:web/webflux-webclient/client-attributes.adoc[]
|
||||
*** xref:web/webflux-webclient/client-context.adoc[]
|
||||
*** xref:web/webflux-webclient/client-synchronous.adoc[]
|
||||
*** xref:web/webflux-webclient/client-testing.adoc[]
|
||||
** xref:web/webflux-http-interface-client.adoc[]
|
||||
** xref:web/webflux-websocket.adoc[]
|
||||
** 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[]
|
||||
*** xref:integration/jms/using.adoc[]
|
||||
*** xref:integration/jms/sending.adoc[]
|
||||
*** xref:integration/jms/receiving.adoc[]
|
||||
*** xref:integration/jms/jca-message-endpoint-manager.adoc[]
|
||||
*** xref:integration/jms/annotated.adoc[]
|
||||
*** xref:integration/jms/namespace.adoc[]
|
||||
** xref:integration/jmx.adoc[]
|
||||
*** xref:integration/jmx/exporting.adoc[]
|
||||
*** xref:integration/jmx/interface.adoc[]
|
||||
*** xref:integration/jmx/naming.adoc[]
|
||||
*** xref:integration/jmx/jsr160.adoc[]
|
||||
*** xref:integration/jmx/proxy.adoc[]
|
||||
*** xref:integration/jmx/notifications.adoc[]
|
||||
*** xref:integration/jmx/resources.adoc[]
|
||||
** xref:integration/email.adoc[]
|
||||
** xref:integration/scheduling.adoc[]
|
||||
** xref:integration/cache.adoc[]
|
||||
*** xref:integration/cache/strategies.adoc[]
|
||||
*** xref:integration/cache/annotations.adoc[]
|
||||
*** xref:integration/cache/jsr-107.adoc[]
|
||||
*** xref:integration/cache/declarative-xml.adoc[]
|
||||
*** xref:integration/cache/store-configuration.adoc[]
|
||||
*** xref:integration/cache/plug.adoc[]
|
||||
*** xref:integration/cache/specific-config.adoc[]
|
||||
** xref:integration/observability.adoc[]
|
||||
** xref:integration/checkpoint-restore.adoc[]
|
||||
** xref:integration/cds.adoc[]
|
||||
** xref:integration/appendix.adoc[]
|
||||
* xref:languages.adoc[]
|
||||
** xref:languages/kotlin.adoc[]
|
||||
*** xref:languages/kotlin/requirements.adoc[]
|
||||
*** xref:languages/kotlin/extensions.adoc[]
|
||||
*** xref:languages/kotlin/null-safety.adoc[]
|
||||
*** xref:languages/kotlin/classes-interfaces.adoc[]
|
||||
*** xref:languages/kotlin/annotations.adoc[]
|
||||
*** xref:languages/kotlin/bean-definition-dsl.adoc[]
|
||||
*** xref:languages/kotlin/web.adoc[]
|
||||
*** xref:languages/kotlin/coroutines.adoc[]
|
||||
*** xref:languages/kotlin/spring-projects-in.adoc[]
|
||||
*** xref:languages/kotlin/getting-started.adoc[]
|
||||
*** xref:languages/kotlin/resources.adoc[]
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
|
||||
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
|
||||
* {spring-framework-wiki}[Wiki, window=_blank, role=link-external]
|
||||
@@ -1,137 +0,0 @@
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
|
||||
This part of the reference documentation covers topics that apply to multiple modules
|
||||
within the core Spring Framework.
|
||||
|
||||
|
||||
[[appendix-spring-properties]]
|
||||
== Spring Properties
|
||||
|
||||
{spring-framework-api}/core/SpringProperties.html[`SpringProperties`] is a static holder
|
||||
for properties that control certain low-level aspects of the Spring Framework. Users can
|
||||
configure these properties via JVM system properties or programmatically via the
|
||||
`SpringProperties.setProperty(String key, String value)` method. The latter may be
|
||||
necessary if the deployment environment disallows custom JVM system properties. As an
|
||||
alternative, these properties may be configured in a `spring.properties` file in the root
|
||||
of the classpath -- for example, deployed within the application's JAR file.
|
||||
|
||||
The following table lists all currently supported Spring properties.
|
||||
|
||||
.Supported Spring Properties
|
||||
[cols="1,1"]
|
||||
|===
|
||||
| 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
|
||||
{spring-framework-api}++/aot/AotDetector.html#AOT_ENABLED++[`AotDetector`]
|
||||
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`]
|
||||
for details.
|
||||
|
||||
| `spring.cache.reactivestreams.ignore`
|
||||
| Instructs Spring's caching infrastructure to ignore the presence of Reactive Streams,
|
||||
in particular Reactor's `Mono`/`Flux` in `@Cacheable` method return type declarations. See
|
||||
{spring-framework-api}++/cache/interceptor/CacheAspectSupport.html#IGNORE_REACTIVESTREAMS_PROPERTY_NAME++[`CacheAspectSupport`]
|
||||
for details.
|
||||
|
||||
| `spring.classformat.ignore`
|
||||
| Instructs Spring to ignore class format exceptions during classpath scanning, in
|
||||
particular for unsupported class file versions. See
|
||||
{spring-framework-api}++/context/annotation/ClassPathScanningCandidateComponentProvider.html#IGNORE_CLASSFORMAT_PROPERTY_NAME++[`ClassPathScanningCandidateComponentProvider`]
|
||||
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
|
||||
{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
|
||||
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
|
||||
for details.
|
||||
|
||||
| `spring.context.expression.maxLength`
|
||||
| The maximum length for
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
expressions used in XML bean definitions, `@Value`, etc.
|
||||
|
||||
| `spring.expression.compiler.mode`
|
||||
| The mode to use when compiling expressions for the
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
|
||||
|
||||
| `spring.getenv.ignore`
|
||||
| Instructs Spring to ignore operating system environment variables if a Spring
|
||||
`Environment` property -- for example, a placeholder in a configuration String -- isn't
|
||||
resolvable otherwise. See
|
||||
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
|
||||
| `spring.jndi.ignore`
|
||||
| Instructs Spring to ignore a default JNDI environment, as an optimization for scenarios
|
||||
where nothing is ever to be found for such JNDI fallback searches to begin with, avoiding
|
||||
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.
|
||||
See xref:testing/testcontext-framework/aot.adoc[Ahead of Time Support for Tests].
|
||||
|
||||
| `spring.test.constructor.autowire.mode`
|
||||
| The default _test constructor autowire mode_ to use if `@TestConstructor` is not present
|
||||
on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[Changing the default test constructor autowire mode].
|
||||
|
||||
| `spring.test.context.cache.maxSize`
|
||||
| The maximum size of the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
|
||||
|
||||
| `spring.test.context.failure.threshold`
|
||||
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
|
||||
in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[Changing the default enclosing configuration inheritance mode].
|
||||
|
||||
|===
|
||||
@@ -1,19 +0,0 @@
|
||||
[[spring-core]]
|
||||
= Core Technologies
|
||||
|
||||
This part of the reference documentation covers all the technologies that are
|
||||
absolutely integral to the Spring Framework.
|
||||
|
||||
Foremost amongst these is the Spring Framework's Inversion of Control (IoC) container.
|
||||
A thorough treatment of the Spring Framework's IoC container is closely followed by
|
||||
comprehensive coverage of Spring's Aspect-Oriented Programming (AOP) technologies.
|
||||
The Spring Framework has its own AOP framework, which is conceptually easy to
|
||||
understand and which successfully addresses the 80% sweet spot of AOP requirements
|
||||
in Java enterprise programming.
|
||||
|
||||
Coverage of Spring's integration with AspectJ (currently the richest -- in terms of
|
||||
features -- and certainly most mature AOP implementation in the Java enterprise space)
|
||||
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.
|
||||
@@ -1,8 +0,0 @@
|
||||
[[aop-api]]
|
||||
= Spring AOP APIs
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
The previous chapter described the Spring's support for AOP with @AspectJ and schema-based
|
||||
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.
|
||||
@@ -1,575 +0,0 @@
|
||||
[[aop-api-advice]]
|
||||
= Advice API in Spring
|
||||
|
||||
Now we can examine how Spring AOP handles advice.
|
||||
|
||||
|
||||
[[aop-api-advice-lifecycle]]
|
||||
== Advice Lifecycles
|
||||
|
||||
Each advice is a Spring bean. An advice instance can be shared across all advised
|
||||
objects or be unique to each advised object. This corresponds to per-class or
|
||||
per-instance advice.
|
||||
|
||||
Per-class advice is used most often. It is appropriate for generic advice, such as
|
||||
transaction advisors. These do not depend on the state of the proxied object or add new
|
||||
state. They merely act on the method and arguments.
|
||||
|
||||
Per-instance advice is appropriate for introductions, to support mixins. In this case,
|
||||
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_.
|
||||
|
||||
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:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MethodInterceptor extends Interceptor {
|
||||
|
||||
Object invoke(MethodInvocation invocation) throws Throwable;
|
||||
}
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
The following example shows a simple `MethodInterceptor` implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class DebugInterceptor implements MethodInterceptor {
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
System.out.println("Before: invocation=[" + invocation + "]");
|
||||
Object result = invocation.proceed();
|
||||
System.out.println("Invocation returned");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class DebugInterceptor : MethodInterceptor {
|
||||
|
||||
override fun invoke(invocation: MethodInvocation): Any {
|
||||
println("Before: invocation=[$invocation]")
|
||||
val result = invocation.proceed()
|
||||
println("Invocation returned")
|
||||
return result
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Note the call to the `proceed()` method of `MethodInvocation`. This proceeds down the
|
||||
interceptor chain towards the join point. Most interceptors invoke this method and
|
||||
return its return value. However, a `MethodInterceptor`, like any around advice, can
|
||||
return a different value or throw an exception rather than invoke the proceed method.
|
||||
However, you do not want to do this without good reason.
|
||||
|
||||
NOTE: `MethodInterceptor` implementations offer interoperability with other AOP Alliance-compliant AOP
|
||||
implementations. The other advice types discussed in the remainder of this section
|
||||
implement common AOP concepts but in a Spring-specific way. While there is an advantage
|
||||
in using the most specific advice type, stick with `MethodInterceptor` around advice if
|
||||
you are likely to want to run the aspect in another AOP framework. Note that pointcuts
|
||||
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`
|
||||
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()`
|
||||
method and, therefore, no possibility of inadvertently failing to proceed down the
|
||||
interceptor chain.
|
||||
|
||||
The following listing shows the `MethodBeforeAdvice` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MethodBeforeAdvice extends BeforeAdvice {
|
||||
|
||||
void before(Method m, Object[] args, Object target) throws Throwable;
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
propagates back up the interceptor chain. If it is unchecked or on the signature of
|
||||
the invoked method, it is passed directly to the client. Otherwise, it is
|
||||
wrapped in an unchecked exception by the AOP proxy.
|
||||
|
||||
The following example shows a before advice in Spring, which counts all method invocations:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class CountingBeforeAdvice implements MethodBeforeAdvice {
|
||||
|
||||
private int count;
|
||||
|
||||
public void before(Method m, Object[] args, Object target) throws Throwable {
|
||||
++count;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class CountingBeforeAdvice : MethodBeforeAdvice {
|
||||
|
||||
var count: Int = 0
|
||||
|
||||
override fun before(m: Method, args: Array<Any>, target: Any?) {
|
||||
++count
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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
|
||||
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
|
||||
advice methods. These should be in the following form:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
afterThrowing([Method, args, target], subclassOfThrowable)
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
The following advice is invoked if a `RemoteException` is thrown (including subclasses of
|
||||
`RemoteException`):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class RemoteThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
public void afterThrowing(RemoteException ex) throws Throwable {
|
||||
// Do something with remote exception
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class RemoteThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
fun afterThrowing(ex: RemoteException) {
|
||||
// Do something with remote exception
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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"]
|
||||
----
|
||||
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
|
||||
|
||||
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
|
||||
|
||||
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The final example illustrates how these two methods could be used in a single class
|
||||
that handles both `RemoteException` and `ServletException`. Any number of throws advice
|
||||
methods can be combined in a single class. The following listing shows the final example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public static class CombinedThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
public void afterThrowing(RemoteException ex) throws Throwable {
|
||||
// Do something with remote exception
|
||||
}
|
||||
|
||||
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class CombinedThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
fun afterThrowing(ex: RemoteException) {
|
||||
// Do something with remote exception
|
||||
}
|
||||
|
||||
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: If a throws-advice method throws an exception itself, it overrides the
|
||||
original exception (that is, it changes the exception thrown to the user). The overriding
|
||||
exception is typically a RuntimeException, which is compatible with any method
|
||||
signature. However, if a throws-advice method throws a checked exception, it must
|
||||
match the declared exceptions of the target method and is, hence, to some degree
|
||||
coupled to specific target method signatures. _Do not throw an undeclared checked
|
||||
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
|
||||
`org.springframework.aop.AfterReturningAdvice` interface, which the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface AfterReturningAdvice extends Advice {
|
||||
|
||||
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
|
||||
throws Throwable;
|
||||
}
|
||||
----
|
||||
|
||||
An after returning advice has access to the return value (which it cannot modify),
|
||||
the invoked method, the method's arguments, and the target.
|
||||
|
||||
The following after returning advice counts all successful method invocations that have
|
||||
not thrown exceptions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
|
||||
|
||||
private int count;
|
||||
|
||||
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
|
||||
throws Throwable {
|
||||
++count;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class CountingAfterReturningAdvice : AfterReturningAdvice {
|
||||
|
||||
var count: Int = 0
|
||||
private set
|
||||
|
||||
override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
|
||||
++count
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This advice does not change the execution path. If it throws an exception, it is
|
||||
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.
|
||||
|
||||
Introduction requires an `IntroductionAdvisor` and an `IntroductionInterceptor` that
|
||||
implement the following interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface IntroductionInterceptor extends MethodInterceptor {
|
||||
|
||||
boolean implementsInterface(Class intf);
|
||||
}
|
||||
----
|
||||
|
||||
The `invoke()` method inherited from the AOP Alliance `MethodInterceptor` interface must
|
||||
implement the introduction. That is, if the invoked method is on an introduced
|
||||
interface, the introduction interceptor is responsible for handling the method call -- it
|
||||
cannot invoke `proceed()`.
|
||||
|
||||
Introduction advice cannot be used with any pointcut, as it applies only at the class,
|
||||
rather than the method, level. You can only use introduction advice with the
|
||||
`IntroductionAdvisor`, which has the following methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
|
||||
|
||||
ClassFilter getClassFilter();
|
||||
|
||||
void validateInterfaces() throws IllegalArgumentException;
|
||||
}
|
||||
|
||||
public interface IntroductionInfo {
|
||||
|
||||
Class<?>[] getInterfaces();
|
||||
}
|
||||
----
|
||||
|
||||
There is no `MethodMatcher` and, hence, no `Pointcut` associated with introduction
|
||||
advice. Only class filtering is logical.
|
||||
|
||||
The `getInterfaces()` method returns the interfaces introduced by this advisor.
|
||||
|
||||
The `validateInterfaces()` method is used internally to see whether or not the
|
||||
introduced interfaces can be implemented by the configured `IntroductionInterceptor`.
|
||||
|
||||
Consider an example from the Spring test suite and suppose we want to
|
||||
introduce the following interface to one or more objects:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface Lockable {
|
||||
void lock();
|
||||
void unlock();
|
||||
boolean locked();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
interface Lockable {
|
||||
fun lock()
|
||||
fun unlock()
|
||||
fun locked(): Boolean
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This illustrates a mixin. We want to be able to cast advised objects to `Lockable`,
|
||||
whatever their type and call lock and unlock methods. If we call the `lock()` method, we
|
||||
want all setter methods to throw a `LockedException`. Thus, we can add an aspect that
|
||||
provides the ability to make objects immutable without them having any knowledge of it:
|
||||
a good example of AOP.
|
||||
|
||||
First, we need an `IntroductionInterceptor` that does the heavy lifting. In this
|
||||
case, we extend the `org.springframework.aop.support.DelegatingIntroductionInterceptor`
|
||||
convenience class. We could implement `IntroductionInterceptor` directly, but using
|
||||
`DelegatingIntroductionInterceptor` is best for most cases.
|
||||
|
||||
The `DelegatingIntroductionInterceptor` is designed to delegate an introduction to an
|
||||
actual implementation of the introduced interfaces, concealing the use of interception
|
||||
to do so. You can set the delegate to any object using a constructor argument. The
|
||||
default delegate (when the no-argument constructor is used) is `this`. Thus, in the next example,
|
||||
the delegate is the `LockMixin` subclass of `DelegatingIntroductionInterceptor`.
|
||||
Given a delegate (by default, itself), a `DelegatingIntroductionInterceptor` instance
|
||||
looks for all interfaces implemented by the delegate (other than
|
||||
`IntroductionInterceptor`) and supports introductions against any of them.
|
||||
Subclasses such as `LockMixin` can call the `suppressInterface(Class intf)`
|
||||
method to suppress interfaces that should not be exposed. However, no matter how many
|
||||
interfaces an `IntroductionInterceptor` is prepared to support, the
|
||||
`IntroductionAdvisor` used controls which interfaces are actually exposed. An
|
||||
introduced interface conceals any implementation of the same interface by the target.
|
||||
|
||||
Thus, `LockMixin` extends `DelegatingIntroductionInterceptor` and implements `Lockable`
|
||||
itself. The superclass automatically picks up that `Lockable` can be supported for
|
||||
introduction, so we do not need to specify that. We could introduce any number of
|
||||
interfaces in this way.
|
||||
|
||||
Note the use of the `locked` instance variable. This effectively adds additional state
|
||||
to that held in the target object.
|
||||
|
||||
The following example shows the example `LockMixin` class:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
|
||||
|
||||
private boolean locked;
|
||||
|
||||
public void lock() {
|
||||
this.locked = true;
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
this.locked = false;
|
||||
}
|
||||
|
||||
public boolean locked() {
|
||||
return this.locked;
|
||||
}
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
|
||||
throw new LockedException();
|
||||
}
|
||||
return super.invoke(invocation);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
|
||||
|
||||
private var locked: Boolean = false
|
||||
|
||||
fun lock() {
|
||||
this.locked = true
|
||||
}
|
||||
|
||||
fun unlock() {
|
||||
this.locked = false
|
||||
}
|
||||
|
||||
fun locked(): Boolean {
|
||||
return this.locked
|
||||
}
|
||||
|
||||
override fun invoke(invocation: MethodInvocation): Any? {
|
||||
if (locked() && invocation.method.name.indexOf("set") == 0) {
|
||||
throw LockedException()
|
||||
}
|
||||
return super.invoke(invocation)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Often, you need not override the `invoke()` method. The
|
||||
`DelegatingIntroductionInterceptor` implementation (which calls the `delegate` method if
|
||||
the method is introduced, otherwise proceeds towards the join point) usually
|
||||
suffices. In the present case, we need to add a check: no setter method can be invoked
|
||||
if in locked mode.
|
||||
|
||||
The required introduction only needs to hold a distinct
|
||||
`LockMixin` instance and specify the introduced interfaces (in this case, only
|
||||
`Lockable`). A more complex example might take a reference to the introduction
|
||||
interceptor (which would be defined as a prototype). In this case, there is no
|
||||
configuration relevant for a `LockMixin`, so we create it by using `new`.
|
||||
The following example shows our `LockMixinAdvisor` class:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
|
||||
|
||||
public LockMixinAdvisor() {
|
||||
super(new LockMixin(), Lockable.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
We can apply this advisor very simply, because it requires no configuration. (However, it
|
||||
is impossible to use an `IntroductionInterceptor` without an
|
||||
`IntroductionAdvisor`.) As usual with introductions, the advisor must be per-instance,
|
||||
as it is stateful. We need a different instance of `LockMixinAdvisor`, and hence
|
||||
`LockMixin`, for each advised object. The advisor comprises part of the advised object's
|
||||
state.
|
||||
|
||||
We can apply this advisor programmatically by using the `Advised.addAdvisor()` method or
|
||||
(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.
|
||||
@@ -1,144 +0,0 @@
|
||||
[[aop-api-advised]]
|
||||
= Manipulating Advised Objects
|
||||
|
||||
However you create AOP proxies, you can manipulate them BY using the
|
||||
`org.springframework.aop.framework.Advised` interface. Any AOP proxy can be cast to this
|
||||
interface, no matter which other interfaces it implements. This interface includes the
|
||||
following methods:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Advisor[] getAdvisors();
|
||||
|
||||
void addAdvice(Advice advice) throws AopConfigException;
|
||||
|
||||
void addAdvice(int pos, Advice advice) throws AopConfigException;
|
||||
|
||||
void addAdvisor(Advisor advisor) throws AopConfigException;
|
||||
|
||||
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
|
||||
|
||||
int indexOf(Advisor advisor);
|
||||
|
||||
boolean removeAdvisor(Advisor advisor) throws AopConfigException;
|
||||
|
||||
void removeAdvisor(int index) throws AopConfigException;
|
||||
|
||||
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
|
||||
|
||||
boolean isFrozen();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
fun getAdvisors(): Array<Advisor>
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvice(advice: Advice)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvice(pos: Int, advice: Advice)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvisor(advisor: Advisor)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvisor(pos: Int, advisor: Advisor)
|
||||
|
||||
fun indexOf(advisor: Advisor): Int
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun removeAdvisor(advisor: Advisor): Boolean
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun removeAdvisor(index: Int)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun replaceAdvisor(a: Advisor, b: Advisor): Boolean
|
||||
|
||||
fun isFrozen(): Boolean
|
||||
----
|
||||
======
|
||||
|
||||
The `getAdvisors()` method returns an `Advisor` for every advisor, interceptor, or
|
||||
other advice type that has been added to the factory. If you added an `Advisor`, the
|
||||
returned advisor at this index is the object that you added. If you added an
|
||||
interceptor or other advice type, Spring wrapped this in an advisor with a
|
||||
pointcut that always returns `true`. Thus, if you added a `MethodInterceptor`, the advisor
|
||||
returned for this index is a `DefaultPointcutAdvisor` that returns your
|
||||
`MethodInterceptor` and a pointcut that matches all classes and methods.
|
||||
|
||||
The `addAdvisor()` methods can be used to add any `Advisor`. Usually, the advisor holding
|
||||
pointcut and advice is the generic `DefaultPointcutAdvisor`, which you can use with
|
||||
any advice or pointcut (but not for introductions).
|
||||
|
||||
By default, it is possible to add or remove advisors or interceptors even once a proxy
|
||||
has been created. The only restriction is that it is impossible to add or remove an
|
||||
introduction advisor, as existing proxies from the factory do not show the interface
|
||||
change. (You can obtain a new proxy from the factory to avoid this problem.)
|
||||
|
||||
The following example shows casting an AOP proxy to the `Advised` interface and examining and
|
||||
manipulating its advice:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Advised advised = (Advised) myObject;
|
||||
Advisor[] advisors = advised.getAdvisors();
|
||||
int oldAdvisorCount = advisors.length;
|
||||
System.out.println(oldAdvisorCount + " advisors");
|
||||
|
||||
// Add an advice like an interceptor without a pointcut
|
||||
// Will match all proxied methods
|
||||
// Can use for interceptors, before, after returning or throws advice
|
||||
advised.addAdvice(new DebugInterceptor());
|
||||
|
||||
// Add selective advice using a pointcut
|
||||
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
|
||||
|
||||
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val advised = myObject as Advised
|
||||
val advisors = advised.advisors
|
||||
val oldAdvisorCount = advisors.size
|
||||
println("$oldAdvisorCount advisors")
|
||||
|
||||
// Add an advice like an interceptor without a pointcut
|
||||
// Will match all proxied methods
|
||||
// Can use for interceptors, before, after returning or throws advice
|
||||
advised.addAdvice(DebugInterceptor())
|
||||
|
||||
// Add selective advice using a pointcut
|
||||
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))
|
||||
|
||||
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: It is questionable whether it is advisable (no pun intended) to modify advice on a
|
||||
business object in production, although there are, no doubt, legitimate usage cases.
|
||||
However, it can be very useful in development (for example, in tests). We have sometimes
|
||||
found it very useful to be able to add test code in the form of an interceptor or other
|
||||
advice, getting inside a method invocation that we want to test. (For example, the advice can
|
||||
get inside a transaction created for that method, perhaps to run SQL to check that
|
||||
a database was correctly updated, before marking the transaction for roll back.)
|
||||
|
||||
Depending on how you created the proxy, you can usually set a `frozen` flag. In that
|
||||
case, the `Advised` `isFrozen()` method returns `true`, and any attempts to modify
|
||||
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).
|
||||
@@ -1,16 +0,0 @@
|
||||
[[aop-api-advisor]]
|
||||
= The Advisor API in Spring
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
In Spring, an Advisor is an aspect that contains only a single advice object associated
|
||||
with a pointcut expression.
|
||||
|
||||
Apart from the special case of introductions, any advisor can be used with any advice.
|
||||
`org.springframework.aop.support.DefaultPointcutAdvisor` is the most commonly used
|
||||
advisor class. It can be used with a `MethodInterceptor`, `BeforeAdvice`, or
|
||||
`ThrowsAdvice`.
|
||||
|
||||
It is possible to mix advisor and advice types in Spring in the same AOP proxy. For
|
||||
example, you could use an interception around advice, throws advice, and before advice in
|
||||
one proxy configuration. Spring automatically creates the necessary interceptor
|
||||
chain.
|
||||
@@ -1,124 +0,0 @@
|
||||
[[aop-autoproxy]]
|
||||
= Using the "auto-proxy" facility
|
||||
|
||||
So far, we have considered explicit creation of AOP proxies by using a `ProxyFactoryBean` or
|
||||
similar factory bean.
|
||||
|
||||
Spring also lets us use "`auto-proxy`" bean definitions, which can automatically
|
||||
proxy selected bean definitions. This is built on Spring's "`bean post processor`"
|
||||
infrastructure, which enables modification of any bean definition as the container loads.
|
||||
|
||||
In this model, you set up some special bean definitions in your XML bean definition file
|
||||
to configure the auto-proxy infrastructure. This lets you declare the targets
|
||||
eligible for auto-proxying. You need not use `ProxyFactoryBean`.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
* By using an auto-proxy creator that refers to specific beans in the current context.
|
||||
* A special case of auto-proxy creation that deserves to be considered separately:
|
||||
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`
|
||||
|
||||
The `BeanNameAutoProxyCreator` class is a `BeanPostProcessor` that automatically creates
|
||||
AOP proxies for beans with names that match literal values or wildcards. The following
|
||||
example shows how to create a `BeanNameAutoProxyCreator` bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
|
||||
<property name="beanNames" value="jdk*,onlyJdk"/>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>myInterceptor</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
As with `ProxyFactoryBean`, there is an `interceptorNames` property rather than a list
|
||||
of interceptors, to allow correct behavior for prototype advisors. Named "`interceptors`"
|
||||
can be advisors or any advice type.
|
||||
|
||||
As with auto-proxying in general, the main point of using `BeanNameAutoProxyCreator` is
|
||||
to apply the same configuration consistently to multiple objects, with minimal volume of
|
||||
configuration. It is a popular choice for applying declarative transactions to multiple
|
||||
objects.
|
||||
|
||||
Bean definitions whose names match, such as `jdkMyBean` and `onlyJdk` in the preceding
|
||||
example, are plain old bean definitions with the target class. An AOP proxy is
|
||||
automatically created by the `BeanNameAutoProxyCreator`. The same advice is applied
|
||||
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`
|
||||
|
||||
A more general and extremely powerful auto-proxy creator is
|
||||
`DefaultAdvisorAutoProxyCreator`. This automagically applies eligible advisors in the
|
||||
current context, without the need to include specific bean names in the auto-proxy
|
||||
advisor's bean definition. It offers the same merit of consistent configuration and
|
||||
avoidance of duplication as `BeanNameAutoProxyCreator`.
|
||||
|
||||
Using this mechanism involves:
|
||||
|
||||
* Specifying a `DefaultAdvisorAutoProxyCreator` bean definition.
|
||||
* Specifying any number of advisors in the same or related contexts. Note that these
|
||||
must be advisors, not interceptors or other advice. This is necessary,
|
||||
because there must be a pointcut to evaluate, to check the eligibility of each advice
|
||||
to candidate bean definitions.
|
||||
|
||||
The `DefaultAdvisorAutoProxyCreator` automatically evaluates the pointcut contained
|
||||
in each advisor, to see what (if any) advice it should apply to each business object
|
||||
(such as `businessObject1` and `businessObject2` in the example).
|
||||
|
||||
This means that any number of advisors can be applied automatically to each business
|
||||
object. If no pointcut in any of the advisors matches any method in a business object,
|
||||
the object is not proxied. As bean definitions are added for new business objects,
|
||||
they are automatically proxied if necessary.
|
||||
|
||||
Auto-proxying in general has the advantage of making it impossible for callers or
|
||||
dependencies to obtain an un-advised object. Calling `getBean("businessObject1")` on this
|
||||
`ApplicationContext` returns an AOP proxy, not the target business object. (The "`inner
|
||||
bean`" idiom shown earlier also offers this benefit.)
|
||||
|
||||
The following example creates a `DefaultAdvisorAutoProxyCreator` bean and the other
|
||||
elements discussed in this section:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
|
||||
|
||||
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
|
||||
<property name="transactionInterceptor" ref="transactionInterceptor"/>
|
||||
</bean>
|
||||
|
||||
<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
|
||||
|
||||
<bean id="businessObject1" class="com.mycompany.BusinessObject1">
|
||||
<!-- Properties omitted -->
|
||||
</bean>
|
||||
|
||||
<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
|
||||
----
|
||||
|
||||
The `DefaultAdvisorAutoProxyCreator` is very useful if you want to apply the same advice
|
||||
consistently to many business objects. Once the infrastructure definitions are in place,
|
||||
you can add new business objects without including specific proxy configuration.
|
||||
You can also easily drop in additional aspects (for example, tracing or
|
||||
performance monitoring aspects) with minimal change to configuration.
|
||||
|
||||
The `DefaultAdvisorAutoProxyCreator` offers support for filtering (by using a naming
|
||||
convention so that only certain advisors are evaluated, which allows the use of multiple,
|
||||
differently configured, AdvisorAutoProxyCreators in the same factory) and ordering.
|
||||
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.
|
||||
@@ -1,67 +0,0 @@
|
||||
[[aop-concise-proxy]]
|
||||
= Concise Proxy Definitions
|
||||
|
||||
Especially when defining transactional proxies, you may end up with many similar proxy
|
||||
definitions. The use of parent and child bean definitions, along with inner bean
|
||||
definitions, can result in much cleaner and more concise proxy definitions.
|
||||
|
||||
First, we create a parent, template, bean definition for the proxy, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="txProxyTemplate" abstract="true"
|
||||
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
|
||||
<property name="transactionManager" ref="transactionManager"/>
|
||||
<property name="transactionAttributes">
|
||||
<props>
|
||||
<prop key="*">PROPAGATION_REQUIRED</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
This is never instantiated itself, so it can actually be incomplete. Then, each proxy
|
||||
that needs to be created is a child bean definition, which wraps the target of the
|
||||
proxy as an inner bean definition, since the target is never used on its own anyway.
|
||||
The following example shows such a child bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="myService" parent="txProxyTemplate">
|
||||
<property name="target">
|
||||
<bean class="org.springframework.samples.MyServiceImpl">
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You can override properties from the parent template. In the following example,
|
||||
we override the transaction propagation settings:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="mySpecialService" parent="txProxyTemplate">
|
||||
<property name="target">
|
||||
<bean class="org.springframework.samples.MySpecialServiceImpl">
|
||||
</bean>
|
||||
</property>
|
||||
<property name="transactionAttributes">
|
||||
<props>
|
||||
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
|
||||
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
|
||||
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
|
||||
<prop key="store*">PROPAGATION_REQUIRED</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that in the parent bean example, we explicitly marked the parent bean definition as
|
||||
being abstract by setting the `abstract` attribute to `true`, as described
|
||||
xref:core/beans/child-bean-definitions.adoc[previously], so that it may not actually ever be
|
||||
instantiated. Application contexts (but not simple bean factories), by default,
|
||||
pre-instantiate all singletons. Therefore, it is important (at least for singleton beans)
|
||||
that, if you have a (parent) bean definition that you intend to use only as a template,
|
||||
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.
|
||||
@@ -1,16 +0,0 @@
|
||||
[[aop-extensibility]]
|
||||
= Defining New Advice Types
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring AOP is designed to be extensible. While the interception implementation strategy
|
||||
is presently used internally, it is possible to support arbitrary advice types in
|
||||
addition to the interception around advice, before, throws advice, and
|
||||
after returning advice.
|
||||
|
||||
The `org.springframework.aop.framework.adapter` package is an SPI package that lets
|
||||
support for new custom advice types be added without changing the core framework.
|
||||
The only constraint on a custom `Advice` type is that it must implement the
|
||||
`org.aopalliance.aop.Advice` marker interface.
|
||||
|
||||
See the {spring-framework-api}/aop/framework/adapter/package-summary.html[`org.springframework.aop.framework.adapter`]
|
||||
javadoc for further information.
|
||||
@@ -1,321 +0,0 @@
|
||||
[[aop-pfb]]
|
||||
= Using the `ProxyFactoryBean` to Create AOP Proxies
|
||||
|
||||
If you use the Spring IoC container (an `ApplicationContext` or `BeanFactory`) for your
|
||||
business objects (and you should be!), you want to use one of Spring's AOP
|
||||
`FactoryBean` implementations. (Remember that a factory bean introduces a layer of indirection, letting
|
||||
it create objects of a different type.)
|
||||
|
||||
NOTE: The Spring AOP support also uses factory beans under the covers.
|
||||
|
||||
The basic way to create an AOP proxy in Spring is to use the
|
||||
`org.springframework.aop.framework.ProxyFactoryBean`. This gives complete control over
|
||||
the pointcuts, any advice that applies, and their ordering. However, there are simpler
|
||||
options that are preferable if you do not need such control.
|
||||
|
||||
|
||||
[[aop-pfb-1]]
|
||||
== Basics
|
||||
|
||||
The `ProxyFactoryBean`, like other Spring `FactoryBean` implementations, introduces a
|
||||
level of indirection. If you define a `ProxyFactoryBean` named `foo`, objects that
|
||||
reference `foo` do not see the `ProxyFactoryBean` instance itself but an object
|
||||
created by the implementation of the `getObject()` method in the `ProxyFactoryBean` . This
|
||||
method creates an AOP proxy that wraps a target object.
|
||||
|
||||
One of the most important benefits of using a `ProxyFactoryBean` or another IoC-aware
|
||||
class to create AOP proxies is that advice and pointcuts can also be
|
||||
managed by IoC. This is a powerful feature, enabling certain approaches that are hard to
|
||||
achieve with other AOP frameworks. For example, an advice may itself reference
|
||||
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
|
||||
|
||||
In common with most `FactoryBean` implementations provided with Spring, the
|
||||
`ProxyFactoryBean` class is itself a JavaBean. Its properties are used to:
|
||||
|
||||
* Specify the target you want to proxy.
|
||||
* Specify whether to use CGLIB (described later and see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
|
||||
Some key properties are inherited from `org.springframework.aop.framework.ProxyConfig`
|
||||
(the superclass for all AOP proxy factories in Spring). These key properties include
|
||||
the following:
|
||||
|
||||
* `proxyTargetClass`: `true` if the target class is to be proxied, rather than the
|
||||
target class's interfaces. If this property value is set to `true`, then CGLIB proxies
|
||||
are created (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
* `optimize`: Controls whether or not aggressive optimizations are applied to proxies
|
||||
created through CGLIB. You should not blithely use this setting unless you fully
|
||||
understand how the relevant AOP proxy handles optimization. This is currently used
|
||||
only for CGLIB proxies. It has no effect with JDK dynamic proxies.
|
||||
* `frozen`: If a proxy configuration is `frozen`, changes to the configuration are
|
||||
no longer allowed. This is useful both as a slight optimization and for those cases
|
||||
when you do not want callers to be able to manipulate the proxy (through the `Advised`
|
||||
interface) after the proxy has been created. The default value of this property is
|
||||
`false`, so changes (such as adding additional advice) are allowed.
|
||||
* `exposeProxy`: Determines whether or not the current proxy should be exposed in a
|
||||
`ThreadLocal` so that it can be accessed by the target. If a target needs to obtain
|
||||
the proxy and the `exposeProxy` property is set to `true`, the target can use the
|
||||
`AopContext.currentProxy()` method.
|
||||
|
||||
Other properties specific to `ProxyFactoryBean` include the following:
|
||||
|
||||
* `proxyInterfaces`: An array of `String` interface names. If this is not supplied, a CGLIB
|
||||
proxy for the target class is used (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
* `interceptorNames`: A `String` array of `Advisor`, interceptor, or other advice names to
|
||||
apply. Ordering is significant, on a first come-first served basis. That is to say
|
||||
that the first interceptor in the list is the first to be able to intercept the
|
||||
invocation.
|
||||
+
|
||||
The names are bean names in the current factory, including bean names from ancestor
|
||||
factories. You cannot mention bean references here, since doing so results in the
|
||||
`ProxyFactoryBean` ignoring the singleton setting of the advice.
|
||||
+
|
||||
You can append an interceptor name with an asterisk (`*`). Doing so results in the
|
||||
application of all advisor beans with names that start with the part before the asterisk
|
||||
to be applied. You can find an example of using this feature in xref:core/aop-api/pfb.adoc#aop-global-advisors[Using "`Global`" Advisors].
|
||||
|
||||
* singleton: Whether or not the factory should return a single object, no matter how
|
||||
often the `getObject()` method is called. Several `FactoryBean` implementations offer
|
||||
such a method. The default value is `true`. If you want to use stateful advice - for
|
||||
example, for stateful mixins - use prototype advice along with a singleton value of
|
||||
`false`.
|
||||
|
||||
|
||||
[[aop-pfb-proxy-types]]
|
||||
== JDK- and CGLIB-based proxies
|
||||
|
||||
This section serves as the definitive documentation on how the `ProxyFactoryBean`
|
||||
chooses to create either a JDK-based proxy or a CGLIB-based proxy for a particular target
|
||||
object (which is to be proxied).
|
||||
|
||||
NOTE: The behavior of the `ProxyFactoryBean` with regard to creating JDK- or CGLIB-based
|
||||
proxies changed between versions 1.2.x and 2.0 of Spring. The `ProxyFactoryBean` now
|
||||
exhibits similar semantics with regard to auto-detecting interfaces as those of the
|
||||
`TransactionProxyFactoryBean` class.
|
||||
|
||||
If the class of a target object that is to be proxied (hereafter simply referred to as
|
||||
the target class) does not implement any interfaces, a CGLIB-based proxy is
|
||||
created. This is the easiest scenario, because JDK proxies are interface-based, and no
|
||||
interfaces means JDK proxying is not even possible. You can plug in the target bean
|
||||
and specify the list of interceptors by setting the `interceptorNames` property. Note that a
|
||||
CGLIB-based proxy is created even if the `proxyTargetClass` property of the
|
||||
`ProxyFactoryBean` has been set to `false`. (Doing so makes no sense and is best
|
||||
removed from the bean definition, because it is, at best, redundant, and, at worst
|
||||
confusing.)
|
||||
|
||||
If the target class implements one (or more) interfaces, the type of proxy that is
|
||||
created depends on the configuration of the `ProxyFactoryBean`.
|
||||
|
||||
If the `proxyTargetClass` property of the `ProxyFactoryBean` has been set to `true`,
|
||||
a CGLIB-based proxy is created. This makes sense and is in keeping with the
|
||||
principle of least surprise. Even if the `proxyInterfaces` property of the
|
||||
`ProxyFactoryBean` has been set to one or more fully qualified interface names, the fact
|
||||
that the `proxyTargetClass` property is set to `true` causes CGLIB-based
|
||||
proxying to be in effect.
|
||||
|
||||
If the `proxyInterfaces` property of the `ProxyFactoryBean` has been set to one or more
|
||||
fully qualified interface names, a JDK-based proxy is created. The created
|
||||
proxy implements all of the interfaces that were specified in the `proxyInterfaces`
|
||||
property. If the target class happens to implement a whole lot more interfaces than
|
||||
those specified in the `proxyInterfaces` property, that is all well and good, but those
|
||||
additional interfaces are not implemented by the returned proxy.
|
||||
|
||||
If the `proxyInterfaces` property of the `ProxyFactoryBean` has not been set, but
|
||||
the target class does implement one (or more) interfaces, the
|
||||
`ProxyFactoryBean` auto-detects the fact that the target class does actually
|
||||
implement at least one interface, and a JDK-based proxy is created. The interfaces
|
||||
that are actually proxied are all of the interfaces that the target class
|
||||
implements. In effect, this is the same as supplying a list of each and every
|
||||
interface that the target class implements to the `proxyInterfaces` property. However,
|
||||
it is significantly less work and less prone to typographical errors.
|
||||
|
||||
|
||||
[[aop-api-proxying-intf]]
|
||||
== Proxying Interfaces
|
||||
|
||||
Consider a simple example of `ProxyFactoryBean` in action. This example involves:
|
||||
|
||||
* A target bean that is proxied. This is the `personTarget` bean definition in
|
||||
the example.
|
||||
* An `Advisor` and an `Interceptor` used to provide advice.
|
||||
* An AOP proxy bean definition to specify the target object (the `personTarget` bean),
|
||||
the interfaces to proxy, and the advice to apply.
|
||||
|
||||
The following listing shows the example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="personTarget" class="com.mycompany.PersonImpl">
|
||||
<property name="name" value="Tony"/>
|
||||
<property name="age" value="51"/>
|
||||
</bean>
|
||||
|
||||
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
|
||||
<property name="someProperty" value="Custom string property value"/>
|
||||
</bean>
|
||||
|
||||
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
|
||||
</bean>
|
||||
|
||||
<bean id="person"
|
||||
class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="proxyInterfaces" value="com.mycompany.Person"/>
|
||||
|
||||
<property name="target" ref="personTarget"/>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>myAdvisor</value>
|
||||
<value>debugInterceptor</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that the `interceptorNames` property takes a list of `String`, which holds the bean names of the
|
||||
interceptors or advisors in the current factory. You can use advisors, interceptors, before, after
|
||||
returning, and throws advice objects. The ordering of advisors is significant.
|
||||
|
||||
NOTE: You might be wondering why the list does not hold bean references. The reason for this is
|
||||
that, if the singleton property of the `ProxyFactoryBean` is set to `false`, it must be able to
|
||||
return independent proxy instances. If any of the advisors is itself a prototype, an
|
||||
independent instance would need to be returned, so it is necessary to be able to obtain
|
||||
an instance of the prototype from the factory. Holding a reference is not sufficient.
|
||||
|
||||
The `person` bean definition shown earlier can be used in place of a `Person` implementation, as
|
||||
follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Person person = (Person) factory.getBean("person");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val person = factory.getBean("person") as Person
|
||||
----
|
||||
======
|
||||
|
||||
Other beans in the same IoC context can express a strongly typed dependency on it, as
|
||||
with an ordinary Java object. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="personUser" class="com.mycompany.PersonUser">
|
||||
<property name="person"><ref bean="person"/></property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The `PersonUser` class in this example exposes a property of type `Person`. As far as
|
||||
it is concerned, the AOP proxy can be used transparently in place of a "`real`" person
|
||||
implementation. However, its class would be a dynamic proxy class. It would be possible
|
||||
to cast it to the `Advised` interface (discussed later).
|
||||
|
||||
You can conceal the distinction between target and proxy by using an anonymous
|
||||
inner bean. Only the `ProxyFactoryBean` definition is different. The
|
||||
advice is included only for completeness. The following example shows how to use an
|
||||
anonymous inner bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
|
||||
<property name="someProperty" value="Custom string property value"/>
|
||||
</bean>
|
||||
|
||||
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
|
||||
|
||||
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="proxyInterfaces" value="com.mycompany.Person"/>
|
||||
<!-- Use inner bean, not local reference to target -->
|
||||
<property name="target">
|
||||
<bean class="com.mycompany.PersonImpl">
|
||||
<property name="name" value="Tony"/>
|
||||
<property name="age" value="51"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>myAdvisor</value>
|
||||
<value>debugInterceptor</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Using an anonymous inner bean has the advantage that there is only one object of type `Person`. This is useful if we want
|
||||
to prevent users of the application context from obtaining a reference to the un-advised
|
||||
object or need to avoid any ambiguity with Spring IoC autowiring. There is also,
|
||||
arguably, an advantage in that the `ProxyFactoryBean` definition is self-contained.
|
||||
However, there are times when being able to obtain the un-advised target from the
|
||||
factory might actually be an advantage (for example, in certain test scenarios).
|
||||
|
||||
|
||||
[[aop-api-proxying-class]]
|
||||
== Proxying Classes
|
||||
|
||||
What if you need to proxy a class, rather than one or more interfaces?
|
||||
|
||||
Imagine that in our earlier example, there was no `Person` interface. We needed to advise
|
||||
a class called `Person` that did not implement any business interface. In this case, you
|
||||
can configure Spring to use CGLIB proxying rather than dynamic proxies. To do so, set the
|
||||
`proxyTargetClass` property on the `ProxyFactoryBean` shown earlier to `true`. While it is best to
|
||||
program to interfaces rather than classes, the ability to advise classes that do not
|
||||
implement interfaces can be useful when working with legacy code. (In general, Spring
|
||||
is not prescriptive. While it makes it easy to apply good practices, it avoids forcing a
|
||||
particular approach.)
|
||||
|
||||
If you want to, you can force the use of CGLIB in any case, even if you do have
|
||||
interfaces.
|
||||
|
||||
CGLIB proxying works by generating a subclass of the target class at runtime. Spring
|
||||
configures this generated subclass to delegate method calls to the original target. The
|
||||
subclass is used to implement the Decorator pattern, weaving in the advice.
|
||||
|
||||
CGLIB proxying should generally be transparent to users. However, there are some issues
|
||||
to consider:
|
||||
|
||||
* `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, typically package private methods in a parent class
|
||||
from a different package, cannot be advised because they are effectively private.
|
||||
|
||||
NOTE: There is no need to add CGLIB to your classpath. CGLIB is repackaged and included
|
||||
in the `spring-core` JAR. In other words, CGLIB-based AOP works "out of the box", as do
|
||||
JDK dynamic proxies.
|
||||
|
||||
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
|
||||
|
||||
By appending an asterisk to an interceptor name, all advisors with bean names that match
|
||||
the part before the asterisk are added to the advisor chain. This can come in handy
|
||||
if you need to add a standard set of "`global`" advisors. The following example defines
|
||||
two global advisors:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="target" ref="service"/>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>global*</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
|
||||
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
|
||||
----
|
||||
@@ -1,219 +0,0 @@
|
||||
[[aop-api-pointcuts]]
|
||||
= Pointcut API in Spring
|
||||
|
||||
This section describes how Spring handles the crucial pointcut concept.
|
||||
|
||||
|
||||
[[aop-api-concepts]]
|
||||
== Concepts
|
||||
|
||||
Spring's pointcut model enables pointcut reuse independent of advice types. You can
|
||||
target different advice with the same pointcut.
|
||||
|
||||
The `org.springframework.aop.Pointcut` interface is the central interface, used to
|
||||
target advice to particular classes and methods. The complete interface follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface Pointcut {
|
||||
|
||||
ClassFilter getClassFilter();
|
||||
|
||||
MethodMatcher getMethodMatcher();
|
||||
}
|
||||
----
|
||||
|
||||
Splitting the `Pointcut` interface into two parts allows reuse of class and method
|
||||
matching parts and fine-grained composition operations (such as performing a "`union`"
|
||||
with another method matcher).
|
||||
|
||||
The `ClassFilter` interface is used to restrict the pointcut to a given set of target
|
||||
classes. If the `matches()` method always returns true, all target classes are
|
||||
matched. The following listing shows the `ClassFilter` interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface ClassFilter {
|
||||
|
||||
boolean matches(Class clazz);
|
||||
}
|
||||
----
|
||||
|
||||
The `MethodMatcher` interface is normally more important. The complete interface follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MethodMatcher {
|
||||
|
||||
boolean matches(Method m, Class<?> targetClass);
|
||||
|
||||
boolean isRuntime();
|
||||
|
||||
boolean matches(Method m, Class<?> targetClass, Object... args);
|
||||
}
|
||||
----
|
||||
|
||||
The `matches(Method, Class)` method is used to test whether this pointcut ever
|
||||
matches a given method on a target class. This evaluation can be performed when an AOP
|
||||
proxy is created to avoid the need for a test on every method invocation. If the
|
||||
two-argument `matches` method returns `true` for a given method, and the `isRuntime()`
|
||||
method for the MethodMatcher returns `true`, the three-argument matches method is
|
||||
invoked on every method invocation. This lets a pointcut look at the arguments passed
|
||||
to the method invocation immediately before the target advice starts.
|
||||
|
||||
Most `MethodMatcher` implementations are static, meaning that their `isRuntime()` method
|
||||
returns `false`. In this case, the three-argument `matches` method is never invoked.
|
||||
|
||||
TIP: If possible, try to make pointcuts static, allowing the AOP framework to cache the
|
||||
results of pointcut evaluation when an AOP proxy is created.
|
||||
|
||||
|
||||
[[aop-api-pointcut-ops]]
|
||||
== Operations on Pointcuts
|
||||
|
||||
Spring supports operations (notably, union and intersection) on pointcuts.
|
||||
|
||||
Union means the methods that either pointcut matches.
|
||||
Intersection means the methods that both pointcuts match.
|
||||
Union is usually more useful.
|
||||
You can compose pointcuts by using the static methods in the
|
||||
`org.springframework.aop.support.Pointcuts` class or by using the
|
||||
`ComposablePointcut` class in the same package. However, using AspectJ pointcut
|
||||
expressions is usually a simpler approach.
|
||||
|
||||
|
||||
[[aop-api-pointcuts-aspectj]]
|
||||
== AspectJ Expression Pointcuts
|
||||
|
||||
Since 2.0, the most important type of pointcut used by Spring is
|
||||
`org.springframework.aop.aspectj.AspectJExpressionPointcut`. This is a pointcut that
|
||||
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
|
||||
|
||||
Static pointcuts are based on the method and the target class and cannot take into account
|
||||
the method's arguments. Static pointcuts suffice -- and are best -- for most usages.
|
||||
Spring can evaluate a static pointcut only once, when a method is first invoked.
|
||||
After that, there is no need to evaluate the pointcut again with each method invocation.
|
||||
|
||||
The rest of this section describes some of the static pointcut implementations that are
|
||||
included with Spring.
|
||||
|
||||
[[aop-api-pointcuts-regex]]
|
||||
==== Regular Expression Pointcuts
|
||||
|
||||
One obvious way to specify static pointcuts is regular expressions. Several AOP
|
||||
frameworks besides Spring make this possible.
|
||||
`org.springframework.aop.support.JdkRegexpMethodPointcut` is a generic regular
|
||||
expression pointcut that uses the regular expression support in the JDK.
|
||||
|
||||
With the `JdkRegexpMethodPointcut` class, you can provide a list of pattern strings.
|
||||
If any of these is a match, the pointcut evaluates to `true`. (As a consequence,
|
||||
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]
|
||||
|
||||
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
|
||||
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
|
||||
throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPointcut`.
|
||||
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]
|
||||
|
||||
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
|
||||
[[aop-api-pointcuts-attribute-driven]]
|
||||
==== Attribute-driven Pointcuts
|
||||
|
||||
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
|
||||
|
||||
Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account
|
||||
method arguments as well as static information. This means that they must be
|
||||
evaluated with every method invocation and that the result cannot be cached, as arguments will
|
||||
vary.
|
||||
|
||||
The main example is the `control flow` pointcut.
|
||||
|
||||
[[aop-api-pointcuts-cflow]]
|
||||
==== Control Flow Pointcuts
|
||||
|
||||
Spring control flow pointcuts are conceptually similar to AspectJ `cflow` pointcuts,
|
||||
although less powerful. (There is currently no way to specify that a pointcut runs
|
||||
below a join point matched by another pointcut.) A control flow pointcut matches the
|
||||
current call stack. For example, it might fire if the join point was invoked by a method
|
||||
in the `com.mycompany.web` package or by the `SomeCaller` class. Control flow pointcuts
|
||||
are specified by using the `org.springframework.aop.support.ControlFlowPointcut` class.
|
||||
|
||||
NOTE: Control flow pointcuts are significantly more expensive to evaluate at runtime than even
|
||||
other dynamic pointcuts. In Java 1.4, the cost is about five times that of other dynamic
|
||||
pointcuts.
|
||||
|
||||
|
||||
[[aop-api-pointcuts-superclasses]]
|
||||
== Pointcut Superclasses
|
||||
|
||||
Spring provides useful pointcut superclasses to help you to implement your own pointcuts.
|
||||
|
||||
Because static pointcuts are most useful, you should probably subclass
|
||||
`StaticMethodMatcherPointcut`. This requires implementing only one
|
||||
abstract method (although you can override other methods to customize behavior). The
|
||||
following example shows how to subclass `StaticMethodMatcherPointcut`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class TestStaticPointcut extends StaticMethodMatcherPointcut {
|
||||
|
||||
public boolean matches(Method m, Class targetClass) {
|
||||
// return true if custom criteria match
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class TestStaticPointcut : StaticMethodMatcherPointcut() {
|
||||
|
||||
override fun matches(method: Method, targetClass: Class<*>): Boolean {
|
||||
// return true if custom criteria match
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
There are also superclasses for dynamic pointcuts.
|
||||
You can use custom pointcuts with any advice type.
|
||||
|
||||
|
||||
[[aop-api-pointcuts-custom]]
|
||||
== Custom Pointcuts
|
||||
|
||||
Because pointcuts in Spring AOP are Java classes rather than language features (as in
|
||||
AspectJ), you can declare custom pointcuts, whether static or dynamic. Custom
|
||||
pointcuts in Spring can be arbitrarily complex. However, we recommend using the AspectJ pointcut
|
||||
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.`"
|
||||
@@ -1,54 +0,0 @@
|
||||
[[aop-prog]]
|
||||
= Creating AOP Proxies Programmatically with the `ProxyFactory`
|
||||
|
||||
It is easy to create AOP proxies programmatically with Spring. This lets you use
|
||||
Spring AOP without dependency on Spring IoC.
|
||||
|
||||
The interfaces implemented by the target object are
|
||||
automatically proxied. The following listing shows creation of a proxy for a target object, with one
|
||||
interceptor and one advisor:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
|
||||
factory.addAdvice(myMethodInterceptor);
|
||||
factory.addAdvisor(myAdvisor);
|
||||
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val factory = ProxyFactory(myBusinessInterfaceImpl)
|
||||
factory.addAdvice(myMethodInterceptor)
|
||||
factory.addAdvisor(myAdvisor)
|
||||
val tb = factory.proxy as MyBusinessInterface
|
||||
----
|
||||
======
|
||||
|
||||
The first step is to construct an object of type
|
||||
`org.springframework.aop.framework.ProxyFactory`. You can create this with a target
|
||||
object, as in the preceding example, or specify the interfaces to be proxied in an alternate
|
||||
constructor.
|
||||
|
||||
You can add advice (with interceptors as a specialized kind of advice), advisors, or both
|
||||
and manipulate them for the life of the `ProxyFactory`. If you add an
|
||||
`IntroductionInterceptionAroundAdvisor`, you can cause the proxy to implement additional
|
||||
interfaces.
|
||||
|
||||
There are also convenience methods on `ProxyFactory` (inherited from `AdvisedSupport`)
|
||||
that let you add other advice types, such as before and throws advice.
|
||||
`AdvisedSupport` is the superclass of both `ProxyFactory` and `ProxyFactoryBean`.
|
||||
|
||||
TIP: Integrating AOP proxy creation with the IoC framework is best practice in most
|
||||
applications. We recommend that you externalize configuration from Java code with AOP,
|
||||
as you should in general.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
[[aop-targetsource]]
|
||||
= Using `TargetSource` Implementations
|
||||
|
||||
Spring offers the concept of a `TargetSource`, expressed in the
|
||||
`org.springframework.aop.TargetSource` interface. This interface is responsible for
|
||||
returning the "`target object`" that implements the join point. The `TargetSource`
|
||||
implementation is asked for a target instance each time the AOP proxy handles a method
|
||||
invocation.
|
||||
|
||||
Developers who use Spring AOP do not normally need to work directly with `TargetSource` implementations, but
|
||||
this provides a powerful means of supporting pooling, hot swappable, and other
|
||||
sophisticated targets. For example, a pooling `TargetSource` can return a different target
|
||||
instance for each invocation, by using a pool to manage instances.
|
||||
|
||||
If you do not specify a `TargetSource`, a default implementation is used to wrap a
|
||||
local object. The same target is returned for each invocation (as you would expect).
|
||||
|
||||
The rest of this section describes the standard target sources provided with Spring and how you can use them.
|
||||
|
||||
TIP: When using a custom target source, your target will usually need to be a prototype
|
||||
rather than a singleton bean definition. This allows Spring to create a new target
|
||||
instance when required.
|
||||
|
||||
|
||||
[[aop-ts-swap]]
|
||||
== Hot-swappable Target Sources
|
||||
|
||||
The `org.springframework.aop.target.HotSwappableTargetSource` exists to let the target
|
||||
of an AOP proxy be switched while letting callers keep their references to it.
|
||||
|
||||
Changing the target source's target takes effect immediately. The
|
||||
`HotSwappableTargetSource` is thread-safe.
|
||||
|
||||
You can change the target by using the `swap()` method on HotSwappableTargetSource, as the follow example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
|
||||
Object oldTarget = swapper.swap(newTarget);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
|
||||
val oldTarget = swapper.swap(newTarget)
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the required XML definitions:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="initialTarget" class="mycompany.OldTarget"/>
|
||||
|
||||
<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
|
||||
<constructor-arg ref="initialTarget"/>
|
||||
</bean>
|
||||
|
||||
<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="targetSource" ref="swapper"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding `swap()` call changes the target of the swappable bean. Clients that hold a
|
||||
reference to that bean are unaware of the change but immediately start hitting
|
||||
the new target.
|
||||
|
||||
Although this example does not add any advice (it is not necessary to add advice to
|
||||
use a `TargetSource`), any `TargetSource` can be used in conjunction with
|
||||
arbitrary advice.
|
||||
|
||||
|
||||
[[aop-ts-pool]]
|
||||
== Pooling Target Sources
|
||||
|
||||
Using a pooling target source provides a similar programming model to stateless session
|
||||
EJBs, in which a pool of identical instances is maintained, with method invocations
|
||||
going to free objects in the pool.
|
||||
|
||||
A crucial difference between Spring pooling and SLSB pooling is that Spring pooling can
|
||||
be applied to any POJO. As with Spring in general, this service can be applied in a
|
||||
non-invasive way.
|
||||
|
||||
Spring provides support for Commons Pool 2.2, which provides a
|
||||
fairly efficient pooling implementation. You need the `commons-pool` Jar on your
|
||||
application's classpath to use this feature. You can also subclass
|
||||
`org.springframework.aop.target.AbstractPoolingTargetSource` to support any other
|
||||
pooling API.
|
||||
|
||||
NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.
|
||||
|
||||
The following listing shows an example configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
|
||||
scope="prototype">
|
||||
... properties omitted
|
||||
</bean>
|
||||
|
||||
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
|
||||
<property name="targetBeanName" value="businessObjectTarget"/>
|
||||
<property name="maxSize" value="25"/>
|
||||
</bean>
|
||||
|
||||
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="targetSource" ref="poolTargetSource"/>
|
||||
<property name="interceptorNames" value="myInterceptor"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that the target object (`businessObjectTarget` in the preceding example) must be a
|
||||
prototype. This lets the `PoolingTargetSource` implementation create new instances
|
||||
of the target to grow the pool as necessary. See the {spring-framework-api}/aop/target/AbstractPoolingTargetSource.html[javadoc of
|
||||
`AbstractPoolingTargetSource`] and the concrete subclass you wish to use for information
|
||||
about its properties. `maxSize` is the most basic and is always guaranteed to be present.
|
||||
|
||||
In this case, `myInterceptor` is the name of an interceptor that would need to be
|
||||
defined in the same IoC context. However, you need not specify interceptors to
|
||||
use pooling. If you want only pooling and no other advice, do not set the
|
||||
`interceptorNames` property at all.
|
||||
|
||||
You can configure Spring to be able to cast any pooled object to the
|
||||
`org.springframework.aop.target.PoolingConfig` interface, which exposes information
|
||||
about the configuration and current size of the pool through an introduction. You
|
||||
need to define an advisor similar to the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
|
||||
<property name="targetObject" ref="poolTargetSource"/>
|
||||
<property name="targetMethod" value="getPoolingConfigMixin"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
This advisor is obtained by calling a convenience method on the
|
||||
`AbstractPoolingTargetSource` class, hence the use of `MethodInvokingFactoryBean`. This
|
||||
advisor's name (`poolConfigAdvisor`, here) must be in the list of interceptors names in
|
||||
the `ProxyFactoryBean` that exposes the pooled object.
|
||||
|
||||
The cast is defined as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
|
||||
System.out.println("Max pool size is " + conf.getMaxSize());
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val conf = beanFactory.getBean("businessObject") as PoolingConfig
|
||||
println("Max pool size is " + conf.maxSize)
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Pooling stateless service objects is not usually necessary. We do not believe it should
|
||||
be the default choice, as most stateless objects are naturally thread-safe, and instance
|
||||
pooling is problematic if resources are cached.
|
||||
|
||||
Simpler pooling is available by using auto-proxying. You can set the `TargetSource` implementations
|
||||
used by any auto-proxy creator.
|
||||
|
||||
|
||||
[[aop-ts-prototype]]
|
||||
== Prototype Target Sources
|
||||
|
||||
Setting up a "`prototype`" target source is similar to setting up a pooling `TargetSource`. In this
|
||||
case, a new instance of the target is created on every method invocation. Although
|
||||
the cost of creating a new object is not high in a modern JVM, the cost of wiring up the
|
||||
new object (satisfying its IoC dependencies) may be more expensive. Thus, you should not
|
||||
use this approach without very good reason.
|
||||
|
||||
To do this, you could modify the `poolTargetSource` definition shown earlier as follows
|
||||
(we also changed the name, for clarity):
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
|
||||
<property name="targetBeanName" ref="businessObjectTarget"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The only property is the name of the target bean. Inheritance is used in the
|
||||
`TargetSource` implementations to ensure consistent naming. As with the pooling target
|
||||
source, the target bean must be a prototype bean definition.
|
||||
|
||||
|
||||
[[aop-ts-threadlocal]]
|
||||
== `ThreadLocal` Target Sources
|
||||
|
||||
`ThreadLocal` target sources are useful if you need an object to be created for each
|
||||
incoming request (per thread that is). The concept of a `ThreadLocal` provides a JDK-wide
|
||||
facility to transparently store a resource alongside a thread. Setting up a
|
||||
`ThreadLocalTargetSource` is pretty much the same as was explained for the other types
|
||||
of target source, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
|
||||
<property name="targetBeanName" value="businessObjectTarget"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in memory leaks) when
|
||||
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
|
||||
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.
|
||||
@@ -1,34 +0,0 @@
|
||||
[[aop]]
|
||||
= Aspect Oriented Programming with Spring
|
||||
|
||||
Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by
|
||||
providing another way of thinking about program structure. The key unit of modularity
|
||||
in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects
|
||||
enable the modularization of concerns (such as transaction management) that cut across
|
||||
multiple types and objects. (Such concerns are often termed "crosscutting" concerns
|
||||
in AOP literature.)
|
||||
|
||||
One of the key components of Spring is the AOP framework. While the Spring IoC
|
||||
container does not depend on AOP (meaning you do not need to use AOP if you don't want
|
||||
to), AOP complements Spring IoC to provide a very capable middleware solution.
|
||||
|
||||
.Spring AOP with AspectJ pointcuts
|
||||
****
|
||||
Spring provides simple and powerful ways of writing custom aspects by using either a
|
||||
xref:core/aop/schema.adoc[schema-based approach] or the xref:core/aop/ataspectj.adoc[@AspectJ annotation style].
|
||||
Both of these styles offer fully typed advice and use of the AspectJ pointcut language
|
||||
while still using Spring AOP for weaving.
|
||||
|
||||
This chapter discusses the schema- and @AspectJ-based AOP support.
|
||||
The lower-level AOP support is discussed in xref:core/aop-api.adoc[the following chapter].
|
||||
****
|
||||
|
||||
AOP is used in the Spring Framework to:
|
||||
|
||||
* Provide declarative enterprise services. The most important such service is
|
||||
xref:data-access/transaction/declarative.adoc[declarative transaction management].
|
||||
* Let users implement custom aspects, complementing their use of OOP with AOP.
|
||||
|
||||
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.
|
||||
@@ -1,55 +0,0 @@
|
||||
[[aop-aspectj-programmatic]]
|
||||
= Programmatic Creation of @AspectJ Proxies
|
||||
|
||||
In addition to declaring aspects in your configuration by using either `<aop:config>`
|
||||
or `<aop:aspectj-autoproxy>`, it is also possible to programmatically create proxies
|
||||
that advise target objects. For the full details of Spring's AOP API, see the
|
||||
xref:core/aop-api.adoc[next chapter]. Here, we want to focus on the ability to automatically
|
||||
create proxies by using @AspectJ aspects.
|
||||
|
||||
You can use the `org.springframework.aop.aspectj.annotation.AspectJProxyFactory` class
|
||||
to create a proxy for a target object that is advised by one or more @AspectJ aspects.
|
||||
The basic usage for this class is very simple, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
|
||||
|
||||
// add an aspect, the class must be an @AspectJ aspect
|
||||
// you can call this as many times as you need with different aspects
|
||||
factory.addAspect(SecurityManager.class);
|
||||
|
||||
// you can also add existing aspect instances, the type of the object supplied
|
||||
// must be an @AspectJ aspect
|
||||
factory.addAspect(usageTracker);
|
||||
|
||||
// now get the proxy object...
|
||||
MyInterfaceType proxy = factory.getProxy();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
val factory = AspectJProxyFactory(targetObject)
|
||||
|
||||
// add an aspect, the class must be an @AspectJ aspect
|
||||
// you can call this as many times as you need with different aspects
|
||||
factory.addAspect(SecurityManager::class.java)
|
||||
|
||||
// you can also add existing aspect instances, the type of the object supplied
|
||||
// must be an @AspectJ aspect
|
||||
factory.addAspect(usageTracker)
|
||||
|
||||
// now get the proxy object...
|
||||
val proxy = factory.getProxy<Any>()
|
||||
----
|
||||
======
|
||||
|
||||
See the {spring-framework-api}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information.
|
||||
@@ -1,13 +0,0 @@
|
||||
[[aop-ataspectj]]
|
||||
= @AspectJ support
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
@AspectJ refers to a style of declaring aspects as regular Java classes annotated with
|
||||
annotations. The @AspectJ style was introduced by the
|
||||
{aspectj-site}[AspectJ project] as part of the AspectJ 5 release. Spring
|
||||
interprets the same annotations as AspectJ 5, using a library supplied by AspectJ
|
||||
for pointcut parsing and matching. The AOP runtime is still pure Spring AOP, though, and
|
||||
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].
|
||||
@@ -1,939 +0,0 @@
|
||||
[[aop-advice]]
|
||||
= Declaring Advice
|
||||
|
||||
Advice is associated with a pointcut expression and runs before, after, or around method
|
||||
executions matched by the pointcut. The pointcut expression may be either an _inline
|
||||
pointcut_ or a reference to a xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[_named pointcut_].
|
||||
|
||||
|
||||
[[aop-advice-before]]
|
||||
== Before Advice
|
||||
|
||||
You can declare before advice in an aspect by using the `@Before` annotation.
|
||||
|
||||
The following example uses an inline pointcut expression.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
|
||||
@Aspect
|
||||
public class BeforeExample {
|
||||
|
||||
@Before("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
|
||||
@Aspect
|
||||
class BeforeExample {
|
||||
|
||||
@Before("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If we use a xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[named pointcut], we can rewrite the preceding example
|
||||
as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
|
||||
@Aspect
|
||||
public class BeforeExample {
|
||||
|
||||
@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
|
||||
public void doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
|
||||
@Aspect
|
||||
class BeforeExample {
|
||||
|
||||
@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
|
||||
fun doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[aop-advice-after-returning]]
|
||||
== After Returning Advice
|
||||
|
||||
After returning advice runs when a matched method execution returns normally.
|
||||
You can declare it by using the `@AfterReturning` annotation.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
|
||||
@Aspect
|
||||
public class AfterReturningExample {
|
||||
|
||||
@AfterReturning("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
|
||||
@Aspect
|
||||
class AfterReturningExample {
|
||||
|
||||
@AfterReturning("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: You can have multiple advice declarations (and other members as well),
|
||||
all inside the same aspect. We show only a single advice declaration in these
|
||||
examples to focus the effect of each one.
|
||||
|
||||
Sometimes, you need access in the advice body to the actual value that was returned.
|
||||
You can use the form of `@AfterReturning` that binds the return value to get that
|
||||
access, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
|
||||
@Aspect
|
||||
public class AfterReturningExample {
|
||||
|
||||
@AfterReturning(
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))",
|
||||
returning="retVal")
|
||||
public void doAccessCheck(Object retVal) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
|
||||
@Aspect
|
||||
class AfterReturningExample {
|
||||
|
||||
@AfterReturning(
|
||||
pointcut = "execution(* com.xyz.dao.*.*(..))",
|
||||
returning = "retVal")
|
||||
fun doAccessCheck(retVal: Any?) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The name used in the `returning` attribute must correspond to the name of a parameter
|
||||
in the advice method. When a method execution returns, the return value is passed to
|
||||
the advice method as the corresponding argument value. A `returning` clause also
|
||||
restricts matching to only those method executions that return a value of the
|
||||
specified type (in this case, `Object`, which matches any return value).
|
||||
|
||||
Please note that it is not possible to return a totally different reference when
|
||||
using after returning advice.
|
||||
|
||||
|
||||
[[aop-advice-after-throwing]]
|
||||
== After Throwing Advice
|
||||
|
||||
After throwing advice runs when a matched method execution exits by throwing an
|
||||
exception. You can declare it by using the `@AfterThrowing` annotation, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
|
||||
@Aspect
|
||||
public class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doRecoveryActions() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
|
||||
@Aspect
|
||||
class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doRecoveryActions() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Often, you want the advice to run only when exceptions of a given type are thrown,
|
||||
and you also often need access to the thrown exception in the advice body. You can
|
||||
use the `throwing` attribute to both restrict matching (if desired -- use `Throwable`
|
||||
as the exception type otherwise) and bind the thrown exception to an advice parameter.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
|
||||
@Aspect
|
||||
public class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing(
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))",
|
||||
throwing="ex")
|
||||
public void doRecoveryActions(DataAccessException ex) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
|
||||
@Aspect
|
||||
class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing(
|
||||
pointcut = "execution(* com.xyz.dao.*.*(..))",
|
||||
throwing = "ex")
|
||||
fun doRecoveryActions(ex: DataAccessException) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The name used in the `throwing` attribute must correspond to the name of a parameter in
|
||||
the advice method. When a method execution exits by throwing an exception, the exception
|
||||
is passed to the advice method as the corresponding argument value. A `throwing` clause
|
||||
also restricts matching to only those method executions that throw an exception of the
|
||||
specified type (`DataAccessException`, in this case).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Note that `@AfterThrowing` does not indicate a general exception handling callback.
|
||||
Specifically, an `@AfterThrowing` advice method is only supposed to receive exceptions
|
||||
from the join point (user-declared target method) itself but not from an accompanying
|
||||
`@After`/`@AfterReturning` method.
|
||||
====
|
||||
|
||||
|
||||
[[aop-advice-after-finally]]
|
||||
== After (Finally) Advice
|
||||
|
||||
After (finally) advice runs when a matched method execution exits. It is declared by
|
||||
using the `@After` annotation. After advice must be prepared to handle both normal and
|
||||
exception return conditions. It is typically used for releasing resources and similar
|
||||
purposes. The following example shows how to use after finally advice:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
|
||||
@Aspect
|
||||
public class AfterFinallyExample {
|
||||
|
||||
@After("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doReleaseLock() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.After
|
||||
|
||||
@Aspect
|
||||
class AfterFinallyExample {
|
||||
|
||||
@After("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doReleaseLock() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Note that `@After` advice in AspectJ is defined as "after finally advice", analogous
|
||||
to a finally block in a try-catch statement. It will be invoked for any outcome,
|
||||
normal return or exception thrown from the join point (user-declared target method),
|
||||
in contrast to `@AfterReturning` which only applies to successful normal returns.
|
||||
====
|
||||
|
||||
|
||||
[[aop-ataspectj-around-advice]]
|
||||
== Around Advice
|
||||
|
||||
The last kind of advice is _around_ advice. Around advice runs "around" a matched
|
||||
method's execution. It has the opportunity to do work both before and after the method
|
||||
runs and to determine when, how, and even if the method actually gets to run at all.
|
||||
Around advice is often used if you need to share state before and after a method
|
||||
execution in a thread-safe manner – for example, starting and stopping a timer.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Always use the least powerful form of advice that meets your requirements.
|
||||
|
||||
For example, do not use _around_ advice if _before_ advice is sufficient for your needs.
|
||||
====
|
||||
|
||||
Around advice is declared by annotating a method with the `@Around` annotation. The
|
||||
method should declare `Object` as its return type, and the first parameter of the method
|
||||
must be of type `ProceedingJoinPoint`. Within the body of the advice method, you must
|
||||
invoke `proceed()` on the `ProceedingJoinPoint` in order for the underlying method to
|
||||
run. Invoking `proceed()` without arguments will result in the caller's original
|
||||
arguments being supplied to the underlying method when it is invoked. For advanced use
|
||||
cases, there is an overloaded variant of the `proceed()` method which accepts an array of
|
||||
arguments (`Object[]`). The values in the array will be used as the arguments to the
|
||||
underlying method when it is invoked.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The behavior of `proceed` when called with an `Object[]` is a little different than the
|
||||
behavior of `proceed` for around advice compiled by the AspectJ compiler. For around
|
||||
advice written using the traditional AspectJ language, the number of arguments passed to
|
||||
`proceed` must match the number of arguments passed to the around advice (not the number
|
||||
of arguments taken by the underlying join point), and the value passed to proceed in a
|
||||
given argument position supplants the original value at the join point for the entity the
|
||||
value was bound to (do not worry if this does not make sense right now).
|
||||
|
||||
The approach taken by Spring is simpler and a better match to its proxy-based,
|
||||
execution-only semantics. You only need to be aware of this difference if you compile
|
||||
`@AspectJ` aspects written for Spring and use `proceed` with arguments with the AspectJ
|
||||
compiler and weaver. There is a way to write such aspects that is 100% compatible across
|
||||
both Spring AOP and AspectJ, and this is discussed in the
|
||||
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-proceeding-with-the-call[following section on advice parameters].
|
||||
====
|
||||
|
||||
The value returned by the around advice is the return value seen by the caller of the
|
||||
method. For example, a simple caching aspect could return a value from a cache if it has
|
||||
one or invoke `proceed()` (and return that value) if it does not. Note that `proceed`
|
||||
may be invoked once, many times, or not at all within the body of the around advice. All
|
||||
of these are legal.
|
||||
|
||||
WARNING: If you declare the return type of your around advice method as `void`, `null`
|
||||
will always be returned to the caller, effectively ignoring the result of any invocation
|
||||
of `proceed()`. It is therefore recommended that an around advice method declare a return
|
||||
type of `Object`. The advice method should typically return the value returned from an
|
||||
invocation of `proceed()`, even if the underlying method has a `void` return type.
|
||||
However, the advice may optionally return a cached value, a wrapped value, or some other
|
||||
value depending on the use case.
|
||||
|
||||
The following example shows how to use around advice:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
|
||||
@Aspect
|
||||
public class AroundExample {
|
||||
|
||||
@Around("execution(* com.xyz..service.*.*(..))")
|
||||
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// start stopwatch
|
||||
Object retVal = pjp.proceed();
|
||||
// stop stopwatch
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
|
||||
@Aspect
|
||||
class AroundExample {
|
||||
|
||||
@Around("execution(* com.xyz..service.*.*(..))")
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
|
||||
// start stopwatch
|
||||
val retVal = pjp.proceed()
|
||||
// stop stopwatch
|
||||
return retVal
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-ataspectj-advice-params]]
|
||||
== Advice Parameters
|
||||
|
||||
Spring offers fully typed advice, meaning that you declare the parameters you need in the
|
||||
advice signature (as we saw earlier for the returning and throwing examples) rather than
|
||||
work with `Object[]` arrays all the time. We see how to make argument and other contextual
|
||||
values available to the advice body later in this section. First, we take a look at how to
|
||||
write generic advice that can find out about the method the advice is currently advising.
|
||||
|
||||
[[aop-ataspectj-advice-params-the-joinpoint]]
|
||||
=== Access to the Current `JoinPoint`
|
||||
|
||||
Any advice method may declare, as its first parameter, a parameter of type
|
||||
`org.aspectj.lang.JoinPoint`. Note that around advice is required to declare a first
|
||||
parameter of type `ProceedingJoinPoint`, which is a subclass of `JoinPoint`.
|
||||
|
||||
The `JoinPoint` interface provides a number of useful methods:
|
||||
|
||||
* `getArgs()`: Returns the method arguments.
|
||||
* `getThis()`: Returns the proxy object.
|
||||
* `getTarget()`: Returns the target object.
|
||||
* `getSignature()`: Returns a description of the method that is being advised.
|
||||
* `toString()`: Prints a useful description of the method being advised.
|
||||
|
||||
See the {aspectj-api}/org/aspectj/lang/JoinPoint.html[javadoc] for more detail.
|
||||
|
||||
[[aop-ataspectj-advice-params-passing]]
|
||||
=== Passing Parameters to Advice
|
||||
|
||||
We have already seen how to bind the returned value or exception value (using after
|
||||
returning and after throwing advice). To make argument values available to the advice
|
||||
body, you can use the binding form of `args`. If you use a parameter name in place of a
|
||||
type name in an `args` expression, the value of the corresponding argument is passed as
|
||||
the parameter value when the advice is invoked. An example should make this clearer.
|
||||
Suppose you want to advise the execution of DAO operations that take an `Account`
|
||||
object as the first parameter, and you need access to the account in the advice body.
|
||||
You could write the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
public void validateAccount(Account account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
fun validateAccount(account: Account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The `args(account,..)` part of the pointcut expression serves two purposes. First, it
|
||||
restricts matching to only those method executions where the method takes at least one
|
||||
parameter, and the argument passed to that parameter is an instance of `Account`.
|
||||
Second, it makes the actual `Account` object available to the advice through the `account`
|
||||
parameter.
|
||||
|
||||
Another way of writing this is to declare a pointcut that "provides" the `Account`
|
||||
object value when it matches a join point, and then refer to the named pointcut
|
||||
from the advice. This would look as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private void accountDataAccessOperation(Account account) {}
|
||||
|
||||
@Before("accountDataAccessOperation(account)")
|
||||
public void validateAccount(Account account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private fun accountDataAccessOperation(account: Account) {
|
||||
}
|
||||
|
||||
@Before("accountDataAccessOperation(account)")
|
||||
fun validateAccount(account: Account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
See the AspectJ programming guide for more details.
|
||||
|
||||
The proxy object (`this`), target object (`target`), and annotations (`@within`,
|
||||
`@target`, `@annotation`, and `@args`) can all be bound in a similar fashion. The next
|
||||
set of examples shows how to match the execution of methods annotated with an
|
||||
`@Auditable` annotation and extract the audit code:
|
||||
|
||||
The following shows the definition of the `@Auditable` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Auditable {
|
||||
AuditCode value();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Auditable(val value: AuditCode)
|
||||
----
|
||||
======
|
||||
|
||||
The following shows the advice that matches the execution of `@Auditable` methods:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
public void audit(Auditable auditable) {
|
||||
AuditCode code = auditable.value();
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
fun audit(auditable: Auditable) {
|
||||
val code = auditable.value()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
======
|
||||
|
||||
[[aop-ataspectj-advice-params-generics]]
|
||||
=== Advice Parameters and Generics
|
||||
|
||||
Spring AOP can handle generics used in class declarations and method parameters. Suppose
|
||||
you have a generic type like the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public interface Sample<T> {
|
||||
void sampleGenericMethod(T param);
|
||||
void sampleGenericCollectionMethod(Collection<T> param);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
interface Sample<T> {
|
||||
fun sampleGenericMethod(param: T)
|
||||
fun sampleGenericCollectionMethod(param: Collection<T>)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can restrict interception of method types to certain parameter types by
|
||||
tying the advice parameter to the parameter type for which you want to intercept the method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(MyType param) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: MyType) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This approach does not work for generic collections. So you cannot define a
|
||||
pointcut as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(Collection<MyType> param) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: Collection<MyType>) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To make this work, we would have to inspect every element of the collection, which is not
|
||||
reasonable, as we also cannot decide how to treat `null` values in general. To achieve
|
||||
something similar to this, you have to type the parameter to `Collection<?>` and manually
|
||||
check the type of the elements.
|
||||
|
||||
[[aop-ataspectj-advice-params-names]]
|
||||
=== Determining Argument Names
|
||||
|
||||
Parameter binding in advice invocations relies on matching the names used in pointcut
|
||||
expressions to the parameter names declared in advice and pointcut method signatures.
|
||||
|
||||
NOTE: This section uses the terms _argument_ and _parameter_ interchangeably, since
|
||||
AspectJ APIs refer to parameter names as argument names.
|
||||
|
||||
Spring AOP uses the following `ParameterNameDiscoverer` implementations to determine
|
||||
parameter names. Each discoverer will be given a chance to discover parameter names, and
|
||||
the first successful discoverer wins. If none of the registered discoverers is capable
|
||||
of determining parameter names, an exception will be thrown.
|
||||
|
||||
`AspectJAnnotationParameterNameDiscoverer` :: Uses parameter names that have been explicitly
|
||||
specified by the user via the `argNames` attribute in the corresponding advice or
|
||||
pointcut annotation. See xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names-explicit[Explicit Argument Names] for details.
|
||||
`KotlinReflectionParameterNameDiscoverer` :: Uses Kotlin reflection APIs to determine
|
||||
parameter names. This discoverer is only used if such APIs are present on the classpath.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{spring-framework-api}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
for details on the algorithm used.
|
||||
|
||||
[[aop-ataspectj-advice-params-names-explicit]]
|
||||
=== Explicit Argument Names
|
||||
|
||||
@AspectJ advice and pointcut annotations have an optional `argNames` attribute that you
|
||||
can use to specify the argument names of the annotated method.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If an @AspectJ aspect has been compiled by the AspectJ compiler (`ajc`) even without
|
||||
debug information, you do not need to add the `argNames` attribute, since the compiler
|
||||
retains the needed information.
|
||||
|
||||
Similarly, if an @AspectJ aspect has been compiled with `javac` using the `-parameters`
|
||||
flag, you do not need to add the `argNames` attribute, since the compiler retains the
|
||||
needed information.
|
||||
====
|
||||
|
||||
The following example shows how to use the `argNames` attribute:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
public void audit(Object bean, Auditable auditable) {
|
||||
AuditCode code = auditable.value();
|
||||
// ... use code and bean
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
fun audit(bean: Any, auditable: Auditable) {
|
||||
val code = auditable.value()
|
||||
// ... use code and bean
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
======
|
||||
|
||||
If the first parameter is of type `JoinPoint`, `ProceedingJoinPoint`, or
|
||||
`JoinPoint.StaticPart`, you can omit the name of the parameter from the value of the
|
||||
`argNames` attribute. For example, if you modify the preceding advice to receive the join
|
||||
point object, the `argNames` attribute does not need to include it:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
|
||||
AuditCode code = auditable.value();
|
||||
// ... use code, bean, and jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
|
||||
val code = auditable.value()
|
||||
// ... use code, bean, and jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
======
|
||||
|
||||
The special treatment given to the first parameter of type `JoinPoint`,
|
||||
`ProceedingJoinPoint`, or `JoinPoint.StaticPart` is particularly convenient for advice
|
||||
methods that do not collect any other join point context. In such situations, you may
|
||||
omit the `argNames` attribute. For example, the following advice does not need to declare
|
||||
the `argNames` attribute:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
public void audit(JoinPoint jp) {
|
||||
// ... use jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
fun audit(jp: JoinPoint) {
|
||||
// ... use jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
======
|
||||
|
||||
|
||||
[[aop-ataspectj-advice-proceeding-with-the-call]]
|
||||
=== Proceeding with Arguments
|
||||
|
||||
We remarked earlier that we would describe how to write a `proceed` call with
|
||||
arguments that works consistently across Spring AOP and AspectJ. The solution is
|
||||
to ensure that the advice signature binds each of the method parameters in order.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
"args(accountHolderNamePattern)") // <1>
|
||||
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
|
||||
String accountHolderNamePattern) throws Throwable {
|
||||
String newPattern = preProcess(accountHolderNamePattern);
|
||||
return pjp.proceed(new Object[] {newPattern});
|
||||
}
|
||||
----
|
||||
<1> References the `inDataAccessLayer` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
"args(accountHolderNamePattern)") // <1>
|
||||
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
|
||||
accountHolderNamePattern: String): Any? {
|
||||
val newPattern = preProcess(accountHolderNamePattern)
|
||||
return pjp.proceed(arrayOf<Any>(newPattern))
|
||||
}
|
||||
----
|
||||
<1> References the `inDataAccessLayer` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
In many cases, you do this binding anyway (as in the preceding example).
|
||||
|
||||
|
||||
[[aop-ataspectj-advice-ordering]]
|
||||
== Advice Ordering
|
||||
|
||||
What happens when multiple pieces of advice all want to run at the same join point?
|
||||
Spring AOP follows the same precedence rules as AspectJ to determine the order of advice
|
||||
execution. The highest precedence advice runs first "on the way in" (so, given two pieces
|
||||
of before advice, the one with highest precedence runs first). "On the way out" from a
|
||||
join point, the highest precedence advice runs last (so, given two pieces of after
|
||||
advice, the one with the highest precedence will run second).
|
||||
|
||||
When two pieces of advice defined in different aspects both need to run at the same
|
||||
join point, unless you specify otherwise, the order of execution is undefined. You can
|
||||
control the order of execution by specifying precedence. This is done in the normal
|
||||
Spring way by either implementing the `org.springframework.core.Ordered` interface in
|
||||
the aspect class or annotating it with the `@Order` annotation. Given two aspects, the
|
||||
aspect returning the lower value from `Ordered.getOrder()` (or the annotation value) has
|
||||
the higher precedence.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Each of the distinct advice types of a particular aspect is conceptually meant to apply
|
||||
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
|
||||
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
|
||||
effectively be invoked after any `@AfterReturning` or `@AfterThrowing` advice methods
|
||||
in the same aspect, following AspectJ's "after finally advice" semantics for `@After`.
|
||||
|
||||
When two pieces of the same type of advice (for example, two `@After` advice methods)
|
||||
defined in the same `@Aspect` class both need to run at the same join point, the ordering
|
||||
is undefined (since there is no way to retrieve the source code declaration order through
|
||||
reflection for javac-compiled classes). Consider collapsing such advice methods into one
|
||||
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`.
|
||||
====
|
||||
@@ -1,15 +0,0 @@
|
||||
[[aop-aspectj-support]]
|
||||
= Enabling @AspectJ Support
|
||||
|
||||
To use @AspectJ aspects in a Spring configuration, you need to enable Spring support for
|
||||
configuring Spring AOP based on @AspectJ aspects and auto-proxying beans based on
|
||||
whether or not they are advised by those aspects. By auto-proxying, we mean that, if Spring
|
||||
determines that a bean is advised by one or more aspects, it automatically generates
|
||||
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).
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
@@ -1,34 +0,0 @@
|
||||
[[aop-at-aspectj]]
|
||||
= Declaring an Aspect
|
||||
|
||||
With @AspectJ support enabled, any bean defined in your application context with a
|
||||
class that is an @AspectJ aspect (has the `@Aspect` annotation) is automatically
|
||||
detected by Spring and used to configure Spring AOP. The next two examples show the
|
||||
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]
|
||||
|
||||
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
|
||||
annotated with `@Aspect`:
|
||||
|
||||
include-code::./NotVeryUsefulAspect[tag=snippet,indent=0]
|
||||
|
||||
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)
|
||||
declarations.
|
||||
|
||||
.Autodetecting aspects through component scanning
|
||||
NOTE: You can register aspect classes as regular beans in your Spring XML configuration,
|
||||
via `@Bean` methods in `@Configuration` classes, or have Spring autodetect them through
|
||||
classpath scanning -- the same as any other Spring-managed bean. However, note that the
|
||||
`@Aspect` annotation is not sufficient for autodetection in the classpath. For that
|
||||
purpose, you need to add a separate `@Component` annotation (or, alternatively, a custom
|
||||
stereotype annotation that qualifies, as per the rules of Spring's component scanner).
|
||||
|
||||
.Advising aspects with other aspects?
|
||||
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.
|
||||
@@ -1,45 +0,0 @@
|
||||
[[aop-ataspectj-example]]
|
||||
= An AOP Example
|
||||
|
||||
Now that you have seen how all the constituent parts work, we can put them together to do
|
||||
something useful.
|
||||
|
||||
The execution of business services can sometimes fail due to concurrency issues (for
|
||||
example, a deadlock loser). If the operation is retried, it is likely to succeed
|
||||
on the next try. For business services where it is appropriate to retry in such
|
||||
conditions (idempotent operations that do not need to go back to the user for conflict
|
||||
resolution), we want to transparently retry the operation to avoid the client seeing a
|
||||
`PessimisticLockingFailureException`. This is a requirement that clearly cuts across
|
||||
multiple services in the service layer and, hence, is ideal for implementing through an
|
||||
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]
|
||||
|
||||
`@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].
|
||||
|
||||
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
|
||||
retry). The `maxRetries` and `order` properties are both configured by Spring. The
|
||||
main action happens in the `doConcurrentOperation` around advice. Notice that, for the
|
||||
moment, we apply the retry logic to each `businessService`. We try to proceed,
|
||||
and if we fail with a `PessimisticLockingFailureException`, we try again, unless
|
||||
we have exhausted all of our retry attempts.
|
||||
|
||||
The corresponding Spring configuration follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
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]
|
||||
|
||||
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]
|
||||
@@ -1,62 +0,0 @@
|
||||
[[aop-instantiation-models]]
|
||||
= Aspect Instantiation Models
|
||||
|
||||
NOTE: This is an advanced topic. If you are just starting out with AOP, you can safely skip
|
||||
it until later.
|
||||
|
||||
By default, there is a single instance of each aspect within the application
|
||||
context. AspectJ calls this the singleton instantiation model. It is possible to define
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis`, `pertarget`, and
|
||||
`pertypewithin` instantiation models; `percflow` and `percflowbelow` are not currently
|
||||
supported.
|
||||
|
||||
You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect`
|
||||
annotation. Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
public class MyAspect {
|
||||
|
||||
private int someState;
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..))")
|
||||
public void recordServiceUsage() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
class MyAspect {
|
||||
|
||||
private val someState: Int = 0
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..))")
|
||||
fun recordServiceUsage() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, the effect of the `perthis` clause is that one aspect instance
|
||||
is created for each unique service object that performs a business service (each unique
|
||||
object bound to `this` at join points matched by the pointcut expression). The aspect
|
||||
instance is created the first time that a method is invoked on the service object. The
|
||||
aspect goes out of scope when the service object goes out of scope. Before the aspect
|
||||
instance is created, none of the advice within it runs. As soon as the aspect instance
|
||||
has been created, the advice declared within it runs at matched join points, but only
|
||||
when the service object is the one with which this aspect is associated. See the AspectJ
|
||||
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.
|
||||
@@ -1,77 +0,0 @@
|
||||
[[aop-introductions]]
|
||||
= Introductions
|
||||
|
||||
Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare
|
||||
that advised objects implement a given interface, and to provide an implementation of
|
||||
that interface on behalf of those objects.
|
||||
|
||||
You can make an introduction by using the `@DeclareParents` annotation. This annotation
|
||||
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):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Aspect
|
||||
public class UsageTracking {
|
||||
|
||||
@DeclareParents(value="com.xyz.service.*+", defaultImpl=DefaultUsageTracked.class)
|
||||
public static UsageTracked mixin;
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
|
||||
public void recordUsage(UsageTracked usageTracked) {
|
||||
usageTracked.incrementUseCount();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Aspect
|
||||
class UsageTracking {
|
||||
|
||||
companion object {
|
||||
@DeclareParents(value = "com.xyz.service.*+",
|
||||
defaultImpl = DefaultUsageTracked::class)
|
||||
lateinit var mixin: UsageTracked
|
||||
}
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
|
||||
fun recordUsage(usageTracked: UsageTracked) {
|
||||
usageTracked.incrementUseCount()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The interface to be implemented is determined by the type of the annotated field. The
|
||||
`value` attribute of the `@DeclareParents` annotation is an AspectJ type pattern. Any
|
||||
bean of a matching type implements the `UsageTracked` interface. Note that, in the
|
||||
before advice of the preceding example, service beans can be directly used as
|
||||
implementations of the `UsageTracked` interface. If accessing a bean programmatically,
|
||||
you would write the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
val usageTracked = context.getBean<UsageTracked>("myService")
|
||||
----
|
||||
======
|
||||
@@ -1,583 +0,0 @@
|
||||
[[aop-pointcuts]]
|
||||
= Declaring a Pointcut
|
||||
|
||||
Pointcuts determine join points of interest and thus enable us to control
|
||||
when advice runs. Spring AOP only supports method execution join points for Spring
|
||||
beans, so you can think of a pointcut as matching the execution of methods on Spring
|
||||
beans. A pointcut declaration has two parts: a signature comprising a name and any
|
||||
parameters and a pointcut expression that determines exactly which method
|
||||
executions we are interested in. In the @AspectJ annotation-style of AOP, a pointcut
|
||||
signature is provided by a regular method definition, and the pointcut expression is
|
||||
indicated by using the `@Pointcut` annotation (the method serving as the pointcut signature
|
||||
must have a `void` return type).
|
||||
|
||||
An example may help make this distinction between a pointcut signature and a pointcut
|
||||
expression clear. The following example defines a pointcut named `anyOldTransfer` that
|
||||
matches the execution of any method named `transfer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private void anyOldTransfer() {} // the pointcut signature
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private fun anyOldTransfer() {} // the pointcut signature
|
||||
----
|
||||
======
|
||||
|
||||
The pointcut expression that forms the value of the `@Pointcut` annotation is a regular
|
||||
AspectJ pointcut expression. For a full discussion of AspectJ's pointcut language, see
|
||||
the {aspectj-docs-progguide}/index.html[AspectJ
|
||||
Programming Guide] (and, for extensions, the
|
||||
{aspectj-docs}/adk15notebook/index.html[AspectJ 5
|
||||
Developer's Notebook]) or one of the books on AspectJ (such as _Eclipse AspectJ_, by Colyer
|
||||
et al., or _AspectJ in Action_, by Ramnivas Laddad).
|
||||
|
||||
|
||||
[[aop-pointcuts-designators]]
|
||||
== Supported Pointcut Designators
|
||||
|
||||
Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut
|
||||
expressions:
|
||||
|
||||
* `execution`: For matching method execution join points. This is the primary
|
||||
pointcut designator to use when working with Spring AOP.
|
||||
* `within`: Limits matching to join points within certain types (the execution
|
||||
of a method declared within a matching type when using Spring AOP).
|
||||
* `this`: Limits matching to join points (the execution of methods when using Spring
|
||||
AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.
|
||||
* `target`: Limits matching to join points (the execution of methods when using
|
||||
Spring AOP) where the target object (application object being proxied) is an instance
|
||||
of the given type.
|
||||
* `args`: Limits matching to join points (the execution of methods when using Spring
|
||||
AOP) where the arguments are instances of the given types.
|
||||
* `@target`: Limits matching to join points (the execution of methods when using
|
||||
Spring AOP) where the class of the executing object has an annotation of the given type.
|
||||
* `@args`: Limits matching to join points (the execution of methods when using Spring
|
||||
AOP) where the runtime type of the actual arguments passed have annotations of the
|
||||
given types.
|
||||
* `@within`: Limits matching to join points within types that have the given
|
||||
annotation (the execution of methods declared in types with the given annotation when
|
||||
using Spring AOP).
|
||||
* `@annotation`: Limits matching to join points where the subject of the join point
|
||||
(the method being run in Spring AOP) has the given annotation.
|
||||
|
||||
.Other pointcut types
|
||||
****
|
||||
The full AspectJ pointcut language supports additional pointcut designators that are not
|
||||
supported in Spring: `call`, `get`, `set`, `preinitialization`,
|
||||
`staticinitialization`, `initialization`, `handler`, `adviceexecution`, `withincode`, `cflow`,
|
||||
`cflowbelow`, `if`, `@this`, and `@withincode`. Use of these pointcut designators in pointcut
|
||||
expressions interpreted by Spring AOP results in an `IllegalArgumentException` being
|
||||
thrown.
|
||||
|
||||
The set of pointcut designators supported by Spring AOP may be extended in future
|
||||
releases to support more of the AspectJ pointcut designators.
|
||||
****
|
||||
|
||||
Because Spring AOP limits matching to only method execution join points, the preceding discussion
|
||||
of the pointcut designators gives a narrower definition than you can find in the
|
||||
AspectJ programming guide. In addition, AspectJ itself has type-based semantics and, at
|
||||
an execution join point, both `this` and `target` refer to the same object: the
|
||||
object executing the method. Spring AOP is a proxy-based system and differentiates
|
||||
between the proxy object itself (which is bound to `this`) and the target object behind the
|
||||
proxy (which is bound to `target`).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Due to the proxy-based nature of Spring's AOP framework, calls within the target object
|
||||
are, by definition, not intercepted. For JDK proxies, only public interface method
|
||||
calls on the proxy can be intercepted. With CGLIB, public and protected method calls on
|
||||
the proxy are intercepted (and even package-visible methods, if necessary). However,
|
||||
common interactions through proxies should always be designed through public signatures.
|
||||
|
||||
Note that pointcut definitions are generally matched against any intercepted method.
|
||||
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
|
||||
potential non-public interactions through proxies, it needs to be defined accordingly.
|
||||
|
||||
If your interception needs to include method calls or even constructors within the target
|
||||
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
|
||||
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
|
||||
with different characteristics, so be sure to make yourself familiar with weaving
|
||||
before making a decision.
|
||||
====
|
||||
|
||||
Spring AOP also supports an additional PCD named `bean`. This PCD lets you limit
|
||||
the matching of join points to a particular named Spring bean or to a set of named
|
||||
Spring beans (when using wildcards). The `bean` PCD has the following form:
|
||||
|
||||
[source,indent=0,subs="verbatim"]
|
||||
----
|
||||
bean(idOrNameOfBean)
|
||||
----
|
||||
|
||||
The `idOrNameOfBean` token can be the name of any Spring bean. Limited wildcard
|
||||
support that uses the `*` character is provided, so, if you establish some naming
|
||||
conventions for your Spring beans, you can write a `bean` PCD expression
|
||||
to select them. As is the case with other pointcut designators, the `bean` PCD can
|
||||
be used with the `&&` (and), `||` (or), and `!` (negation) operators, too.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `bean` PCD is supported only in Spring AOP and not in
|
||||
native AspectJ weaving. It is a Spring-specific extension to the standard PCDs that
|
||||
AspectJ defines and is, therefore, not available for aspects declared in the `@Aspect` model.
|
||||
|
||||
The `bean` PCD operates at the instance level (building on the Spring bean name
|
||||
concept) rather than at the type level only (to which weaving-based AOP is limited).
|
||||
Instance-based pointcut designators are a special capability of Spring's
|
||||
proxy-based AOP framework and its close integration with the Spring bean factory, where
|
||||
it is natural and straightforward to identify specific beans by name.
|
||||
====
|
||||
|
||||
|
||||
[[aop-pointcuts-combining]]
|
||||
== Combining Pointcut Expressions
|
||||
|
||||
You can combine pointcut expressions by using `&&,` `||` and `!`. You can also refer to
|
||||
pointcut expressions by name. The following example shows three pointcut expressions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
public class Pointcuts {
|
||||
|
||||
@Pointcut("execution(public * *(..))")
|
||||
public void publicMethod() {} // <1>
|
||||
|
||||
@Pointcut("within(com.xyz.trading..*)")
|
||||
public void inTrading() {} // <2>
|
||||
|
||||
@Pointcut("publicMethod() && inTrading()")
|
||||
public void tradingOperation() {} // <3>
|
||||
}
|
||||
----
|
||||
<1> `publicMethod` matches if a method execution join point represents the execution
|
||||
of any public method.
|
||||
<2> `inTrading` matches if a method execution is in the trading module.
|
||||
<3> `tradingOperation` matches if a method execution represents any public method in the
|
||||
trading module.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
class Pointcuts {
|
||||
|
||||
@Pointcut("execution(public * *(..))")
|
||||
fun publicMethod() {} // <1>
|
||||
|
||||
@Pointcut("within(com.xyz.trading..*)")
|
||||
fun inTrading() {} // <2>
|
||||
|
||||
@Pointcut("publicMethod() && inTrading()")
|
||||
fun tradingOperation() {} // <3>
|
||||
}
|
||||
----
|
||||
<1> `publicMethod` matches if a method execution join point represents the execution
|
||||
of any public method.
|
||||
<2> `inTrading` matches if a method execution is in the trading module.
|
||||
<3> `tradingOperation` matches if a method execution represents any public method in the
|
||||
trading module.
|
||||
======
|
||||
|
||||
It is a best practice to build more complex pointcut expressions out of smaller _named
|
||||
pointcuts_, as shown above. When referring to pointcuts by name, normal Java visibility
|
||||
rules apply (you can see `private` pointcuts in the same type, `protected` pointcuts in
|
||||
the hierarchy, `public` pointcuts anywhere, and so on). Visibility does not affect
|
||||
pointcut matching.
|
||||
|
||||
|
||||
[[aop-common-pointcuts]]
|
||||
== Sharing Named Pointcut Definitions
|
||||
|
||||
When working with enterprise applications, developers often have the need to refer to
|
||||
modules of the application and particular sets of operations from within several aspects.
|
||||
We recommend defining a dedicated class that captures commonly used _named pointcut_
|
||||
expressions for this purpose. Such a class typically resembles the following
|
||||
`CommonPointcuts` example (though what you name the class is up to you):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
|
||||
public class CommonPointcuts {
|
||||
|
||||
/**
|
||||
* A join point is in the web layer if the method is defined
|
||||
* in a type in the com.xyz.web package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.web..*)")
|
||||
public void inWebLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the service layer if the method is defined
|
||||
* in a type in the com.xyz.service package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.service..*)")
|
||||
public void inServiceLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the data access layer if the method is defined
|
||||
* in a type in the com.xyz.dao package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.dao..*)")
|
||||
public void inDataAccessLayer() {}
|
||||
|
||||
/**
|
||||
* A business service is the execution of any method defined on a service
|
||||
* interface. This definition assumes that interfaces are placed in the
|
||||
* "service" package, and that implementation types are in sub-packages.
|
||||
*
|
||||
* If you group service interfaces by functional area (for example,
|
||||
* in packages com.xyz.abc.service and com.xyz.def.service) then
|
||||
* the pointcut expression "execution(* com.xyz..service.*.*(..))"
|
||||
* could be used instead.
|
||||
*
|
||||
* Alternatively, you can write the expression using the 'bean'
|
||||
* PCD, like so "bean(*Service)". (This assumes that you have
|
||||
* named your Spring service beans in a consistent fashion.)
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz..service.*.*(..))")
|
||||
public void businessService() {}
|
||||
|
||||
/**
|
||||
* A data access operation is the execution of any method defined on a
|
||||
* DAO interface. This definition assumes that interfaces are placed in the
|
||||
* "dao" package, and that implementation types are in sub-packages.
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..))")
|
||||
public void dataAccessOperation() {}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Pointcut
|
||||
|
||||
class CommonPointcuts {
|
||||
|
||||
/**
|
||||
* A join point is in the web layer if the method is defined
|
||||
* in a type in the com.xyz.web package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.web..*)")
|
||||
fun inWebLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the service layer if the method is defined
|
||||
* in a type in the com.xyz.service package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.service..*)")
|
||||
fun inServiceLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the data access layer if the method is defined
|
||||
* in a type in the com.xyz.dao package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.dao..*)")
|
||||
fun inDataAccessLayer() {}
|
||||
|
||||
/**
|
||||
* A business service is the execution of any method defined on a service
|
||||
* interface. This definition assumes that interfaces are placed in the
|
||||
* "service" package, and that implementation types are in sub-packages.
|
||||
*
|
||||
* If you group service interfaces by functional area (for example,
|
||||
* in packages com.xyz.abc.service and com.xyz.def.service) then
|
||||
* the pointcut expression "execution(* com.xyz..service.*.*(..))"
|
||||
* could be used instead.
|
||||
*
|
||||
* Alternatively, you can write the expression using the 'bean'
|
||||
* PCD, like so "bean(*Service)". (This assumes that you have
|
||||
* named your Spring service beans in a consistent fashion.)
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz..service.*.*(..))")
|
||||
fun businessService() {}
|
||||
|
||||
/**
|
||||
* A data access operation is the execution of any method defined on a
|
||||
* DAO interface. This definition assumes that interfaces are placed in the
|
||||
* "dao" package, and that implementation types are in sub-packages.
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..))")
|
||||
fun dataAccessOperation() {}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can refer to the pointcuts defined in such a class anywhere you need a pointcut
|
||||
expression by referencing the fully-qualified name of the class combined with the
|
||||
`@Pointcut` method's name. For example, to make the service layer transactional, you
|
||||
could write the following which references the
|
||||
`com.xyz.CommonPointcuts.businessService()` _named pointcut_:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
<aop:advisor
|
||||
pointcut="com.xyz.CommonPointcuts.businessService()"
|
||||
advice-ref="tx-advice"/>
|
||||
</aop:config>
|
||||
|
||||
<tx:advice id="tx-advice">
|
||||
<tx:attributes>
|
||||
<tx:method name="*" propagation="REQUIRED"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
|
||||
The `<aop:config>` and `<aop:advisor>` elements are discussed in xref:core/aop/schema.adoc[Schema-based AOP Support]. The
|
||||
transaction elements are discussed in xref:data-access/transaction.adoc[Transaction Management].
|
||||
|
||||
|
||||
[[aop-pointcuts-examples]]
|
||||
== Examples
|
||||
|
||||
Spring AOP users are likely to use the `execution` pointcut designator the most often.
|
||||
The format of an execution expression follows:
|
||||
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(modifiers-pattern?
|
||||
ret-type-pattern
|
||||
declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
----
|
||||
|
||||
All parts except the returning type pattern (`ret-type-pattern` in the preceding snippet),
|
||||
the name pattern, and the parameters pattern are optional. The returning type pattern determines
|
||||
what the return type of the method must be in order for a join point to be matched.
|
||||
`{asterisk}` is most frequently used as the returning type pattern. It matches any return
|
||||
type. A fully-qualified type name matches only when the method returns the given
|
||||
type. The name pattern matches the method name. You can use the `{asterisk}` wildcard as all or
|
||||
part of a name pattern. If you specify a declaring type pattern,
|
||||
include a trailing `.` to join it to the name pattern component.
|
||||
The parameters pattern is slightly more complex: `()` matches a
|
||||
method that takes no parameters, whereas `(..)` matches any number (zero or more) of parameters.
|
||||
The `({asterisk})` pattern matches a method that takes one parameter of any type.
|
||||
`(*,String)` matches a method that takes two parameters. The first can be of any type, while the
|
||||
second must be a `String`. Consult the
|
||||
{aspectj-docs-progguide}/semantics-pointcuts.html[Language
|
||||
Semantics] section of the AspectJ Programming Guide for more information.
|
||||
|
||||
The following examples show some common pointcut expressions:
|
||||
|
||||
* The execution of any public method:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(public * *(..))
|
||||
----
|
||||
|
||||
* The execution of any method with a name that begins with `set`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* set*(..))
|
||||
----
|
||||
|
||||
* The execution of any method defined by the `AccountService` interface:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
----
|
||||
|
||||
* The execution of any method defined in the `service` package:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
----
|
||||
|
||||
* The execution of any method defined in the service package or one of its sub-packages:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) within the service package:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
within(com.xyz.service.*)
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) within the service package or one of its
|
||||
sub-packages:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
within(com.xyz.service..*)
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the proxy implements the
|
||||
`AccountService` interface:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
this(com.xyz.service.AccountService)
|
||||
----
|
||||
+
|
||||
NOTE: `this` is more commonly used in a binding form. See the section on xref:core/aop/ataspectj/advice.adoc[Declaring Advice]
|
||||
for how to make the proxy object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the target object
|
||||
implements the `AccountService` interface:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
target(com.xyz.service.AccountService)
|
||||
----
|
||||
+
|
||||
NOTE: `target` is more commonly used in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
for how to make the target object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) that takes a single parameter
|
||||
and where the argument passed at runtime is `Serializable`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
args(java.io.Serializable)
|
||||
----
|
||||
+
|
||||
NOTE: `args` is more commonly used in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
for how to make the method arguments available in the advice body.
|
||||
+
|
||||
Note that the pointcut given in this example is different from `execution(*
|
||||
*(java.io.Serializable))`. The args version matches if the argument passed at runtime is
|
||||
`Serializable`, and the execution version matches if the method signature declares a single
|
||||
parameter of type `Serializable`.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the target object has a
|
||||
`@Transactional` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@target(org.springframework.transaction.annotation.Transactional)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@target` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section for
|
||||
how to make the annotation object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the declared type of the
|
||||
target object has an `@Transactional` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@within(org.springframework.transaction.annotation.Transactional)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@within` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section for
|
||||
how to make the annotation object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the executing method has an
|
||||
`@Transactional` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@annotation(org.springframework.transaction.annotation.Transactional)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@annotation` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
for how to make the annotation object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) which takes a single parameter,
|
||||
and where the runtime type of the argument passed has the `@Classified` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@args(com.xyz.security.Classified)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@args` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
how to make the annotation object(s) available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) on a Spring bean named
|
||||
`tradeService`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
bean(tradeService)
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) on Spring beans having names that
|
||||
match the wildcard expression `*Service`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
bean(*Service)
|
||||
----
|
||||
|
||||
|
||||
[[writing-good-pointcuts]]
|
||||
== Writing Good Pointcuts
|
||||
|
||||
During compilation, AspectJ processes pointcuts in order to optimize matching
|
||||
performance. Examining code and determining if each join point matches (statically or
|
||||
dynamically) a given pointcut is a costly process. (A dynamic match means the match
|
||||
cannot be fully determined from static analysis and that a test is placed in the code to
|
||||
determine if there is an actual match when the code is running). On first encountering a
|
||||
pointcut declaration, AspectJ rewrites it into an optimal form for the matching
|
||||
process. What does this mean? Basically, pointcuts are rewritten in DNF (Disjunctive
|
||||
Normal Form) and the components of the pointcut are sorted such that those components
|
||||
that are cheaper to evaluate are checked first. This means you do not have to worry
|
||||
about understanding the performance of various pointcut designators and may supply them
|
||||
in any order in a pointcut declaration.
|
||||
|
||||
However, AspectJ can work only with what it is told. For optimal performance of
|
||||
matching, you should think about what you are trying to achieve and narrow the search
|
||||
space for matches as much as possible in the definition. The existing designators
|
||||
naturally fall into one of three groups: kinded, scoping, and contextual:
|
||||
|
||||
* Kinded designators select a particular kind of join point:
|
||||
`execution`, `get`, `set`, `call`, and `handler`.
|
||||
* Scoping designators select a group of join points of interest
|
||||
(probably of many kinds): `within` and `withincode`
|
||||
* Contextual designators match (and optionally bind) based on context:
|
||||
`this`, `target`, and `@annotation`
|
||||
|
||||
A well written pointcut should include at least the first two types (kinded and
|
||||
scoping). You can include the contextual designators to match based on
|
||||
join point context or bind that context for use in the advice. Supplying only a
|
||||
kinded designator or only a contextual designator works but could affect weaving
|
||||
performance (time and memory used), due to extra processing and analysis. Scoping
|
||||
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.
|
||||
@@ -1,107 +0,0 @@
|
||||
[[aop-choosing]]
|
||||
= Choosing which AOP Declaration Style to Use
|
||||
|
||||
Once you have decided that an aspect is the best approach for implementing a given
|
||||
requirement, how do you decide between using Spring AOP or AspectJ and between the
|
||||
Aspect language (code) style, the @AspectJ annotation style, or the Spring XML style? These
|
||||
decisions are influenced by a number of factors including application requirements,
|
||||
development tools, and team familiarity with AOP.
|
||||
|
||||
|
||||
[[aop-spring-or-aspectj]]
|
||||
== Spring AOP or Full AspectJ?
|
||||
|
||||
Use the simplest thing that can work. Spring AOP is simpler than using full AspectJ, as
|
||||
there is no requirement to introduce the AspectJ compiler / weaver into your development
|
||||
and build processes. If you only need to advise the execution of operations on Spring
|
||||
beans, Spring AOP is the right choice. If you need to advise objects not managed by
|
||||
the Spring container (such as domain objects, typically), you need to use
|
||||
AspectJ. You also need to use AspectJ if you wish to advise join points other than
|
||||
simple method executions (for example, field get or set join points and so on).
|
||||
|
||||
When you use AspectJ, you have the choice of the AspectJ language syntax (also known as
|
||||
the "code style") or the @AspectJ annotation style. If aspects play a large
|
||||
role in your design, and you are able to use the https://www.eclipse.org/ajdt/[AspectJ
|
||||
Development Tools (AJDT)] plugin for Eclipse, the AspectJ language syntax is the
|
||||
preferred option. It is cleaner and simpler because the language was purposefully
|
||||
designed for writing aspects. If you do not use Eclipse or have only a few aspects
|
||||
that do not play a major role in your application, you may want to consider using
|
||||
the @AspectJ style, sticking with regular Java compilation in your IDE, and adding
|
||||
an aspect weaving phase to your build script.
|
||||
|
||||
|
||||
[[aop-ataspectj-or-xml]]
|
||||
== @AspectJ or XML for Spring AOP?
|
||||
|
||||
If you have chosen to use Spring AOP, you have a choice of @AspectJ or XML style.
|
||||
There are various tradeoffs to consider.
|
||||
|
||||
The XML style may be most familiar to existing Spring users, and it is backed by genuine
|
||||
POJOs. When using AOP as a tool to configure enterprise services, XML can be a good
|
||||
choice (a good test is whether you consider the pointcut expression to be a part of your
|
||||
configuration that you might want to change independently). With the XML style, it is
|
||||
arguably clearer from your configuration which aspects are present in the system.
|
||||
|
||||
The XML style has two disadvantages. First, it does not fully encapsulate the
|
||||
implementation of the requirement it addresses in a single place. The DRY principle says
|
||||
that there should be a single, unambiguous, authoritative representation of any piece of
|
||||
knowledge within a system. When using the XML style, the knowledge of how a requirement
|
||||
is implemented is split across the declaration of the backing bean class and the XML in
|
||||
the configuration file. When you use the @AspectJ style, this information is encapsulated
|
||||
in a single module: the aspect. Secondly, the XML style is slightly more limited in what
|
||||
it can express than the @AspectJ style: Only the "singleton" aspect instantiation model
|
||||
is supported, and it is not possible to combine named pointcuts declared in XML.
|
||||
For example, in the @AspectJ style you can write something like the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
public void propertyAccess() {}
|
||||
|
||||
@Pointcut("execution(com.xyz.Account+ *(..))")
|
||||
public void operationReturningAnAccount() {}
|
||||
|
||||
@Pointcut("propertyAccess() && operationReturningAnAccount()")
|
||||
public void accountPropertyAccess() {}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
fun propertyAccess() {}
|
||||
|
||||
@Pointcut("execution(com.xyz.Account+ *(..))")
|
||||
fun operationReturningAnAccount() {}
|
||||
|
||||
@Pointcut("propertyAccess() && operationReturningAnAccount()")
|
||||
fun accountPropertyAccess() {}
|
||||
----
|
||||
======
|
||||
|
||||
In the XML style you can declare the first two pointcuts:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:pointcut id="propertyAccess"
|
||||
expression="execution(* get*())"/>
|
||||
|
||||
<aop:pointcut id="operationReturningAnAccount"
|
||||
expression="execution(com.xyz.Account+ *(..))"/>
|
||||
----
|
||||
|
||||
The downside of the XML approach is that you cannot define the
|
||||
`accountPropertyAccess` pointcut by combining these definitions.
|
||||
|
||||
The @AspectJ style supports additional instantiation models and richer pointcut
|
||||
composition. It has the advantage of keeping the aspect as a modular unit. It also has
|
||||
the advantage that the @AspectJ aspects can be understood (and thus consumed) both by
|
||||
Spring AOP and by AspectJ. So, if you later decide you need the capabilities of AspectJ
|
||||
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.
|
||||
@@ -1,75 +0,0 @@
|
||||
[[aop-introduction-defn]]
|
||||
= AOP Concepts
|
||||
|
||||
Let us begin by defining some central AOP concepts and terminology. These terms are not
|
||||
Spring-specific. Unfortunately, AOP terminology is not particularly intuitive.
|
||||
However, it would be even more confusing if Spring used its own terminology.
|
||||
|
||||
* Aspect: A modularization of a concern that cuts across multiple classes.
|
||||
Transaction management is a good example of a crosscutting concern in enterprise Java
|
||||
applications. In Spring AOP, aspects are implemented by using regular classes
|
||||
(the xref:core/aop/schema.adoc[schema-based approach]) or regular classes annotated with the
|
||||
`@Aspect` annotation (the xref:core/aop/ataspectj.adoc[@AspectJ style]).
|
||||
* Join point: A point during the execution of a program, such as the execution of a
|
||||
method or the handling of an exception. In Spring AOP, a join point always
|
||||
represents a method execution.
|
||||
* Advice: Action taken by an aspect at a particular join point. Different types of
|
||||
advice include "around", "before", and "after" advice. (Advice types are discussed
|
||||
later.) Many AOP frameworks, including Spring, model an advice as an interceptor and
|
||||
maintain a chain of interceptors around the join point.
|
||||
* Pointcut: A predicate that matches join points. Advice is associated with a
|
||||
pointcut expression and runs at any join point matched by the pointcut (for example,
|
||||
the execution of a method with a certain name). The concept of join points as matched
|
||||
by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut
|
||||
expression language by default.
|
||||
* Introduction: Declaring additional methods or fields on behalf of a type. Spring
|
||||
AOP lets you introduce new interfaces (and a corresponding implementation) to any
|
||||
advised object. For example, you could use an introduction to make a bean implement an
|
||||
`IsModified` interface, to simplify caching. (An introduction is known as an
|
||||
inter-type declaration in the AspectJ community.)
|
||||
* Target object: An object being advised by one or more aspects. Also referred to as
|
||||
the "advised object". Since Spring AOP is implemented by using runtime proxies, this
|
||||
object is always a proxied object.
|
||||
* AOP proxy: An object created by the AOP framework in order to implement the aspect
|
||||
contracts (advice method executions and so on). In the Spring Framework, an AOP proxy
|
||||
is a JDK dynamic proxy or a CGLIB proxy.
|
||||
* Weaving: linking aspects with other application types or objects to create an
|
||||
advised object. This can be done at compile time (using the AspectJ compiler, for
|
||||
example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks,
|
||||
performs weaving at runtime.
|
||||
|
||||
Spring AOP includes the following types of advice:
|
||||
|
||||
* Before advice: Advice that runs before a join point but that does not have
|
||||
the ability to prevent execution flow proceeding to the join point (unless it throws
|
||||
an exception).
|
||||
* After returning advice: Advice to be run after a join point completes
|
||||
normally (for example, if a method returns without throwing an exception).
|
||||
* After throwing advice: Advice to be run if a method exits by throwing an
|
||||
exception.
|
||||
* After (finally) advice: Advice to be run regardless of the means by which a
|
||||
join point exits (normal or exceptional return).
|
||||
* Around advice: Advice that surrounds a join point such as a method invocation.
|
||||
This is the most powerful kind of advice. Around advice can perform custom behavior
|
||||
before and after the method invocation. It is also responsible for choosing whether to
|
||||
proceed to the join point or to shortcut the advised method execution by returning its
|
||||
own return value or throwing an exception.
|
||||
|
||||
Around advice is the most general kind of advice. Since Spring AOP, like AspectJ,
|
||||
provides a full range of advice types, we recommend that you use the least powerful
|
||||
advice type that can implement the required behavior. For example, if you need only to
|
||||
update a cache with the return value of a method, you are better off implementing an
|
||||
after returning advice than an around advice, although an around advice can accomplish
|
||||
the same thing. Using the most specific advice type provides a simpler programming model
|
||||
with less potential for errors. For example, you do not need to invoke the `proceed()`
|
||||
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
|
||||
than `Object` arrays.
|
||||
|
||||
The concept of join points matched by pointcuts is the key to AOP, which distinguishes
|
||||
it from older technologies offering only interception. Pointcuts enable advice to be
|
||||
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).
|
||||
@@ -1,18 +0,0 @@
|
||||
[[aop-introduction-proxies]]
|
||||
= AOP Proxies
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This
|
||||
enables any interface (or set of interfaces) to be proxied.
|
||||
|
||||
Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than
|
||||
interfaces. By default, CGLIB is used if a business object does not implement an
|
||||
interface. As it is good practice to program to interfaces rather than classes, business
|
||||
classes normally implement one or more business interfaces. It is possible to
|
||||
xref:core/aop/proxying.adoc[force the use of CGLIB], in those (hopefully rare) cases where you
|
||||
need to advise a method that is not declared on an interface or where you need to
|
||||
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.
|
||||
@@ -1,57 +0,0 @@
|
||||
[[aop-introduction-spring-defn]]
|
||||
= Spring AOP Capabilities and Goals
|
||||
|
||||
Spring AOP is implemented in pure Java. There is no need for a special compilation
|
||||
process. Spring AOP does not need to control the class loader hierarchy and is thus
|
||||
suitable for use in a servlet container or application server.
|
||||
|
||||
Spring AOP currently supports only method execution join points (advising the execution
|
||||
of methods on Spring beans). Field interception is not implemented, although support for
|
||||
field interception could be added without breaking the core Spring AOP APIs. If you need
|
||||
to advise field access and update join points, consider a language such as AspectJ.
|
||||
|
||||
Spring AOP's approach to AOP differs from that of most other AOP frameworks. The aim is
|
||||
not to provide the most complete AOP implementation (although Spring AOP is quite
|
||||
capable). Rather, the aim is to provide a close integration between AOP implementation and
|
||||
Spring IoC, to help solve common problems in enterprise applications.
|
||||
|
||||
Thus, for example, the Spring Framework's AOP functionality is normally used in
|
||||
conjunction with the Spring IoC container. Aspects are configured by using normal bean
|
||||
definition syntax (although this allows powerful "auto-proxying" capabilities). This is a
|
||||
crucial difference from other AOP implementations. You cannot do some things
|
||||
easily or efficiently with Spring AOP, such as advise very fine-grained objects (typically,
|
||||
domain objects). AspectJ is the best choice in such cases. However, our
|
||||
experience is that Spring AOP provides an excellent solution to most problems in
|
||||
enterprise Java applications that are amenable to AOP.
|
||||
|
||||
Spring AOP never strives to compete with AspectJ to provide a comprehensive AOP
|
||||
solution. We believe that both proxy-based frameworks such as Spring AOP and full-blown
|
||||
frameworks such as AspectJ are valuable and that they are complementary, rather than in
|
||||
competition. Spring seamlessly integrates Spring AOP and IoC with AspectJ, to enable
|
||||
all uses of AOP within a consistent Spring-based application
|
||||
architecture. This integration does not affect the Spring AOP API or the AOP Alliance
|
||||
API. Spring AOP remains backward-compatible. See xref:core/aop-api.adoc[the following chapter]
|
||||
for a discussion of the Spring AOP APIs.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
One of the central tenets of the Spring Framework is that of non-invasiveness. This
|
||||
is the idea that you should not be forced to introduce framework-specific classes and
|
||||
interfaces into your business or domain model. However, in some places, the Spring Framework
|
||||
does give you the option to introduce Spring Framework-specific dependencies into your
|
||||
codebase. The rationale in giving you such options is because, in certain scenarios, it
|
||||
might be just plain easier to read or code some specific piece of functionality in such
|
||||
a way. However, the Spring Framework (almost) always offers you the choice: You have the
|
||||
freedom to make an informed decision as to which option best suits your particular use
|
||||
case or scenario.
|
||||
|
||||
One such choice that is relevant to this chapter is that of which AOP framework (and
|
||||
which AOP style) to choose. You have the choice of AspectJ, Spring AOP, or both. You
|
||||
also have the choice of either the @AspectJ annotation-style approach or the Spring XML
|
||||
configuration-style approach. The fact that this chapter chooses to introduce the
|
||||
@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.
|
||||
====
|
||||
@@ -1,8 +0,0 @@
|
||||
[[aop-mixing-styles]]
|
||||
= Mixing Aspect Types
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
It is perfectly possible to mix @AspectJ style aspects by using the auto-proxying support,
|
||||
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.
|
||||
@@ -1,292 +0,0 @@
|
||||
[[aop-proxying]]
|
||||
= Proxying Mechanisms
|
||||
|
||||
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given
|
||||
target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a common
|
||||
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.
|
||||
|
||||
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
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
of the `<aop:config>` element to true, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config proxy-target-class="true">
|
||||
<!-- other beans defined here... -->
|
||||
</aop:config>
|
||||
----
|
||||
|
||||
To force CGLIB proxying when you use the @AspectJ auto-proxy support, set the
|
||||
`proxy-target-class` attribute of the `<aop:aspectj-autoproxy>` element to `true`,
|
||||
as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy proxy-target-class="true"/>
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Multiple `<aop:config/>` sections are collapsed into a single unified auto-proxy creator
|
||||
at runtime, which applies the _strongest_ proxy settings that any of the
|
||||
`<aop:config/>` sections (typically from different XML bean definition files) specified.
|
||||
This also applies to the `<tx:annotation-driven/>` and `<aop:aspectj-autoproxy/>`
|
||||
elements.
|
||||
|
||||
To be clear, using `proxy-target-class="true"` on `<tx:annotation-driven/>`,
|
||||
`<aop:aspectj-autoproxy/>`, or `<aop:config/>` elements forces the use of CGLIB
|
||||
proxies _for all three of them_.
|
||||
====
|
||||
|
||||
|
||||
[[aop-understanding-aop-proxies]]
|
||||
== Understanding AOP Proxies
|
||||
|
||||
Spring AOP is proxy-based. It is vitally important that you grasp the semantics of
|
||||
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:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public class SimplePojo implements Pojo {
|
||||
|
||||
public void foo() {
|
||||
// this next method invocation is a direct call on the 'this' reference
|
||||
this.bar();
|
||||
}
|
||||
|
||||
public void bar() {
|
||||
// some logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
class SimplePojo : Pojo {
|
||||
|
||||
fun foo() {
|
||||
// this next method invocation is a direct call on the 'this' reference
|
||||
this.bar()
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
// some logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you invoke a method on an object reference, the method is invoked directly on
|
||||
that object reference, as the following image and listing show:
|
||||
|
||||
image::aop-proxy-plain-pojo-call.png[]
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Pojo pojo = new SimplePojo();
|
||||
// this is a direct method call on the 'pojo' reference
|
||||
pojo.foo();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun main() {
|
||||
val pojo = SimplePojo()
|
||||
// this is a direct method call on the 'pojo' reference
|
||||
pojo.foo()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Things change slightly when the reference that client code has is a proxy. Consider the
|
||||
following diagram and code snippet:
|
||||
|
||||
image::aop-proxy-call.png[]
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ProxyFactory factory = new ProxyFactory(new SimplePojo());
|
||||
factory.addInterface(Pojo.class);
|
||||
factory.addAdvice(new RetryAdvice());
|
||||
|
||||
Pojo pojo = (Pojo) factory.getProxy();
|
||||
// this is a method call on the proxy!
|
||||
pojo.foo();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun main() {
|
||||
val factory = ProxyFactory(SimplePojo())
|
||||
factory.addInterface(Pojo::class.java)
|
||||
factory.addAdvice(RetryAdvice())
|
||||
|
||||
val pojo = factory.proxy as Pojo
|
||||
// this is a method call on the proxy!
|
||||
pojo.foo()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The key thing to understand here is that the client code inside the `main(..)` method
|
||||
of the `Main` class has a reference to the proxy. This means that method calls on that
|
||||
object reference are calls on the proxy. As a result, the proxy can delegate to all of
|
||||
the interceptors (advice) that are relevant to that particular method call. However,
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public class SimplePojo implements Pojo {
|
||||
|
||||
public void foo() {
|
||||
// This works, but it should be avoided if possible.
|
||||
((Pojo) AopContext.currentProxy()).bar();
|
||||
}
|
||||
|
||||
public void bar() {
|
||||
// some logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
class SimplePojo : Pojo {
|
||||
|
||||
fun foo() {
|
||||
// This works, but it should be avoided if possible.
|
||||
(AopContext.currentProxy() as Pojo).bar()
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
// some logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ProxyFactory factory = new ProxyFactory(new SimplePojo());
|
||||
factory.addInterface(Pojo.class);
|
||||
factory.addAdvice(new RetryAdvice());
|
||||
factory.setExposeProxy(true);
|
||||
|
||||
Pojo pojo = (Pojo) factory.getProxy();
|
||||
// this is a method call on the proxy!
|
||||
pojo.foo();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun main() {
|
||||
val factory = ProxyFactory(SimplePojo())
|
||||
factory.addInterface(Pojo::class.java)
|
||||
factory.addAdvice(RetryAdvice())
|
||||
factory.isExposeProxy = true
|
||||
|
||||
val pojo = factory.proxy as Pojo
|
||||
// this is a method call on the proxy!
|
||||
pojo.foo()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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.
|
||||
@@ -1,12 +0,0 @@
|
||||
[[aop-resources]]
|
||||
= Further Resources
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
More information on AspectJ can be found on the {aspectj-site}[AspectJ website].
|
||||
|
||||
_Eclipse AspectJ_ by Adrian Colyer et. al. (Addison-Wesley, 2005) provides a
|
||||
comprehensive introduction and reference for the AspectJ language.
|
||||
|
||||
_AspectJ in Action_, Second Edition by Ramnivas Laddad (Manning, 2009) comes highly
|
||||
recommended. The focus of the book is on AspectJ, but a lot of general AOP themes are
|
||||
explored (in some depth).
|
||||
@@ -1,973 +0,0 @@
|
||||
[[aop-schema]]
|
||||
= Schema-based AOP Support
|
||||
|
||||
If you prefer an XML-based format, Spring also offers support for defining aspects
|
||||
using the `aop` namespace tags. The exact same pointcut expressions and advice kinds
|
||||
as when using the @AspectJ style are supported. Hence, in this section we focus on
|
||||
that syntax and refer the reader to the discussion in the previous section
|
||||
(xref:core/aop/ataspectj.adoc[@AspectJ support]) for an understanding of writing pointcut expressions and the binding
|
||||
of advice parameters.
|
||||
|
||||
To use the aop namespace tags described in this section, you need to import the
|
||||
`spring-aop` schema, as described in xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]
|
||||
. See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema]
|
||||
for how to import the tags in the `aop` namespace.
|
||||
|
||||
Within your Spring configurations, all aspect and advisor elements must be placed within
|
||||
an `<aop:config>` element (you can have more than one `<aop:config>` element in an
|
||||
application context configuration). An `<aop:config>` element can contain pointcut,
|
||||
advisor, and aspect elements (note that these must be declared in that order).
|
||||
|
||||
WARNING: The `<aop:config>` style of configuration makes heavy use of Spring's
|
||||
xref:core/aop-api/autoproxy.adoc[auto-proxying] mechanism. This can cause issues (such as advice
|
||||
not being woven) if you already use explicit auto-proxying through the use of
|
||||
`BeanNameAutoProxyCreator` or something similar. The recommended usage pattern is to
|
||||
use either only the `<aop:config>` style or only the `AutoProxyCreator` style and
|
||||
never mix them.
|
||||
|
||||
|
||||
[[aop-schema-declaring-an-aspect]]
|
||||
== Declaring an Aspect
|
||||
|
||||
When you use the schema support, an aspect is a regular Java object defined as a bean in
|
||||
your Spring application context. The state and behavior are captured in the fields and
|
||||
methods of the object, and the pointcut and advice information are captured in the XML.
|
||||
|
||||
You can declare an aspect by using the `<aop:aspect>` element, and reference the backing bean
|
||||
by using the `ref` attribute, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
<aop:aspect id="myAspect" ref="aBean">
|
||||
...
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
<bean id="aBean" class="...">
|
||||
...
|
||||
</bean>
|
||||
----
|
||||
|
||||
The bean that backs the aspect (`aBean` in this case) can of course be configured and
|
||||
dependency injected just like any other Spring bean.
|
||||
|
||||
|
||||
[[aop-schema-pointcuts]]
|
||||
== Declaring a Pointcut
|
||||
|
||||
You can declare a _named pointcut_ inside an `<aop:config>` element, letting the pointcut
|
||||
definition be shared across several aspects and advisors.
|
||||
|
||||
A pointcut that represents the execution of any business service in the service layer can
|
||||
be defined as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
|
||||
<aop:pointcut id="businessService"
|
||||
expression="execution(* com.xyz.service.*.*(..))" />
|
||||
|
||||
</aop:config>
|
||||
----
|
||||
|
||||
Note that the pointcut expression itself uses the same AspectJ pointcut expression
|
||||
language as described in xref:core/aop/ataspectj.adoc[@AspectJ support]. If you use the schema based declaration
|
||||
style, you can also refer to _named pointcuts_ defined in `@Aspect` types within the
|
||||
pointcut expression. Thus, another way of defining the above pointcut would be as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
|
||||
<aop:pointcut id="businessService"
|
||||
expression="com.xyz.CommonPointcuts.businessService()" /> <1>
|
||||
|
||||
</aop:config>
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Declaring a pointcut _inside_ an aspect is very similar to declaring a top-level pointcut,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
|
||||
<aop:aspect id="myAspect" ref="aBean">
|
||||
|
||||
<aop:pointcut id="businessService"
|
||||
expression="execution(* com.xyz.service.*.*(..))"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
|
||||
</aop:config>
|
||||
----
|
||||
|
||||
In much the same way as an @AspectJ aspect, pointcuts declared by using the schema based
|
||||
definition style can collect join point context. For example, the following pointcut
|
||||
collects the `this` object as the join point context and passes it to the advice:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
|
||||
<aop:aspect id="myAspect" ref="aBean">
|
||||
|
||||
<aop:pointcut id="businessService"
|
||||
expression="execution(* com.xyz.service.*.*(..)) && this(service)"/>
|
||||
|
||||
<aop:before pointcut-ref="businessService" method="monitor"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
|
||||
</aop:config>
|
||||
----
|
||||
|
||||
The advice must be declared to receive the collected join point context by including
|
||||
parameters of the matching names, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public void monitor(Object service) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun monitor(service: Any) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
When combining pointcut sub-expressions, `+&&+` is awkward within an XML
|
||||
document, so you can use the `and`, `or`, and `not` keywords in place of `+&&+`,
|
||||
`||`, and `!`, respectively. For example, the previous pointcut can be better written as
|
||||
follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
|
||||
<aop:aspect id="myAspect" ref="aBean">
|
||||
|
||||
<aop:pointcut id="businessService"
|
||||
expression="execution(* com.xyz.service.*.*(..)) and this(service)"/>
|
||||
|
||||
<aop:before pointcut-ref="businessService" method="monitor"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
|
||||
</aop:config>
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
|
||||
[[aop-schema-advice]]
|
||||
== Declaring Advice
|
||||
|
||||
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
|
||||
|
||||
Before advice runs before a matched method execution. It is declared inside an
|
||||
`<aop:aspect>` by using the `<aop:before>` element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="beforeExample" ref="aBean">
|
||||
|
||||
<aop:before
|
||||
pointcut-ref="dataAccessOperation"
|
||||
method="doAccessCheck"/>
|
||||
|
||||
...
|
||||
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
In the example above, `dataAccessOperation` is the `id` of a _named pointcut_ defined at
|
||||
the top (`<aop:config>`) level (see xref:core/aop/schema.adoc#aop-schema-pointcuts[Declaring a Pointcut]).
|
||||
|
||||
NOTE: As we noted in the discussion of the @AspectJ style, using _named pointcuts_ can
|
||||
significantly improve the readability of your code. See xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions] for
|
||||
details.
|
||||
|
||||
To define the pointcut inline instead, replace the `pointcut-ref` attribute with a
|
||||
`pointcut` attribute, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="beforeExample" ref="aBean">
|
||||
|
||||
<aop:before
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))"
|
||||
method="doAccessCheck"/>
|
||||
|
||||
...
|
||||
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
The `method` attribute identifies a method (`doAccessCheck`) that provides the body of
|
||||
the advice. This method must be defined for the bean referenced by the aspect element
|
||||
that contains the advice. Before a data access operation is performed (a method execution
|
||||
join point matched by the pointcut expression), the `doAccessCheck` method on the aspect
|
||||
bean is invoked.
|
||||
|
||||
[[aop-schema-advice-after-returning]]
|
||||
=== After Returning Advice
|
||||
|
||||
After returning advice runs when a matched method execution completes normally. It is
|
||||
declared inside an `<aop:aspect>` in the same way as before advice. The following example
|
||||
shows how to declare it:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="afterReturningExample" ref="aBean">
|
||||
|
||||
<aop:after-returning
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))"
|
||||
method="doAccessCheck"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
As in the @AspectJ style, you can get the return value within the advice body.
|
||||
To do so, use the `returning` attribute to specify the name of the parameter to which
|
||||
the return value should be passed, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="afterReturningExample" ref="aBean">
|
||||
|
||||
<aop:after-returning
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))"
|
||||
returning="retVal"
|
||||
method="doAccessCheck"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
The `doAccessCheck` method must declare a parameter named `retVal`. The type of this
|
||||
parameter constrains matching in the same way as described for `@AfterReturning`. For
|
||||
example, you can declare the method signature as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public void doAccessCheck(Object retVal) {...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun doAccessCheck(retVal: Any) {...
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-schema-advice-after-throwing]]
|
||||
=== After Throwing Advice
|
||||
|
||||
After throwing advice runs when a matched method execution exits by throwing an
|
||||
exception. It is declared inside an `<aop:aspect>` by using the `after-throwing` element,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="afterThrowingExample" ref="aBean">
|
||||
|
||||
<aop:after-throwing
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))"
|
||||
method="doRecoveryActions"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
As in the @AspectJ style, you can get the thrown exception within the advice body.
|
||||
To do so, use the `throwing` attribute to specify the name of the parameter to
|
||||
which the exception should be passed as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="afterThrowingExample" ref="aBean">
|
||||
|
||||
<aop:after-throwing
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))"
|
||||
throwing="dataAccessEx"
|
||||
method="doRecoveryActions"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
The `doRecoveryActions` method must declare a parameter named `dataAccessEx`.
|
||||
The type of this parameter constrains matching in the same way as described for
|
||||
`@AfterThrowing`. For example, the method signature may be declared as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public void doRecoveryActions(DataAccessException dataAccessEx) {...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-schema-advice-after-finally]]
|
||||
=== After (Finally) Advice
|
||||
|
||||
After (finally) advice runs no matter how a matched method execution exits.
|
||||
You can declare it by using the `after` element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="afterFinallyExample" ref="aBean">
|
||||
|
||||
<aop:after
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))"
|
||||
method="doReleaseLock"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
[[aop-schema-advice-around]]
|
||||
=== Around Advice
|
||||
|
||||
The last kind of advice is _around_ advice. Around advice runs "around" a matched
|
||||
method's execution. It has the opportunity to do work both before and after the method
|
||||
runs and to determine when, how, and even if the method actually gets to run at all.
|
||||
Around advice is often used if you need to share state before and after a method
|
||||
execution in a thread-safe manner – for example, starting and stopping a timer.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Always use the least powerful form of advice that meets your requirements.
|
||||
|
||||
For example, do not use _around_ advice if _before_ advice is sufficient for your needs.
|
||||
====
|
||||
|
||||
You can declare around advice by using the `aop:around` element. The advice method should
|
||||
declare `Object` as its return type, and the first parameter of the method must be of
|
||||
type `ProceedingJoinPoint`. Within the body of the advice method, you must invoke
|
||||
`proceed()` on the `ProceedingJoinPoint` in order for the underlying method to run.
|
||||
Invoking `proceed()` without arguments will result in the caller's original arguments
|
||||
being supplied to the underlying method when it is invoked. For advanced use cases, there
|
||||
is an overloaded variant of the `proceed()` method which accepts an array of arguments
|
||||
(`Object[]`). The values in the array will be used as the arguments to the underlying
|
||||
method when it is invoked. See xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-around-advice[Around Advice] for notes on calling
|
||||
`proceed` with an `Object[]`.
|
||||
|
||||
The following example shows how to declare around advice in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="aroundExample" ref="aBean">
|
||||
|
||||
<aop:around
|
||||
pointcut="execution(* com.xyz.service.*.*(..))"
|
||||
method="doBasicProfiling"/>
|
||||
|
||||
...
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
The implementation of the `doBasicProfiling` advice can be exactly the same as in the
|
||||
@AspectJ example (minus the annotation, of course), as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// start stopwatch
|
||||
Object retVal = pjp.proceed();
|
||||
// stop stopwatch
|
||||
return retVal;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
|
||||
// start stopwatch
|
||||
val retVal = pjp.proceed()
|
||||
// stop stopwatch
|
||||
return pjp.proceed()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[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
|
||||
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]).
|
||||
The following example shows how to specify an argument name in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:before
|
||||
pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" <1>
|
||||
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].
|
||||
|
||||
The `arg-names` attribute accepts a comma-delimited list of parameter names.
|
||||
|
||||
The following slightly more involved example of the XSD-based approach shows
|
||||
some around advice used in conjunction with a number of strongly typed parameters:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.service;
|
||||
|
||||
public interface PersonService {
|
||||
|
||||
Person getPerson(String personName, int age);
|
||||
}
|
||||
|
||||
public class DefaultPersonService implements PersonService {
|
||||
|
||||
public Person getPerson(String name, int age) {
|
||||
return new Person(name, age);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.service
|
||||
|
||||
interface PersonService {
|
||||
|
||||
fun getPerson(personName: String, age: Int): Person
|
||||
}
|
||||
|
||||
class DefaultPersonService : PersonService {
|
||||
|
||||
fun getPerson(name: String, age: Int): Person {
|
||||
return Person(name, age)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Next up is the aspect. Notice the fact that the `profile(..)` method accepts a number of
|
||||
strongly-typed parameters, the first of which happens to be the join point used to
|
||||
proceed with the method call. The presence of this parameter is an indication that the
|
||||
`profile(..)` is to be used as `around` advice, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
public class SimpleProfiler {
|
||||
|
||||
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
|
||||
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
|
||||
try {
|
||||
clock.start(call.toShortString());
|
||||
return call.proceed();
|
||||
} finally {
|
||||
clock.stop();
|
||||
System.out.println(clock.prettyPrint());
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.springframework.util.StopWatch
|
||||
|
||||
class SimpleProfiler {
|
||||
|
||||
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any? {
|
||||
val clock = StopWatch("Profiling for '$name' and '$age'")
|
||||
try {
|
||||
clock.start(call.toShortString())
|
||||
return call.proceed()
|
||||
} finally {
|
||||
clock.stop()
|
||||
println(clock.prettyPrint())
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Finally, the following example XML configuration effects the execution of the
|
||||
preceding advice for a particular join point:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
|
||||
<bean id="personService" class="com.xyz.service.DefaultPersonService"/>
|
||||
|
||||
<!-- this is the actual advice itself -->
|
||||
<bean id="profiler" class="com.xyz.SimpleProfiler"/>
|
||||
|
||||
<aop:config>
|
||||
<aop:aspect ref="profiler">
|
||||
|
||||
<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
|
||||
expression="execution(* com.xyz.service.PersonService.getPerson(String,int))
|
||||
and args(name, age)"/>
|
||||
|
||||
<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
|
||||
method="profile"/>
|
||||
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
Consider the following driver script:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public class Boot {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
||||
PersonService person = ctx.getBean(PersonService.class);
|
||||
person.getPerson("Pengo", 12);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun main() {
|
||||
val ctx = ClassPathXmlApplicationContext("beans.xml")
|
||||
val person = ctx.getBean(PersonService.class)
|
||||
person.getPerson("Pengo", 12)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
With such a `Boot` class, we would get output similar to the following on standard output:
|
||||
|
||||
[literal,subs="verbatim"]
|
||||
----
|
||||
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
|
||||
-----------------------------------------
|
||||
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.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
In contrast to the precedence rules for advice methods defined in the same `@Aspect`
|
||||
class, when two pieces of advice defined in the same `<aop:aspect>` element both need to
|
||||
run at the same join point, the precedence is determined by the order in which the advice
|
||||
elements are declared within the enclosing `<aop:aspect>` element, from highest to lowest
|
||||
precedence.
|
||||
|
||||
For example, given an `around` advice and a `before` advice defined in the same
|
||||
`<aop:aspect>` element that apply to the same join point, to ensure that the `around`
|
||||
advice has higher precedence than the `before` advice, the `<aop:around>` element must be
|
||||
declared before the `<aop:before>` element.
|
||||
|
||||
As a general rule of thumb, if you find that you have multiple pieces of advice defined
|
||||
in the same `<aop:aspect>` element that apply to the same join point, consider collapsing
|
||||
such advice methods into one advice method per join point in each `<aop:aspect>` element
|
||||
or refactor the pieces of advice into separate `<aop:aspect>` elements that you can order
|
||||
at the aspect level.
|
||||
====
|
||||
|
||||
|
||||
[[aop-schema-introductions]]
|
||||
== Introductions
|
||||
|
||||
Introductions (known as inter-type declarations in AspectJ) let an aspect declare
|
||||
that advised objects implement a given interface and provide an implementation of
|
||||
that interface on behalf of those objects.
|
||||
|
||||
You can make an introduction by using the `aop:declare-parents` element inside an `aop:aspect`.
|
||||
You can use the `aop:declare-parents` element 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. (In order to expose statistics
|
||||
through JMX for example.)
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
|
||||
|
||||
<aop:declare-parents
|
||||
types-matching="com.xyz.service.*+"
|
||||
implement-interface="com.xyz.service.tracking.UsageTracked"
|
||||
default-impl="com.xyz.service.tracking.DefaultUsageTracked"/>
|
||||
|
||||
<aop:before
|
||||
pointcut="execution(* com.xyz..service.*.*(..))
|
||||
and this(usageTracked)"
|
||||
method="recordUsage"/>
|
||||
|
||||
</aop:aspect>
|
||||
----
|
||||
|
||||
The class that backs the `usageTracking` bean would then contain the following method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public void recordUsage(UsageTracked usageTracked) {
|
||||
usageTracked.incrementUseCount();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
fun recordUsage(usageTracked: UsageTracked) {
|
||||
usageTracked.incrementUseCount()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The interface to be implemented is determined by the `implement-interface` attribute. The
|
||||
value of the `types-matching` attribute is an AspectJ type pattern. Any bean of a
|
||||
matching type implements the `UsageTracked` interface. Note that, in the before
|
||||
advice of the preceding example, service beans can be directly used as implementations of
|
||||
the `UsageTracked` interface. To access a bean programmatically, you could write the
|
||||
following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[aop-schema-instantiation-models]]
|
||||
== Aspect Instantiation Models
|
||||
|
||||
The only supported instantiation model for schema-defined aspects is the singleton
|
||||
model. Other instantiation models may be supported in future releases.
|
||||
|
||||
|
||||
[[aop-schema-advisors]]
|
||||
== Advisors
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
namespace support in Spring. The following example shows an advisor:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
|
||||
<aop:pointcut id="businessService"
|
||||
expression="execution(* com.xyz.service.*.*(..))"/>
|
||||
|
||||
<aop:advisor
|
||||
pointcut-ref="businessService"
|
||||
advice-ref="tx-advice" />
|
||||
|
||||
</aop:config>
|
||||
|
||||
<tx:advice id="tx-advice">
|
||||
<tx:attributes>
|
||||
<tx:method name="*" propagation="REQUIRED"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
|
||||
As well as the `pointcut-ref` attribute used in the preceding example, you can also use the
|
||||
`pointcut` attribute to define a pointcut expression inline.
|
||||
|
||||
To define the precedence of an advisor so that the advice can participate in ordering,
|
||||
use the `order` attribute to define the `Ordered` value of the advisor.
|
||||
|
||||
|
||||
[[aop-schema-example]]
|
||||
== An AOP Schema Example
|
||||
|
||||
This section shows how the concurrent locking failure retry example from
|
||||
xref:core/aop/ataspectj/example.adoc[An AOP Example] looks when rewritten with the schema support.
|
||||
|
||||
The execution of business services can sometimes fail due to concurrency issues (for
|
||||
example, a deadlock loser). If the operation is retried, it is likely to succeed
|
||||
on the next try. For business services where it is appropriate to retry in such
|
||||
conditions (idempotent operations that do not need to go back to the user for conflict
|
||||
resolution), we want to transparently retry the operation to avoid the client seeing a
|
||||
`PessimisticLockingFailureException`. This is a requirement that clearly cuts across
|
||||
multiple services in the service layer and, hence, is ideal for implementing through an
|
||||
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
|
||||
(which is a regular Java class that uses the schema support):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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
|
||||
retry). The `maxRetries` and `order` properties are both configured by Spring. The
|
||||
main action happens in the `doConcurrentOperation` around advice method. We try to
|
||||
proceed. If we fail with a `PessimisticLockingFailureException`, we try again,
|
||||
unless we have exhausted all of our retry attempts.
|
||||
|
||||
NOTE: This class is identical to the one used in the @AspectJ example, but with the
|
||||
annotations removed.
|
||||
|
||||
The corresponding Spring configuration is as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
|
||||
<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
|
||||
|
||||
<aop:pointcut id="idempotentOperation"
|
||||
expression="execution(* com.xyz.service.*.*(..))"/>
|
||||
|
||||
<aop:around
|
||||
pointcut-ref="idempotentOperation"
|
||||
method="doConcurrentOperation"/>
|
||||
|
||||
</aop:aspect>
|
||||
|
||||
</aop:config>
|
||||
|
||||
<bean id="concurrentOperationExecutor"
|
||||
class="com.xyz.service.impl.ConcurrentOperationExecutor">
|
||||
<property name="maxRetries" value="3"/>
|
||||
<property name="order" value="100"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Notice that, for the time being, we assume that all business services are idempotent. If
|
||||
this is not the case, we can refine the aspect so that it retries only genuinely
|
||||
idempotent operations, by introducing an `Idempotent` annotation and using the annotation
|
||||
to annotate the implementation of service operations, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
annotation class Idempotent
|
||||
----
|
||||
======
|
||||
|
||||
The
|
||||
change to the aspect to retry only idempotent operations involves refining the
|
||||
pointcut expression so that only `@Idempotent` operations match, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:pointcut id="idempotentOperation"
|
||||
expression="execution(* com.xyz.service.*.*(..)) and
|
||||
@annotation(com.xyz.service.Idempotent)"/>
|
||||
----
|
||||
@@ -1,873 +0,0 @@
|
||||
[[aop-using-aspectj]]
|
||||
= Using AspectJ with Spring Applications
|
||||
|
||||
Everything we have covered so far in this chapter is pure Spring AOP. In this section,
|
||||
we look at how you can use the AspectJ compiler or weaver instead of or in
|
||||
addition to Spring AOP if your needs go beyond the facilities offered by Spring AOP
|
||||
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.
|
||||
|
||||
|
||||
[[aop-atconfigurable]]
|
||||
== Using AspectJ to Dependency Inject Domain Objects with Spring
|
||||
|
||||
The Spring container instantiates and configures beans defined in your application
|
||||
context. It is also possible to ask a bean factory to configure a pre-existing
|
||||
object, given the name of a bean definition that contains the configuration to be applied.
|
||||
`spring-aspects.jar` contains an annotation-driven aspect that exploits this
|
||||
capability to allow dependency injection of any object. The support is intended to
|
||||
be used for objects created outside of the control of any container. Domain objects
|
||||
often fall into this category because they are often created programmatically with the
|
||||
`new` operator or by an ORM tool as a result of a database query.
|
||||
|
||||
The `@Configurable` annotation marks a class as being eligible for Spring-driven
|
||||
configuration. In the simplest case, you can use purely it as a marker annotation, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
|
||||
@Configurable
|
||||
public class Account {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain
|
||||
|
||||
import org.springframework.beans.factory.annotation.Configurable
|
||||
|
||||
@Configurable
|
||||
class Account {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean class="com.xyz.domain.Account" scope="prototype">
|
||||
<property name="fundsTransferService" ref="fundsTransferService"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
If you want to explicitly specify the name of the prototype bean definition to use, you
|
||||
can do so directly in the annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
|
||||
@Configurable("account")
|
||||
public class Account {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain
|
||||
|
||||
import org.springframework.beans.factory.annotation.Configurable
|
||||
|
||||
@Configurable("account")
|
||||
class Account {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Spring now looks for a bean definition named `account` and uses that as the
|
||||
definition to configure new `Account` instances.
|
||||
|
||||
You can also use autowiring to avoid having to specify a dedicated bean definition at
|
||||
all. To have Spring apply autowiring, use the `autowire` property of the `@Configurable`
|
||||
annotation. You can specify either `@Configurable(autowire=Autowire.BY_TYPE)` or
|
||||
`@Configurable(autowire=Autowire.BY_NAME)` for autowiring by type or by name,
|
||||
respectively. As an alternative, it is preferable to specify explicit, annotation-driven
|
||||
dependency injection for your `@Configurable` beans through `@Autowired` or `@Inject`
|
||||
at the field or method level (see xref:core/beans/annotation-config.adoc[Annotation-based Container Configuration] for further details).
|
||||
|
||||
Finally, you can enable Spring dependency checking for the object references in the newly
|
||||
created and configured object by using the `dependencyCheck` attribute (for example,
|
||||
`@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)`). If this attribute is
|
||||
set to `true`, Spring validates after configuration that all properties (which
|
||||
are not primitives or collections) have been set.
|
||||
|
||||
Note that using the annotation on its own does nothing. It is the
|
||||
`AnnotationBeanConfigurerAspect` in `spring-aspects.jar` that acts on the presence of
|
||||
the annotation. In essence, the aspect says, "after returning from the initialization of
|
||||
a new object of a type annotated with `@Configurable`, configure the newly created object
|
||||
using Spring in accordance with the properties of the annotation". In this context,
|
||||
"initialization" refers to newly instantiated objects (for example, objects instantiated
|
||||
with the `new` operator) as well as to `Serializable` objects that are undergoing
|
||||
deserialization (for example, through
|
||||
{java-api}/java.base/java/io/Serializable.html[readResolve()]).
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
One of the key phrases in the above paragraph is "in essence". For most cases, the
|
||||
exact semantics of "after returning from the initialization of a new object" are
|
||||
fine. In this context, "after initialization" means that the dependencies are
|
||||
injected after the object has been constructed. This means that the dependencies
|
||||
are not available for use in the constructor bodies of the class. If you want the
|
||||
dependencies to be injected before the constructor bodies run and thus be
|
||||
available for use in the body of the constructors, you need to define this on the
|
||||
`@Configurable` declaration, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Configurable(preConstruction = true)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Configurable(preConstruction = true)
|
||||
----
|
||||
======
|
||||
|
||||
You can find more information about the language semantics of the various pointcut
|
||||
types in AspectJ
|
||||
{aspectj-docs-progguide}/semantics-joinPoints.html[in this appendix] of the
|
||||
{aspectj-docs-progguide}/index.html[AspectJ Programming Guide].
|
||||
=====
|
||||
|
||||
For this to work, the annotated types must be woven with the AspectJ weaver. You can
|
||||
either use a build-time Ant or Maven task to do this (see, for example, the
|
||||
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
|
||||
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
|
||||
`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:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
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
|
||||
object taking place. An example might be a bean in the Spring configuration that creates
|
||||
domain objects when it is initialized by Spring. In this case, you can use the
|
||||
`depends-on` bean attribute to manually specify that the bean depends on the
|
||||
configuration aspect. The following example shows how to use the `depends-on` attribute:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="myService"
|
||||
class="com.xyz.service.MyService"
|
||||
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
</bean>
|
||||
----
|
||||
|
||||
NOTE: Do not activate `@Configurable` processing through the bean configurer aspect unless you
|
||||
really mean to rely on its semantics at runtime. In particular, make sure that you do
|
||||
not use `@Configurable` on bean classes that are registered as regular Spring beans
|
||||
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
|
||||
|
||||
One of the goals of the `@Configurable` support is to enable independent unit testing
|
||||
of domain objects without the difficulties associated with hard-coded lookups.
|
||||
If `@Configurable` types have not been woven by AspectJ, the annotation has no affect
|
||||
during unit testing. You can set mock or stub property references in the object under
|
||||
test and proceed as normal. If `@Configurable` types have been woven by AspectJ,
|
||||
you can still unit test outside of the container as normal, but you see a warning
|
||||
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
|
||||
|
||||
The `AnnotationBeanConfigurerAspect` that is used to implement the `@Configurable` support
|
||||
is an AspectJ singleton aspect. The scope of a singleton aspect is the same as the scope
|
||||
of `static` members: There is one aspect instance per `ClassLoader` that defines the type.
|
||||
This means that, if you define multiple application contexts within the same `ClassLoader`
|
||||
hierarchy, you need to consider where to define the `@EnableSpringConfigured` bean and
|
||||
where to place `spring-aspects.jar` on the classpath.
|
||||
|
||||
Consider a typical Spring web application configuration that has a shared parent application
|
||||
context that defines common business services, everything needed to support those services,
|
||||
and one child application context for each servlet (which contains definitions particular
|
||||
to that servlet). All of these contexts co-exist within the same `ClassLoader` hierarchy,
|
||||
and so the `AnnotationBeanConfigurerAspect` can hold a reference to only one of them.
|
||||
In this case, we recommend defining the `@EnableSpringConfigured` bean in the shared
|
||||
(parent) application context. This defines the services that you are likely to want to
|
||||
inject into domain objects. A consequence is that you cannot configure domain objects
|
||||
with references to beans defined in the child (servlet-specific) contexts by using the
|
||||
@Configurable mechanism (which is probably not something you want to do anyway).
|
||||
|
||||
When deploying multiple web applications within the same container, ensure that each
|
||||
web application loads the types in `spring-aspects.jar` by using its own `ClassLoader`
|
||||
(for example, by placing `spring-aspects.jar` in `WEB-INF/lib`). If `spring-aspects.jar`
|
||||
is added only to the container-wide classpath (and hence loaded by the shared parent
|
||||
`ClassLoader`), all web applications share the same aspect instance (which is probably
|
||||
not what you want).
|
||||
|
||||
|
||||
[[aop-ajlib-other]]
|
||||
== Other Spring aspects for AspectJ
|
||||
|
||||
In addition to the `@Configurable` aspect, `spring-aspects.jar` contains an AspectJ
|
||||
aspect that you can use to drive Spring's transaction management for types and methods
|
||||
annotated with the `@Transactional` annotation. This is primarily intended for users who
|
||||
want to use the Spring Framework's transaction support outside of the Spring container.
|
||||
|
||||
The aspect that interprets `@Transactional` annotations is the
|
||||
`AnnotationTransactionAspect`. When you use this aspect, you must annotate the
|
||||
implementation class (or methods within that class or both), not the interface (if
|
||||
any) that the class implements. AspectJ follows Java's rule that annotations on
|
||||
interfaces are not inherited.
|
||||
|
||||
A `@Transactional` annotation on a class specifies the default transaction semantics for
|
||||
the execution of any public operation in the class.
|
||||
|
||||
A `@Transactional` annotation on a method within the class overrides the default
|
||||
transaction semantics given by the class annotation (if present). Methods of any
|
||||
visibility may be annotated, including private methods. Annotating non-public methods
|
||||
directly is the only way to get transaction demarcation for the execution of such methods.
|
||||
|
||||
TIP: Since Spring Framework 4.2, `spring-aspects` provides a similar aspect that offers the
|
||||
exact same features for the standard `jakarta.transaction.Transactional` annotation. Check
|
||||
`JtaAnnotationTransactionAspect` for more details.
|
||||
|
||||
For AspectJ programmers who want to use the Spring configuration and transaction
|
||||
management support but do not want to (or cannot) use annotations, `spring-aspects.jar`
|
||||
also contains `abstract` aspects you can extend to provide your own pointcut
|
||||
definitions. See the sources for the `AbstractBeanConfigurerAspect` and
|
||||
`AbstractTransactionAspect` aspects for more information. As an example, the following
|
||||
excerpt shows how you could write an aspect to configure all instances of objects
|
||||
defined in the domain model by using prototype bean definitions that match the
|
||||
fully qualified class names:
|
||||
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
|
||||
|
||||
public DomainObjectConfiguration() {
|
||||
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
|
||||
}
|
||||
|
||||
// the creation of a new bean (any object in the domain model)
|
||||
protected pointcut beanCreation(Object beanInstance) :
|
||||
initialization(new(..)) &&
|
||||
CommonPointcuts.inDomainModel() &&
|
||||
this(beanInstance);
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[aop-aj-configure]]
|
||||
== Configuring AspectJ Aspects by Using Spring IoC
|
||||
|
||||
When you use AspectJ aspects with Spring applications, it is natural to both want and
|
||||
expect to be able to configure such aspects with Spring. The AspectJ runtime itself is
|
||||
responsible for aspect creation, and the means of configuring the AspectJ-created
|
||||
aspects through Spring depends on the AspectJ instantiation model (the `per-xxx` clause)
|
||||
used by the aspect.
|
||||
|
||||
The majority of AspectJ aspects are singleton aspects. Configuration of these
|
||||
aspects is easy. You can create a bean definition that references the aspect type as
|
||||
normal and include the `factory-method="aspectOf"` bean attribute. This ensures that
|
||||
Spring obtains the aspect instance by asking AspectJ for it rather than trying to create
|
||||
an instance itself. The following example shows how to use the `factory-method="aspectOf"` attribute:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="profiler" class="com.xyz.profiler.Profiler"
|
||||
factory-method="aspectOf"> <1>
|
||||
|
||||
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
|
||||
</bean>
|
||||
----
|
||||
<1> Note the `factory-method="aspectOf"` attribute
|
||||
|
||||
|
||||
Non-singleton aspects are harder to configure. However, it is possible to do so by
|
||||
creating prototype bean definitions and using the `@Configurable` support from
|
||||
`spring-aspects.jar` to configure the aspect instances once they have bean created by
|
||||
the AspectJ runtime.
|
||||
|
||||
If you have some @AspectJ aspects that you want to weave with AspectJ (for example,
|
||||
using load-time weaving for domain model types) and other @AspectJ aspects that you want
|
||||
to use with Spring AOP, and these aspects are all configured in Spring, you
|
||||
need to tell the Spring AOP @AspectJ auto-proxying support which exact subset of the
|
||||
@AspectJ aspects defined in the configuration should be used for auto-proxying. You can
|
||||
do this by using one or more `<include/>` elements inside the `<aop:aspectj-autoproxy/>`
|
||||
declaration. Each `<include/>` element specifies a name pattern, and only beans with
|
||||
names matched by at least one of the patterns are used for Spring AOP auto-proxy
|
||||
configuration. The following example shows how to use `<include/>` elements:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy>
|
||||
<aop:include name="thisBean"/>
|
||||
<aop:include name="thatBean"/>
|
||||
</aop:aspectj-autoproxy>
|
||||
----
|
||||
|
||||
NOTE: Do not be misled by the name of the `<aop:aspectj-autoproxy/>` element. Using it
|
||||
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
|
||||
|
||||
Load-time weaving (LTW) refers to the process of weaving AspectJ aspects into an
|
||||
application's class files as they are being loaded into the Java virtual machine (JVM).
|
||||
The focus of this section is on configuring and using LTW in the specific context of the
|
||||
Spring Framework. This section is not a general introduction to LTW. For full details on
|
||||
the specifics of LTW and configuring LTW with only AspectJ (with Spring not being
|
||||
involved at all), see the
|
||||
{aspectj-docs-devguide}/ltw.html[LTW section of the AspectJ
|
||||
Development Environment Guide].
|
||||
|
||||
The value that the Spring Framework brings to AspectJ LTW is in enabling much
|
||||
finer-grained control over the weaving process. 'Vanilla' AspectJ LTW is effected by using
|
||||
a Java (5+) agent, which is switched on by specifying a VM argument when starting up a
|
||||
JVM. It is, thus, a JVM-wide setting, which may be fine in some situations but is often a
|
||||
little too coarse. Spring-enabled LTW lets you switch on LTW on a
|
||||
per-`ClassLoader` basis, which is more fine-grained and which can make more
|
||||
sense in a 'single-JVM-multiple-application' environment (such as is found in a typical
|
||||
application server environment).
|
||||
|
||||
Further, xref:core/aop/using-aspectj.adoc#aop-aj-ltw-environments[in certain environments], this support enables
|
||||
load-time weaving without making any modifications to the application server's launch
|
||||
script that is needed to add `-javaagent:path/to/aspectjweaver.jar` or (as we describe
|
||||
later in this section) `-javaagent:path/to/spring-instrument.jar`. Developers configure
|
||||
the application context to enable load-time weaving instead of relying on administrators
|
||||
who typically are in charge of the deployment configuration, such as the launch script.
|
||||
|
||||
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].
|
||||
|
||||
[[aop-aj-ltw-first-example]]
|
||||
=== A First Example
|
||||
|
||||
Assume that you are an application developer who has been tasked with diagnosing
|
||||
the cause of some performance problems in a system. Rather than break out a
|
||||
profiling tool, we are going to switch on a simple profiling aspect that lets us
|
||||
quickly get some performance metrics. We can then apply a finer-grained profiling
|
||||
tool to that specific area immediately afterwards.
|
||||
|
||||
NOTE: The example presented here uses XML configuration. You can also configure and
|
||||
use @AspectJ with xref:core/beans/java.adoc[Java configuration]. Specifically, you can use the
|
||||
`@EnableLoadTimeWeaving` annotation as an alternative to `<context:load-time-weaver/>`
|
||||
(see xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[below] for details).
|
||||
|
||||
The following example shows the profiling aspect, which is not fancy.
|
||||
It is a time-based profiler that uses the @AspectJ-style of aspect declaration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
@Aspect
|
||||
public class ProfilingAspect {
|
||||
|
||||
@Around("methodsToBeProfiled()")
|
||||
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
|
||||
StopWatch sw = new StopWatch(getClass().getSimpleName());
|
||||
try {
|
||||
sw.start(pjp.getSignature().getName());
|
||||
return pjp.proceed();
|
||||
} finally {
|
||||
sw.stop();
|
||||
System.out.println(sw.prettyPrint());
|
||||
}
|
||||
}
|
||||
|
||||
@Pointcut("execution(public * com.xyz..*.*(..))")
|
||||
public void methodsToBeProfiled(){}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.annotation.Pointcut
|
||||
import org.springframework.util.StopWatch
|
||||
import org.springframework.core.annotation.Order
|
||||
|
||||
@Aspect
|
||||
class ProfilingAspect {
|
||||
|
||||
@Around("methodsToBeProfiled()")
|
||||
fun profile(pjp: ProceedingJoinPoint): Any? {
|
||||
val sw = StopWatch(javaClass.simpleName)
|
||||
try {
|
||||
sw.start(pjp.getSignature().getName())
|
||||
return pjp.proceed()
|
||||
} finally {
|
||||
sw.stop()
|
||||
println(sw.prettyPrint())
|
||||
}
|
||||
}
|
||||
|
||||
@Pointcut("execution(public * com.xyz..*.*(..))")
|
||||
fun methodsToBeProfiled() {
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
We also need to create an `META-INF/aop.xml` file, to inform the AspectJ weaver that
|
||||
we want to weave our `ProfilingAspect` into our classes. This file convention, namely
|
||||
the presence of a file (or files) on the Java classpath called `META-INF/aop.xml` is
|
||||
standard AspectJ. The following example shows the `aop.xml` file:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
|
||||
<aspectj>
|
||||
|
||||
<weaver>
|
||||
<!-- only weave classes in our application-specific packages and sub-packages -->
|
||||
<include within="com.xyz..*"/>
|
||||
</weaver>
|
||||
|
||||
<aspects>
|
||||
<!-- weave in just this aspect -->
|
||||
<aspect name="com.xyz.ProfilingAspect"/>
|
||||
</aspects>
|
||||
|
||||
</aspectj>
|
||||
----
|
||||
|
||||
NOTE: It is recommended to only weave specific classes (typically those in the
|
||||
application packages, as shown in the `aop.xml` example above) in order
|
||||
to avoid side effects such as AspectJ dump files and warnings.
|
||||
This is also a best practice from an efficiency perspective.
|
||||
|
||||
Now we can move on to the Spring-specific portion of the configuration. We need
|
||||
to configure a `LoadTimeWeaver` (explained later). This load-time weaver is the
|
||||
essential component responsible for weaving the aspect configuration in one or
|
||||
more `META-INF/aop.xml` files into the classes in your application. The good
|
||||
thing is that it does not require a lot of configuration (there are some more
|
||||
options that you can specify, but these are detailed later), as can be seen in
|
||||
the following example:
|
||||
|
||||
[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">
|
||||
|
||||
<!-- a service object; we will be profiling its methods -->
|
||||
<bean id="entitlementCalculationService"
|
||||
class="com.xyz.StubEntitlementCalculationService"/>
|
||||
|
||||
<!-- this switches on the load-time weaving -->
|
||||
<context:load-time-weaver/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
Now that all the required artifacts (the aspect, the `META-INF/aop.xml`
|
||||
file, and the Spring configuration) are in place, we can create the following
|
||||
driver class with a `main(..)` method to demonstrate the LTW in action:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
// imports
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
||||
|
||||
EntitlementCalculationService service =
|
||||
ctx.getBean(EntitlementCalculationService.class);
|
||||
|
||||
// the profiling aspect is 'woven' around this method execution
|
||||
service.calculateEntitlement();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
// imports
|
||||
|
||||
fun main() {
|
||||
val ctx = ClassPathXmlApplicationContext("beans.xml")
|
||||
|
||||
val service = ctx.getBean(EntitlementCalculationService.class)
|
||||
|
||||
// the profiling aspect is 'woven' around this method execution
|
||||
service.calculateEntitlement()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
We have one last thing to do. The introduction to this section did say that one could
|
||||
switch on LTW selectively on a per-`ClassLoader` basis with Spring, and this is true.
|
||||
However, for this example, we use a Java agent (supplied with Spring) to switch on LTW.
|
||||
We use the following command to run the `Main` class shown earlier:
|
||||
|
||||
[literal,subs="verbatim"]
|
||||
----
|
||||
java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main
|
||||
----
|
||||
|
||||
The `-javaagent` is a flag for specifying and enabling
|
||||
{java-api}/java.instrument/java/lang/instrument/package-summary.html[agents
|
||||
to instrument programs that run on the JVM]. The Spring Framework ships with such an
|
||||
agent, the `InstrumentationSavingAgent`, which is packaged in the
|
||||
`spring-instrument.jar` that was supplied as the value of the `-javaagent` argument in
|
||||
the preceding example.
|
||||
|
||||
The output from the execution of the `Main` program looks something like the next example.
|
||||
(I have introduced a `Thread.sleep(..)` statement into the `calculateEntitlement()`
|
||||
implementation so that the profiler actually captures something other than 0
|
||||
milliseconds (the `01234` milliseconds is not an overhead introduced by the AOP).
|
||||
The following listing shows the output we got when we ran our profiler:
|
||||
|
||||
[literal,subs="verbatim"]
|
||||
----
|
||||
Calculating entitlement
|
||||
|
||||
StopWatch 'ProfilingAspect': running time (millis) = 1234
|
||||
------ ----- ----------------------------
|
||||
ms % Task name
|
||||
------ ----- ----------------------------
|
||||
01234 100% calculateEntitlement
|
||||
----
|
||||
|
||||
Since this LTW is effected by using full-blown AspectJ, we are not limited only to advising
|
||||
Spring beans. The following slight variation on the `Main` program yields the same
|
||||
result:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
// imports
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new ClassPathXmlApplicationContext("beans.xml");
|
||||
|
||||
EntitlementCalculationService service =
|
||||
new StubEntitlementCalculationService();
|
||||
|
||||
// the profiling aspect will be 'woven' around this method execution
|
||||
service.calculateEntitlement();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
// imports
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
ClassPathXmlApplicationContext("beans.xml")
|
||||
|
||||
val service = StubEntitlementCalculationService()
|
||||
|
||||
// the profiling aspect will be 'woven' around this method execution
|
||||
service.calculateEntitlement()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Notice how, in the preceding program, we bootstrap the Spring container and
|
||||
then create a new instance of the `StubEntitlementCalculationService` totally outside
|
||||
the context of Spring. The profiling advice still gets woven in.
|
||||
|
||||
Admittedly, the example is simplistic. However, the basics of the LTW support in Spring
|
||||
have all been introduced in the earlier example, and the rest of this section explains
|
||||
the "why" behind each bit of configuration and usage in detail.
|
||||
|
||||
NOTE: The `ProfilingAspect` used in this example may be basic, but it is quite useful. It is a
|
||||
nice example of a development-time aspect that developers can use during development
|
||||
and then easily exclude from builds of the application being deployed
|
||||
into UAT or production.
|
||||
|
||||
[[aop-aj-ltw-the-aspects]]
|
||||
=== Aspects
|
||||
|
||||
The aspects that you use in LTW have to be AspectJ aspects. You can write them in
|
||||
either the AspectJ language itself, or you can write your aspects in the @AspectJ-style.
|
||||
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`
|
||||
|
||||
The AspectJ LTW infrastructure is configured by using one or more `META-INF/aop.xml`
|
||||
files that are on the Java classpath (either directly or, more typically, in jar files).
|
||||
For example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
|
||||
<aspectj>
|
||||
|
||||
<weaver>
|
||||
<!-- only weave classes in our application-specific packages and sub-packages -->
|
||||
<include within="com.xyz..*"/>
|
||||
</weaver>
|
||||
|
||||
</aspectj>
|
||||
----
|
||||
|
||||
NOTE: It is recommended to only weave specific classes (typically those in the
|
||||
application packages, as shown in the `aop.xml` example above) in order
|
||||
to avoid side effects such as AspectJ dump files and warnings.
|
||||
This is also a best practice from an efficiency perspective.
|
||||
|
||||
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)
|
||||
|
||||
At minimum, you need the following libraries to use the Spring Framework's support
|
||||
for AspectJ LTW:
|
||||
|
||||
* `spring-aop.jar`
|
||||
* `aspectjweaver.jar`
|
||||
|
||||
If you use the xref:core/aop/using-aspectj.adoc#aop-aj-ltw-environments-generic[Spring-provided agent to enable instrumentation]
|
||||
, you also need:
|
||||
|
||||
* `spring-instrument.jar`
|
||||
|
||||
[[aop-aj-ltw-spring]]
|
||||
=== Spring Configuration
|
||||
|
||||
The key component in Spring's LTW support is the `LoadTimeWeaver` interface (in the
|
||||
`org.springframework.instrument.classloading` package), and the numerous implementations
|
||||
of it that ship with the Spring distribution. A `LoadTimeWeaver` is responsible for
|
||||
adding one or more `java.lang.instrument.ClassFileTransformers` to a `ClassLoader` at
|
||||
runtime, which opens the door to all manner of interesting applications, one of which
|
||||
happens to be the LTW of aspects.
|
||||
|
||||
TIP: If you are unfamiliar with the idea of runtime class file transformation, see the
|
||||
javadoc API documentation for the `java.lang.instrument` package before continuing.
|
||||
While that documentation is not comprehensive, at least you can see the key interfaces
|
||||
and classes (for reference as you read through this section).
|
||||
|
||||
Configuring a `LoadTimeWeaver` for a particular `ApplicationContext` can be as easy as
|
||||
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:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding configuration automatically defines and registers a number of LTW-specific
|
||||
infrastructure beans, such as a `LoadTimeWeaver` and an `AspectJWeavingEnabler`, for you.
|
||||
The default `LoadTimeWeaver` is the `DefaultContextLoadTimeWeaver` class, which attempts
|
||||
to decorate an automatically detected `LoadTimeWeaver`. The exact type of `LoadTimeWeaver`
|
||||
that is "automatically detected" is dependent upon your runtime environment.
|
||||
The following table summarizes various `LoadTimeWeaver` implementations:
|
||||
|
||||
[[aop-aj-ltw-spring-env-impls]]
|
||||
.DefaultContextLoadTimeWeaver LoadTimeWeavers
|
||||
|===
|
||||
| Runtime Environment| `LoadTimeWeaver` implementation
|
||||
|
||||
| Running in https://tomcat.apache.org/[Apache Tomcat]
|
||||
| `TomcatLoadTimeWeaver`
|
||||
|
||||
| Running in https://eclipse-ee4j.github.io/glassfish/[GlassFish] (limited to EAR deployments)
|
||||
| `GlassFishLoadTimeWeaver`
|
||||
|
||||
| Running in Red Hat's https://www.jboss.org/jbossas/[JBoss AS] or https://www.wildfly.org/[WildFly]
|
||||
| `JBossLoadTimeWeaver`
|
||||
|
||||
| JVM started with Spring `InstrumentationSavingAgent`
|
||||
(`java -javaagent:path/to/spring-instrument.jar`)
|
||||
| `InstrumentationLoadTimeWeaver`
|
||||
|
||||
| Fallback, expecting the underlying ClassLoader to follow common conventions
|
||||
(namely `addTransformer` and optionally a `getThrowawayClassLoader` method)
|
||||
| `ReflectiveLoadTimeWeaver`
|
||||
|===
|
||||
|
||||
Note that the table lists only the `LoadTimeWeavers` that are autodetected when you
|
||||
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).
|
||||
The following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
include-code::./CustomWeaverConfiguration[tag=snippet,indent=0]
|
||||
|
||||
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`.
|
||||
Remember that the `LoadTimeWeaver` exists only as a mechanism for Spring's LTW
|
||||
infrastructure to add one or more `ClassFileTransformers`. The actual
|
||||
`ClassFileTransformer` that does the LTW is the `ClassPreProcessorAgentAdapter` (from
|
||||
the `org.aspectj.weaver.loadtime` package) class. See the class-level javadoc of the
|
||||
`ClassPreProcessorAgentAdapter` class for further details, because the specifics of how
|
||||
the weaving is actually effected is beyond the scope of this document.
|
||||
|
||||
There is one final attribute of the configuration left to discuss: the `aspectjWeaving`
|
||||
attribute (or `aspectj-weaving` if you use XML). This attribute controls whether LTW
|
||||
is enabled or not. It accepts one of three possible values, with the default value being
|
||||
`autodetect` if the attribute is not present. The following table summarizes the three
|
||||
possible values:
|
||||
|
||||
[[aop-aj-ltw-ltw-tag-attrs]]
|
||||
.AspectJ weaving attribute values
|
||||
|===
|
||||
| Annotation Value| XML Value| Explanation
|
||||
|
||||
| `ENABLED`
|
||||
| `on`
|
||||
| AspectJ weaving is on, and aspects are woven at load-time as appropriate.
|
||||
|
||||
| `DISABLED`
|
||||
| `off`
|
||||
| LTW is off. No aspect is woven at load-time.
|
||||
|
||||
| `AUTODETECT`
|
||||
| `autodetect`
|
||||
| If the Spring LTW infrastructure can find at least one `META-INF/aop.xml` file,
|
||||
then AspectJ weaving is on. Otherwise, it is off. This is the default value.
|
||||
|===
|
||||
|
||||
[[aop-aj-ltw-environments]]
|
||||
=== Environment-specific Configuration
|
||||
|
||||
This last section contains any additional settings and configuration that you need
|
||||
when you use Spring's LTW support in environments such as application servers and web
|
||||
containers.
|
||||
|
||||
[[aop-aj-ltw-environments-tomcat-jboss-etc]]
|
||||
==== Tomcat, JBoss, WildFly
|
||||
|
||||
Tomcat and JBoss/WildFly provide a general app `ClassLoader` that is capable of local
|
||||
instrumentation. Spring's native LTW may leverage those ClassLoader implementations
|
||||
to provide AspectJ weaving.
|
||||
You can simply enable load-time weaving, as xref:core/aop/using-aspectj.adoc[described earlier].
|
||||
Specifically, you do not need to modify the JVM launch script to add
|
||||
`-javaagent:path/to/spring-instrument.jar`.
|
||||
|
||||
Note that on JBoss, you may need to disable the app server scanning to prevent it from
|
||||
loading the classes before the application actually starts. A quick workaround is to add
|
||||
to your artifact a file named `WEB-INF/jboss-scanning.xml` with the following content:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<scanning xmlns="urn:jboss:scanning:1.0"/>
|
||||
----
|
||||
|
||||
[[aop-aj-ltw-environments-generic]]
|
||||
==== Generic Java Applications
|
||||
|
||||
When class instrumentation is required in environments that are not supported by
|
||||
specific `LoadTimeWeaver` implementations, a JVM agent is the general solution.
|
||||
For such cases, Spring provides `InstrumentationLoadTimeWeaver` which requires a
|
||||
Spring-specific (but very general) JVM agent, `spring-instrument.jar`, autodetected
|
||||
by common `@EnableLoadTimeWeaving` and `<context:load-time-weaver/>` setups.
|
||||
|
||||
To use it, you must start the virtual machine with the Spring agent by supplying
|
||||
the following JVM options:
|
||||
|
||||
[literal]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
-javaagent:/path/to/spring-instrument.jar
|
||||
----
|
||||
|
||||
Note that this requires modification of the JVM launch script, which may prevent you
|
||||
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.
|
||||
@@ -1,770 +0,0 @@
|
||||
[[aot]]
|
||||
= Ahead of Time Optimizations
|
||||
|
||||
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
|
||||
|
||||
Spring's support for AOT optimizations is meant to inspect an `ApplicationContext` at build time and apply decisions and discovery logic that usually happens at runtime.
|
||||
Doing so allows building an application startup arrangement that is more straightforward and focused on a fixed set of features based mainly on the classpath and the `Environment`.
|
||||
|
||||
Applying such optimizations early implies the following restrictions:
|
||||
|
||||
* The classpath is fixed and fully defined at build time.
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration, needs to be chosen at build time and is automatically enabled at runtime when AOT is enabled.
|
||||
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead of time.
|
||||
* Beans registered as singletons (using `registerSingleton`, typically from
|
||||
`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.
|
||||
|
||||
TIP: See also the xref:core/aot.adoc#aot.bestpractices[] section.
|
||||
|
||||
When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets.
|
||||
A Spring AOT processed application typically generates:
|
||||
|
||||
* Java source code
|
||||
* Bytecode (usually for dynamic proxies)
|
||||
* {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies
|
||||
|
||||
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
|
||||
|
||||
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`]:
|
||||
|
||||
* Refresh an `ApplicationContext` for AOT processing. Contrary to a traditional refresh, this version only creates bean definitions, not bean instances.
|
||||
* Invoke the available `BeanFactoryInitializationAotProcessor` implementations and apply their contributions against the `GenerationContext`.
|
||||
For instance, a core implementation iterates over all candidate bean definitions and generates the necessary code to restore the state of the `BeanFactory`.
|
||||
|
||||
Once this process completes, the `GenerationContext` will have been updated with the generated code, resources, and classes that are necessary for the application to run.
|
||||
The `RuntimeHints` instance can also be used to generate the relevant GraalVM native image configuration files.
|
||||
|
||||
`ApplicationContextAotGenerator#processAheadOfTime` returns the class name of the `ApplicationContextInitializer` entry point that allows the context to be started with AOT optimizations.
|
||||
|
||||
Those steps are covered in greater detail in the sections below.
|
||||
|
||||
|
||||
[[aot.refresh]]
|
||||
== Refresh for AOT Processing
|
||||
|
||||
Refresh for AOT processing is supported on all `GenericApplicationContext` implementations.
|
||||
An application context is created with any number of entry points, usually in the form of `@Configuration`-annotated classes.
|
||||
|
||||
Let's look at a basic example:
|
||||
|
||||
include-code::./AotProcessingSample[tag=myapplication]
|
||||
|
||||
Starting this application with the regular runtime involves a number of steps including classpath scanning, configuration class parsing, bean instantiation, and lifecycle callback handling.
|
||||
Refresh for AOT processing only applies a subset of what happens with a xref:core/beans/introduction.adoc[regular `refresh`].
|
||||
AOT processing can be triggered as follows:
|
||||
|
||||
include-code::./AotProcessingSample[tag=aotcontext]
|
||||
|
||||
In this mode, xref:core/beans/factory-extension.adoc#beans-factory-extension-factory-postprocessors[`BeanFactoryPostProcessor` implementations] are invoked as usual.
|
||||
This includes configuration class parsing, import selectors, classpath scanning, etc.
|
||||
Such steps make sure that the `BeanRegistry` contains the relevant bean definitions for the application.
|
||||
If bean definitions are guarded by conditions (such as `@Profile`), these are evaluated,
|
||||
and bean definitions that don't match their conditions are discarded at this stage.
|
||||
|
||||
If custom code needs to register extra beans programmatically, make sure that custom
|
||||
registration code uses `BeanDefinitionRegistry` instead of `BeanFactory` as only bean
|
||||
definitions are taken into account. A good pattern is to implement
|
||||
`ImportBeanDefinitionRegistrar` and register it via an `@Import` on one of your
|
||||
configuration classes.
|
||||
|
||||
Because this mode does not actually create bean instances, `BeanPostProcessor` implementations are not invoked, except for specific variants that are relevant for AOT processing.
|
||||
These are:
|
||||
|
||||
* `MergedBeanDefinitionPostProcessor` implementations post-process bean definitions to extract additional settings, such as `init` and `destroy` methods.
|
||||
* `SmartInstantiationAwareBeanPostProcessor` implementations determine a more precise bean type if necessary.
|
||||
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
|
||||
|
||||
Components that want to participate in this step can implement the {spring-framework-api}/beans/factory/aot/BeanFactoryInitializationAotProcessor.html[`BeanFactoryInitializationAotProcessor`] interface.
|
||||
Each implementation can return an AOT contribution, based on the state of the bean factory.
|
||||
|
||||
An AOT contribution is a component that contributes generated code which reproduces a particular behavior.
|
||||
It can also contribute `RuntimeHints` to indicate the need for reflection, resource loading, serialization, or JDK proxies.
|
||||
|
||||
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
|
||||
|
||||
The `BeanFactoryInitializationAotProcessor` interface can also be implemented directly by a bean.
|
||||
In this mode, the bean provides an AOT contribution equivalent to the feature it provides with a regular runtime.
|
||||
Consequently, such a bean is automatically excluded from the AOT-optimized context.
|
||||
|
||||
[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.
|
||||
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
|
||||
|
||||
A core `BeanFactoryInitializationAotProcessor` implementation is responsible for collecting the necessary contributions for each candidate `BeanDefinition`.
|
||||
It does so using a dedicated `BeanRegistrationAotProcessor`.
|
||||
|
||||
This interface is used as follows:
|
||||
|
||||
* Implemented by a `BeanPostProcessor` bean, to replace its runtime behavior.
|
||||
For instance xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[`AutowiredAnnotationBeanPostProcessor`] implements this interface to generate code that injects members annotated with `@Autowired`.
|
||||
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
|
||||
Typically used when the bean definition needs to be tuned for specific features of the core framework.
|
||||
|
||||
[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.
|
||||
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.
|
||||
====
|
||||
|
||||
If no `BeanRegistrationAotProcessor` handles a particular registered bean, a default implementation processes it.
|
||||
This is the default behavior, since tuning the generated code for a bean definition should be restricted to corner cases.
|
||||
|
||||
Taking our previous example, let's assume that `DataSourceConfiguration` is as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class DataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public SimpleDataSource dataSource() {
|
||||
return new SimpleDataSource();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
fun dataSource() = SimpleDataSource()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
WARNING: Kotlin class names with backticks that use invalid Java identifiers (not starting with a letter, containing spaces, etc.) are not supported.
|
||||
|
||||
Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates.
|
||||
The AOT engine will convert the configuration class above to code similar to the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0]
|
||||
----
|
||||
/**
|
||||
* Bean definitions for {@link DataSourceConfiguration}
|
||||
*/
|
||||
@Generated
|
||||
public class DataSourceConfiguration__BeanDefinitions {
|
||||
/**
|
||||
* Get the bean definition for 'dataSourceConfiguration'
|
||||
*/
|
||||
public static BeanDefinition getDataSourceConfigurationBeanDefinition() {
|
||||
Class<?> beanType = DataSourceConfiguration.class;
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
|
||||
beanDefinition.setInstanceSupplier(DataSourceConfiguration::new);
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bean instance supplier for 'dataSource'.
|
||||
*/
|
||||
private static BeanInstanceSupplier<SimpleDataSource> getDataSourceInstanceSupplier() {
|
||||
return BeanInstanceSupplier.<SimpleDataSource>forFactoryMethod(DataSourceConfiguration.class, "dataSource")
|
||||
.withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bean definition for 'dataSource'
|
||||
*/
|
||||
public static BeanDefinition getDataSourceBeanDefinition() {
|
||||
Class<?> beanType = SimpleDataSource.class;
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
|
||||
beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier());
|
||||
return beanDefinition;
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: The exact code generated may differ depending on the exact nature of your bean definitions.
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
[[aot.bestpractices]]
|
||||
== Best Practices
|
||||
|
||||
The AOT engine is designed to handle as many use cases as possible, with no code change in applications.
|
||||
However, keep in mind that some optimizations are made at build time based on a static definition of the beans.
|
||||
|
||||
This section lists the best practices that make sure your application is ready for AOT.
|
||||
|
||||
[[aot.bestpractices.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
|
||||
beans programmatically, make sure to use a `BeanDefinitionRegistry` to register
|
||||
bean definitions.
|
||||
|
||||
This can typically be done via a `BeanDefinitionRegistryPostProcessor`. Note that, if it
|
||||
is registered itself as a bean, it will be invoked again at runtime unless you make
|
||||
sure to implement `BeanFactoryInitializationAotProcessor` as well. A more idiomatic
|
||||
way is to implement `ImportBeanDefinitionRegistrar` and register it using `@Import` on
|
||||
one of your configuration classes. This invokes your custom code as part of configuration
|
||||
class parsing.
|
||||
|
||||
If you declare additional beans programmatically using a different callback, they are
|
||||
likely not going to be handled by the AOT engine, and therefore no hints are going to be
|
||||
generated for them. Depending on the environment, those beans may not be registered at
|
||||
all. For instance, classpath scanning does not work in a native image as there is no
|
||||
notion of a classpath. For cases like this, it is crucial that the scanning happens at
|
||||
build time.
|
||||
|
||||
[[aot.bestpractices.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.
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
public MyInterface myInterface() {
|
||||
return new MyImplementation();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
The example above should therefore be rewritten as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
public MyImplementation myInterface() {
|
||||
return new MyImplementation();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
[[aot.bestpractices.constructors]]
|
||||
=== 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.
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
[[aot.bestpractices.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).
|
||||
|
||||
If your `FactoryBean` implementation does not resolve the object type (i.e. `T`), extra care is necessary.
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
public ClientFactoryBean<MyClient> myClient() {
|
||||
return new ClientFactoryBean<>(...);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
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:
|
||||
|
||||
1. Use `RootBeanDefinition`.
|
||||
2. Set the `beanClass` to the `FactoryBean` class so that AOT knows that it is an intermediate layer.
|
||||
3. Set the `ResolvableType` to a resolved generic, which makes sure the most precise type is exposed.
|
||||
|
||||
The following example showcases a basic definition:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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)
|
||||
----
|
||||
======
|
||||
|
||||
[[aot.bestpractices.jpa]]
|
||||
=== JPA
|
||||
|
||||
The JPA persistence unit has to be known upfront for certain optimizations to apply. Consider the following basic example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
factoryBean.setDataSource(dataSource);
|
||||
factoryBean.setPackagesToScan("com.example.app");
|
||||
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
|
||||
factory bean definition, as shown by the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
|
||||
return new PersistenceManagedTypesScanner(resourceLoader)
|
||||
.scan("com.example.app");
|
||||
}
|
||||
|
||||
@Bean
|
||||
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource, PersistenceManagedTypes managedTypes) {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
factoryBean.setDataSource(dataSource);
|
||||
factoryBean.setManagedTypes(managedTypes);
|
||||
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
|
||||
|
||||
Running an application as a native image requires additional information compared to a regular JVM runtime.
|
||||
For instance, GraalVM needs to know ahead of time if a component uses reflection.
|
||||
Similarly, classpath resources are not included in a native image unless specified explicitly.
|
||||
Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file.
|
||||
|
||||
The {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime.
|
||||
The following example makes sure that `config/app.properties` can be loaded from the classpath at runtime within a native image:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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.
|
||||
For instance, the return type of a `@Controller` method is inspected, and relevant reflection hints are added if Spring detects that the type should be serialized (typically to JSON).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Library authors can reuse this annotation for their own purposes.
|
||||
An example of such customization is covered in the next section.
|
||||
|
||||
[[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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class TimestampConversionRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
ReflectionHints reflectionHints = hints.reflection();
|
||||
|
||||
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.
|
||||
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]
|
||||
|
||||
With `RuntimeHintsPredicates`, we can check for reflection, resource, serialization, or proxy generation hints.
|
||||
This approach works well for unit tests but implies that the runtime behavior of a component is well known.
|
||||
|
||||
You can learn more about the global runtime behavior of an application by running its test suite (or the app itself) with the {graalvm-docs}/native-image/metadata/AutomaticMetadataCollection/[GraalVM tracing agent].
|
||||
This agent will record all relevant calls requiring GraalVM hints at runtime and write them out as JSON configuration files.
|
||||
|
||||
For more targeted discovery and testing, Spring Framework ships a dedicated module with core AOT testing utilities, `"org.springframework:spring-core-test"`.
|
||||
This module contains the RuntimeHints Agent, a Java agent that records all method invocations that are related to runtime hints and helps you to assert that a given `RuntimeHints` instance covers all recorded invocations.
|
||||
Let's consider a piece of infrastructure for which we'd like to test the hints we're contributing during the AOT processing phase.
|
||||
|
||||
include-code::./SampleReflection[]
|
||||
|
||||
We can then write a unit test (no native compilation required) that checks our contributed hints:
|
||||
|
||||
include-code::./SampleReflectionRuntimeHintsTests[]
|
||||
|
||||
If you forgot to contribute a hint, the test will fail and provide some details about the invocation:
|
||||
|
||||
[source,txt,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
|
||||
INFO: Spring version: 6.2.0
|
||||
|
||||
Missing <"ReflectionHints"> for invocation <java.lang.Class#forName>
|
||||
with arguments ["org.springframework.core.SpringVersion",
|
||||
false,
|
||||
jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7].
|
||||
Stacktrace:
|
||||
<"org.springframework.util.ClassUtils#forName, Line 284
|
||||
io.spring.runtimehintstesting.SampleReflection#performReflection, Line 19
|
||||
io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0, Line 25
|
||||
----
|
||||
|
||||
There are various ways to configure this Java agent in your build, so please refer to the documentation of your build tool and test execution plugin.
|
||||
The agent itself can be configured to instrument specific packages (by default, only `org.springframework` is instrumented).
|
||||
You'll find more details in the {spring-framework-code}/buildSrc/README.md[Spring Framework `buildSrc` README] file.
|
||||
@@ -1,3 +0,0 @@
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
:page-section-summary-toc: 1
|
||||
@@ -1,57 +0,0 @@
|
||||
[[application-startup-steps]]
|
||||
= Application Startup Steps
|
||||
|
||||
This part of the appendix lists the existing `StartupSteps` that the core container is instrumented with.
|
||||
|
||||
WARNING: The name and detailed information about each startup step is not part of the public contract and
|
||||
is subject to change; this is considered as an implementation detail of the core container and will follow
|
||||
its behavior changes.
|
||||
|
||||
.Application startup steps defined in the core container
|
||||
|===
|
||||
| Name| Description| Tags
|
||||
|
||||
| `spring.beans.instantiate`
|
||||
| Instantiation of a bean and its dependencies.
|
||||
| `beanName` the name of the bean, `beanType` the type required at the injection point.
|
||||
|
||||
| `spring.beans.smart-initialize`
|
||||
| Initialization of `SmartInitializingSingleton` beans.
|
||||
| `beanName` the name of the bean.
|
||||
|
||||
| `spring.context.annotated-bean-reader.create`
|
||||
| Creation of the `AnnotatedBeanDefinitionReader`.
|
||||
|
|
||||
|
||||
| `spring.context.base-packages.scan`
|
||||
| Scanning of base packages.
|
||||
| `packages` array of base packages for scanning.
|
||||
|
||||
| `spring.context.beans.post-process`
|
||||
| Beans post-processing phase.
|
||||
|
|
||||
|
||||
| `spring.context.bean-factory.post-process`
|
||||
| Invocation of the `BeanFactoryPostProcessor` beans.
|
||||
| `postProcessor` the current post-processor.
|
||||
|
||||
| `spring.context.beandef-registry.post-process`
|
||||
| Invocation of the `BeanDefinitionRegistryPostProcessor` beans.
|
||||
| `postProcessor` the current post-processor.
|
||||
|
||||
| `spring.context.component-classes.register`
|
||||
| Registration of component classes through `AnnotationConfigApplicationContext#register`.
|
||||
| `classes` array of given classes for registration.
|
||||
|
||||
| `spring.context.config-classes.enhance`
|
||||
| Enhancement of configuration classes with CGLIB proxies.
|
||||
| `classCount` count of enhanced classes.
|
||||
|
||||
| `spring.context.config-classes.parse`
|
||||
| Configuration classes parsing phase with the `ConfigurationClassPostProcessor`.
|
||||
| `classCount` count of processed classes.
|
||||
|
||||
| `spring.context.refresh`
|
||||
| Application context refresh phase.
|
||||
|
|
||||
|===
|
||||
@@ -1,997 +0,0 @@
|
||||
[[xml-custom]]
|
||||
= XML Schema Authoring
|
||||
|
||||
[[xsd-custom-introduction]]
|
||||
Since version 2.0, Spring has featured a mechanism for adding schema-based extensions to the
|
||||
basic Spring XML format for defining and configuring beans. This section covers
|
||||
how to write your own custom XML bean definition parsers and
|
||||
integrate such parsers into the Spring IoC container.
|
||||
|
||||
To facilitate authoring configuration files that use a schema-aware XML editor,
|
||||
Spring's extensible XML configuration mechanism is based on XML Schema. If you are not
|
||||
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).
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-namespacehandler[Code] a custom `NamespaceHandler` implementation.
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-parser[Code] one or more `BeanDefinitionParser` implementations
|
||||
(this is where the real work is done).
|
||||
. xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-registration[Register] your new artifacts with Spring.
|
||||
|
||||
For a unified example, we create an
|
||||
XML extension (a custom XML element) that lets us configure objects of the type
|
||||
`SimpleDateFormat` (from the `java.text` package). When we are done,
|
||||
we will be able to define bean definitions of type `SimpleDateFormat` as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<myns:dateformat id="dateFormat"
|
||||
pattern="yyyy-MM-dd HH:mm"
|
||||
lenient="true"/>
|
||||
----
|
||||
|
||||
(We include much more detailed
|
||||
examples follow later in this appendix. The intent of this first simple example is to walk you
|
||||
through the basic steps of making a custom extension.)
|
||||
|
||||
|
||||
[[xsd-custom-schema]]
|
||||
== Authoring the Schema
|
||||
|
||||
Creating an XML configuration extension for use with Spring's IoC container starts with
|
||||
authoring an XML Schema to describe the extension. For our example, we use the following schema
|
||||
to configure `SimpleDateFormat` objects:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- myns.xsd (inside package org/springframework/samples/xml) -->
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
targetNamespace="http://www.mycompany.example/schema/myns"
|
||||
elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
|
||||
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
|
||||
|
||||
<xsd:element name="dateformat">
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="beans:identifiedType"> <1>
|
||||
<xsd:attribute name="lenient" type="xsd:boolean"/>
|
||||
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
----
|
||||
<1> The indicated line contains an extension base for all identifiable tags
|
||||
(meaning they have an `id` attribute that we can use as the bean identifier in the
|
||||
container). We can use this attribute because we imported the Spring-provided
|
||||
`beans` namespace.
|
||||
|
||||
|
||||
The preceding schema lets us configure `SimpleDateFormat` objects directly in an
|
||||
XML application context file by using the `<myns:dateformat/>` element, as the following
|
||||
example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<myns:dateformat id="dateFormat"
|
||||
pattern="yyyy-MM-dd HH:mm"
|
||||
lenient="true"/>
|
||||
----
|
||||
|
||||
Note that, after we have created the infrastructure classes, the preceding snippet of XML is
|
||||
essentially the same as the following XML snippet:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dateFormat" class="java.text.SimpleDateFormat">
|
||||
<constructor-arg value="yyyy-MM-dd HH:mm"/>
|
||||
<property name="lenient" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The second of the two preceding snippets
|
||||
creates a bean in the container (identified by the name `dateFormat` of type
|
||||
`SimpleDateFormat`) with a couple of properties set.
|
||||
|
||||
NOTE: The schema-based approach to creating configuration format allows for tight integration
|
||||
with an IDE that has a schema-aware XML editor. By using a properly authored schema, you
|
||||
can use autocompletion to let a user choose between several configuration options
|
||||
defined in the enumeration.
|
||||
|
||||
|
||||
[[xsd-custom-namespacehandler]]
|
||||
== Coding a `NamespaceHandler`
|
||||
|
||||
In addition to the schema, we need a `NamespaceHandler` to parse all elements of
|
||||
this specific namespace that Spring encounters while parsing configuration files. For this example, the
|
||||
`NamespaceHandler` should take care of the parsing of the `myns:dateformat`
|
||||
element.
|
||||
|
||||
The `NamespaceHandler` interface features three methods:
|
||||
|
||||
* `init()`: Allows for initialization of the `NamespaceHandler` and is called by
|
||||
Spring before the handler is used.
|
||||
* `BeanDefinition parse(Element, ParserContext)`: Called when Spring encounters a
|
||||
top-level element (not nested inside a bean definition or a different namespace).
|
||||
This method can itself register bean definitions, return a bean definition, or both.
|
||||
* `BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)`: Called
|
||||
when Spring encounters an attribute or nested element of a different namespace.
|
||||
The decoration of one or more bean definitions is used (for example) with the
|
||||
xref:core/beans/factory-scopes.adoc[scopes that Spring supports].
|
||||
We start by highlighting a simple example, without using decoration, after which
|
||||
we show decoration in a somewhat more advanced example.
|
||||
|
||||
Although you can code your own `NamespaceHandler` for the entire
|
||||
namespace (and hence provide code that parses each and every element in the namespace),
|
||||
it is often the case that each top-level XML element in a Spring XML configuration file
|
||||
results in a single bean definition (as in our case, where a single `<myns:dateformat/>`
|
||||
element results in a single `SimpleDateFormat` bean definition). Spring features a
|
||||
number of convenience classes that support this scenario. In the following example, we
|
||||
use the `NamespaceHandlerSupport` class:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
|
||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
||||
|
||||
public class MyNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
|
||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
|
||||
|
||||
class MyNamespaceHandler : NamespaceHandlerSupport {
|
||||
|
||||
override fun init() {
|
||||
registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You may notice that there is not actually a whole lot of parsing logic
|
||||
in this class. Indeed, the `NamespaceHandlerSupport` class has a built-in notion of
|
||||
delegation. It supports the registration of any number of `BeanDefinitionParser`
|
||||
instances, to which it delegates to when it needs to parse an element in its
|
||||
namespace. This clean separation of concerns lets a `NamespaceHandler` handle the
|
||||
orchestration of the parsing of all of the custom elements in its namespace while
|
||||
delegating to `BeanDefinitionParsers` to do the grunt work of the XML parsing. This
|
||||
means that each `BeanDefinitionParser` contains only the logic for parsing a single
|
||||
custom element, as we can see in the next step.
|
||||
|
||||
|
||||
[[xsd-custom-parser]]
|
||||
== Using `BeanDefinitionParser`
|
||||
|
||||
A `BeanDefinitionParser` is used if the `NamespaceHandler` encounters an XML
|
||||
element of the type that has been mapped to the specific bean definition parser
|
||||
(`dateformat` in this case). In other words, the `BeanDefinitionParser` is
|
||||
responsible for parsing one distinct top-level XML element defined in the schema. In
|
||||
the parser, we' have access to the XML element (and thus to its subelements, too) so that
|
||||
we can parse our custom XML content, as you can see in the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { // <1>
|
||||
|
||||
protected Class getBeanClass(Element element) {
|
||||
return SimpleDateFormat.class; // <2>
|
||||
}
|
||||
|
||||
protected void doParse(Element element, BeanDefinitionBuilder bean) {
|
||||
// this will never be null since the schema explicitly requires that a value be supplied
|
||||
String pattern = element.getAttribute("pattern");
|
||||
bean.addConstructorArgValue(pattern);
|
||||
|
||||
// this however is an optional property
|
||||
String lenient = element.getAttribute("lenient");
|
||||
if (StringUtils.hasText(lenient)) {
|
||||
bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
<1> We use the Spring-provided `AbstractSingleBeanDefinitionParser` to handle a lot of
|
||||
the basic grunt work of creating a single `BeanDefinition`.
|
||||
<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our
|
||||
single `BeanDefinition` represents.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder
|
||||
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
|
||||
import org.springframework.util.StringUtils
|
||||
import org.w3c.dom.Element
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { // <1>
|
||||
|
||||
override fun getBeanClass(element: Element): Class<*>? { // <2>
|
||||
return SimpleDateFormat::class.java
|
||||
}
|
||||
|
||||
override fun doParse(element: Element, bean: BeanDefinitionBuilder) {
|
||||
// this will never be null since the schema explicitly requires that a value be supplied
|
||||
val pattern = element.getAttribute("pattern")
|
||||
bean.addConstructorArgValue(pattern)
|
||||
|
||||
// this however is an optional property
|
||||
val lenient = element.getAttribute("lenient")
|
||||
if (StringUtils.hasText(lenient)) {
|
||||
bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> We use the Spring-provided `AbstractSingleBeanDefinitionParser` to handle a lot of
|
||||
the basic grunt work of creating a single `BeanDefinition`.
|
||||
<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our
|
||||
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
|
||||
|
||||
The coding is finished. All that remains to be done is to make the Spring XML
|
||||
parsing infrastructure aware of our custom element. We do so by registering our custom
|
||||
`namespaceHandler` and custom XSD file in two special-purpose properties files. These
|
||||
properties files are both placed in a `META-INF` directory in your application and
|
||||
can, for example, be distributed alongside your binary classes in a JAR file. The Spring
|
||||
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`
|
||||
|
||||
The properties file called `spring.handlers` contains a mapping of XML Schema URIs to
|
||||
namespace handler classes. For our example, we need to write the following:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
|
||||
----
|
||||
|
||||
(The `:` character is a valid delimiter in the Java properties format, so
|
||||
`:` character in the URI needs to be escaped with a backslash.)
|
||||
|
||||
The first part (the key) of the key-value pair is the URI associated with your custom
|
||||
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'
|
||||
|
||||
The properties file called `spring.schemas` contains a mapping of XML Schema locations
|
||||
(referred to, along with the schema declaration, in XML files that use the schema as part
|
||||
of the `xsi:schemaLocation` attribute) to classpath resources. This file is needed
|
||||
to prevent Spring from absolutely having to use a default `EntityResolver` that requires
|
||||
Internet access to retrieve the schema file. If you specify the mapping in this
|
||||
properties file, Spring searches for the schema (in this case,
|
||||
`myns.xsd` in the `org.springframework.samples.xml` package) on the classpath.
|
||||
The following snippet shows the line we need to add for our custom schema:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
|
||||
----
|
||||
|
||||
(Remember that the `:` character must be escaped.)
|
||||
|
||||
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
|
||||
|
||||
Using a custom extension that you yourself have implemented is no different from using
|
||||
one of the "`custom`" extensions that Spring provides. The following
|
||||
example uses the custom `<dateformat/>` element developed in the previous steps
|
||||
in a Spring XML configuration file:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?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:myns="http://www.mycompany.example/schema/myns"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
|
||||
|
||||
<!-- as a top-level bean -->
|
||||
<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> <1>
|
||||
|
||||
<bean id="jobDetailTemplate" abstract="true">
|
||||
<property name="dateFormat">
|
||||
<!-- as an inner bean -->
|
||||
<myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
<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
|
||||
|
||||
The example presented in this section shows how you to write the various artifacts required
|
||||
to satisfy a target of the following configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?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:foo="http://www.foo.example/schema/component"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">
|
||||
|
||||
<foo:component id="bionic-family" name="Bionic-1">
|
||||
<foo:component name="Mother-1">
|
||||
<foo:component name="Karate-1"/>
|
||||
<foo:component name="Sport-1"/>
|
||||
</foo:component>
|
||||
<foo:component name="Rock-1"/>
|
||||
</foo:component>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The preceding configuration nests custom extensions within each other. The class
|
||||
that is actually configured by the `<foo:component/>` element is the `Component`
|
||||
class (shown in the next example). Notice how the `Component` class does not expose a
|
||||
setter method for the `components` property. This makes it hard (or rather impossible)
|
||||
to configure a bean definition for the `Component` class by using setter injection.
|
||||
The following listing shows the `Component` class:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Component {
|
||||
|
||||
private String name;
|
||||
private List<Component> components = new ArrayList<Component> ();
|
||||
|
||||
// there is no setter method for the 'components'
|
||||
public void addComponent(Component component) {
|
||||
this.components.add(component);
|
||||
}
|
||||
|
||||
public List<Component> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
class Component {
|
||||
|
||||
var name: String? = null
|
||||
private val components = ArrayList<Component>()
|
||||
|
||||
// there is no setter method for the 'components'
|
||||
fun addComponent(component: Component) {
|
||||
this.components.add(component)
|
||||
}
|
||||
|
||||
fun getComponents(): List<Component> {
|
||||
return components
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The typical solution to this issue is to create a custom `FactoryBean` that exposes a
|
||||
setter property for the `components` property. The following listing shows such a custom
|
||||
`FactoryBean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ComponentFactoryBean implements FactoryBean<Component> {
|
||||
|
||||
private Component parent;
|
||||
private List<Component> children;
|
||||
|
||||
public void setParent(Component parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void setChildren(List<Component> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public Component getObject() throws Exception {
|
||||
if (this.children != null && this.children.size() > 0) {
|
||||
for (Component child : children) {
|
||||
this.parent.addComponent(child);
|
||||
}
|
||||
}
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public Class<Component> getObjectType() {
|
||||
return Component.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
class ComponentFactoryBean : FactoryBean<Component> {
|
||||
|
||||
private var parent: Component? = null
|
||||
private var children: List<Component>? = null
|
||||
|
||||
fun setParent(parent: Component) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
fun setChildren(children: List<Component>) {
|
||||
this.children = children
|
||||
}
|
||||
|
||||
override fun getObject(): Component? {
|
||||
if (this.children != null && this.children!!.isNotEmpty()) {
|
||||
for (child in children!!) {
|
||||
this.parent!!.addComponent(child)
|
||||
}
|
||||
}
|
||||
return this.parent
|
||||
}
|
||||
|
||||
override fun getObjectType(): Class<Component>? {
|
||||
return Component::class.java
|
||||
}
|
||||
|
||||
override fun isSingleton(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This works nicely, but it exposes a lot of Spring plumbing to the end user. What we are
|
||||
going to do is write a custom extension that hides away all of this Spring plumbing.
|
||||
If we stick to xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-introduction[the steps described previously], we start off
|
||||
by creating the XSD schema to define the structure of our custom tag, as the following
|
||||
listing shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<xsd:schema xmlns="http://www.foo.example/schema/component"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://www.foo.example/schema/component"
|
||||
elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
|
||||
<xsd:element name="component">
|
||||
<xsd:complexType>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element ref="component"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="id" type="xsd:ID"/>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
</xsd:schema>
|
||||
----
|
||||
|
||||
Again following xref:core/appendix/xml-custom.adoc#core.appendix.xsd-custom-introduction[the process described earlier],
|
||||
we then create a custom `NamespaceHandler`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
||||
|
||||
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
|
||||
|
||||
class ComponentNamespaceHandler : NamespaceHandlerSupport() {
|
||||
|
||||
override fun init() {
|
||||
registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Next up is the custom `BeanDefinitionParser`. Remember that we are creating
|
||||
a `BeanDefinition` that describes a `ComponentFactoryBean`. The following
|
||||
listing shows our custom `BeanDefinitionParser` implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
|
||||
return parseComponentElement(element);
|
||||
}
|
||||
|
||||
private static AbstractBeanDefinition parseComponentElement(Element element) {
|
||||
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
|
||||
factory.addPropertyValue("parent", parseComponent(element));
|
||||
|
||||
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
|
||||
if (childElements != null && childElements.size() > 0) {
|
||||
parseChildComponents(childElements, factory);
|
||||
}
|
||||
|
||||
return factory.getBeanDefinition();
|
||||
}
|
||||
|
||||
private static BeanDefinition parseComponent(Element element) {
|
||||
BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
|
||||
component.addPropertyValue("name", element.getAttribute("name"));
|
||||
return component.getBeanDefinition();
|
||||
}
|
||||
|
||||
private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
|
||||
ManagedList<BeanDefinition> children = new ManagedList<>(childElements.size());
|
||||
for (Element element : childElements) {
|
||||
children.add(parseComponentElement(element));
|
||||
}
|
||||
factory.addPropertyValue("children", children);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder
|
||||
import org.springframework.beans.factory.support.ManagedList
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
|
||||
import org.springframework.beans.factory.xml.ParserContext
|
||||
import org.springframework.util.xml.DomUtils
|
||||
import org.w3c.dom.Element
|
||||
|
||||
import java.util.List
|
||||
|
||||
class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() {
|
||||
|
||||
override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? {
|
||||
return parseComponentElement(element)
|
||||
}
|
||||
|
||||
private fun parseComponentElement(element: Element): AbstractBeanDefinition {
|
||||
val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java)
|
||||
factory.addPropertyValue("parent", parseComponent(element))
|
||||
|
||||
val childElements = DomUtils.getChildElementsByTagName(element, "component")
|
||||
if (childElements != null && childElements.size > 0) {
|
||||
parseChildComponents(childElements, factory)
|
||||
}
|
||||
|
||||
return factory.getBeanDefinition()
|
||||
}
|
||||
|
||||
private fun parseComponent(element: Element): BeanDefinition {
|
||||
val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java)
|
||||
component.addPropertyValue("name", element.getAttribute("name"))
|
||||
return component.beanDefinition
|
||||
}
|
||||
|
||||
private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) {
|
||||
val children = ManagedList<BeanDefinition>(childElements.size)
|
||||
for (element in childElements) {
|
||||
children.add(parseComponentElement(element))
|
||||
}
|
||||
factory.addPropertyValue("children", children)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Finally, the various artifacts need to be registered with the Spring XML infrastructure,
|
||||
by modifying the `META-INF/spring.handlers` and `META-INF/spring.schemas` files, as follows:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
# in 'META-INF/spring.handlers'
|
||||
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
|
||||
----
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
# in 'META-INF/spring.schemas'
|
||||
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
|
||||
----
|
||||
|
||||
[[xsd-custom-custom-just-attributes]]
|
||||
=== Custom Attributes on "`Normal`" Elements
|
||||
|
||||
Writing your own custom parser and the associated artifacts is not hard. However,
|
||||
it is sometimes not the right thing to do. Consider a scenario where you need to
|
||||
add metadata to already existing bean definitions. In this case, you certainly
|
||||
do not want to have to write your own entire custom extension. Rather, you merely
|
||||
want to add an additional attribute to the existing bean definition element.
|
||||
|
||||
By way of another example, suppose that you define a bean definition for a
|
||||
service object that (unknown to it) accesses a clustered
|
||||
{JSR}107[JCache], and you want to ensure that the
|
||||
named JCache instance is eagerly started within the surrounding cluster.
|
||||
The following listing shows such a definition:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
|
||||
jcache:cache-name="checking.account">
|
||||
<!-- other dependencies here... -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
We can then create another `BeanDefinition` when the
|
||||
`'jcache:cache-name'` attribute is parsed. This `BeanDefinition` then initializes
|
||||
the named JCache for us. We can also modify the existing `BeanDefinition` for the
|
||||
`'checkingAccountService'` so that it has a dependency on this new
|
||||
JCache-initializing `BeanDefinition`. The following listing shows our `JCacheInitializer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
public class JCacheInitializer {
|
||||
|
||||
private final String name;
|
||||
|
||||
public JCacheInitializer(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
// lots of JCache API calls to initialize the named cache...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
class JCacheInitializer(private val name: String) {
|
||||
|
||||
fun initialize() {
|
||||
// lots of JCache API calls to initialize the named cache...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Now we can move onto the custom extension. First, we need to author
|
||||
the XSD schema that describes the custom attribute, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<xsd:schema xmlns="http://www.foo.example/schema/jcache"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://www.foo.example/schema/jcache"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xsd:attribute name="cache-name" type="xsd:string"/>
|
||||
|
||||
</xsd:schema>
|
||||
----
|
||||
|
||||
Next, we need to create the associated `NamespaceHandler`, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
||||
|
||||
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
public void init() {
|
||||
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
|
||||
new JCacheInitializingBeanDefinitionDecorator());
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
|
||||
|
||||
class JCacheNamespaceHandler : NamespaceHandlerSupport() {
|
||||
|
||||
override fun init() {
|
||||
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
|
||||
JCacheInitializingBeanDefinitionDecorator())
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Next, we need to create the parser. Note that, in this case, because we are going to parse
|
||||
an XML attribute, we write a `BeanDefinitionDecorator` rather than a `BeanDefinitionParser`.
|
||||
The following listing shows our `BeanDefinitionDecorator` implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
|
||||
ParserContext ctx) {
|
||||
String initializerBeanName = registerJCacheInitializer(source, ctx);
|
||||
createDependencyOnJCacheInitializer(holder, initializerBeanName);
|
||||
return holder;
|
||||
}
|
||||
|
||||
private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
|
||||
String initializerBeanName) {
|
||||
AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
|
||||
String[] dependsOn = definition.getDependsOn();
|
||||
if (dependsOn == null) {
|
||||
dependsOn = new String[]{initializerBeanName};
|
||||
} else {
|
||||
List dependencies = new ArrayList(Arrays.asList(dependsOn));
|
||||
dependencies.add(initializerBeanName);
|
||||
dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
|
||||
}
|
||||
definition.setDependsOn(dependsOn);
|
||||
}
|
||||
|
||||
private String registerJCacheInitializer(Node source, ParserContext ctx) {
|
||||
String cacheName = ((Attr) source).getValue();
|
||||
String beanName = cacheName + "-initializer";
|
||||
if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
|
||||
BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
|
||||
initializer.addConstructorArg(cacheName);
|
||||
ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
|
||||
}
|
||||
return beanName;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionDecorator
|
||||
import org.springframework.beans.factory.xml.ParserContext
|
||||
import org.w3c.dom.Attr
|
||||
import org.w3c.dom.Node
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator {
|
||||
|
||||
override fun decorate(source: Node, holder: BeanDefinitionHolder,
|
||||
ctx: ParserContext): BeanDefinitionHolder {
|
||||
val initializerBeanName = registerJCacheInitializer(source, ctx)
|
||||
createDependencyOnJCacheInitializer(holder, initializerBeanName)
|
||||
return holder
|
||||
}
|
||||
|
||||
private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder,
|
||||
initializerBeanName: String) {
|
||||
val definition = holder.beanDefinition as AbstractBeanDefinition
|
||||
var dependsOn = definition.dependsOn
|
||||
dependsOn = if (dependsOn == null) {
|
||||
arrayOf(initializerBeanName)
|
||||
} else {
|
||||
val dependencies = ArrayList(listOf(*dependsOn))
|
||||
dependencies.add(initializerBeanName)
|
||||
dependencies.toTypedArray()
|
||||
}
|
||||
definition.setDependsOn(*dependsOn)
|
||||
}
|
||||
|
||||
private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String {
|
||||
val cacheName = (source as Attr).value
|
||||
val beanName = "$cacheName-initializer"
|
||||
if (!ctx.registry.containsBeanDefinition(beanName)) {
|
||||
val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java)
|
||||
initializer.addConstructorArg(cacheName)
|
||||
ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition())
|
||||
}
|
||||
return beanName
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Finally, we need to register the various artifacts with the Spring XML infrastructure
|
||||
by modifying the `META-INF/spring.handlers` and `META-INF/spring.schemas` files, as follows:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
# in 'META-INF/spring.handlers'
|
||||
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
|
||||
----
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
# in 'META-INF/spring.schemas'
|
||||
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
|
||||
----
|
||||