Compare commits
1 Commits
main
..
v4.1.0.RC2
| Author | SHA1 | Date | |
|---|---|---|---|
| c125f44d17 |
@@ -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: '25'
|
||||
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,35 +0,0 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":warning: Attention Required"
|
||||
labels:
|
||||
- "for: upgrade-attention"
|
||||
summary:
|
||||
mode: "member-comment"
|
||||
config:
|
||||
prefix: "Attention Required:"
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
- 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,55 +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: '25'
|
||||
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' || '' }}
|
||||
25
|
||||
- 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,3 +0,0 @@
|
||||
require:
|
||||
members: false
|
||||
|
||||
@@ -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:
|
||||
- main
|
||||
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-7.1.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,55 +0,0 @@
|
||||
name: CI
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
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,95 +0,0 @@
|
||||
name: Release Milestone
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.1.0-M[1-9]
|
||||
- v7.1.0-RC[1-9]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-stage-release:
|
||||
name: Build and Stage Release
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-staging-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
sync-to-maven-central:
|
||||
name: Sync to Maven Central
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
|
||||
central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
|
||||
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
promote-release:
|
||||
name: Promote Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- sync-to-maven-central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- promote-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
milestone: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
pre-release: true
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.1.[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.2.x', '7.0.x', 'main' ]
|
||||
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,58 +1,39 @@
|
||||
# Miscellaneous
|
||||
*.java.hsp
|
||||
*.sonarj
|
||||
*.sw*
|
||||
.DS_Store
|
||||
.settings
|
||||
.springBeans
|
||||
bin
|
||||
build.sh
|
||||
integration-repo
|
||||
ivy-cache
|
||||
argfile*
|
||||
activemq-data/
|
||||
classes/
|
||||
.cursor/
|
||||
|
||||
# Log files
|
||||
jxl.log
|
||||
jmx.log
|
||||
derby.log
|
||||
|
||||
# Gradle artifacts
|
||||
spring-test/test-output/
|
||||
.gradle
|
||||
.gradletasknamecache
|
||||
argfile*
|
||||
pom.xml
|
||||
activemq-data/
|
||||
|
||||
/build
|
||||
buildSrc/build
|
||||
/spring-*/build
|
||||
/framework-*/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
spring-test/test-output/
|
||||
|
||||
# Maven artifacts
|
||||
pom.xml
|
||||
/target/
|
||||
target/
|
||||
|
||||
# Eclipse artifacts, including WTP generated manifests
|
||||
bin
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
|
||||
# IntelliJ IDEA artifacts and output dirs
|
||||
# IDEA artifacts and output dirs
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/*
|
||||
!.idea/icon.svg
|
||||
.idea
|
||||
out
|
||||
test-output
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
/.kotlin/
|
||||
.gradletasknamecache
|
||||
|
||||
@@ -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 |
@@ -0,0 +1,14 @@
|
||||
Juergen Hoeller <jhoeller@vmware.com> jhoeller <jhoeller@vmware.com>
|
||||
<rstoyanchev@gopivotal.com> <rstoyanchev@vmware.com>
|
||||
<pwebb@gopivotal.com> <pwebb@vmware.com>
|
||||
<cbeams@gopivotal.com> <cbeams@vmware.com>
|
||||
<cbeams@gopivotal.com> <cbeams@gmail.com>
|
||||
<apoutsma@gopivotal.com> <apoutsma@vmware.com>
|
||||
<apoutsma@gopivotal.com> <poutsma@mac.com>
|
||||
<ogierke@gopivotal.com> <ogierke@vmware.com>
|
||||
<dsyer@gopivotal.com> <david_syer@hotmail.com>
|
||||
<dsyer@gopivotal.com> <dsyer@vmware.com>
|
||||
<aclement@gopivotal.com> <andrew.clement@gmail.com>
|
||||
<aclement@gopivotal.com> <aclement@vmware.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net> Nicholas Williams <nicholas@nicholaswilliams.net>
|
||||
@@ -1,3 +0,0 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=25-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;
|
||||
@@ -0,0 +1,51 @@
|
||||
= How to contribute to the reference
|
||||
|
||||
The Spring Framework reference now uses http://asciidoctor.org/[asciidoctor]. This
|
||||
document describes how to contribute documentation updates.
|
||||
|
||||
== Building with Gradle
|
||||
|
||||
You can build the documentation using gradle using the `reference` task. For example, from
|
||||
the project root execute the following command:
|
||||
|
||||
./gradlew reference
|
||||
|
||||
the output will be available at `spring-framework/build/reference/htmlsingle/index.html`
|
||||
|
||||
== Live editing
|
||||
|
||||
One of the nice features about using asciidoctor is the support for live editing.
|
||||
|
||||
You will find a Guardfile already present at `spring-framework/src/asciidoc/Guardfile`.
|
||||
Make sure first to follow the setup instructions within the
|
||||
http://asciidoctor.org/docs/editing-asciidoc-with-live-preview/[Editing AsciiDoc with Live Preview]
|
||||
document. Once you have done that, there are additional gems to install to make it work
|
||||
(assuming that you are using http://livereload.com/[LiveReload]):
|
||||
|
||||
gem install guard-rspec guard-livereload
|
||||
|
||||
When running `guard start` within the `src/asciidoc/` folder, any changes to the
|
||||
`src/asciidoc/index.adoc` file will automatically be written at
|
||||
`src/asciidoc/build/index.html`.
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
* If you are using LiveReload, make sure to select _Allow access to file URLs_ in the
|
||||
LiveEdit plugin options of your browser.
|
||||
* The icon used to enable _LiveReload_ can be a bit confusing. The dot is empty when it is
|
||||
disabled and full when the plugin is active. Make sure to enable it on the tab
|
||||
displaying the `index.html` file.
|
||||
* Ensure you are _not_ running guard start at all as two instances could not run at the
|
||||
same time. To exit a current session in a clean way, just type e in the shell.
|
||||
|
||||
== Documentation notes
|
||||
|
||||
Some notes on documentation
|
||||
|
||||
* Documentation is wrapped at 90 chars, ensure that you manually wrap your edits
|
||||
* Tabs are used for indentation, do not use spaces
|
||||
* Follow the existing style when inserting `source` blocks
|
||||
* http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/[Asciidoctor Quick Reference]
|
||||
* http://asciidoctor.org/docs/user-manual/[Asciidoctor Manual]
|
||||
* http://asciidoctor.org/docs/asciidoc-writers-guide/[Asciidoctor Writers Guide]
|
||||
|
||||
@@ -1,136 +1,315 @@
|
||||
# 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
|
||||
## Take Your First Steps
|
||||
|
||||
* [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)
|
||||
### Understand the basics
|
||||
|
||||
### 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 you
|
||||
do not find something similar, please create a new JIRA issue before submitting
|
||||
a pull request unless the change is truly trivial -- for example: 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.
|
||||
Once 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 above statement to all
|
||||
future pull requests as well, simply so that 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.
|
||||
## Create a Branch
|
||||
|
||||
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.
|
||||
### 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).
|
||||
Master currently represents work toward Spring Framework 4.0. Please submit
|
||||
all pull requests there, even bug fixes and minor improvements. Backports to
|
||||
`3.2.x` will be considered on a case-by-case basis.
|
||||
|
||||
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.
|
||||
|
||||
1. If there is a prior issue, reference the GitHub issue number in the description of the
|
||||
pull request.
|
||||
### Use short branch names
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
## Use Spring Framework Code Style
|
||||
|
||||
#### Participate in Reviews
|
||||
The complete [Spring Framework Code Style][] reference is available on the wiki, but
|
||||
here's a quick summary:
|
||||
|
||||
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 IntelliJ editor settings we customize.
|
||||
### Add Apache license header to all new classes
|
||||
|
||||
### Reference Docs
|
||||
```java
|
||||
/*
|
||||
* Copyright 2002-2014 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 in 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 2014 whose header still reads:
|
||||
|
||||
```java
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
```
|
||||
|
||||
Then be sure to update it to 2014 accordingly:
|
||||
|
||||
```java
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
```
|
||||
|
||||
### Use @since tags for newly-added public API types and methods
|
||||
|
||||
For example:
|
||||
|
||||
```java
|
||||
/**
|
||||
* ...
|
||||
*
|
||||
* @author First Last
|
||||
* @since 4.0
|
||||
* @see ...
|
||||
*/
|
||||
```
|
||||
|
||||
## Prepare Your Commit
|
||||
|
||||
### Submit JUnit test cases for all behavior changes
|
||||
|
||||
Search the codebase to find related tests and add additional `@Test` methods
|
||||
as appropriate. It is also acceptable to submit test cases on a per JIRA issue
|
||||
basis, for example:
|
||||
|
||||
```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 --autosquash`, `git add --patch`, and other tools
|
||||
to "squash" multiple commits into a single atomic commit. In addition to the man
|
||||
pages for git, there are many resources online to help you understand how these
|
||||
tools work. The [Rewriting History section of Pro Git][] provides a good overview.
|
||||
|
||||
|
||||
### 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 via the account admin area in GitHub (useful for
|
||||
fork-and-edit cases); _globally_ on your machine 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 with a capitalized verb, e.g. "Add, Prune, Fix,
|
||||
Introduce, Avoid, etc."
|
||||
1. Do not end the subject line with a period
|
||||
1. Restrict 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/spring-projects/spring-framework/commit/08e2669b84ec0faa2f7904441fe39ac70b65b078
|
||||
- https://github.com/spring-projects/spring-framework/commit/1d9d3e6ff79ce9f0eca03b02cd1df705925575da
|
||||
- https://github.com/spring-projects/spring-framework/commit/8e0b1c3a5f957af3049cfa0438317177e16d6de6
|
||||
- https://github.com/spring-projects/spring-framework/commit/b787a68f2050df179f7036b209aa741230a02477
|
||||
|
||||
## Run the Final Checklist
|
||||
|
||||
### 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. In other words, 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.spring.io/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
|
||||
[Spring Framework Code Style]: https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Code-Style
|
||||
[Rewriting History section of Pro Git]: http://git-scm.com/book/en/Git-Tools-Rewriting-History
|
||||
[Commit Guidelines section of Pro Git]: http://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines
|
||||
[building from source]: https://github.com/spring-projects/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,101 @@
|
||||
# <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 [Spring 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## Build from Source
|
||||
### Prerequisites
|
||||
|
||||
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.
|
||||
[Git][] and [OpenJDK 8 early access build 100 or later][JDK8 build]
|
||||
|
||||
## Continuous Integration Builds
|
||||
Be sure that your `JAVA_HOME` environment variable points to the `jdk1.8.0` folder
|
||||
extracted from the JDK download.
|
||||
|
||||
CI builds are defined with [GitHub Actions workflows](.github/workflows).
|
||||
### Check out sources
|
||||
`git clone git@github.com:spring-projects/spring-framework.git`
|
||||
|
||||
## Stay in Touch
|
||||
### Import sources into your IDE
|
||||
Run `./import-into-eclipse.sh` or read `import-into-idea.md` as appropriate.
|
||||
> **Note:** Per the prerequisites above, ensure that you have JDK 8 configured properly in your IDE.
|
||||
|
||||
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).
|
||||
### Install all spring-\* jars into your local Maven cache
|
||||
`./gradlew install`
|
||||
|
||||
### Compile and test; build all jars, distribution zips, and docs
|
||||
`./gradlew build`
|
||||
|
||||
... and discover more commands with `./gradlew tasks`. See also the [Gradle
|
||||
build and release FAQ][].
|
||||
|
||||
## Contributing
|
||||
[Pull requests][] are welcome; see the [contributor guidelines][] for details.
|
||||
|
||||
## Staying in Touch
|
||||
Follow [@SpringCentral][] as well as [@SpringFramework][] and its [team members][]
|
||||
on Twitter. In-depth articles can be found at [The Spring 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/spring-projects/spring-integration
|
||||
[Spring Batch]: https://github.com/spring-projects/spring-batch
|
||||
[family of projects]: http://spring.io/projects
|
||||
[Spring organization]: https://github.com/spring-projects
|
||||
[downloading Spring artifacts]: https://github.com/spring-projects/spring-framework/wiki/Downloading-Spring-artifacts
|
||||
[building a distribution with dependencies]: https://github.com/spring-projects/spring-framework/wiki/Building-a-distribution-with-dependencies
|
||||
[Javadoc]: http://docs.spring.io/spring-framework/docs/current/javadoc-api/
|
||||
[reference docs]: http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/
|
||||
[Spring forums]: http://forum.spring.io/
|
||||
[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://spring.io/services
|
||||
[Spring Framework JIRA]: https://jira.spring.io/browse/SPR
|
||||
[the lifecycle of an issue]: https://github.com/spring-projects/spring-framework/wiki/The-Lifecycle-of-an-Issue
|
||||
[spring-framework-issues]: https://github.com/spring-projects/spring-framework-issues#readme
|
||||
[readme]: https://github.com/spring-projects/spring-framework-issues#readme
|
||||
[Gradle]: http://gradle.org
|
||||
[`./gradlew`]: http://vimeo.com/34436402
|
||||
[Git]: http://help.github.com/set-up-git-redirect
|
||||
[JDK8 build]: http://jdk8.java.net/download.html
|
||||
[Gradle build and release FAQ]: https://github.com/spring-projects/spring-framework/wiki/Gradle-build-and-release-FAQ
|
||||
[Pull requests]: http://help.github.com/send-pull-requests
|
||||
[contributor guidelines]: https://github.com/spring-projects/spring-framework/blob/master/CONTRIBUTING.md
|
||||
[@SpringFramework]: https://twitter.com/springframework
|
||||
[@SpringCentral]: https://twitter.com/springcentral
|
||||
[team members]: https://twitter.com/springframework/team/members
|
||||
[The Spring Blog]: http://spring.io/blog/
|
||||
[news feed]: http://spring.io/blog/category/news
|
||||
[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,90 +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`
|
||||
* Configuring the ArchUnit rules for the project, see `org.springframework.build.architecture.ArchitectureRules`
|
||||
|
||||
This plugin also provides a DSL extension to optionally enable Java preview features for
|
||||
compiling and testing sources in a module. This can be applied with the following in a
|
||||
module build file:
|
||||
|
||||
```groovy
|
||||
springFramework {
|
||||
enableJavaPreviewFeatures = true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
### MultiRelease Jar
|
||||
|
||||
The `org.springframework.build.multiReleaseJar` plugin configures the project with MultiRelease JAR support.
|
||||
It creates a new SourceSet and dedicated tasks for each Java variant considered.
|
||||
This can be configured with the DSL, by setting a list of Java variants to configure:
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
|
||||
multiRelease {
|
||||
releaseVersions 21, 24
|
||||
}
|
||||
```
|
||||
|
||||
Note, Java classes will be compiled with the toolchain pre-configured by the project, assuming that its
|
||||
Java language version is equal or higher than all variants we consider. Each compilation task will only
|
||||
set the "-release" compilation option accordingly to produce the expected bytecode version.
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
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,68 +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.dokka:dokka-gradle-plugin:2.2.0"
|
||||
implementation "com.tngtech.archunit:archunit:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
|
||||
testImplementation("org.assertj:assertj-core:${assertjVersion}")
|
||||
testImplementation(platform("org.junit:junit-bom:${junitVersion}"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
architecturePlugin {
|
||||
id = "org.springframework.architecture"
|
||||
implementationClass = "org.springframework.build.architecture.ArchitecturePlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
}
|
||||
localDevPlugin {
|
||||
id = "org.springframework.build.localdev"
|
||||
implementationClass = "org.springframework.build.dev.LocalDevelopmentPlugin"
|
||||
}
|
||||
multiReleasePlugin {
|
||||
id = "org.springframework.build.multiReleaseJar"
|
||||
implementationClass = "org.springframework.build.multirelease.MultiReleaseJarPlugin"
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jar.dependsOn check
|
||||
@@ -1,26 +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="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="TreeWalker">
|
||||
|
||||
<!-- Imports -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
<module name="RedundantImport"/>
|
||||
<!-- Modifiers -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck"/>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -1,4 +0,0 @@
|
||||
org.gradle.caching=true
|
||||
assertjVersion=3.27.3
|
||||
javaFormatVersion=0.0.43
|
||||
junitVersion=5.12.2
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.
|
||||
*/
|
||||
|
||||
package org.springframework.build.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
|
||||
/**
|
||||
* Gradle plugin that detects identically named, non-empty packages split across multiple
|
||||
* subprojects, e.g. "org.springframework.context.annotation" existing in both spring-core
|
||||
* and spring-aspects. Adds a 'detectSplitPackages' task to the current project's task
|
||||
* collection. If the project already contains a 'check' task (i.e. is a typical Gradle
|
||||
* project with the "java" plugin applied), the 'check' task will be updated to depend on
|
||||
* the execution of 'detectSplitPackages'.
|
||||
*
|
||||
* By default, all subprojects will be scanned. Use the 'projectsToScan' task property to
|
||||
* modify this value. Example usage:
|
||||
*
|
||||
* apply plugin: 'detect-split-packages // typically applied to root project
|
||||
*
|
||||
* detectSplitPackages {
|
||||
* packagesToScan -= project(":spring-xyz") // scan every project but spring-xyz
|
||||
* }
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Glyn Normington
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class DetectSplitPackagesPlugin implements Plugin<Project> {
|
||||
public void apply(Project project) {
|
||||
def tasks = project.tasks
|
||||
Task detectSplitPackages = tasks.create("detectSplitPackages", DetectSplitPackagesTask.class)
|
||||
if (tasks.asMap.containsKey("check")) {
|
||||
tasks.getByName("check").dependsOn detectSplitPackages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DetectSplitPackagesTask extends DefaultTask {
|
||||
|
||||
private static final String JAVA_FILE_SUFFIX = ".java"
|
||||
private static final String PACKAGE_SEPARATOR = "."
|
||||
private static final String HIDDEN_DIRECTORY_PREFIX = "."
|
||||
|
||||
@Input
|
||||
Set<Project> projectsToScan = project.subprojects
|
||||
|
||||
public DetectSplitPackagesTask() {
|
||||
this.group = "Verification"
|
||||
this.description = "Detects packages split across two or more subprojects."
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void detectSplitPackages() {
|
||||
def splitPackages = doDetectSplitPackages()
|
||||
if (!splitPackages.isEmpty()) {
|
||||
def message = "The following split package(s) have been detected:\n"
|
||||
splitPackages.each { pkg, mod ->
|
||||
message += " - ${pkg} (split across ${mod[0].name} and ${mod[1].name})\n"
|
||||
}
|
||||
throw new GradleException(message)
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<Project>> doDetectSplitPackages() {
|
||||
def splitPackages = [:]
|
||||
def mergedProjects = findMergedProjects()
|
||||
def packagesByProject = mapPackagesByProject()
|
||||
|
||||
def projects = packagesByProject.keySet().toArray()
|
||||
def nProjects = projects.length
|
||||
|
||||
for (int i = 0; i < nProjects - 1; i++) {
|
||||
for (int j = i + 1; j < nProjects - 1; j++) {
|
||||
def prj_i = projects[i]
|
||||
def prj_j = projects[j]
|
||||
|
||||
def pkgs_i = new HashSet(packagesByProject.get(prj_i))
|
||||
def pkgs_j = packagesByProject.get(prj_j)
|
||||
pkgs_i.retainAll(pkgs_j)
|
||||
|
||||
if (!pkgs_i.isEmpty()
|
||||
&& mergedProjects.get(prj_i) != prj_j
|
||||
&& mergedProjects.get(prj_j) != prj_i) {
|
||||
pkgs_i.each { pkg ->
|
||||
def readablePkg = pkg.substring(1).replaceAll(File.separator, PACKAGE_SEPARATOR)
|
||||
splitPackages[readablePkg] = [prj_i, prj_j]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return splitPackages;
|
||||
}
|
||||
|
||||
private Map<Project, Set<String>> mapPackagesByProject() {
|
||||
def packagesByProject = [:]
|
||||
this.projectsToScan.each { Project p ->
|
||||
def packages = new HashSet<String>()
|
||||
p.sourceSets.main.java.srcDirs.each { File dir ->
|
||||
findPackages(packages, dir, "")
|
||||
}
|
||||
if (!packages.isEmpty()) {
|
||||
packagesByProject.put(p, packages)
|
||||
}
|
||||
}
|
||||
return packagesByProject;
|
||||
}
|
||||
|
||||
private Map<Project, Project> findMergedProjects() {
|
||||
def mergedProjects = [:]
|
||||
this.projectsToScan.findAll { p ->
|
||||
p.plugins.findPlugin(MergePlugin)
|
||||
}.findAll { p ->
|
||||
p.merge.into
|
||||
}.each { p ->
|
||||
mergedProjects.put(p, p.merge.into)
|
||||
}
|
||||
return mergedProjects
|
||||
}
|
||||
|
||||
private static void findPackages(Set<String> packages, File dir, String packagePath) {
|
||||
def scanDir = new File(dir, packagePath)
|
||||
def File[] javaFiles = scanDir.listFiles({ file ->
|
||||
!file.isDirectory() && file.name.endsWith(JAVA_FILE_SUFFIX)
|
||||
} as FileFilter)
|
||||
|
||||
if (javaFiles != null && javaFiles.length != 0) {
|
||||
packages.add(packagePath)
|
||||
}
|
||||
|
||||
scanDir.listFiles({ File file ->
|
||||
file.isDirectory() && !file.name.startsWith(HIDDEN_DIRECTORY_PREFIX)
|
||||
} as FileFilter).each { File subDir ->
|
||||
findPackages(packages, dir, packagePath + File.separator + subDir.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.
|
||||
*/
|
||||
|
||||
package org.springframework.build.gradle
|
||||
|
||||
import org.gradle.api.*
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.ProjectDependency;
|
||||
import org.gradle.api.artifacts.maven.Conf2ScopeMapping
|
||||
import org.gradle.api.plugins.MavenPlugin
|
||||
import org.gradle.plugins.ide.eclipse.EclipsePlugin
|
||||
import org.gradle.plugins.ide.idea.IdeaPlugin
|
||||
import org.gradle.api.invocation.*
|
||||
|
||||
/**
|
||||
* Gradle plugin that allows projects to merged together. Primarily developed to
|
||||
* allow Spring to support multiple incompatible versions of third-party
|
||||
* dependencies (for example Hibernate v3 and v4).
|
||||
* <p>
|
||||
* The 'merge' extension should be used to define how projects are merged, for example:
|
||||
* <pre class="code">
|
||||
* configure(subprojects) {
|
||||
* apply plugin: MergePlugin
|
||||
* }
|
||||
*
|
||||
* project("myproject") {
|
||||
* }
|
||||
*
|
||||
* project("myproject-extra") {
|
||||
* merge.into = project("myproject")
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* This plugin adds two new configurations:
|
||||
* <ul>
|
||||
* <li>merging - Contains the projects being merged into this project<li>
|
||||
* <li>runtimeMerge - Contains all dependencies that are merge projects. These are used
|
||||
* to allow an IDE to reference merge projects.</li>
|
||||
* <ul>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MergePlugin implements Plugin<Project> {
|
||||
|
||||
private static boolean attachedProjectsEvaluated;
|
||||
|
||||
public void apply(Project project) {
|
||||
project.plugins.apply(MavenPlugin)
|
||||
project.plugins.apply(EclipsePlugin)
|
||||
project.plugins.apply(IdeaPlugin)
|
||||
|
||||
MergeModel model = project.extensions.create("merge", MergeModel)
|
||||
project.configurations.create("merging")
|
||||
Configuration runtimeMerge = project.configurations.create("runtimeMerge")
|
||||
|
||||
// Ensure the IDE can reference merged projects
|
||||
project.eclipse.classpath.plusConfigurations += [ runtimeMerge ]
|
||||
project.idea.module.scopes.PROVIDED.plus += [ runtimeMerge ]
|
||||
|
||||
// Hook to perform the actual merge logic
|
||||
project.afterEvaluate{
|
||||
if (it.merge.into != null) {
|
||||
setup(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Hook to build runtimeMerge dependencies
|
||||
if (!attachedProjectsEvaluated) {
|
||||
project.gradle.projectsEvaluated{
|
||||
postProcessProjects(it)
|
||||
}
|
||||
attachedProjectsEvaluated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void setup(Project project) {
|
||||
project.merge.into.dependencies.add("merging", project)
|
||||
project.dependencies.add("provided", project.merge.into.sourceSets.main.output)
|
||||
project.dependencies.add("runtimeMerge", project.merge.into)
|
||||
setupTaskDependencies(project)
|
||||
setupMaven(project)
|
||||
}
|
||||
|
||||
private void setupTaskDependencies(Project project) {
|
||||
// invoking a task will invoke the task with the same name on 'into' project
|
||||
["sourcesJar", "jar", "javadocJar", "javadoc", "install", "artifactoryPublish"].each {
|
||||
def task = project.tasks.findByPath(it)
|
||||
if (task) {
|
||||
task.enabled = false
|
||||
task.dependsOn(project.merge.into.tasks.findByPath(it))
|
||||
}
|
||||
}
|
||||
|
||||
// update 'into' project artifacts to contain the source artifact contents
|
||||
project.merge.into.sourcesJar.from(project.sourcesJar.source)
|
||||
project.merge.into.jar.from(project.sourceSets.main.output)
|
||||
project.merge.into.javadoc {
|
||||
source += project.javadoc.source
|
||||
classpath += project.javadoc.classpath
|
||||
}
|
||||
}
|
||||
|
||||
private void setupMaven(Project project) {
|
||||
project.configurations.each { configuration ->
|
||||
Conf2ScopeMapping mapping = project.conf2ScopeMappings.getMapping([configuration])
|
||||
if (mapping.scope) {
|
||||
Configuration intoConfiguration = project.merge.into.configurations.create(
|
||||
project.name + "-" + configuration.name)
|
||||
configuration.excludeRules.each {
|
||||
configuration.exclude([
|
||||
(ExcludeRule.GROUP_KEY) : it.group,
|
||||
(ExcludeRule.MODULE_KEY) : it.module])
|
||||
}
|
||||
configuration.dependencies.each {
|
||||
def intoCompile = project.merge.into.configurations.getByName("compile")
|
||||
// Protect against changing a compile scope dependency (SPR-10218)
|
||||
if (!intoCompile.dependencies.contains(it)) {
|
||||
intoConfiguration.dependencies.add(it)
|
||||
}
|
||||
}
|
||||
project.merge.into.install.repositories.mavenInstaller.pom.scopeMappings.addMapping(
|
||||
mapping.priority + 100, intoConfiguration, mapping.scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private postProcessProjects(Gradle gradle) {
|
||||
gradle.allprojects(new Action<Project>() {
|
||||
public void execute(Project project) {
|
||||
project.configurations.getByName("runtime").allDependencies.withType(ProjectDependency).each{
|
||||
Configuration dependsOnMergedFrom = it.dependencyProject.configurations.getByName("merging");
|
||||
dependsOnMergedFrom.dependencies.each{ dep ->
|
||||
project.dependencies.add("runtimeMerge", dep.dependencyProject)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MergeModel {
|
||||
Project into;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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.
|
||||
*/
|
||||
|
||||
package org.springframework.build.gradle
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ProjectDependency;
|
||||
|
||||
/**
|
||||
* Gradle plugin that automatically updates testCompile dependencies to include
|
||||
* the test source sets of project dependencies.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class TestSourceSetDependenciesPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.afterEvaluate {
|
||||
Set<ProjectDependency> projectDependencies = new LinkedHashSet<ProjectDependency>()
|
||||
collectProjectDependencies(projectDependencies, project)
|
||||
projectDependencies.each {
|
||||
project.dependencies.add("testCompile", it.dependencyProject.sourceSets.test.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectProjectDependencies(Set<ProjectDependency> projectDependencies, Project project) {
|
||||
for (def configurationName in ["compile", "optional", "provided", "testCompile"]) {
|
||||
Configuration configuration = project.getConfigurations().findByName(configurationName)
|
||||
if (configuration) {
|
||||
configuration.dependencies.findAll { it instanceof ProjectDependency }.each {
|
||||
projectDependencies.add(it)
|
||||
collectProjectDependencies(projectDependencies, it.dependencyProject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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("13.4.2");
|
||||
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/**", "**/spring-jcl/**", "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,50 +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;
|
||||
|
||||
import org.springframework.build.architecture.ArchitecturePlugin;
|
||||
|
||||
/**
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
* Conventions are applied in response to various plugins being applied.
|
||||
*
|
||||
* <p>When the {@link JavaBasePlugin} is applied, the conventions in {@link CheckstyleConventions},
|
||||
* {@link TestConventions} and {@link JavaConventions} are applied.
|
||||
* The {@link ArchitecturePlugin} plugin is also applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* are applied.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getExtensions().create("springFramework", SpringFrameworkExtension.class);
|
||||
new ArchitecturePlugin().apply(project);
|
||||
new CheckstyleConventions().apply(project);
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
new TestConventions().apply(project);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
|
||||
/**
|
||||
* The Java version we should use as the JVM baseline for building the project.
|
||||
* <p>NOTE: If you update this value, you should also update the value used in
|
||||
* the {@code javadoc} task in {@code framework-api.gradle}.
|
||||
*/
|
||||
private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(25);
|
||||
|
||||
/**
|
||||
* The Java version we should use as the baseline for the compiled bytecode
|
||||
* (the "-release" compiler argument).
|
||||
*/
|
||||
private static final JavaLanguageVersion DEFAULT_RELEASE_VERSION = JavaLanguageVersion.of(17);
|
||||
|
||||
static {
|
||||
List<String> commonCompilerArgs = List.of(
|
||||
"-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(List.of(
|
||||
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
|
||||
"-Xlint:unchecked", "-Werror"
|
||||
));
|
||||
TEST_COMPILER_ARGS = new ArrayList<>();
|
||||
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
TEST_COMPILER_ARGS.addAll(List.of("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||
"-Xlint:-deprecation", "-Xlint:-unchecked"));
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> {
|
||||
applyToolchainConventions(project);
|
||||
applyJavaCompileConventions(project);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the Toolchain support for the project.
|
||||
* @param project the current project
|
||||
*/
|
||||
private static void applyToolchainConventions(Project project) {
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getLanguageVersion().set(DEFAULT_LANGUAGE_VERSION);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the common Java compiler options for main sources, test fixture sources, and
|
||||
* test sources.
|
||||
* @param project the current project
|
||||
*/
|
||||
private void applyJavaCompileConventions(Project project) {
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* We should pick the {@link #DEFAULT_RELEASE_VERSION} for all compiled classes,
|
||||
* unless the current task is compiling multi-release JAR code with a higher version.
|
||||
*/
|
||||
private void setJavaRelease(JavaCompile task) {
|
||||
int defaultVersion = DEFAULT_RELEASE_VERSION.asInt();
|
||||
int releaseVersion = defaultVersion;
|
||||
int compilerVersion = task.getJavaCompiler().get().getMetadata().getLanguageVersion().asInt();
|
||||
for (int version = defaultVersion ; version <= compilerVersion ; version++) {
|
||||
if (task.getName().contains("Java" + version)) {
|
||||
releaseVersion = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
task.getOptions().getRelease().set(releaseVersion);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.jetbrains.dokka.gradle.DokkaExtension;
|
||||
import org.jetbrains.dokka.gradle.DokkaPlugin;
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion;
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class KotlinConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm", plugin -> {
|
||||
project.getTasks().withType(KotlinCompile.class, this::configure);
|
||||
if (project.getLayout().getProjectDirectory().dir("src/main/kotlin").getAsFile().exists()) {
|
||||
project.getPlugins().apply(DokkaPlugin.class);
|
||||
project.getExtensions().configure(DokkaExtension.class, dokka -> configure(project, dokka));
|
||||
project.project(":framework-api").getDependencies().add("dokka", project);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void configure(KotlinCompile compile) {
|
||||
compile.compilerOptions(options -> {
|
||||
options.getApiVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getLanguageVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getJvmTarget().set(JvmTarget.JVM_17);
|
||||
options.getJavaParameters().set(true);
|
||||
options.getAllWarningsAsErrors().set(true);
|
||||
options.getFreeCompilerArgs().addAll(
|
||||
"-Xsuppress-version-warnings",
|
||||
"-Xjsr305=strict", // For dependencies using JSR 305
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void configure(Project project, DokkaExtension dokka) {
|
||||
dokka.getDokkaSourceSets().forEach(sourceSet -> {
|
||||
sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin"));
|
||||
sourceSet.getClasspath()
|
||||
.from(project.getExtensions()
|
||||
.getByType(SourceSetContainer.class)
|
||||
.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
|
||||
.getOutput());
|
||||
var externalDocumentationLinks = sourceSet.getExternalDocumentationLinks();
|
||||
var springVersion = project.getVersion();
|
||||
externalDocumentationLinks.register("spring-framework", spec -> {
|
||||
spec.url("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/");
|
||||
spec.packageListUrl("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/element-list");
|
||||
});
|
||||
externalDocumentationLinks.register("reactor-core", spec ->
|
||||
spec.url("https://projectreactor.io/docs/core/release/api/"));
|
||||
externalDocumentationLinks.register("reactive-streams", spec ->
|
||||
spec.url("https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/"));
|
||||
externalDocumentationLinks.register("kotlinx-coroutines", spec ->
|
||||
spec.url("https://kotlinlang.org/api/kotlinx.coroutines/"));
|
||||
externalDocumentationLinks.register("hamcrest", spec ->
|
||||
spec.url("https://javadoc.io/doc/org.hamcrest/hamcrest/2.1/"));
|
||||
externalDocumentationLinks.register("jakarta-servlet", spec -> {
|
||||
spec.url("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/");
|
||||
spec.packageListUrl("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list");
|
||||
});
|
||||
externalDocumentationLinks.register("rsocket-core", spec ->
|
||||
spec.url("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.process.CommandLineArgumentProvider;
|
||||
|
||||
public class SpringFrameworkExtension {
|
||||
|
||||
private final Property<Boolean> enableJavaPreviewFeatures;
|
||||
|
||||
public SpringFrameworkExtension(Project project) {
|
||||
this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class);
|
||||
project.getTasks().withType(JavaCompile.class).configureEach(javaCompile ->
|
||||
javaCompile.getOptions().getCompilerArgumentProviders().add(asArgumentProvider()));
|
||||
project.getTasks().withType(Test.class).configureEach(test ->
|
||||
test.getJvmArgumentProviders().add(asArgumentProvider()));
|
||||
|
||||
}
|
||||
|
||||
public Property<Boolean> getEnableJavaPreviewFeatures() {
|
||||
return this.enableJavaPreviewFeatures;
|
||||
}
|
||||
|
||||
private CommandLineArgumentProvider asArgumentProvider() {
|
||||
return () -> {
|
||||
if (getEnableJavaPreviewFeatures().getOrElse(false)) {
|
||||
return List.of("--enable-preview");
|
||||
}
|
||||
return Collections.emptyList();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,109 +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.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.api.tasks.testing.TestFrameworkOptions;
|
||||
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
|
||||
import org.gradle.testretry.TestRetryPlugin;
|
||||
import org.gradle.testretry.TestRetryTaskExtension;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
|
||||
* plugin is applied:
|
||||
* <ul>
|
||||
* <li>The {@link TestRetryPlugin Test Retry} plugin is applied so that flaky tests
|
||||
* are retried 3 times when running on the CI server.
|
||||
* <li>Common test properties are configured
|
||||
* <li>The ByteBuddy Java agent is configured on test tasks.
|
||||
* </ul>
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @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) {
|
||||
configureByteBuddyAgent(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",
|
||||
"junit.platform.discovery.issue.severity.critical", "INFO"
|
||||
));
|
||||
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 configureByteBuddyAgent(Project project) {
|
||||
if (project.hasProperty("byteBuddyVersion")) {
|
||||
String byteBuddyVersion = (String) project.getProperties().get("byteBuddyVersion");
|
||||
Configuration byteBuddyAgentConfig = project.getConfigurations().create("byteBuddyAgentConfig");
|
||||
byteBuddyAgentConfig.setTransitive(false);
|
||||
Dependency byteBuddyAgent = project.getDependencies().create("net.bytebuddy:byte-buddy-agent:" + byteBuddyVersion);
|
||||
byteBuddyAgentConfig.getDependencies().add(byteBuddyAgent);
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().withType(Test.class, test -> test
|
||||
.jvmArgs("-javaagent:" + byteBuddyAgentConfig.getAsPath()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
testRetry.getFailOnPassedAfterRetry().set(true);
|
||||
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isCi() {
|
||||
return Boolean.parseBoolean(System.getenv("CI"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.EvaluationResult;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.SkipWhenEmpty;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import static org.springframework.build.architecture.ArchitectureRules.allPackagesShouldBeFreeOfTangles;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.classesShouldNotImportForbiddenTypes;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.javaClassesShouldNotImportKotlinAnnotations;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToLowerCaseWithoutLocale;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToUpperCaseWithoutLocale;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks for architecture problems.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public abstract class ArchitectureCheck extends DefaultTask {
|
||||
|
||||
private FileCollection classes;
|
||||
|
||||
public ArchitectureCheck() {
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
getProhibitObjectsRequireNonNull().convention(true);
|
||||
getRules().addAll(classesShouldNotImportForbiddenTypes(),
|
||||
javaClassesShouldNotImportKotlinAnnotations(),
|
||||
allPackagesShouldBeFreeOfTangles(),
|
||||
noClassesShouldCallStringToLowerCaseWithoutLocale(),
|
||||
noClassesShouldCallStringToUpperCaseWithoutLocale());
|
||||
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void checkArchitecture() throws IOException {
|
||||
JavaClasses javaClasses = new ClassFileImporter()
|
||||
.importPaths(this.classes.getFiles().stream().map(File::toPath).toList());
|
||||
List<EvaluationResult> violations = getRules().get()
|
||||
.stream()
|
||||
.map((rule) -> rule.evaluate(javaClasses))
|
||||
.filter(EvaluationResult::hasViolation)
|
||||
.toList();
|
||||
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
||||
outputFile.getParentFile().mkdirs();
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder report = new StringBuilder();
|
||||
for (EvaluationResult violation : violations) {
|
||||
report.append(violation.getFailureReport());
|
||||
report.append(String.format("%n"));
|
||||
}
|
||||
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
throw new GradleException("Architecture check failed. See '" + outputFile + "' for details.");
|
||||
}
|
||||
else {
|
||||
outputFile.createNewFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void setClasses(FileCollection classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public FileCollection getClasses() {
|
||||
return this.classes;
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
@SkipWhenEmpty
|
||||
@IgnoreEmptyDirectories
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
final FileTree getInputClasses() {
|
||||
return this.classes.getAsFileTree();
|
||||
}
|
||||
|
||||
@Optional
|
||||
@InputFiles
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract DirectoryProperty getResourcesDirectory();
|
||||
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDirectory();
|
||||
|
||||
@Internal
|
||||
public abstract ListProperty<ArchRule> getRules();
|
||||
|
||||
@Internal
|
||||
public abstract Property<Boolean> getProhibitObjectsRequireNonNull();
|
||||
|
||||
@Input
|
||||
// The rules themselves can't be an input as they aren't serializable so we use
|
||||
// their descriptions instead
|
||||
abstract ListProperty<String> getRuleDescriptions();
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
/**
|
||||
* {@link Plugin} for verifying a project's architecture.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ArchitecturePlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerTasks(project));
|
||||
}
|
||||
|
||||
private void registerTasks(Project project) {
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
List<TaskProvider<ArchitectureCheck>> architectureChecks = new ArrayList<>();
|
||||
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
|
||||
if (sourceSet.getName().contains("test")) {
|
||||
// skip test source sets.
|
||||
continue;
|
||||
}
|
||||
TaskProvider<ArchitectureCheck> checkArchitecture = project.getTasks()
|
||||
.register(taskName(sourceSet), ArchitectureCheck.class,
|
||||
(task) -> {
|
||||
task.setClasses(sourceSet.getOutput().getClassesDirs());
|
||||
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
|
||||
task.dependsOn(sourceSet.getProcessResourcesTaskName());
|
||||
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
|
||||
+ " source set.");
|
||||
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
});
|
||||
architectureChecks.add(checkArchitecture);
|
||||
}
|
||||
if (!architectureChecks.isEmpty()) {
|
||||
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
|
||||
checkTask.configure((check) -> check.dependsOn(architectureChecks));
|
||||
}
|
||||
}
|
||||
|
||||
private static String taskName(SourceSet sourceSet) {
|
||||
return "checkArchitecture"
|
||||
+ sourceSet.getName().substring(0, 1).toUpperCase()
|
||||
+ sourceSet.getName().substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import com.tngtech.archunit.base.DescribedPredicate;
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
|
||||
import com.tngtech.archunit.library.dependencies.SliceAssignment;
|
||||
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
|
||||
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||
import java.util.List;
|
||||
|
||||
abstract class ArchitectureRules {
|
||||
|
||||
static ArchRule allPackagesShouldBeFreeOfTangles() {
|
||||
return SlicesRuleDefinition.slices()
|
||||
.assignedFrom(new SpringSlices()).should().beFreeOfCycles();
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToLowerCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toLowerCase")
|
||||
.because("String.toLowerCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToUpperCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toUpperCase")
|
||||
.because("String.toUpperCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule classesShouldNotImportForbiddenTypes() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("reactor.core.support.Assert")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.slf4j.LoggerFactory")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.NonNull")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.Nullable");
|
||||
}
|
||||
|
||||
static ArchRule javaClassesShouldNotImportKotlinAnnotations() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.that(new DescribedPredicate<JavaClass>("is not a Kotlin class") {
|
||||
@Override
|
||||
public boolean test(JavaClass javaClass) {
|
||||
return javaClass.getSourceCodeLocation()
|
||||
.getSourceFileName().endsWith(".java");
|
||||
}
|
||||
}
|
||||
)
|
||||
.should().dependOnClassesThat()
|
||||
.resideInAnyPackage("org.jetbrains.annotations..")
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
static class SpringSlices implements SliceAssignment {
|
||||
|
||||
private final List<String> ignoredPackages = List.of("org.springframework.asm",
|
||||
"org.springframework.cglib",
|
||||
"org.springframework.javapoet",
|
||||
"org.springframework.objenesis");
|
||||
|
||||
@Override
|
||||
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
|
||||
|
||||
String packageName = javaClass.getPackageName();
|
||||
for (String ignoredPackage : ignoredPackages) {
|
||||
if (packageName.startsWith(ignoredPackage)) {
|
||||
return SliceIdentifier.ignore();
|
||||
}
|
||||
}
|
||||
return SliceIdentifier.of("spring framework");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Spring Framework Slices";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.attributes.LibraryElements;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
/**
|
||||
* @author Cedric Champeau
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public abstract class MultiReleaseExtension {
|
||||
private final TaskContainer tasks;
|
||||
private final SourceSetContainer sourceSets;
|
||||
private final DependencyHandler dependencies;
|
||||
private final ObjectFactory objects;
|
||||
private final ConfigurationContainer configurations;
|
||||
|
||||
@Inject
|
||||
public MultiReleaseExtension(SourceSetContainer sourceSets,
|
||||
ConfigurationContainer configurations,
|
||||
TaskContainer tasks,
|
||||
DependencyHandler dependencies,
|
||||
ObjectFactory objectFactory) {
|
||||
this.sourceSets = sourceSets;
|
||||
this.configurations = configurations;
|
||||
this.tasks = tasks;
|
||||
this.dependencies = dependencies;
|
||||
this.objects = objectFactory;
|
||||
}
|
||||
|
||||
public void releaseVersions(int... javaVersions) {
|
||||
releaseVersions("src/main/", "src/test/", javaVersions);
|
||||
}
|
||||
|
||||
private void releaseVersions(String mainSourceDirectory, String testSourceDirectory, int... javaVersions) {
|
||||
for (int javaVersion : javaVersions) {
|
||||
addLanguageVersion(javaVersion, mainSourceDirectory, testSourceDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private void addLanguageVersion(int javaVersion, String mainSourceDirectory, String testSourceDirectory) {
|
||||
String javaN = "java" + javaVersion;
|
||||
|
||||
SourceSet langSourceSet = sourceSets.create(javaN, srcSet -> srcSet.getJava().srcDir(mainSourceDirectory + javaN));
|
||||
SourceSet testSourceSet = sourceSets.create(javaN + "Test", srcSet -> srcSet.getJava().srcDir(testSourceDirectory + javaN));
|
||||
SourceSet sharedSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||
SourceSet sharedTestSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME);
|
||||
|
||||
FileCollection mainClasses = objects.fileCollection().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs());
|
||||
dependencies.add(javaN + "Implementation", mainClasses);
|
||||
|
||||
tasks.named(langSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||
task.getOptions().getRelease().set(javaVersion)
|
||||
);
|
||||
tasks.named(testSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||
task.getOptions().getRelease().set(javaVersion)
|
||||
);
|
||||
|
||||
TaskProvider<Test> testTask = createTestTask(javaVersion, testSourceSet, sharedTestSourceSet, langSourceSet, sharedSourceSet);
|
||||
tasks.named("check", task -> task.dependsOn(testTask));
|
||||
|
||||
configureMultiReleaseJar(javaVersion, langSourceSet);
|
||||
}
|
||||
|
||||
private TaskProvider<Test> createTestTask(int javaVersion, SourceSet testSourceSet, SourceSet sharedTestSourceSet, SourceSet langSourceSet, SourceSet sharedSourceSet) {
|
||||
Configuration testImplementation = configurations.getByName(testSourceSet.getImplementationConfigurationName());
|
||||
testImplementation.extendsFrom(configurations.getByName(sharedTestSourceSet.getImplementationConfigurationName()));
|
||||
Configuration testCompileOnly = configurations.getByName(testSourceSet.getCompileOnlyConfigurationName());
|
||||
testCompileOnly.extendsFrom(configurations.getByName(sharedTestSourceSet.getCompileOnlyConfigurationName()));
|
||||
testCompileOnly.getDependencies().add(dependencies.create(langSourceSet.getOutput().getClassesDirs()));
|
||||
testCompileOnly.getDependencies().add(dependencies.create(sharedSourceSet.getOutput().getClassesDirs()));
|
||||
|
||||
Configuration testRuntimeClasspath = configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName());
|
||||
// so here's the deal. MRjars are JARs! Which means that to execute tests, we need
|
||||
// the JAR on classpath, not just classes + resources as Gradle usually does
|
||||
testRuntimeClasspath.getAttributes()
|
||||
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
|
||||
|
||||
TaskProvider<Test> testTask = tasks.register("java" + javaVersion + "Test", Test.class, test -> {
|
||||
test.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
|
||||
ConfigurableFileCollection testClassesDirs = objects.fileCollection();
|
||||
testClassesDirs.from(testSourceSet.getOutput());
|
||||
testClassesDirs.from(sharedTestSourceSet.getOutput());
|
||||
test.setTestClassesDirs(testClassesDirs);
|
||||
ConfigurableFileCollection classpath = objects.fileCollection();
|
||||
// must put the MRJar first on classpath
|
||||
classpath.from(tasks.named("jar"));
|
||||
// then we put the specific test sourceset tests, so that we can override
|
||||
// the shared versions
|
||||
classpath.from(testSourceSet.getOutput());
|
||||
|
||||
// then we add the shared tests
|
||||
classpath.from(sharedTestSourceSet.getRuntimeClasspath());
|
||||
test.setClasspath(classpath);
|
||||
});
|
||||
return testTask;
|
||||
}
|
||||
|
||||
private void configureMultiReleaseJar(int version, SourceSet languageSourceSet) {
|
||||
tasks.named("jar", Jar.class, jar -> {
|
||||
jar.into("META-INF/versions/" + version, s -> s.from(languageSourceSet.getOutput()));
|
||||
Attributes attributes = jar.getManifest().getAttributes();
|
||||
attributes.put("Multi-Release", "true");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.plugins.ExtensionContainer;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
/**
|
||||
* A plugin which adds support for building multi-release jars
|
||||
* with Gradle.
|
||||
* @author Cedric Champeau
|
||||
* @author Brian Clozel
|
||||
* @see <a href="https://github.com/melix/mrjar-gradle-plugin">original project</a>
|
||||
*/
|
||||
public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||
|
||||
public static String VALIDATE_JAR_TASK_NAME = "validateMultiReleaseJar";
|
||||
|
||||
@Inject
|
||||
protected JavaToolchainService getToolchains() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply(JavaPlugin.class);
|
||||
ExtensionContainer extensions = project.getExtensions();
|
||||
JavaPluginExtension javaPluginExtension = extensions.getByType(JavaPluginExtension.class);
|
||||
ConfigurationContainer configurations = project.getConfigurations();
|
||||
TaskContainer tasks = project.getTasks();
|
||||
DependencyHandler dependencies = project.getDependencies();
|
||||
ObjectFactory objects = project.getObjects();
|
||||
extensions.create("multiRelease", MultiReleaseExtension.class,
|
||||
javaPluginExtension.getSourceSets(),
|
||||
configurations,
|
||||
tasks,
|
||||
dependencies,
|
||||
objects);
|
||||
|
||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.CacheableTask;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@CacheableTask
|
||||
public abstract class MultiReleaseJarValidateTask extends JavaExec {
|
||||
|
||||
|
||||
public MultiReleaseJarValidateTask() {
|
||||
getMainModule().set("jdk.jartool");
|
||||
getArgumentProviders().add(() -> List.of("--validate", "--file", getJar().get().getAsFile().getAbsolutePath()));
|
||||
}
|
||||
|
||||
@Inject
|
||||
protected abstract JavaToolchainService getJavaToolchainService();
|
||||
|
||||
@InputFile
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract RegularFileProperty getJar();
|
||||
|
||||
}
|
||||
@@ -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
|
||||
public 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=org.springframework.build.gradle.DetectSplitPackagesPlugin
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=org.springframework.build.gradle.MergePlugin
|
||||
@@ -0,0 +1 @@
|
||||
implementation-class=org.springframework.build.gradle.TestSourceSetDependenciesPlugin
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link MultiReleaseJarPlugin}
|
||||
*/
|
||||
public class MultiReleaseJarPluginTests {
|
||||
|
||||
private File projectDir;
|
||||
|
||||
private File buildFile;
|
||||
|
||||
private File propertiesFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
this.buildFile = new File(this.projectDir, "build.gradle");
|
||||
this.propertiesFile = new File(this.projectDir, "gradle.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureSourceSets() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
multiRelease { releaseVersions 21, 24 }
|
||||
task printSourceSets {
|
||||
doLast {
|
||||
sourceSets.all { println it.name }
|
||||
}
|
||||
}
|
||||
""");
|
||||
BuildResult buildResult = runGradle("printSourceSets");
|
||||
assertThat(buildResult.getOutput()).contains("main", "test", "java21", "java21Test", "java24", "java24Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureToolchainReleaseVersion() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
multiRelease { releaseVersions 21 }
|
||||
task printReleaseVersion {
|
||||
doLast {
|
||||
tasks.all { println it.name }
|
||||
tasks.named("compileJava21Java") {
|
||||
println "compileJava21Java releaseVersion: ${it.options.release.get()}"
|
||||
}
|
||||
tasks.named("compileJava21TestJava") {
|
||||
println "compileJava21TestJava releaseVersion: ${it.options.release.get()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
BuildResult buildResult = runGradle("printReleaseVersion");
|
||||
assertThat(buildResult.getOutput()).contains("compileJava21Java releaseVersion: 21")
|
||||
.contains("compileJava21TestJava releaseVersion: 21");
|
||||
}
|
||||
|
||||
@Test
|
||||
void packageInJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
version = '1.2.3'
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {}
|
||||
""");
|
||||
BuildResult buildResult = runGradle("assemble");
|
||||
File file = new File(this.projectDir, "/build/libs/" + this.projectDir.getName() + "-1.2.3.jar");
|
||||
assertThat(file).exists();
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
Attributes mainAttributes = jar.getManifest().getMainAttributes();
|
||||
assertThat(mainAttributes.getValue("Multi-Release")).isEqualTo("true");
|
||||
|
||||
assertThat(jar.entries().asIterator()).toIterable()
|
||||
.anyMatch(entry -> entry.getName().equals("META-INF/versions/17/Main.class"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
|
||||
void validateJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
version = '1.2.3'
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.release = 11
|
||||
}
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeGradleProperties("""
|
||||
org.gradle.jvmargs=-Duser.language=en
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {
|
||||
|
||||
public void method() {}
|
||||
|
||||
}
|
||||
""");
|
||||
writeClass("src/main/java", "Main.java", """
|
||||
public class Main {}
|
||||
""");
|
||||
assertThatThrownBy(() ->runGradle("validateMultiReleaseJar"))
|
||||
.isInstanceOf(UnexpectedBuildFailure.class)
|
||||
.hasMessageContaining("entry: META-INF/versions/17/Main.class, contains a class with different api from earlier version");
|
||||
}
|
||||
|
||||
private void writeBuildFile(String buildContent) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
|
||||
out.print(buildContent);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGradleProperties(String properties) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
|
||||
out.print(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClass(String path, String fileName, String fileContent) throws IOException {
|
||||
Path folder = this.projectDir.toPath().resolve(path);
|
||||
Files.createDirectories(folder);
|
||||
Path filePath = folder.resolve(fileName);
|
||||
Files.createFile(filePath);
|
||||
Files.writeString(filePath, fileContent);
|
||||
}
|
||||
|
||||
private BuildResult runGradle(String... args) {
|
||||
return GradleRunner.create().withProjectDir(this.projectDir).withArguments(args).withPluginClasspath().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
plugins {
|
||||
id 'java-platform'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
id 'org.jetbrains.dokka'
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
def springAspectsOutput = project(":spring-aspects").sourceSets.main.output
|
||||
javadoc {
|
||||
javadocTool.set(javaToolchains.javadocToolFor({
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}))
|
||||
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
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)
|
||||
// Change modularity mismatch from warn to info.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
addStringOption("-link-modularity-mismatch", "info")
|
||||
// Fail build on Javadoc warnings.
|
||||
addBooleanOption('Werror', true)
|
||||
}
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
classpath += files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
}
|
||||
|
||||
dokka {
|
||||
moduleName = "spring-framework"
|
||||
dokkaPublications.html {
|
||||
outputDirectory = project.java.docsDir.dir("kdoc-api")
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all Java docs (javadoc & kdoc) into a single archive
|
||||
*/
|
||||
tasks.register('docsZip', Zip) {
|
||||
dependsOn = ['javadoc', 'dokkaGenerate']
|
||||
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(project.java.docsDir.dir("kdoc-api")) {
|
||||
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: include all releases from 6.2.0 to 9.*.*.
|
||||
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
|
||||
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.26/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,98 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
|
||||
|
||||
plugins {
|
||||
id 'kotlin'
|
||||
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
|
||||
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,
|
||||
]
|
||||
}
|
||||
|
||||
node {
|
||||
version = '24.15.0'
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
// To avoid a redeclaration error with Kotlin compiler and set the JVM target
|
||||
tasks.withType(KotlinCompilationTask.class).configureEach {
|
||||
javaSources.from = []
|
||||
compilerOptions.jvmTarget = JvmTarget.JVM_17
|
||||
compilerOptions.freeCompilerArgs.addAll(
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-aspects"))
|
||||
implementation(project(":spring-context"))
|
||||
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.github.ben-manes.caffeine:caffeine")
|
||||
implementation("com.mchange:c3p0:0.9.5.5")
|
||||
implementation("com.oracle.database.jdbc:ojdbc11")
|
||||
implementation("io.micrometer:context-propagation")
|
||||
implementation("io.projectreactor.netty:reactor-netty-http")
|
||||
implementation("jakarta.jms:jakarta.jms-api")
|
||||
implementation("jakarta.servlet:jakarta.servlet-api")
|
||||
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.apache.groovy:groovy-templates")
|
||||
implementation("org.aspectj:aspectjweaver")
|
||||
implementation("org.assertj:assertj-core")
|
||||
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
implementation("org.freemarker:freemarker")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
implementation("tools.jackson.core:jackson-databind")
|
||||
implementation("tools.jackson.dataformat:jackson-dataformat-xml")
|
||||
}
|
||||
|
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: 158 KiB |
@@ -1,625 +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"
|
||||
font-family="Helvetica, Arial, sans-serif"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mvc-splitted-contexts.svg"
|
||||
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
|
||||
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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-opacity:1;fill:#333333;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:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;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:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;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:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;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:#333333;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:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;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:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#F8F9FA;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#333333;font-family:Helvetica, Arial, sans-serif;font-size:2.50001em;font-weight:bold}
|
||||
.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:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6599"
|
||||
width="382.68423"
|
||||
height="146.09897"
|
||||
x="87.884865"
|
||||
y="148.26482"
|
||||
rx="8" /><v:userDefs><v:ud
|
||||
v:nameU="SchemeName"
|
||||
v:val="VT4(Default)" /></v:userDefs><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5725"
|
||||
width="464.31128"
|
||||
height="374.11411"
|
||||
x="41.684383"
|
||||
y="112.3262"
|
||||
rx="8" /><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:#F8F9FA;fill-opacity:1;fill-rule:evenodd;stroke:#6db33f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5715"
|
||||
width="322.8194"
|
||||
height="43.63184"
|
||||
x="119.95335"
|
||||
y="-135.66222"
|
||||
transform="scale(1,-1)"
|
||||
rx="8" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
id="text5717"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5719"
|
||||
x="172.843"
|
||||
y="124.32391"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">DispatcherServlet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
id="text6589"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6591"
|
||||
x="106.770851"
|
||||
y="181.20923"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">Servlet WebApplicationContext</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
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:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555">(containing controllers, view resolvers,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="260.00443"
|
||||
y="212.83057"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
id="tspan6597">and other web-related beans)</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6620"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="114.52653"
|
||||
y="-259.43161"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6622"
|
||||
width="87.843979"
|
||||
height="36.697304"
|
||||
x="223.39864"
|
||||
y="-287.19809"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-264.81918"
|
||||
x="117.92834"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6614"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
id="text6616"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6618"
|
||||
x="124.24728"
|
||||
y="260.14957"
|
||||
style="font-size:11.53426838px;fill:#333333">Controllers</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
y="282.70709"
|
||||
x="221.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:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
id="text6630"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6632"
|
||||
x="332.51276"
|
||||
y="255.28464"
|
||||
style="font-size:11.53426838px;fill:#333333">HandlerMapping</tspan></text>
|
||||
<rect
|
||||
y="338.69724"
|
||||
x="87.803261"
|
||||
height="121.5683"
|
||||
width="382.84744"
|
||||
id="rect6634"
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="8" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6636"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333"
|
||||
y="376.61673"
|
||||
x="113.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:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan6644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
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:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6650"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="167.73116"
|
||||
y="-445.45563"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6652"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
y="448.55054"
|
||||
x="178.87148"
|
||||
id="tspan6654"
|
||||
sodipodi:role="line">Services</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6656"
|
||||
width="86.393044"
|
||||
height="36.704323"
|
||||
x="306.86328"
|
||||
y="-439.60837"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-444.99475"
|
||||
x="310.26624"
|
||||
height="36.701977"
|
||||
width="86.876686"
|
||||
id="rect6658"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
id="text6660"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6662"
|
||||
x="308.30771"
|
||||
y="448.55054"
|
||||
style="font-size:11.53426838px;fill:#333333">Repositories</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
|
||||
id="path7643"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
style="fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
|
||||
id="path7645"
|
||||
inkscape:connector-curvature="0" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
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:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
id="tspan4965">Delegates if no bean found</tspan></text>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 102 KiB |
@@ -1 +0,0 @@
|
||||
../../../src
|
||||
@@ -1,465 +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/java/programmatic-bean-registration.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/data-binding.adoc[]
|
||||
*** xref:core/validation/error-code-resolution.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/resilience.adoc[]
|
||||
** xref:core/null-safety.adoc[]
|
||||
** xref:core/databuffer-codec.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/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/mvc-range.adoc[]
|
||||
*** xref:web/webmvc/mvc-data-binding.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc-versioning.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webmvc/mvc-security.adoc[]
|
||||
*** xref:web/webmvc/mvc-caching.adoc[]
|
||||
*** 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/api-version.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-java.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-xml.adoc[]
|
||||
*** xref:web/webmvc/mvc-http2.adoc[]
|
||||
** 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-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/range.adoc[]
|
||||
*** xref:web/webflux/data-binding.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux-versioning.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webflux/security.adoc[]
|
||||
*** xref:web/webflux/caching.adoc[]
|
||||
*** 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-service-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/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/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/context-pausing.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** 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/resttestclient.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/aot-cache.adoc[]
|
||||
** xref:integration/checkpoint-restore.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-registration-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: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,156 +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.expression.maxOperations`
|
||||
| The default maximum number of operations permitted during
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
expression evaluation.
|
||||
|
||||
| `spring.getenv.ignore`
|
||||
| Instructs Spring to ignore operating system environment variables if a Spring
|
||||
`Environment` property -- for example, a placeholder in a configuration String -- isn't
|
||||
resolvable otherwise. See
|
||||
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.http.response.flush.enabled`
|
||||
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
|
||||
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
|
||||
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
|
||||
| `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.cache.pause`
|
||||
| The pause mode for the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context Pausing].
|
||||
|
||||
| `spring.test.context.failure.threshold`
|
||||
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
|
||||
in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
|
||||
|
||||
| `spring.test.extension.context.scope`
|
||||
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
|
||||
class hierarchies. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
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,222 +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, 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.
|
||||
|
||||
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.
|
||||
`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]
|
||||