mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34764252dc |
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
!!! For Security Vulnerabilities, please go to https://spring.io/security-policy !!!
|
||||
-->
|
||||
**Affects:** \<Spring Framework version>
|
||||
|
||||
---
|
||||
<!--
|
||||
Thanks for taking the time to create an issue. Please read the following:
|
||||
|
||||
- Questions should be asked on Stack Overflow.
|
||||
- For bugs, specify affected versions and explain what you are trying to do.
|
||||
- For enhancements, provide context and describe the problem.
|
||||
|
||||
Issue or Pull Request? Create only one, not both. GitHub treats them as the same.
|
||||
If unsure, start with an issue, and if you submit a pull request later, the
|
||||
issue will be closed as superseded.
|
||||
-->
|
||||
@@ -1,6 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Community Support
|
||||
url: https://stackoverflow.com/tags/spring
|
||||
about: Please ask and answer questions on StackOverflow with the tag `spring`.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: General
|
||||
about: Bugs, enhancements, documentation, tasks.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Have you considered asking for help on stackoverflow.com?
|
||||
|
||||
** Bug Reports **
|
||||
Please submit issues against OSS supported versions, see https://spring.io/projects/spring-framework#support
|
||||
Please provide a minimal sample application that reproduces the problem for faster issue triage.
|
||||
|
||||
** Enhancements requests **
|
||||
Before explaining how you would like things to work,
|
||||
please describe a concrete use case for this feature and how you have tried to solve this so far.
|
||||
|
||||
-->
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name: Await HTTP Resource
|
||||
description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)'
|
||||
description: Waits for an HTTP resource to be available (a HEAD request succeeds)
|
||||
inputs:
|
||||
url:
|
||||
description: 'URL of the resource to await'
|
||||
description: 'The URL of the resource to await'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
@@ -1,35 +1,31 @@
|
||||
name: 'Build'
|
||||
description: 'Builds the project, optionally publishing it to a local deployment repository'
|
||||
inputs:
|
||||
develocity-access-key:
|
||||
description: 'Access key for authentication with ge.spring.io'
|
||||
required: false
|
||||
java-distribution:
|
||||
description: 'Java distribution to use'
|
||||
required: false
|
||||
default: 'liberica'
|
||||
java-early-access:
|
||||
description: 'Whether the Java version is in early access'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-toolchain:
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-version:
|
||||
description: 'Java version to compile and test with'
|
||||
required: false
|
||||
default: '17'
|
||||
publish:
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
description: 'The Java version to compile and test with'
|
||||
java-early-access:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether the Java version is in early access'
|
||||
java-toolchain:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
publish:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
develocity-access-key:
|
||||
required: false
|
||||
description: 'The access key for authentication with ge.spring.io'
|
||||
outputs:
|
||||
build-scan-url:
|
||||
description: 'URL, if any, of the build scan produced by the build'
|
||||
description: 'The 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'
|
||||
description: 'The version that was built'
|
||||
value: ${{ steps.read-version.outputs.version }}
|
||||
runs:
|
||||
using: composite
|
||||
@@ -38,10 +34,9 @@ runs:
|
||||
uses: ./.github/actions/prepare-gradle-build
|
||||
with:
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
java-distribution: ${{ inputs.java-distribution }}
|
||||
java-version: ${{ inputs.java-version }}
|
||||
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' }}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
name: Create GitHub Release
|
||||
description: 'Create the release on GitHub with a changelog'
|
||||
description: Create the release on GitHub with a changelog
|
||||
inputs:
|
||||
milestone:
|
||||
description: 'Name of the GitHub milestone for which a release will be created'
|
||||
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'
|
||||
description: Token to use for authentication with GitHub
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
@@ -17,11 +13,11 @@ runs:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
|
||||
with:
|
||||
config-file: .github/actions/create-github-release/changelog-generator.yml
|
||||
milestone: ${{ inputs.milestone }}
|
||||
token: ${{ inputs.token }}
|
||||
config-file: .github/actions/create-github-release/changelog-generator.yml
|
||||
- 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' || '' }}
|
||||
shell: bash
|
||||
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
name: Prepare Gradle Build
|
||||
name: 'Prepare Gradle Build'
|
||||
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
|
||||
inputs:
|
||||
develocity-access-key:
|
||||
description: 'Access key for authentication with ge.spring.io'
|
||||
required: false
|
||||
java-distribution:
|
||||
description: 'Java distribution to use'
|
||||
required: false
|
||||
default: 'liberica'
|
||||
java-early-access:
|
||||
description: 'Whether the Java version is in early access. When true, forces java-distribution to temurin'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-toolchain:
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-version:
|
||||
description: 'Java version to use for the build'
|
||||
required: false
|
||||
default: '17'
|
||||
description: 'The Java version to use for the build'
|
||||
java-early-access:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether the Java version is in early access'
|
||||
java-toolchain:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
develocity-access-key:
|
||||
required: false
|
||||
description: 'The access key for authentication with ge.spring.io'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || 'liberica' }}
|
||||
java-version: |
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Print JVM thread dumps
|
||||
description: 'Prints a thread dump for all running JVMs'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do
|
||||
jcmd $jvm_pid Thread.print
|
||||
done
|
||||
- if: ${{ runner.os == 'Windows' }}
|
||||
shell: powershell
|
||||
run: |
|
||||
foreach ($jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem)) {
|
||||
jcmd $jvm_pid Thread.print
|
||||
}
|
||||
@@ -1,39 +1,33 @@
|
||||
name: Send Notification
|
||||
description: 'Sends a Google Chat message as a notification of the job''s outcome'
|
||||
description: Sends a Google Chat message as a notification of the job's outcome
|
||||
inputs:
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
required: false
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
required: false
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Variables
|
||||
shell: bash
|
||||
- shell: bash
|
||||
run: |
|
||||
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
|
||||
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
|
||||
- name: Success Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'success' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
|
||||
- name: Failure Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'failure' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
|
||||
- name: Cancel Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'cancelled' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
name: Sync to Maven Central
|
||||
description: 'Syncs a release to Maven Central and waits for it to be available for use'
|
||||
description: Syncs a release to Maven Central and waits for it to be available for use
|
||||
inputs:
|
||||
jfrog-cli-config-token:
|
||||
description: 'Config token for the JFrog CLI'
|
||||
required: true
|
||||
ossrh-s01-staging-profile:
|
||||
description: 'Staging profile to use when syncing to Central'
|
||||
required: true
|
||||
ossrh-s01-token-password:
|
||||
description: 'Password for authentication with s01.oss.sonatype.org'
|
||||
spring-framework-version:
|
||||
description: 'The version of Spring Framework that is being synced to Central'
|
||||
required: true
|
||||
ossrh-s01-token-username:
|
||||
description: 'Username for authentication with s01.oss.sonatype.org'
|
||||
required: true
|
||||
spring-framework-version:
|
||||
description: 'Version of Spring Framework that is being synced to Central'
|
||||
ossrh-s01-token-password:
|
||||
description: 'Password for authentication with s01.oss.sonatype.org'
|
||||
required: true
|
||||
ossrh-s01-staging-profile:
|
||||
description: 'Staging profile to use when syncing to Central'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
|
||||
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
@@ -29,14 +29,14 @@ runs:
|
||||
- name: Sync
|
||||
uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1
|
||||
with:
|
||||
close: true
|
||||
create: true
|
||||
generate-checksums: true
|
||||
password: ${{ inputs.ossrh-s01-token-password }}
|
||||
release: true
|
||||
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
|
||||
upload: true
|
||||
username: ${{ inputs.ossrh-s01-token-username }}
|
||||
password: ${{ inputs.ossrh-s01-token-password }}
|
||||
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
|
||||
create: true
|
||||
upload: true
|
||||
close: true
|
||||
release: true
|
||||
generate-checksums: true
|
||||
- name: Await
|
||||
uses: ./.github/actions/await-http-resource
|
||||
with:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Backport Bot
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
@@ -28,6 +29,6 @@ jobs:
|
||||
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
|
||||
- name: Backport
|
||||
env:
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
|
||||
|
||||
@@ -2,15 +2,15 @@ name: Build and Deploy Snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
name: Build and Deploy Snapshot
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
@@ -18,31 +18,31 @@ jobs:
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: 'spring-framework-6.1.x'
|
||||
repository: 'libs-snapshot-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: 'spring-framework-6.2.x'
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
- name: Send Notification
|
||||
if: always()
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
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:
|
||||
|
||||
@@ -1,36 +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: Set Up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: '17'
|
||||
- name: Check Out
|
||||
uses: actions/checkout@v4
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
- name: Build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
|
||||
- name: Print JVM Thread Dumps When Cancelled
|
||||
if: cancelled()
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
@@ -2,15 +2,15 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
timeout-minutes: 60
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
@@ -21,7 +21,10 @@ jobs:
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 22
|
||||
toolchain: true
|
||||
- version: 23
|
||||
early-access: true
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
@@ -41,16 +44,15 @@ jobs:
|
||||
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 }}
|
||||
java-early-access: ${{ matrix.java.early-access || 'false' }}
|
||||
java-toolchain: ${{ matrix.java.toolchain }}
|
||||
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
- name: Send Notification
|
||||
if: always()
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
status: ${{ job.status }}
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -14,15 +14,14 @@ permissions:
|
||||
actions: write
|
||||
jobs:
|
||||
build:
|
||||
name: Dispatch docs deployment
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
- name: Dispatch (partial build)
|
||||
if: github.ref_type == 'branch'
|
||||
env:
|
||||
|
||||
@@ -2,13 +2,13 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v6.2.[0-9]+
|
||||
- v6.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' }}
|
||||
name: Build and Stage Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
@@ -17,37 +17,37 @@ jobs:
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
repository: 'libs-staging-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: ${{ 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
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
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:
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
|
||||
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
|
||||
@@ -1,35 +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:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on Supported Branches
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.1.x' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: ${{ matrix.branch }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
antora-file-path: 'framework-docs/antora-playbook.yml'
|
||||
update-antora-ui-spring-docs-build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update on docs-build
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
with:
|
||||
docs-branch: 'docs-build'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,11 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push, pull_request]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validate Gradle Wrapper"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
@@ -2,27 +2,21 @@ name: Verify
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
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
|
||||
repository-password:
|
||||
required: false
|
||||
google-chat-webhook-url:
|
||||
required: true
|
||||
token:
|
||||
description: 'Token to use for authentication with GitHub'
|
||||
required: true
|
||||
jobs:
|
||||
verify:
|
||||
@@ -32,13 +26,13 @@ jobs:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'v0.0.2'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
ref: 'v0.0.2'
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: send-notification
|
||||
path: spring-framework
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
@@ -46,7 +40,7 @@ jobs:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
@@ -56,22 +50,22 @@ jobs:
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
- name: Run Release Verification Tests
|
||||
env:
|
||||
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
|
||||
RVT_VERSION: ${{ inputs.version }}
|
||||
RVT_RELEASE_TYPE: oss
|
||||
RVT_STAGING: ${{ inputs.staging }}
|
||||
RVT_VERSION: ${{ inputs.version }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
|
||||
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
|
||||
run: ./gradlew spring-framework-release-verification-tests:test
|
||||
- name: Upload Build Reports on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
- name: Send Notification
|
||||
uses: ./spring-framework/.github/actions/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 }}
|
||||
status: ${{ job.status }}
|
||||
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ multiple edits or corrections of the same logical change. See
|
||||
for an overview of streamlining the commit history.
|
||||
|
||||
1. Format commit messages using 55 characters for the subject line, 72 characters per line
|
||||
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
|
||||
for the description, followed by the issue fixed, e.g. `Closes gh-22276`. See the
|
||||
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
|
||||
for best practices around commit messages, and use `git log` to see some examples.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
@@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework/?tab=coc-ov-file#contributor-code-of-conduct). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
|
||||
## Access to Binaries
|
||||
|
||||
|
||||
+11
-5
@@ -1,10 +1,16 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
Please, [open a draft security advisory](https://github.com/spring-projects/security-advisories/security/advisories/new) if you need to disclose and discuss a security issue in private with the Spring Framework team. Note that we only accept reports against [supported versions](https://spring.io/projects/spring-framework#support).
|
||||
|
||||
For more details, check out our [security policy](https://spring.io/security-policy).
|
||||
# Security Policy
|
||||
|
||||
## JAR signing
|
||||
|
||||
Spring Framework JARs released on Maven Central are signed.
|
||||
You'll find more information about the key here: https://spring.io/GPG-KEY-spring.txt
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see the
|
||||
[Spring Framework Versions](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions)
|
||||
wiki page.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please see https://spring.io/security-policy.
|
||||
|
||||
+3
-4
@@ -2,14 +2,13 @@ plugins {
|
||||
id 'io.freefair.aspectj' version '8.4' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.9.20'
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "3.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -96,13 +95,13 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.11.3/api/",
|
||||
// "https://junit.org/junit5/docs/5.10.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.20.2");
|
||||
checkstyle.setToolVersion("10.17.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -13,7 +13,7 @@ content:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: ['main', '{6..9}.+({1..9}).x']
|
||||
branches: ['main', '{6..9}.+({0..9}).x']
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
@@ -36,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
|
||||
|
||||
@@ -21,7 +21,6 @@ asciidoc:
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
|
||||
include-xml: 'example$docs-src/main/resources/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
spring-site-cve: "{spring-site}/security"
|
||||
@@ -42,8 +41,7 @@ asciidoc:
|
||||
spring-framework-reference: '{spring-framework-docs-root}/{spring-version}/reference'
|
||||
#
|
||||
# Other Spring portfolio projects
|
||||
spring-boot-docs: '{docs-site}/spring-boot'
|
||||
spring-boot-docs-ref: '{spring-boot-docs}/reference'
|
||||
spring-boot-docs: '{docs-site}/spring-boot/docs/current/reference/html'
|
||||
spring-boot-issues: '{spring-github-org}/spring-boot/issues'
|
||||
# TODO add more projects / links or just build up on {docs-site}?
|
||||
# TODO rename the below using new conventions
|
||||
@@ -75,7 +73,6 @@ asciidoc:
|
||||
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'
|
||||
|
||||
@@ -42,31 +42,15 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-aspects"))
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-context-support"))
|
||||
api(project(":spring-jdbc"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-test"))
|
||||
api(project(":spring-web"))
|
||||
api(project(":spring-webflux"))
|
||||
api(project(":spring-webmvc"))
|
||||
api(project(":spring-websocket"))
|
||||
|
||||
api("com.fasterxml.jackson.core:jackson-databind")
|
||||
api("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
api("com.mchange:c3p0:0.9.5.5")
|
||||
api("com.oracle.database.jdbc:ojdbc11")
|
||||
api("io.projectreactor.netty:reactor-netty-http")
|
||||
api("jakarta.jms:jakarta.jms-api")
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
api("jakarta.resource:jakarta.resource-api")
|
||||
api("jakarta.validation:jakarta.validation-api")
|
||||
api("javax.cache:cache-api")
|
||||
api("org.apache.activemq:activemq-ra:6.1.2")
|
||||
api("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
api("org.aspectj:aspectjweaver")
|
||||
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
**** xref:core/expressions/language-ref/constructors.adoc[]
|
||||
**** xref:core/expressions/language-ref/variables.adoc[]
|
||||
**** xref:core/expressions/language-ref/functions.adoc[]
|
||||
**** xref:core/expressions/language-ref/varargs.adoc[]
|
||||
**** xref:core/expressions/language-ref/bean-references.adoc[]
|
||||
**** xref:core/expressions/language-ref/operator-ternary.adoc[]
|
||||
**** xref:core/expressions/language-ref/operator-elvis.adoc[]
|
||||
@@ -107,6 +106,87 @@
|
||||
*** xref:core/appendix/xsd-schemas.adoc[]
|
||||
*** xref:core/appendix/xml-custom.adoc[]
|
||||
*** xref:core/appendix/application-startup-steps.adoc[]
|
||||
* xref:testing.adoc[]
|
||||
** xref:testing/introduction.adoc[]
|
||||
** xref:testing/unit.adoc[]
|
||||
** xref:testing/integration.adoc[]
|
||||
** xref:testing/support-jdbc.adoc[]
|
||||
** xref:testing/testcontext-framework.adoc[]
|
||||
*** xref:testing/testcontext-framework/key-abstractions.adoc[]
|
||||
*** xref:testing/testcontext-framework/bootstrapping.adoc[]
|
||||
*** xref:testing/testcontext-framework/tel-config.adoc[]
|
||||
*** xref:testing/testcontext-framework/application-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/ctx-management.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
*** xref:testing/testcontext-framework/tx.adoc[]
|
||||
*** xref:testing/testcontext-framework/executing-sql.adoc[]
|
||||
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/spring-mvc-test-framework.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-static-imports.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-setup-options.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-setup-steps.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-performing-requests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-defining-expectations.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/async-requests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/vs-streaming-response.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-filters.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-resources.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-htmlunit.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/why.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/geb.adoc[]
|
||||
** xref:testing/spring-mvc-test-client.adoc[]
|
||||
** xref:testing/appendix.adoc[]
|
||||
*** xref:testing/annotations.adoc[]
|
||||
**** xref:testing/annotations/integration-standard.adoc[]
|
||||
**** xref:testing/annotations/integration-spring.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-bootstrapwith.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-commit.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-rollback.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-beforetransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-aftertransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sql.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
*** xref:testing/resources.adoc[]
|
||||
* xref:data-access.adoc[]
|
||||
** xref:data-access/transaction.adoc[]
|
||||
*** xref:data-access/transaction/motivation.adoc[]
|
||||
@@ -165,7 +245,6 @@
|
||||
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
|
||||
*** xref:web/webmvc/filters.adoc[]
|
||||
*** xref:web/webmvc/message-converters.adoc[]
|
||||
*** xref:web/webmvc/mvc-controller.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-requestmapping.adoc[]
|
||||
@@ -206,7 +285,6 @@
|
||||
**** xref:web/webmvc-view/mvc-freemarker.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-groovymarkup.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-script.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-fragments.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-jsp.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-feeds.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-document.adoc[]
|
||||
@@ -314,97 +392,6 @@
|
||||
** xref:web/webflux-test.adoc[]
|
||||
** xref:rsocket.adoc[]
|
||||
** xref:web/webflux-reactive-libraries.adoc[]
|
||||
* xref:testing.adoc[]
|
||||
** xref:testing/introduction.adoc[]
|
||||
** xref:testing/unit.adoc[]
|
||||
** xref:testing/integration.adoc[]
|
||||
** xref:testing/support-jdbc.adoc[]
|
||||
** xref:testing/testcontext-framework.adoc[]
|
||||
*** xref:testing/testcontext-framework/key-abstractions.adoc[]
|
||||
*** xref:testing/testcontext-framework/bootstrapping.adoc[]
|
||||
*** xref:testing/testcontext-framework/tel-config.adoc[]
|
||||
*** xref:testing/testcontext-framework/application-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/ctx-management.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
*** xref:testing/testcontext-framework/tx.adoc[]
|
||||
*** xref:testing/testcontext-framework/executing-sql.adoc[]
|
||||
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/mockmvc.adoc[]
|
||||
*** xref:testing/mockmvc/overview.adoc[]
|
||||
*** xref:testing/mockmvc/setup-options.adoc[]
|
||||
*** xref:testing/mockmvc/hamcrest.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/static-imports.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/setup.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/setup-steps.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/requests.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/expectations.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/async-requests.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/vs-streaming-response.adoc[]
|
||||
**** xref:testing/mockmvc/hamcrest/filters.adoc[]
|
||||
*** xref:testing/mockmvc/assertj.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/setup.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/requests.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/assertions.adoc[]
|
||||
**** xref:testing/mockmvc/assertj/integration.adoc[]
|
||||
*** xref:testing/mockmvc/htmlunit.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/why.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/mah.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/webdriver.adoc[]
|
||||
**** xref:testing/mockmvc/htmlunit/geb.adoc[]
|
||||
*** xref:testing/mockmvc/vs-end-to-end-integration-tests.adoc[]
|
||||
*** xref:testing/mockmvc/resources.adoc[]
|
||||
** xref:testing/spring-mvc-test-client.adoc[]
|
||||
** xref:testing/appendix.adoc[]
|
||||
*** xref:testing/annotations.adoc[]
|
||||
**** xref:testing/annotations/integration-standard.adoc[]
|
||||
**** xref:testing/annotations/integration-spring.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-bootstrapwith.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-commit.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-rollback.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-beforetransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-aftertransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sql.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
*** xref:testing/resources.adoc[]
|
||||
* xref:integration.adoc[]
|
||||
** xref:integration/rest-clients.adoc[]
|
||||
** xref:integration/jms.adoc[]
|
||||
@@ -452,6 +439,5 @@
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
|
||||
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
|
||||
* {spring-framework-wiki}[Wiki, window=_blank, role=link-external]
|
||||
* {spring-framework-wiki}[Wiki]
|
||||
|
||||
|
||||
@@ -23,12 +23,6 @@ The following table lists all currently supported Spring properties.
|
||||
|===
|
||||
| Name | Description
|
||||
|
||||
| `spring.aop.ajc.ignore`
|
||||
| Instructs Spring to ignore ajc-compiled aspects for Spring AOP proxying, restoring traditional
|
||||
Spring behavior for scenarios where both weaving and AspectJ auto-proxying are enabled. See
|
||||
{spring-framework-api}++/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.html#IGNORE_AJC_PROPERTY_NAME++[`AbstractAspectJAdvisorFactory`]
|
||||
for details.
|
||||
|
||||
| `spring.aot.enabled`
|
||||
| Indicates the application should run with AOT generated artifacts. See
|
||||
xref:core/aot.adoc[Ahead of Time Optimizations] and
|
||||
@@ -38,7 +32,7 @@ for details.
|
||||
| `spring.beaninfo.ignore`
|
||||
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
|
||||
JavaBeans `Introspector`. See
|
||||
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`StandardBeanInfoFactory`]
|
||||
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
|
||||
for details.
|
||||
|
||||
| `spring.cache.reactivestreams.ignore`
|
||||
@@ -55,13 +49,15 @@ for details.
|
||||
|
||||
| `spring.context.checkpoint`
|
||||
| Property that specifies a common context checkpoint. See
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
|
||||
checkpoint/restore at startup] and
|
||||
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#CHECKPOINT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
|
||||
for details.
|
||||
|
||||
| `spring.context.exit`
|
||||
| Property for terminating the JVM when the context reaches a specific phase. See
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
|
||||
checkpoint/restore at startup] and
|
||||
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
|
||||
for details.
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ arbitrary advice types. This section describes the basic concepts and standard a
|
||||
[[aop-api-advice-around]]
|
||||
=== Interception Around Advice
|
||||
|
||||
The most fundamental advice type in Spring is _interception around advice_.
|
||||
The most fundamental advice type in Spring is interception around advice.
|
||||
|
||||
Spring is compliant with the AOP Alliance interface for around advice that uses method
|
||||
interception. Classes that implement around advice should therefore implement the
|
||||
following `MethodInterceptor` interface from the `org.aopalliance.intercept` package:
|
||||
Spring is compliant with the AOP `Alliance` interface for around advice that uses method
|
||||
interception. Classes that implement `MethodInterceptor` and that implement around advice should also implement the
|
||||
following interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -49,8 +49,8 @@ following `MethodInterceptor` interface from the `org.aopalliance.intercept` pac
|
||||
|
||||
The `MethodInvocation` argument to the `invoke()` method exposes the method being
|
||||
invoked, the target join point, the AOP proxy, and the arguments to the method. The
|
||||
`invoke()` method should return the invocation's result: typically the return value of
|
||||
the join point.
|
||||
`invoke()` method should return the invocation's result: the return value of the join
|
||||
point.
|
||||
|
||||
The following example shows a simple `MethodInterceptor` implementation:
|
||||
|
||||
@@ -58,30 +58,30 @@ The following example shows a simple `MethodInterceptor` implementation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class DebugInterceptor implements MethodInterceptor {
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
System.out.println("Before: invocation=[" + invocation + "]");
|
||||
Object result = invocation.proceed();
|
||||
Object rval = invocation.proceed();
|
||||
System.out.println("Invocation returned");
|
||||
return result;
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class DebugInterceptor : MethodInterceptor {
|
||||
|
||||
override fun invoke(invocation: MethodInvocation): Any {
|
||||
println("Before: invocation=[$invocation]")
|
||||
val result = invocation.proceed()
|
||||
val rval = invocation.proceed()
|
||||
println("Invocation returned")
|
||||
return result
|
||||
return rval
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -105,7 +105,7 @@ currently define pointcut interfaces.
|
||||
[[aop-api-advice-before]]
|
||||
=== Before Advice
|
||||
|
||||
A simpler advice type is a _before advice_. This does not need a `MethodInvocation`
|
||||
A simpler advice type is a before advice. This does not need a `MethodInvocation`
|
||||
object, since it is called only before entering the method.
|
||||
|
||||
The main advantage of a before advice is that there is no need to invoke the `proceed()`
|
||||
@@ -122,6 +122,10 @@ The following listing shows the `MethodBeforeAdvice` interface:
|
||||
}
|
||||
----
|
||||
|
||||
(Spring's API design would allow for
|
||||
field before advice, although the usual objects apply to field interception and it is
|
||||
unlikely for Spring to ever implement it.)
|
||||
|
||||
Note that the return type is `void`. Before advice can insert custom behavior before the join
|
||||
point runs but cannot change the return value. If a before advice throws an
|
||||
exception, it stops further execution of the interceptor chain. The exception
|
||||
@@ -135,7 +139,7 @@ The following example shows a before advice in Spring, which counts all method i
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class CountingBeforeAdvice implements MethodBeforeAdvice {
|
||||
|
||||
@@ -153,7 +157,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CountingBeforeAdvice : MethodBeforeAdvice {
|
||||
|
||||
@@ -172,10 +176,10 @@ TIP: Before advice can be used with any pointcut.
|
||||
[[aop-api-advice-throws]]
|
||||
=== Throws Advice
|
||||
|
||||
_Throws advice_ is invoked after the return of the join point if the join point threw
|
||||
Throws advice is invoked after the return of the join point if the join point threw
|
||||
an exception. Spring offers typed throws advice. Note that this means that the
|
||||
`org.springframework.aop.ThrowsAdvice` interface does not contain any methods. It is a
|
||||
marker interface identifying that the given object implements one or more typed throws
|
||||
tag interface identifying that the given object implements one or more typed throws
|
||||
advice methods. These should be in the following form:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
@@ -185,16 +189,15 @@ advice methods. These should be in the following form:
|
||||
|
||||
Only the last argument is required. The method signatures may have either one or four
|
||||
arguments, depending on whether the advice method is interested in the method and
|
||||
arguments. The next two listings show classes that are examples of throws advice.
|
||||
arguments. The next two listing show classes that are examples of throws advice.
|
||||
|
||||
The following advice is invoked if a `RemoteException` is thrown (including subclasses of
|
||||
`RemoteException`):
|
||||
The following advice is invoked if a `RemoteException` is thrown (including from subclasses):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class RemoteThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
@@ -206,7 +209,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class RemoteThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
@@ -217,15 +220,15 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Unlike the preceding advice, the next example declares four arguments, so that it has
|
||||
access to the invoked method, method arguments, and target object. The following advice
|
||||
is invoked if a `ServletException` is thrown:
|
||||
Unlike the preceding
|
||||
advice, the next example declares four arguments, so that it has access to the invoked method, method
|
||||
arguments, and target object. The following advice is invoked if a `ServletException` is thrown:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
|
||||
|
||||
@@ -237,7 +240,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
|
||||
|
||||
@@ -256,7 +259,7 @@ methods can be combined in a single class. The following listing shows the final
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static class CombinedThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
@@ -272,7 +275,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CombinedThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
@@ -301,7 +304,7 @@ TIP: Throws advice can be used with any pointcut.
|
||||
[[aop-api-advice-after-returning]]
|
||||
=== After Returning Advice
|
||||
|
||||
An _after returning advice_ in Spring must implement the
|
||||
An after returning advice in Spring must implement the
|
||||
`org.springframework.aop.AfterReturningAdvice` interface, which the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
@@ -323,7 +326,7 @@ not thrown exceptions:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
|
||||
|
||||
@@ -342,7 +345,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CountingAfterReturningAdvice : AfterReturningAdvice {
|
||||
|
||||
@@ -365,7 +368,7 @@ TIP: After returning advice can be used with any pointcut.
|
||||
[[aop-api-advice-introduction]]
|
||||
=== Introduction Advice
|
||||
|
||||
Spring treats _introduction advice_ as a special kind of interception advice.
|
||||
Spring treats introduction advice as a special kind of interception advice.
|
||||
|
||||
Introduction requires an `IntroductionAdvisor` and an `IntroductionInterceptor` that
|
||||
implement the following interface:
|
||||
@@ -417,7 +420,7 @@ introduce the following interface to one or more objects:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public interface Lockable {
|
||||
void lock();
|
||||
@@ -428,7 +431,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
interface Lockable {
|
||||
fun lock()
|
||||
@@ -477,7 +480,7 @@ The following example shows the example `LockMixin` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
|
||||
|
||||
@@ -507,7 +510,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
|
||||
|
||||
@@ -553,7 +556,7 @@ The following example shows our `LockMixinAdvisor` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
|
||||
|
||||
@@ -565,7 +568,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
|
||||
----
|
||||
|
||||
@@ -10,7 +10,7 @@ following methods:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Advisor[] getAdvisors();
|
||||
|
||||
@@ -35,7 +35,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun getAdvisors(): Array<Advisor>
|
||||
|
||||
@@ -90,7 +90,7 @@ manipulating its advice:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Advised advised = (Advised) myObject;
|
||||
Advisor[] advisors = advised.getAdvisors();
|
||||
@@ -110,7 +110,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val advised = myObject as Advised
|
||||
val advisors = advised.advisors
|
||||
|
||||
@@ -196,16 +196,16 @@ follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Person person = (Person) factory.getBean("person");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val person = factory.getBean("person") as Person
|
||||
val person = factory.getBean("person") as Person;
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -128,7 +128,18 @@ the resulting pointcut is effectively the union of the specified patterns.)
|
||||
|
||||
The following example shows how to use `JdkRegexpMethodPointcut`:
|
||||
|
||||
include-code::./JdkRegexpConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulatePointcut"
|
||||
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
|
||||
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
|
||||
@@ -136,7 +147,21 @@ throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPoi
|
||||
Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both
|
||||
pointcut and advice, as the following example shows:
|
||||
|
||||
include-code::./RegexpConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulateAdvisor"
|
||||
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
|
||||
<property name="advice">
|
||||
<ref bean="beanNameOfAopAllianceInterceptor"/>
|
||||
</property>
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
|
||||
@@ -187,7 +212,7 @@ following example shows how to subclass `StaticMethodMatcherPointcut`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class TestStaticPointcut extends StaticMethodMatcherPointcut {
|
||||
|
||||
@@ -199,7 +224,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TestStaticPointcut : StaticMethodMatcherPointcut() {
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ interceptor and one advisor:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
|
||||
factory.addAdvice(myMethodInterceptor);
|
||||
@@ -22,7 +22,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val factory = ProxyFactory(myBusinessInterfaceImpl)
|
||||
factory.addAdvice(myMethodInterceptor)
|
||||
|
||||
@@ -38,7 +38,7 @@ You can change the target by using the `swap()` method on HotSwappableTargetSour
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
|
||||
Object oldTarget = swapper.swap(newTarget);
|
||||
@@ -46,7 +46,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
|
||||
val oldTarget = swapper.swap(newTarget)
|
||||
@@ -152,7 +152,7 @@ The cast is defined as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
|
||||
System.out.println("Max pool size is " + conf.getMaxSize());
|
||||
@@ -160,7 +160,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val conf = beanFactory.getBean("businessObject") as PoolingConfig
|
||||
println("Max pool size is " + conf.maxSize)
|
||||
|
||||
@@ -15,7 +15,7 @@ The basic usage for this class is very simple, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
|
||||
@@ -34,7 +34,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
val factory = AspectJProxyFactory(targetObject)
|
||||
|
||||
@@ -17,7 +17,7 @@ The following example uses an inline pointcut expression.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
@@ -34,7 +34,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
@@ -57,7 +57,7 @@ as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
@@ -74,7 +74,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
@@ -101,7 +101,7 @@ You can declare it by using the `@AfterReturning` annotation.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
@@ -118,7 +118,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
@@ -146,7 +146,7 @@ access, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
@@ -165,7 +165,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
@@ -204,7 +204,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
@@ -221,7 +221,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
@@ -247,7 +247,7 @@ The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
@@ -266,7 +266,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
@@ -311,7 +311,7 @@ purposes. The following example shows how to use after finally advice:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
@@ -328,7 +328,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.After
|
||||
@@ -417,7 +417,7 @@ The following example shows how to use around advice:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
@@ -438,7 +438,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Around
|
||||
@@ -500,7 +500,7 @@ You could write the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
public void validateAccount(Account account) {
|
||||
@@ -510,7 +510,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
fun validateAccount(account: Account) {
|
||||
@@ -533,7 +533,7 @@ from the advice. This would look as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private void accountDataAccessOperation(Account account) {}
|
||||
@@ -546,7 +546,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private fun accountDataAccessOperation(account: Account) {
|
||||
@@ -572,7 +572,7 @@ The following shows the definition of the `@Auditable` annotation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@@ -583,7 +583,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@@ -597,7 +597,7 @@ The following shows the advice that matches the execution of `@Auditable` method
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
public void audit(Auditable auditable) {
|
||||
@@ -609,7 +609,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
fun audit(auditable: Auditable) {
|
||||
@@ -630,7 +630,7 @@ you have a generic type like the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public interface Sample<T> {
|
||||
void sampleGenericMethod(T param);
|
||||
@@ -640,7 +640,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
interface Sample<T> {
|
||||
fun sampleGenericMethod(param: T)
|
||||
@@ -656,7 +656,7 @@ tying the advice parameter to the parameter type for which you want to intercept
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(MyType param) {
|
||||
@@ -666,7 +666,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: MyType) {
|
||||
@@ -682,7 +682,7 @@ pointcut as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(Collection<MyType> param) {
|
||||
@@ -692,7 +692,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: Collection<MyType>) {
|
||||
@@ -756,7 +756,7 @@ The following example shows how to use the `argNames` attribute:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -771,7 +771,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -794,7 +794,7 @@ point object, the `argNames` attribute does not need to include it:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -809,7 +809,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
@@ -833,7 +833,7 @@ the `argNames` attribute:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
public void audit(JoinPoint jp) {
|
||||
@@ -844,7 +844,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
fun audit(jp: JoinPoint) {
|
||||
@@ -867,7 +867,7 @@ The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
@@ -882,7 +882,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
@@ -923,7 +923,7 @@ Each of the distinct advice types of a particular aspect is conceptually meant t
|
||||
to the join point directly. As a consequence, an `@AfterThrowing` advice method is not
|
||||
supposed to receive an exception from an accompanying `@After`/`@AfterReturning` method.
|
||||
|
||||
Advice methods defined in the same `@Aspect` class that
|
||||
As of Spring Framework 5.2.7, advice methods defined in the same `@Aspect` class that
|
||||
need to run at the same join point are assigned precedence based on their advice type in
|
||||
the following order, from highest to lowest precedence: `@Around`, `@Before`, `@After`,
|
||||
`@AfterReturning`, `@AfterThrowing`. Note, however, that an `@After` advice method will
|
||||
|
||||
@@ -8,8 +8,54 @@ determines that a bean is advised by one or more aspects, it automatically gener
|
||||
a proxy for that bean to intercept method invocations and ensures that advice is run
|
||||
as needed.
|
||||
|
||||
The @AspectJ support can be enabled with programmatic or XML configuration. In either
|
||||
case, you also need to ensure that AspectJ's `org.aspectj:aspectjweaver` library is on the
|
||||
classpath of your application (version 1.9 or later).
|
||||
The @AspectJ support can be enabled with XML- or Java-style configuration. In either
|
||||
case, you also need to ensure that AspectJ's `aspectjweaver.jar` library is on the
|
||||
classpath of your application (version 1.9 or later). This library is available in the
|
||||
`lib` directory of an AspectJ distribution or from the Maven Central repository.
|
||||
|
||||
|
||||
[[aop-enable-aspectj-java]]
|
||||
== Enabling @AspectJ Support with Java Configuration
|
||||
|
||||
To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy`
|
||||
annotation, as the following example shows:
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
class AppConfig
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-enable-aspectj-xml]]
|
||||
== Enabling @AspectJ Support with XML Configuration
|
||||
|
||||
To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
----
|
||||
|
||||
This assumes that you use schema support as described in
|
||||
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration].
|
||||
See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema] for how to
|
||||
import the tags in the `aop` namespace.
|
||||
|
||||
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -9,12 +9,43 @@ minimal steps required for a not-very-useful aspect.
|
||||
The first of the two examples shows a regular bean definition in the application context
|
||||
that points to a bean class that is annotated with `@Aspect`:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
|
||||
<!-- configure properties of the aspect here -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
|
||||
annotated with `@Aspect`:
|
||||
|
||||
include-code::./NotVeryUsefulAspect[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
@Aspect
|
||||
public class NotVeryUsefulAspect {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
|
||||
@Aspect
|
||||
class NotVeryUsefulAspect
|
||||
----
|
||||
======
|
||||
|
||||
Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any
|
||||
other class. They can also contain pointcut, advice, and introduction (inter-type)
|
||||
|
||||
@@ -16,9 +16,93 @@ aspect.
|
||||
Because we want to retry the operation, we need to use around advice so that we can
|
||||
call `proceed` multiple times. The following listing shows the basic aspect implementation:
|
||||
|
||||
include-code::./ConcurrentOperationExecutor[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
`@Around("com.xyz.CommonPointcuts.businessService()")` references the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
private static final int DEFAULT_MAX_RETRIES = 2;
|
||||
|
||||
private int maxRetries = DEFAULT_MAX_RETRIES;
|
||||
private int order = 1;
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
int numAttempts = 0;
|
||||
PessimisticLockingFailureException lockFailureException;
|
||||
do {
|
||||
numAttempts++;
|
||||
try {
|
||||
return pjp.proceed();
|
||||
}
|
||||
catch(PessimisticLockingFailureException ex) {
|
||||
lockFailureException = ex;
|
||||
}
|
||||
} while(numAttempts <= this.maxRetries);
|
||||
throw lockFailureException;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
private val DEFAULT_MAX_RETRIES = 2
|
||||
private var maxRetries = DEFAULT_MAX_RETRIES
|
||||
private var order = 1
|
||||
|
||||
fun setMaxRetries(maxRetries: Int) {
|
||||
this.maxRetries = maxRetries
|
||||
}
|
||||
|
||||
override fun getOrder(): Int {
|
||||
return this.order
|
||||
}
|
||||
|
||||
fun setOrder(order: Int) {
|
||||
this.order = order
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
numAttempts++
|
||||
try {
|
||||
return pjp.proceed()
|
||||
} catch (ex: PessimisticLockingFailureException) {
|
||||
lockFailureException = ex
|
||||
}
|
||||
|
||||
} while (numAttempts <= this.maxRetries)
|
||||
throw lockFailureException
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
|
||||
the aspect higher than the transaction advice (we want a fresh transaction each time we
|
||||
@@ -30,15 +114,70 @@ we have exhausted all of our retry attempts.
|
||||
|
||||
The corresponding Spring configuration follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<bean id="concurrentOperationExecutor"
|
||||
class="com.xyz.service.impl.ConcurrentOperationExecutor">
|
||||
<property name="maxRetries" value="3"/>
|
||||
<property name="order" value="100"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
To refine the aspect so that it retries only idempotent operations, we might define the following
|
||||
`Idempotent` annotation:
|
||||
|
||||
include-code::./service/Idempotent[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
annotation class Idempotent
|
||||
----
|
||||
======
|
||||
|
||||
We can then use the annotation to annotate the implementation of service operations. The change
|
||||
to the aspect to retry only idempotent operations involves refining the pointcut
|
||||
expression so that only `@Idempotent` operations match, as follows:
|
||||
|
||||
include-code::./service/SampleService[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ annotation. Consider the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
public class MyAspect {
|
||||
@@ -33,7 +33,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
class MyAspect {
|
||||
|
||||
@@ -9,13 +9,13 @@ You can make an introduction by using the `@DeclareParents` annotation. This ann
|
||||
is used to declare that matching types have a new parent (hence the name). For example,
|
||||
given an interface named `UsageTracked` and an implementation of that interface named
|
||||
`DefaultUsageTracked`, the following aspect declares that all implementors of service
|
||||
interfaces also implement the `UsageTracked` interface (for example, for statistics via JMX):
|
||||
interfaces also implement the `UsageTracked` interface (e.g. for statistics via JMX):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class UsageTracking {
|
||||
@@ -33,7 +33,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class UsageTracking {
|
||||
@@ -63,16 +63,16 @@ you would write the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean<UsageTracked>("myService")
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ matches the execution of any method named `transfer`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private void anyOldTransfer() {} // the pointcut signature
|
||||
@@ -27,7 +27,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private fun anyOldTransfer() {} // the pointcut signature
|
||||
@@ -150,7 +150,7 @@ pointcut expressions by name. The following example shows three pointcut express
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -174,7 +174,7 @@ trading module.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -217,7 +217,7 @@ expressions for this purpose. Such a class typically resembles the following
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages",fold="none"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -279,7 +279,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages",fold="none"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ For example, in the @AspectJ style you can write something like the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
public void propertyAccess() {}
|
||||
@@ -73,7 +73,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
fun propertyAccess() {}
|
||||
|
||||
@@ -65,7 +65,7 @@ with less potential for errors. For example, you do not need to invoke the `proc
|
||||
method on the `JoinPoint` used for around advice, and, hence, you cannot fail to invoke it.
|
||||
|
||||
All advice parameters are statically typed so that you work with advice parameters of
|
||||
the appropriate type (for example, the type of the return value from a method execution) rather
|
||||
the appropriate type (e.g. the type of the return value from a method execution) rather
|
||||
than `Object` arrays.
|
||||
|
||||
The concept of join points matched by pointcuts is the key to AOP, which distinguishes
|
||||
|
||||
@@ -6,26 +6,22 @@ target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a co
|
||||
open-source class definition library (repackaged into `spring-core`).
|
||||
|
||||
If the target object to be proxied implements at least one interface, a JDK dynamic
|
||||
proxy is used, and all of the interfaces implemented by the target type are proxied.
|
||||
If the target object does not implement any interfaces, a CGLIB proxy is created which
|
||||
is a runtime-generated subclass of the target type.
|
||||
proxy is used. All of the interfaces implemented by the target type are proxied.
|
||||
If the target object does not implement any interfaces, a CGLIB proxy is created.
|
||||
|
||||
If you want to force the use of CGLIB proxying (for example, to proxy every method
|
||||
defined for the target object, not only those implemented by its interfaces),
|
||||
you can do so. However, you should consider the following issues:
|
||||
|
||||
* `final` classes cannot be proxied, because they cannot be extended.
|
||||
* `final` methods cannot be advised, because they cannot be overridden.
|
||||
* `private` methods cannot be advised, because they cannot be overridden.
|
||||
* Methods that are not visible – for example, package-private methods in a parent class
|
||||
from a different package – cannot be advised because they are effectively private.
|
||||
* The constructor of your proxied object will not be called twice, since the CGLIB proxy
|
||||
instance is created through Objenesis. However, if your JVM does not allow for
|
||||
constructor bypassing, you might see double invocations and corresponding debug log
|
||||
entries from Spring's AOP support.
|
||||
* Your CGLIB proxy usage may face limitations with the Java Module System. As a typical
|
||||
case, you cannot create a CGLIB proxy for a class from the `java.lang` package when
|
||||
deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
* With CGLIB, `final` methods cannot be advised, as they cannot be overridden in
|
||||
runtime-generated subclasses.
|
||||
* As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore,
|
||||
since the CGLIB proxy instance is created through Objenesis. Only if your JVM does
|
||||
not allow for constructor bypassing, you might see double invocations and
|
||||
corresponding debug log entries from Spring's AOP support.
|
||||
* Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system.
|
||||
As a typical case, you cannot create a CGLIB proxy for a class from the `java.lang`
|
||||
package when deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
@@ -69,14 +65,15 @@ Spring AOP is proxy-based. It is vitally important that you grasp the semantics
|
||||
what that last statement actually means before you write your own aspects or use any of
|
||||
the Spring AOP-based aspects supplied with the Spring Framework.
|
||||
|
||||
Consider first the scenario where you have a plain-vanilla, un-proxied object reference,
|
||||
as the following code snippet shows:
|
||||
Consider first the scenario where you have a plain-vanilla, un-proxied,
|
||||
nothing-special-about-it, straight object reference, as the following
|
||||
code snippet shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class SimplePojo implements Pojo {
|
||||
|
||||
@@ -93,7 +90,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
class SimplePojo : Pojo {
|
||||
|
||||
@@ -118,7 +115,7 @@ image::aop-proxy-plain-pojo-call.png[]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
@@ -132,7 +129,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val pojo = SimplePojo()
|
||||
@@ -151,7 +148,7 @@ image::aop-proxy-call.png[]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
@@ -169,7 +166,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val factory = ProxyFactory(SimplePojo())
|
||||
@@ -190,35 +187,26 @@ the interceptors (advice) that are relevant to that particular method call. Howe
|
||||
once the call has finally reached the target object (the `SimplePojo` reference in
|
||||
this case), any method calls that it may make on itself, such as `this.bar()` or
|
||||
`this.foo()`, are going to be invoked against the `this` reference, and not the proxy.
|
||||
This has important implications. It means that self invocation is not going to result
|
||||
in the advice associated with a method invocation getting a chance to run. In other words,
|
||||
self invocation via an explicit or implicit `this` reference will bypass the advice.
|
||||
This has important implications. It means that self-invocation is not going to result
|
||||
in the advice associated with a method invocation getting a chance to run.
|
||||
|
||||
To address that, you have the following options.
|
||||
|
||||
Avoid self invocation ::
|
||||
The best approach (the term "best" is used loosely here) is to refactor your code such
|
||||
that the self invocation does not happen. This does entail some work on your part, but
|
||||
it is the best, least-invasive approach.
|
||||
Inject a self reference ::
|
||||
An alternative approach is to make use of
|
||||
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-self-injection[self injection],
|
||||
and invoke methods on the proxy via the self reference instead of via `this`.
|
||||
Use `AopContext.currentProxy()` ::
|
||||
This last approach is highly discouraged, and we hesitate to point it out, in favor of
|
||||
the previous options. However, as a last resort you can choose to tie the logic within
|
||||
your class to Spring AOP, as the following example shows.
|
||||
Okay, so what is to be done about this? The best approach (the term "best" is used
|
||||
loosely here) is to refactor your code such that the self-invocation does not happen.
|
||||
This does entail some work on your part, but it is the best, least-invasive approach.
|
||||
The next approach is absolutely horrendous, and we hesitate to point it out, precisely
|
||||
because it is so horrendous. You can (painful as it is to us) totally tie the logic
|
||||
within your class to Spring AOP, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class SimplePojo implements Pojo {
|
||||
|
||||
public void foo() {
|
||||
// This works, but it should be avoided if possible.
|
||||
// this works, but... gah!
|
||||
((Pojo) AopContext.currentProxy()).bar();
|
||||
}
|
||||
|
||||
@@ -230,12 +218,12 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
class SimplePojo : Pojo {
|
||||
|
||||
fun foo() {
|
||||
// This works, but it should be avoided if possible.
|
||||
// this works, but... gah!
|
||||
(AopContext.currentProxy() as Pojo).bar()
|
||||
}
|
||||
|
||||
@@ -246,16 +234,16 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The use of `AopContext.currentProxy()` totally couples your code to Spring AOP, and it
|
||||
makes the class itself aware of the fact that it is being used in an AOP context, which
|
||||
reduces some of the benefits of AOP. It also requires that the `ProxyFactory` is
|
||||
configured to expose the proxy, as the following example shows:
|
||||
This totally couples your code to Spring AOP, and it makes the class itself aware of
|
||||
the fact that it is being used in an AOP context, which flies in the face of AOP. It
|
||||
also requires some additional configuration when the proxy is being created, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
@@ -274,7 +262,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val factory = ProxyFactory(SimplePojo())
|
||||
@@ -289,6 +277,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: AspectJ compile-time weaving and load-time weaving do not have this self-invocation
|
||||
issue because they apply advice within the bytecode instead of via a proxy.
|
||||
Finally, it must be noted that AspectJ does not have this self-invocation issue because
|
||||
it is not a proxy-based AOP framework.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ parameters of the matching names, as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void monitor(Object service) {
|
||||
// ...
|
||||
@@ -145,7 +145,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun monitor(service: Any) {
|
||||
// ...
|
||||
@@ -282,14 +282,14 @@ example, you can declare the method signature as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void doAccessCheck(Object retVal) {...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun doAccessCheck(retVal: Any) {...
|
||||
----
|
||||
@@ -340,14 +340,14 @@ The type of this parameter constrains matching in the same way as described for
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void doRecoveryActions(DataAccessException dataAccessEx) {...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
|
||||
----
|
||||
@@ -421,7 +421,7 @@ The implementation of the `doBasicProfiling` advice can be exactly the same as i
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// start stopwatch
|
||||
@@ -433,7 +433,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
|
||||
// start stopwatch
|
||||
@@ -475,7 +475,7 @@ some around advice used in conjunction with a number of strongly typed parameter
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.service;
|
||||
|
||||
@@ -494,7 +494,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.service
|
||||
|
||||
@@ -521,7 +521,7 @@ proceed with the method call. The presence of this parameter is an indication th
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -545,7 +545,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -610,7 +610,7 @@ Consider the following driver script:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class Boot {
|
||||
|
||||
@@ -624,7 +624,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val ctx = ClassPathXmlApplicationContext("beans.xml")
|
||||
@@ -714,7 +714,7 @@ The class that backs the `usageTracking` bean would then contain the following m
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public void recordUsage(UsageTracked usageTracked) {
|
||||
usageTracked.incrementUseCount();
|
||||
@@ -723,7 +723,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun recordUsage(usageTracked: UsageTracked) {
|
||||
usageTracked.incrementUseCount()
|
||||
@@ -742,14 +742,14 @@ following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
----
|
||||
@@ -829,7 +829,7 @@ call `proceed` multiple times. The following listing shows the basic aspect impl
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
@@ -869,7 +869,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
@@ -953,7 +953,7 @@ to annotate the implementation of service operations, as the following example s
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
@@ -963,7 +963,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
|
||||
@@ -36,7 +36,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain;
|
||||
|
||||
@@ -50,7 +50,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain
|
||||
|
||||
@@ -66,7 +66,7 @@ Kotlin::
|
||||
When used as a marker interface in this way, Spring configures new instances of the
|
||||
annotated type (`Account`, in this case) by using a bean definition (typically
|
||||
prototype-scoped) with the same name as the fully-qualified type name
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean defined via XML is the
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean is the
|
||||
fully-qualified name of its type, a convenient way to declare the prototype definition
|
||||
is to omit the `id` attribute, as the following example shows:
|
||||
|
||||
@@ -84,7 +84,7 @@ can do so directly in the annotation, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain;
|
||||
|
||||
@@ -98,7 +98,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz.domain
|
||||
|
||||
@@ -153,14 +153,14 @@ available for use in the body of the constructors, you need to define this on th
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configurable(preConstruction = true)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configurable(preConstruction = true)
|
||||
----
|
||||
@@ -177,10 +177,41 @@ either use a build-time Ant or Maven task to do this (see, for example, the
|
||||
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
|
||||
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
|
||||
`AnnotationBeanConfigurerAspect` itself needs to be configured by Spring (in order to obtain
|
||||
a reference to the bean factory that is to be used to configure new objects). You can define
|
||||
the related configuration as follows:
|
||||
a reference to the bean factory that is to be used to configure new objects). If you
|
||||
use Java-based configuration, you can add `@EnableSpringConfigured` to any
|
||||
`@Configuration` class, as follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML based configuration, the Spring
|
||||
xref:core/appendix/xsd-schemas.adoc#context[`context` namespace]
|
||||
defines a convenient `context:spring-configured` element, which you can use as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<context:spring-configured/>
|
||||
----
|
||||
|
||||
Instances of `@Configurable` objects created before the aspect has been configured
|
||||
result in a message being issued to the debug log and no configuration of the
|
||||
@@ -389,7 +420,7 @@ who typically are in charge of the deployment configuration, such as the launch
|
||||
Now that the sales pitch is over, let us first walk through a quick example of AspectJ
|
||||
LTW that uses Spring, followed by detailed specifics about elements introduced in the
|
||||
example. For a complete example, see the
|
||||
{petclinic-github-org}/spring-framework-petclinic[Petclinic sample application based on Spring Framework].
|
||||
{spring-github-org}/spring-petclinic[Petclinic sample application].
|
||||
|
||||
|
||||
[[aop-aj-ltw-first-example]]
|
||||
@@ -413,7 +444,7 @@ It is a time-based profiler that uses the @AspectJ-style of aspect declaration:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -446,7 +477,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -544,7 +575,7 @@ driver class with a `main(..)` method to demonstrate the LTW in action:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -566,7 +597,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -625,7 +656,7 @@ result:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@@ -647,7 +678,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@@ -752,9 +783,52 @@ adding one line. (Note that you almost certainly need to use an
|
||||
`ApplicationContext` as your Spring container -- typically, a `BeanFactory` is not
|
||||
enough because the LTW support uses `BeanFactoryPostProcessors`.)
|
||||
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver` as follows:
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver`,
|
||||
which typically is done by using the `@EnableLoadTimeWeaving` annotation, as follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, if you prefer XML-based configuration, use the
|
||||
`<context:load-time-weaver/>` element. Note that the element is defined in the
|
||||
`context` namespace. The following example shows how to use `<context:load-time-weaver/>`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The preceding configuration automatically defines and registers a number of LTW-specific
|
||||
infrastructure beans, such as a `LoadTimeWeaver` and an `AspectJWeavingEnabler`, for you.
|
||||
@@ -790,12 +864,63 @@ Note that the table lists only the `LoadTimeWeavers` that are autodetected when
|
||||
use the `DefaultContextLoadTimeWeaver`. You can specify exactly which `LoadTimeWeaver`
|
||||
implementation to use.
|
||||
|
||||
To configure a specific `LoadTimeWeaver`, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method
|
||||
(or use the XML equivalent).
|
||||
To specify a specific `LoadTimeWeaver` with Java configuration, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method.
|
||||
The following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
include-code::./CustomWeaverConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return new ReflectiveLoadTimeWeaver();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig : LoadTimeWeavingConfigurer {
|
||||
|
||||
override fun getLoadTimeWeaver(): LoadTimeWeaver {
|
||||
return ReflectiveLoadTimeWeaver()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you use XML-based configuration, you can specify the fully qualified class name
|
||||
as the value of the `weaver-class` attribute on the `<context:load-time-weaver/>`
|
||||
element. Again, the following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver
|
||||
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The `LoadTimeWeaver` that is defined and registered by the configuration can be later
|
||||
retrieved from the Spring container by using the well known name, `loadTimeWeaver`.
|
||||
|
||||
@@ -36,7 +36,7 @@ NOTE: At the moment, AOT is focused on allowing Spring applications to be deploy
|
||||
We intend to support more JVM-based use cases in future generations.
|
||||
|
||||
[[aot.basics]]
|
||||
== AOT Engine Overview
|
||||
== AOT engine overview
|
||||
|
||||
The entry point of the AOT engine for processing an `ApplicationContext` is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
|
||||
|
||||
@@ -140,7 +140,7 @@ Taking our previous example, let's assume that `DataSourceConfiguration` is as f
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class DataSourceConfiguration {
|
||||
@@ -155,7 +155,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DataSourceConfiguration {
|
||||
@@ -176,7 +176,7 @@ The AOT engine will convert the configuration class above to code similar to the
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0]
|
||||
[source,java,indent=0,role="primary"]
|
||||
----
|
||||
/**
|
||||
* Bean definitions for {@link DataSourceConfiguration}
|
||||
@@ -225,7 +225,7 @@ When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
|
||||
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
|
||||
|
||||
[[aot.running]]
|
||||
== Running with AOT Optimizations
|
||||
== Running with AOT optimizations
|
||||
|
||||
AOT is a mandatory step to transform a Spring application to a native executable, so it
|
||||
is automatically enabled when running in this mode. It is possible to use those optimizations
|
||||
@@ -244,7 +244,7 @@ However, keep in mind that some optimizations are made at build time based on a
|
||||
This section lists the best practices that make sure your application is ready for AOT.
|
||||
|
||||
[[aot.bestpractices.bean-registration]]
|
||||
=== Programmatic Bean Registration
|
||||
== Programmatic bean registration
|
||||
|
||||
The AOT engine takes care of the `@Configuration` model and any callback that might be
|
||||
invoked as part of processing your configuration. If you need to register additional
|
||||
@@ -266,7 +266,7 @@ notion of a classpath. For cases like this, it is crucial that the scanning happ
|
||||
build time.
|
||||
|
||||
[[aot.bestpractices.bean-type]]
|
||||
=== Expose the Most Precise Bean Type
|
||||
=== Expose The Most Precise Bean Type
|
||||
|
||||
While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type.
|
||||
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods.
|
||||
@@ -278,7 +278,7 @@ Consider the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
@@ -290,19 +290,6 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
fun myInterface(): MyInterface = MyImplementation()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the example above, the declared type for the `myInterface` bean is `MyInterface`.
|
||||
@@ -315,7 +302,7 @@ The example above should be rewritten as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
@@ -327,19 +314,6 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
fun myInterface() = MyImplementation()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you are registering bean definitions programmatically, consider using `RootBeanBefinition` as it allows to specify a `ResolvableType` that handles generics.
|
||||
@@ -352,21 +326,21 @@ However, this is not a best practice and flagging the preferred constructor with
|
||||
|
||||
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
|
||||
[[aot.bestpractices.complex-data-structures]]
|
||||
=== Avoid Complex Data Structures for Constructor Parameters and Properties
|
||||
[[aot.bestpractices.comlext-data-structure]]
|
||||
=== Avoid Complex Data Structure for Constructor Parameters and Properties
|
||||
|
||||
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
|
||||
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
|
||||
|
||||
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
|
||||
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
|
||||
Rather than using such structures, decomposing to simple types or referring to a bean that is built as such is recommended.
|
||||
Rather than using such structure, decomposing to simple types or referring to a bean that is built as such is recommended.
|
||||
|
||||
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
|
||||
To use it, register its fully qualified name in `META-INF/spring/aot.factories` using the `Delegate` as the key.
|
||||
|
||||
[[aot.bestpractices.custom-arguments]]
|
||||
=== Avoid Creating Beans with Custom Arguments
|
||||
=== Avoid Creating Bean with Custom Arguments
|
||||
|
||||
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
|
||||
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
|
||||
@@ -378,20 +352,6 @@ For instance, autowiring on fields and methods will be skipped as they are handl
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
[[aot.bestpractices.circular-dependencies]]
|
||||
=== Avoid Circular Dependencies
|
||||
|
||||
Certain use cases can result in circular dependencies between one or more beans. With the
|
||||
regular runtime, it may be possible to wire those circular dependencies via `@Autowired`
|
||||
on setter methods or fields. However, an AOT-optimized context will fail to start with
|
||||
explicit circular dependencies.
|
||||
|
||||
In an AOT-optimized application, you should therefore strive to avoid circular
|
||||
dependencies. If that is not possible, you can use `@Lazy` injection points or
|
||||
`ObjectProvider` to lazily access or retrieve the necessary collaborating beans. See
|
||||
xref:core/beans/classpath-scanning.adoc#beans-factorybeans-annotations-lazy-injection-points[this tip]
|
||||
for further information.
|
||||
|
||||
[[aot.bestpractices.factory-bean]]
|
||||
=== FactoryBean
|
||||
|
||||
@@ -405,21 +365,12 @@ Consider the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class ClientFactoryBean<T : AbstractClient> : FactoryBean<T> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
A concrete client declaration should provide a resolved generic for the client, as shown in the following example:
|
||||
@@ -428,7 +379,7 @@ A concrete client declaration should provide a resolved generic for the client,
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class UserConfiguration {
|
||||
@@ -440,19 +391,6 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class UserConfiguration {
|
||||
|
||||
@Bean
|
||||
fun myClient() = ClientFactoryBean<MyClient>(...)
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If the `FactoryBean` bean definition is registered programmatically, make sure to follow these steps:
|
||||
@@ -467,23 +405,13 @@ The following example showcases a basic definition:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition)
|
||||
----
|
||||
======
|
||||
|
||||
[[aot.bestpractices.jpa]]
|
||||
@@ -495,7 +423,7 @@ The JPA persistence unit has to be known upfront for certain optimizations to ap
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
|
||||
@@ -505,19 +433,6 @@ Java::
|
||||
return factoryBean;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
fun customDBEntityManagerFactory(dataSource: DataSource): LocalContainerEntityManagerFactoryBean {
|
||||
val factoryBean = LocalContainerEntityManagerFactoryBean()
|
||||
factoryBean.dataSource = dataSource
|
||||
factoryBean.setPackagesToScan("com.example.app")
|
||||
return factoryBean
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To make sure the scanning occurs ahead of time, a `PersistenceManagedTypes` bean must be declared and used by the
|
||||
@@ -527,7 +442,7 @@ factory bean definition, as shown by the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
|
||||
@@ -543,25 +458,6 @@ Java::
|
||||
return factoryBean;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
fun persistenceManagedTypes(resourceLoader: ResourceLoader): PersistenceManagedTypes {
|
||||
return PersistenceManagedTypesScanner(resourceLoader)
|
||||
.scan("com.example.app")
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun customDBEntityManagerFactory(dataSource: DataSource, managedTypes: PersistenceManagedTypes): LocalContainerEntityManagerFactoryBean {
|
||||
val factoryBean = LocalContainerEntityManagerFactoryBean()
|
||||
factoryBean.dataSource = dataSource
|
||||
factoryBean.setManagedTypes(managedTypes)
|
||||
return factoryBean
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[aot.hints]]
|
||||
@@ -579,17 +475,10 @@ The following example makes sure that `config/app.properties` can be loaded from
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
runtimeHints.resources().registerPattern("config/app.properties");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
runtimeHints.resources().registerPattern("config/app.properties")
|
||||
----
|
||||
======
|
||||
|
||||
A number of contracts are handled automatically during AOT processing.
|
||||
@@ -620,45 +509,40 @@ It is also possible to register an implementation statically by adding an entry
|
||||
{spring-framework-api}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element.
|
||||
For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection.
|
||||
|
||||
Out-of-the-box, only Spring beans are considered but you can opt-in for scanning using `@ReflectiveScan`.
|
||||
In the example below, all types of the package `com.example.app` and their subpackages are considered:
|
||||
|
||||
include-code::./MyConfiguration[]
|
||||
|
||||
Scanning happens during AOT processing and the types in the target packages do not need to have a class-level annotation to be considered.
|
||||
This performs a "deep scan" and the presence of `@Reflective`, either directly or as a meta-annotation, is checked on types, fields, constructors, methods, and enclosed elements.
|
||||
|
||||
By default, `@Reflective` registers an invocation hint for the annotated element.
|
||||
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the `@Reflective` annotation.
|
||||
By default, only Spring beans are considered, and an invocation hint is registered for the annotated element.
|
||||
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the
|
||||
`@Reflective` annotation.
|
||||
|
||||
Library authors can reuse this annotation for their own purposes.
|
||||
An example of such customization is covered in the next section.
|
||||
If components other than Spring beans need to be processed, a `BeanFactoryInitializationAotProcessor` can detect the relevant types and use `ReflectiveRuntimeHintsRegistrar` to process them.
|
||||
|
||||
|
||||
[[aot.hints.register-reflection]]
|
||||
=== `@RegisterReflection`
|
||||
[[aot.hints.register-reflection-for-binding]]
|
||||
=== `@RegisterReflectionForBinding`
|
||||
|
||||
{spring-framework-api}/aot/hint/annotation/RegisterReflection.html[`@RegisterReflection`] is a specialization of `@Reflective` that provides a declarative way of registering reflection for arbitrary types.
|
||||
|
||||
NOTE: As a specialization of `@Reflective`, this is also detected if you're using `@ReflectiveScan`.
|
||||
|
||||
In the following example, public constructors and public methods can be invoked via reflection on `AccountService`:
|
||||
|
||||
include-code::./MyConfiguration[tag=snippet,indent=0]
|
||||
|
||||
`@RegisterReflection` can be applied to any target type at the class level, but it can also be applied directly to a method to better indicate where the hints are actually required.
|
||||
|
||||
`@RegisterReflection` can be used as a meta-annotation to provide more specific needs.
|
||||
{spring-framework-api}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is such composed annotation and registers the need for serializing arbitrary types.
|
||||
{spring-framework-api}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a specialization of `@Reflective` that registers the need for serializing arbitrary types.
|
||||
A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body.
|
||||
|
||||
The following example registers `Order` for serialization.
|
||||
`@RegisterReflectionForBinding` can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required.
|
||||
The following example registers `Account` for serialization.
|
||||
|
||||
include-code::./OrderService[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class OrderService {
|
||||
|
||||
This registers hints for constructors, fields, properties, and record components of `Order`.
|
||||
Hints are also registered for types transitively used on properties and record components.
|
||||
In other words, if `Order` exposes others types, hints are registered for those as well.
|
||||
@RegisterReflectionForBinding(Account.class)
|
||||
public void process(Order order) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[aot.hints.testing]]
|
||||
=== Testing Runtime Hints
|
||||
@@ -690,7 +574,7 @@ If you forgot to contribute a hint, the test will fail and provide some details
|
||||
[source,txt,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
|
||||
INFO: Spring version: 6.2.0
|
||||
INFO: Spring version:6.0.0-SNAPSHOT
|
||||
|
||||
Missing <"ReflectionHints"> for invocation <java.lang.Class#forName>
|
||||
with arguments ["org.springframework.core.SpringVersion",
|
||||
|
||||
@@ -145,7 +145,7 @@ use the `NamespaceHandlerSupport` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
|
||||
@@ -161,7 +161,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
|
||||
@@ -202,7 +202,7 @@ we can parse our custom XML content, as you can see in the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
|
||||
@@ -240,7 +240,7 @@ single `BeanDefinition` represents.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
|
||||
@@ -416,7 +416,7 @@ The following listing shows the `Component` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -449,7 +449,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -480,7 +480,7 @@ setter property for the `components` property. The following listing shows such
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -522,7 +522,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -598,7 +598,7 @@ we then create a custom `NamespaceHandler`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -614,7 +614,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -637,7 +637,7 @@ listing shows our custom `BeanDefinitionParser` implementation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -688,7 +688,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -787,7 +787,7 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -807,7 +807,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -843,7 +843,7 @@ Next, we need to create the associated `NamespaceHandler`, as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -861,7 +861,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -886,7 +886,7 @@ The following listing shows our `BeanDefinitionDecorator` implementation:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
@@ -942,7 +942,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.foo
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ The following example enumeration shows how easy injecting an enum value is:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package jakarta.persistence;
|
||||
|
||||
@@ -134,7 +134,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package jakarta.persistence
|
||||
|
||||
@@ -152,7 +152,7 @@ Now consider the following setter of type `PersistenceContextType` and the corre
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package example;
|
||||
|
||||
@@ -168,7 +168,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package example
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ can be found in the xref:core/beans/standard-annotations.adoc[relevant section].
|
||||
[NOTE]
|
||||
====
|
||||
Annotation injection is performed before external property injection. Thus, external
|
||||
configuration (for example, XML-specified bean properties) effectively overrides the annotations
|
||||
configuration (e.g. XML-specified bean properties) effectively overrides the annotations
|
||||
for properties when wired through mixed approaches.
|
||||
====
|
||||
|
||||
|
||||
+7
-50
@@ -1,5 +1,5 @@
|
||||
[[beans-autowired-annotation-primary]]
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary` or `@Fallback`
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary`
|
||||
|
||||
Because autowiring by type may lead to multiple candidates, it is often necessary to have
|
||||
more control over the selection process. One way to accomplish this is with Spring's
|
||||
@@ -15,7 +15,7 @@ primary `MovieCatalog`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class MovieConfiguration {
|
||||
@@ -33,7 +33,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MovieConfiguration {
|
||||
@@ -50,57 +50,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, as of 6.2, there is a `@Fallback` annotation for demarcating
|
||||
any beans other than the regular ones to be injected. If only one regular
|
||||
bean is left, it is effectively primary as well:
|
||||
With the preceding configuration, the following `MovieRecommender` is autowired with the
|
||||
`firstMovieCatalog`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
public class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
public MovieCatalog firstMovieCatalog() { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
public MovieCatalog secondMovieCatalog() { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
fun firstMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
fun secondMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
With both variants of the preceding configuration, the following
|
||||
`MovieRecommender` is autowired with the `firstMovieCatalog`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -113,7 +70,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
|
||||
+47
-35
@@ -1,20 +1,19 @@
|
||||
[[beans-autowired-annotation-qualifiers]]
|
||||
= Fine-tuning Annotation-based Autowiring with Qualifiers
|
||||
|
||||
`@Primary` and `@Fallback` are effective ways to use autowiring by type with several
|
||||
instances when one primary (or non-fallback) candidate can be determined.
|
||||
|
||||
When you need more control over the selection process, you can use Spring's `@Qualifier`
|
||||
annotation. You can associate qualifier values with specific arguments, narrowing the set
|
||||
of type matches so that a specific bean is chosen for each argument. In the simplest case,
|
||||
this can be a plain descriptive value, as shown in the following example:
|
||||
`@Primary` is an effective way to use autowiring by type with several instances when one
|
||||
primary candidate can be determined. When you need more control over the selection process,
|
||||
you can use Spring's `@Qualifier` annotation. You can associate qualifier values
|
||||
with specific arguments, narrowing the set of type matches so that a specific bean is
|
||||
chosen for each argument. In the simplest case, this can be a plain descriptive value, as
|
||||
shown in the following example:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -28,7 +27,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -50,7 +49,7 @@ method parameters, as shown in the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -71,7 +70,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -158,31 +157,44 @@ for a non-unique dependency situation, Spring matches the injection point name
|
||||
the same-named candidate, if any (either by bean name or by associated alias).
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
As of 6.2, the container applies fast shortcut resolution for bean name matches,
|
||||
bypassing the full type matching algorithm when the parameter name matches the
|
||||
bean name and no type, qualifier or primary conditions override the match. It is
|
||||
therefore recommendable for your parameter names to match the target bean names.
|
||||
====
|
||||
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
which is semantically defined to identify a specific target component by its unique name,
|
||||
with the declared type being irrelevant for the matching process. `@Autowired` has rather
|
||||
different semantics: after selecting candidate beans by type, the specified `String`
|
||||
different semantics: After selecting candidate beans by type, the specified `String`
|
||||
qualifier value is considered within those type-selected candidates only (for example,
|
||||
matching an `account` qualifier against beans marked with the same qualifier label).
|
||||
|
||||
For beans that are themselves defined as a collection, `Map`, or array type, `@Resource`
|
||||
is a fine solution, referring to the specific collection or array bean by unique name.
|
||||
That said, you can match collection, `Map`, and array types through Spring's
|
||||
That said, as of 4.3, you can match collection, `Map`, and array types through Spring's
|
||||
`@Autowired` type matching algorithm as well, as long as the element type information
|
||||
is preserved in `@Bean` return type signatures or collection inheritance hierarchies.
|
||||
In this case, you can use qualifier values to select among same-typed collections,
|
||||
as outlined in the previous paragraph.
|
||||
|
||||
`@Autowired` also considers self references for injection (that is, references back to
|
||||
the bean that is currently injected). See
|
||||
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-self-injection[Self Injection]
|
||||
for details.
|
||||
As of 4.3, `@Autowired` also considers self references for injection (that is, references
|
||||
back to the bean that is currently injected). Note that self injection is a fallback.
|
||||
Regular dependencies on other components always have precedence. In that sense, self
|
||||
references do not participate in regular candidate selection and are therefore in
|
||||
particular never primary. On the contrary, they always end up as lowest precedence.
|
||||
In practice, you should use self references as a last resort only (for example, for
|
||||
calling other methods on the same instance through the bean's transactional proxy).
|
||||
Consider factoring out the affected methods to a separate delegate bean in such a scenario.
|
||||
Alternatively, you can use `@Resource`, which may obtain a proxy back to the current bean
|
||||
by its unique name.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Trying to inject the results from `@Bean` methods on the same configuration class is
|
||||
effectively a self-reference scenario as well. Either lazily resolve such references
|
||||
in the method signature where it is actually needed (as opposed to an autowired field
|
||||
in the configuration class) or declare the affected `@Bean` methods as `static`,
|
||||
decoupling them from the containing configuration class instance and its lifecycle.
|
||||
Otherwise, such beans are only considered in the fallback phase, with matching beans
|
||||
on other configuration classes selected as primary candidates instead (if available).
|
||||
====
|
||||
|
||||
`@Autowired` applies to fields, constructors, and multi-argument methods, allowing for
|
||||
narrowing through qualifier annotations at the parameter level. In contrast, `@Resource`
|
||||
@@ -198,7 +210,7 @@ provide the `@Qualifier` annotation within your definition, as the following exa
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -211,7 +223,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -229,7 +241,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -250,7 +262,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -322,7 +334,7 @@ the simple annotation, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -333,7 +345,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -351,7 +363,7 @@ following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -366,7 +378,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -406,7 +418,7 @@ consider the following annotation definition:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -421,7 +433,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -438,7 +450,7 @@ In this case `Format` is an enum, defined as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public enum Format {
|
||||
VHS, DVD, BLURAY
|
||||
@@ -447,7 +459,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
enum class Format {
|
||||
VHS, DVD, BLURAY
|
||||
@@ -464,7 +476,7 @@ for both attributes: `genre` and `format`, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -490,7 +502,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ You can apply the `@Autowired` annotation to constructors, as the following exam
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -30,7 +30,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender @Autowired constructor(
|
||||
private val customerPreferenceDao: CustomerPreferenceDao)
|
||||
@@ -54,7 +54,7 @@ as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -71,7 +71,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -91,7 +91,7 @@ arguments, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -112,7 +112,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -139,7 +139,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -159,7 +159,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender @Autowired constructor(
|
||||
private val customerPreferenceDao: CustomerPreferenceDao) {
|
||||
@@ -186,37 +186,14 @@ implementation type, consider declaring the most specific return type on your fa
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
.[[beans-autowired-annotation-self-injection]]Self Injection
|
||||
****
|
||||
`@Autowired` also considers self references for injection (that is, references back to
|
||||
the bean that is currently injected).
|
||||
|
||||
Note, however, that self injection is a fallback mechanism. Regular dependencies on other
|
||||
components always have precedence. In that sense, self references do not participate in
|
||||
regular autowiring candidate selection and are therefore in particular never primary. On
|
||||
the contrary, they always end up as lowest precedence.
|
||||
|
||||
In practice, you should use self references as a last resort only – for example, for
|
||||
calling other methods on the same instance through the bean's transactional proxy. As an
|
||||
alternative, consider factoring out the affected methods to a separate delegate bean in
|
||||
such a scenario.
|
||||
|
||||
Another alternative is to use `@Resource`, which may obtain a proxy back to the current
|
||||
bean by its unique name.
|
||||
|
||||
======
|
||||
[NOTE]
|
||||
====
|
||||
Trying to inject the results from `@Bean` methods in the same `@Configuration` class is
|
||||
effectively a self-reference scenario as well. Either lazily resolve such references
|
||||
in the method signature where it is actually needed (as opposed to an autowired field
|
||||
in the configuration class) or declare the affected `@Bean` methods as `static`,
|
||||
decoupling them from the containing configuration class instance and its lifecycle.
|
||||
Otherwise, such beans are only considered in the fallback phase, with matching beans
|
||||
on other configuration classes selected as primary candidates instead (if available).
|
||||
As of 4.3, `@Autowired` also considers self references for injection (that is, references
|
||||
back to the bean that is currently injected). Note that self injection is a fallback.
|
||||
In practice, you should use self references as a last resort only (for example, for
|
||||
calling other methods on the same instance through the bean's transactional proxy).
|
||||
Consider factoring out the affected methods to a separate delegate bean in such a scenario.
|
||||
====
|
||||
======
|
||||
****
|
||||
|
||||
You can also instruct Spring to provide all beans of a particular type from the
|
||||
`ApplicationContext` by adding the `@Autowired` annotation to a field or method that
|
||||
@@ -226,7 +203,7 @@ expects an array of that type, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -239,7 +216,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -257,7 +234,7 @@ The same applies for typed collections, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -274,7 +251,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -319,7 +296,7 @@ corresponding bean names, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -336,7 +313,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
@@ -361,7 +338,7 @@ non-required (i.e., by setting the `required` attribute in `@Autowired` to `fals
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -378,7 +355,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -444,15 +421,15 @@ through Java 8's `java.util.Optional`, as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
You can also use a `@Nullable` annotation (of any kind in any package -- for example,
|
||||
`javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety
|
||||
support:
|
||||
As of Spring Framework 5.0, you can also use a `@Nullable` annotation (of any kind
|
||||
in any package -- for example, `javax.annotation.Nullable` from JSR-305) or just leverage
|
||||
Kotlin built-in null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -465,7 +442,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -488,7 +465,7 @@ an `ApplicationContext` object:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -504,7 +481,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
|
||||
+6
-6
@@ -9,7 +9,7 @@ configuration:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
@@ -28,7 +28,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfiguration {
|
||||
@@ -50,7 +50,7 @@ used as a qualifier, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Autowired
|
||||
private Store<String> s1; // <String> qualifier, injects the stringStore bean
|
||||
@@ -61,7 +61,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Autowired
|
||||
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean
|
||||
@@ -78,7 +78,7 @@ following example autowires a generic `List`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Inject all Store beans as long as they have an <Integer> generic
|
||||
// Store<String> beans will not appear in this list
|
||||
@@ -88,7 +88,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Inject all Store beans as long as they have an <Integer> generic
|
||||
// Store<String> beans will not appear in this list
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ cleared upon destruction:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class CachingMovieLister {
|
||||
|
||||
@@ -35,7 +35,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CachingMovieLister {
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ as demonstrated in the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -31,7 +31,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -54,7 +54,7 @@ named `movieFinder` injected into its setter method:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -69,7 +69,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -103,7 +103,7 @@ named "customerPreferenceDao" and then falls back to a primary type match for th
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
@@ -124,7 +124,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender {
|
||||
|
||||
|
||||
+16
-16
@@ -7,7 +7,7 @@
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@@ -22,7 +22,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)
|
||||
@@ -35,7 +35,7 @@ With the following configuration:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@PropertySource("classpath:application.properties")
|
||||
@@ -44,7 +44,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@PropertySource("classpath:application.properties")
|
||||
@@ -71,7 +71,7 @@ example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -85,7 +85,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -101,8 +101,8 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
||||
|
||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||
placeholder could not be resolved. It is also possible to use methods like
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
||||
`setEscapeCharacter` to customize placeholders.
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, or `setValueSeparator` to customize
|
||||
placeholders.
|
||||
|
||||
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||
will get properties from `application.properties` and `application.yml` files.
|
||||
@@ -117,7 +117,7 @@ It is possible to provide a default value as following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@@ -132,7 +132,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
|
||||
@@ -148,7 +148,7 @@ provide conversion support for your own custom type, you can provide your own
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -164,7 +164,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -186,7 +186,7 @@ computed at runtime as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@@ -201,7 +201,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MovieRecommender(
|
||||
@@ -215,7 +215,7 @@ SpEL also enables the use of more complex data structures:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@@ -231,7 +231,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MovieRecommender(
|
||||
|
||||
@@ -110,14 +110,14 @@ as the local file system, the Java `CLASSPATH`, and so on.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
|
||||
----
|
||||
@@ -294,7 +294,7 @@ example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// create and configure beans
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
|
||||
@@ -308,7 +308,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -331,14 +331,14 @@ The following example shows Groovy configuration:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy")
|
||||
----
|
||||
@@ -352,7 +352,7 @@ example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
|
||||
@@ -361,7 +361,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val context = GenericApplicationContext()
|
||||
XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml")
|
||||
@@ -376,7 +376,7 @@ example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
|
||||
@@ -385,7 +385,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val context = GenericApplicationContext()
|
||||
GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy")
|
||||
|
||||
@@ -90,7 +90,7 @@ you need to programmatically call `addBeanPostProcessor`, as the following examp
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
||||
// populate the factory with bean definitions
|
||||
@@ -104,7 +104,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val factory = DefaultListableBeanFactory()
|
||||
// populate the factory with bean definitions
|
||||
@@ -124,7 +124,7 @@ you need to call its `postProcessBeanFactory` method, as the following example s
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
|
||||
@@ -140,7 +140,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val factory = DefaultListableBeanFactory()
|
||||
val reader = XmlBeanDefinitionReader(factory)
|
||||
|
||||
@@ -59,7 +59,7 @@ is meta-annotated with `@Component`, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -74,7 +74,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -103,7 +103,7 @@ customization of the `proxyMode`. The following listing shows the definition of
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -123,7 +123,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -142,7 +142,7 @@ You can then use `@SessionScope` without declaring the `proxyMode` as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Service
|
||||
@SessionScope
|
||||
@@ -153,7 +153,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Service
|
||||
@SessionScope
|
||||
@@ -169,7 +169,7 @@ You can also override the value for the `proxyMode`, as the following example sh
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Service
|
||||
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
|
||||
@@ -180,7 +180,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Service
|
||||
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
|
||||
@@ -207,7 +207,7 @@ are eligible for such autodetection:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Service
|
||||
public class SimpleMovieLister {
|
||||
@@ -222,7 +222,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Service
|
||||
class SimpleMovieLister(private val movieFinder: MovieFinder)
|
||||
@@ -233,7 +233,7 @@ Kotlin::
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Repository
|
||||
public class JpaMovieFinder implements MovieFinder {
|
||||
@@ -243,7 +243,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Repository
|
||||
class JpaMovieFinder : MovieFinder {
|
||||
@@ -262,7 +262,7 @@ comma- or semicolon- or space-separated list that includes the parent package of
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example")
|
||||
@@ -273,7 +273,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"])
|
||||
@@ -317,8 +317,8 @@ exposed based on security policies in some environments -- for example, standalo
|
||||
JDK 1.7.0_45 and higher (which requires 'Trusted-Library' setup in your manifests -- see
|
||||
{stackoverflow-questions}/19394570/java-jre-7u45-breaks-classloader-getresources).
|
||||
|
||||
On the module path (Java Module System), Spring's classpath scanning generally works as
|
||||
expected. However, make sure that your component classes are exported in your `module-info`
|
||||
On JDK 9's module path (Jigsaw), Spring's classpath scanning generally works as expected.
|
||||
However, make sure that your component classes are exported in your `module-info`
|
||||
descriptors. If you expect Spring to invoke non-public members of your classes, make
|
||||
sure that they are 'opened' (that is, that they use an `opens` declaration instead of an
|
||||
`exports` declaration in your `module-info` descriptor).
|
||||
@@ -380,7 +380,7 @@ and using "`stub`" repositories instead:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example",
|
||||
@@ -393,7 +393,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"],
|
||||
@@ -438,7 +438,7 @@ annotated classes. The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class FactoryMethodComponent {
|
||||
@@ -457,7 +457,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class FactoryMethodComponent {
|
||||
@@ -480,15 +480,11 @@ factory method and other bean definition properties, such as a qualifier value t
|
||||
the `@Qualifier` annotation. Other method-level annotations that can be specified are
|
||||
`@Scope`, `@Lazy`, and custom qualifier annotations.
|
||||
|
||||
[[beans-factorybeans-annotations-lazy-injection-points]]
|
||||
[TIP]
|
||||
====
|
||||
In addition to its role for component initialization, you can also place the `@Lazy`
|
||||
TIP: In addition to its role for component initialization, you can also place the `@Lazy`
|
||||
annotation on injection points marked with `@Autowired` or `@Inject`. In this context,
|
||||
it leads to the injection of a lazy-resolution proxy. However, such a proxy approach
|
||||
is rather limited. For sophisticated lazy interactions, in particular in combination
|
||||
with optional dependencies, we recommend `ObjectProvider<MyTargetBean>` instead.
|
||||
====
|
||||
|
||||
Autowired fields and methods are supported, as previously discussed, with additional
|
||||
support for autowiring of `@Bean` methods. The following example shows how to do so:
|
||||
@@ -497,7 +493,7 @@ support for autowiring of `@Bean` methods. The following example shows how to do
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class FactoryMethodComponent {
|
||||
@@ -536,7 +532,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class FactoryMethodComponent {
|
||||
@@ -589,7 +585,7 @@ The following example shows how to use `InjectionPoint`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class FactoryMethodComponent {
|
||||
@@ -603,7 +599,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class FactoryMethodComponent {
|
||||
@@ -703,7 +699,7 @@ following component classes were detected, the names would be `myMovieLister` an
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
public class SimpleMovieLister {
|
||||
@@ -713,7 +709,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
class SimpleMovieLister {
|
||||
@@ -726,7 +722,7 @@ Kotlin::
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
@@ -736,7 +732,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
@@ -755,7 +751,7 @@ and bean definition show.
|
||||
TIP: If you run into naming conflicts due to multiple autodetected components having the
|
||||
same non-qualified class name (i.e., classes with identical names but residing in
|
||||
different packages), you may need to configure a `BeanNameGenerator` that defaults to the
|
||||
fully qualified class name for the generated bean name. The
|
||||
fully qualified class name for the generated bean name. As of Spring Framework 5.2.3, the
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` located in package
|
||||
`org.springframework.context.annotation` can be used for such purposes.
|
||||
|
||||
@@ -763,7 +759,7 @@ fully qualified class name for the generated bean name. The
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
|
||||
@@ -774,7 +770,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
|
||||
@@ -810,7 +806,7 @@ scope within the annotation, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
@@ -821,7 +817,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
@@ -853,7 +849,7 @@ an annotation and a bean definition shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
|
||||
@@ -864,7 +860,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
|
||||
@@ -891,7 +887,7 @@ the following configuration results in standard JDK dynamic proxies:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
@@ -902,7 +898,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
@@ -924,8 +920,7 @@ Kotlin::
|
||||
[[beans-scanning-qualifiers]]
|
||||
== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The `@Qualifier` annotation is discussed in xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
@@ -939,7 +934,7 @@ technique:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
@@ -950,7 +945,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
@@ -962,7 +957,7 @@ Kotlin::
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
@@ -973,7 +968,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
@@ -987,7 +982,7 @@ Kotlin::
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
@@ -998,7 +993,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[context-introduction]]
|
||||
= Additional Capabilities of the `ApplicationContext`
|
||||
|
||||
As discussed in the xref:core/beans/introduction.adoc[chapter introduction], the `org.springframework.beans.factory`
|
||||
As discussed in the xref:web/webmvc-view/mvc-xslt.adoc#mvc-view-xslt-beandefs[chapter introduction], the `org.springframework.beans.factory`
|
||||
package provides basic functionality for managing and manipulating beans, including in a
|
||||
programmatic way. The `org.springframework.context` package adds the
|
||||
{spring-framework-api}/context/ApplicationContext.html[`ApplicationContext`]
|
||||
@@ -102,7 +102,7 @@ implementations and so can be cast to the `MessageSource` interface.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
|
||||
@@ -113,7 +113,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val resources = ClassPathXmlApplicationContext("beans.xml")
|
||||
@@ -161,7 +161,7 @@ converted into `String` objects and inserted into placeholders in the lookup mes
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class Example {
|
||||
|
||||
@@ -181,7 +181,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class Example {
|
||||
|
||||
@@ -224,7 +224,7 @@ argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(final String[] args) {
|
||||
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
|
||||
@@ -236,7 +236,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val resources = ClassPathXmlApplicationContext("beans.xml")
|
||||
@@ -344,7 +344,7 @@ simple class that extends Spring's `ApplicationEvent` base class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class BlockedListEvent extends ApplicationEvent {
|
||||
|
||||
@@ -363,7 +363,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class BlockedListEvent(source: Any,
|
||||
val address: String,
|
||||
@@ -380,7 +380,7 @@ example shows such a class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class EmailService implements ApplicationEventPublisherAware {
|
||||
|
||||
@@ -407,7 +407,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class EmailService : ApplicationEventPublisherAware {
|
||||
|
||||
@@ -447,7 +447,7 @@ shows such a class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
|
||||
|
||||
@@ -465,7 +465,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {
|
||||
|
||||
@@ -484,7 +484,7 @@ You can register as many event listeners as you wish, but note that, by default,
|
||||
This means that the `publishEvent()` method blocks until all listeners have finished processing the event.
|
||||
One advantage of this synchronous and single-threaded approach is that, when a listener receives an event,
|
||||
it operates inside the transaction context of the publisher if a transaction context is available.
|
||||
If another strategy for event publication becomes necessary, for example, asynchronous event processing by default,
|
||||
If another strategy for event publication becomes necessary, e.g. asynchronous event processing by default,
|
||||
see the javadoc for Spring's {spring-framework-api}/context/event/ApplicationEventMulticaster.html[`ApplicationEventMulticaster`] interface
|
||||
and {spring-framework-api}/context/event/SimpleApplicationEventMulticaster.html[`SimpleApplicationEventMulticaster`] implementation
|
||||
for configuration options which can be applied to a custom "applicationEventMulticaster" bean definition.
|
||||
@@ -545,7 +545,7 @@ You can register an event listener on any method of a managed bean by using the
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class BlockedListNotifier {
|
||||
|
||||
@@ -564,7 +564,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class BlockedListNotifier {
|
||||
|
||||
@@ -591,7 +591,7 @@ following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
|
||||
public void handleContextStart() {
|
||||
@@ -601,7 +601,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
|
||||
fun handleContextStart() {
|
||||
@@ -621,7 +621,7 @@ The following example shows how our notifier can be rewritten to be invoked only
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@EventListener(condition = "#blEvent.content == 'my-event'")
|
||||
public void processBlockedListEvent(BlockedListEvent blEvent) {
|
||||
@@ -631,7 +631,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@EventListener(condition = "#blEvent.content == 'my-event'")
|
||||
fun processBlockedListEvent(blEvent: BlockedListEvent) {
|
||||
@@ -644,7 +644,7 @@ Each `SpEL` expression evaluates against a dedicated context. The following tabl
|
||||
items made available to the context so that you can use them for conditional event processing:
|
||||
|
||||
[[context-functionality-events-annotation-tbl]]
|
||||
.Event metadata available in SpEL expressions
|
||||
.Event SpEL available metadata
|
||||
|===
|
||||
| Name| Location| Description| Example
|
||||
|
||||
@@ -660,8 +660,8 @@ items made available to the context so that you can use them for conditional eve
|
||||
|
||||
| __Argument name__
|
||||
| evaluation context
|
||||
| The name of a particular method argument. If the names are not available
|
||||
(for example, because the code was compiled without the `-parameters` flag), individual
|
||||
| The name of any of the method arguments. If, for some reason, the names are not available
|
||||
(for example, because there is no debug information in the compiled byte code), individual
|
||||
arguments are also available using the `#a<#arg>` syntax where `<#arg>` stands for the
|
||||
argument index (starting from 0).
|
||||
| `#blEvent` or `#a0` (you can also use `#p0` or `#p<#arg>` parameter notation as an alias)
|
||||
@@ -677,7 +677,7 @@ method signature to return the event that should be published, as the following
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@EventListener
|
||||
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
|
||||
@@ -688,7 +688,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@EventListener
|
||||
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
|
||||
@@ -717,7 +717,7 @@ The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@EventListener
|
||||
@Async
|
||||
@@ -728,7 +728,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@EventListener
|
||||
@Async
|
||||
@@ -763,7 +763,7 @@ annotation to the method declaration, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@EventListener
|
||||
@Order(42)
|
||||
@@ -774,7 +774,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@EventListener
|
||||
@Order(42)
|
||||
@@ -797,7 +797,7 @@ can create the following listener definition to receive only `EntityCreatedEvent
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@EventListener
|
||||
public void onPersonCreated(EntityCreatedEvent<Person> event) {
|
||||
@@ -807,7 +807,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@EventListener
|
||||
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
|
||||
@@ -829,7 +829,7 @@ environment provides. The following event shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
|
||||
|
||||
@@ -846,7 +846,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {
|
||||
|
||||
@@ -864,7 +864,7 @@ Finally, as with classic `ApplicationListener` implementations, the actual multi
|
||||
happens via a context-wide `ApplicationEventMulticaster` at runtime. By default, this is a
|
||||
`SimpleApplicationEventMulticaster` with synchronous event publication in the caller thread.
|
||||
This can be replaced/customized through an "applicationEventMulticaster" bean definition,
|
||||
for example, for processing all events asynchronously and/or for handling listener exceptions:
|
||||
e.g. for processing all events asynchronously and/or for handling listener exceptions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -883,12 +883,11 @@ for example, for processing all events asynchronously and/or for handling listen
|
||||
== Convenient Access to Low-level Resources
|
||||
|
||||
For optimal usage and understanding of application contexts, you should familiarize
|
||||
yourself with Spring's `Resource` abstraction, as described in
|
||||
xref:core/resources.adoc[Resources].
|
||||
yourself with Spring's `Resource` abstraction, as described in xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources].
|
||||
|
||||
An application context is a `ResourceLoader`, which can be used to load `Resource` objects.
|
||||
A `Resource` is essentially a more feature rich version of the JDK `java.net.URL` class.
|
||||
In fact, implementations of `Resource` wrap an instance of `java.net.URL`, where
|
||||
In fact, the implementations of the `Resource` wrap an instance of `java.net.URL`, where
|
||||
appropriate. A `Resource` can obtain low-level resources from almost any location in a
|
||||
transparent fashion, including from the classpath, a filesystem location, anywhere
|
||||
describable with a standard URL, and some other variations. If the resource location
|
||||
@@ -936,7 +935,7 @@ Here is an example of instrumentation in the `AnnotationConfigApplicationContext
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
|
||||
@@ -950,7 +949,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
|
||||
|
||||
@@ -11,7 +11,7 @@ To enable load-time weaving, you can add the `@EnableLoadTimeWeaving` to one of
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
@@ -21,7 +21,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
|
||||
@@ -78,20 +78,19 @@ lead to concurrent access exceptions, inconsistent state in the bean container,
|
||||
[[beans-definition-overriding]]
|
||||
== Overriding Beans
|
||||
|
||||
Bean overriding occurs when a bean is registered using an identifier that is already
|
||||
allocated. While bean overriding is possible, it makes the configuration harder to read.
|
||||
|
||||
WARNING: Bean overriding will be deprecated in a future release.
|
||||
Bean overriding is happening when a bean is registered using an identifier that is
|
||||
already allocated. While bean overriding is possible, it makes the configuration harder
|
||||
to read and this feature will be deprecated in a future release.
|
||||
|
||||
To disable bean overriding altogether, you can set the `allowBeanDefinitionOverriding`
|
||||
flag to `false` on the `ApplicationContext` before it is refreshed. In such a setup, an
|
||||
flag to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
|
||||
exception is thrown if bean overriding is used.
|
||||
|
||||
By default, the container logs every attempt to override a bean at `INFO` level so that
|
||||
you can adapt your configuration accordingly. While not recommended, you can silence
|
||||
those logs by setting the `allowBeanDefinitionOverriding` flag to `true`.
|
||||
By default, the container logs every bean overriding at `INFO` level so that you can
|
||||
adapt your configuration accordingly. While not recommended, you can silence those logs
|
||||
by setting the `allowBeanDefinitionOverriding` flag to `true`.
|
||||
|
||||
.Java Configuration
|
||||
.Java-configuration
|
||||
****
|
||||
If you use Java Configuration, a corresponding `@Bean` method always silently overrides
|
||||
a scanned bean class with the same component name as long as the return type of the
|
||||
@@ -99,10 +98,6 @@ a scanned bean class with the same component name as long as the return type of
|
||||
the `@Bean` factory method in favor of any pre-declared constructor on the bean class.
|
||||
****
|
||||
|
||||
NOTE: We acknowledge that overriding beans in test scenarios is convenient, and there is
|
||||
explicit support for this as of Spring Framework 6.2. Please refer to
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc[this section] for more details.
|
||||
|
||||
|
||||
|
||||
[[beans-beanname]]
|
||||
@@ -298,7 +293,7 @@ The following example shows a class that would work with the preceding bean defi
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ClientService {
|
||||
private static ClientService clientService = new ClientService();
|
||||
@@ -312,7 +307,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ClientService private constructor() {
|
||||
companion object {
|
||||
@@ -378,7 +373,7 @@ The following example shows the corresponding class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class DefaultServiceLocator {
|
||||
|
||||
@@ -392,7 +387,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class DefaultServiceLocator {
|
||||
companion object {
|
||||
@@ -428,7 +423,7 @@ The following example shows the corresponding class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class DefaultServiceLocator {
|
||||
|
||||
@@ -448,7 +443,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class DefaultServiceLocator {
|
||||
companion object {
|
||||
|
||||
@@ -60,7 +60,6 @@ instance's values consist of all bean instances that match the expected type, an
|
||||
`Map` instance's keys contain the corresponding bean names.
|
||||
|
||||
|
||||
|
||||
[[beans-autowired-exceptions]]
|
||||
== Limitations and Disadvantages of Autowiring
|
||||
|
||||
@@ -102,10 +101,10 @@ In the latter scenario, you have several options:
|
||||
== Excluding a Bean from Autowiring
|
||||
|
||||
On a per-bean basis, you can exclude a bean from autowiring. In Spring's XML format, set
|
||||
the `autowire-candidate` attribute of the `<bean/>` element to `false`; with the `@Bean`
|
||||
annotation, the attribute is named `autowireCandidate`. The container makes that specific
|
||||
bean definition unavailable to the autowiring infrastructure, including annotation-based
|
||||
injection points such as xref:core/beans/annotation-config/autowired.adoc[`@Autowired`].
|
||||
the `autowire-candidate` attribute of the `<bean/>` element to `false`. The container
|
||||
makes that specific bean definition unavailable to the autowiring infrastructure
|
||||
(including annotation style configurations such as xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
|
||||
).
|
||||
|
||||
NOTE: The `autowire-candidate` attribute is designed to only affect type-based autowiring.
|
||||
It does not affect explicit references by name, which get resolved even if the
|
||||
@@ -120,25 +119,9 @@ provide multiple patterns, define them in a comma-separated list. An explicit va
|
||||
`true` or `false` for a bean definition's `autowire-candidate` attribute always takes
|
||||
precedence. For such beans, the pattern matching rules do not apply.
|
||||
|
||||
These techniques are useful for beans that you never want to be injected into other beans
|
||||
by autowiring. It does not mean that an excluded bean cannot itself be configured by
|
||||
These techniques are useful for beans that you never want to be injected into other
|
||||
beans by autowiring. It does not mean that an excluded bean cannot itself be configured by
|
||||
using autowiring. Rather, the bean itself is not a candidate for autowiring other beans.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 6.2, `@Bean` methods support two variants of the autowire candidate flag:
|
||||
`autowireCandidate` and `defaultCandidate`.
|
||||
|
||||
When using xref:core/beans/annotation-config/autowired-qualifiers.adoc[qualifiers],
|
||||
a bean marked with `defaultCandidate=false` is only available for injection points
|
||||
where an additional qualifier indication is present. This is useful for restricted
|
||||
delegates that are supposed to be injectable in certain areas but are not meant to
|
||||
get in the way of beans of the same type in other places. Such a bean will never
|
||||
get injected by plain declared type only, rather by type plus specific qualifier.
|
||||
|
||||
In contrast, `autowireCandidate=false` behaves exactly like the `autowire-candidate`
|
||||
attribute as explained above: Such a bean will never get injected by type at all.
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
+28
-31
@@ -34,7 +34,7 @@ injection:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -52,7 +52,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// a constructor so that the Spring container can inject a MovieFinder
|
||||
class SimpleMovieLister(private val movieFinder: MovieFinder) {
|
||||
@@ -77,7 +77,7 @@ being instantiated. Consider the following class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package x.y;
|
||||
|
||||
@@ -91,7 +91,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package x.y
|
||||
|
||||
@@ -127,7 +127,7 @@ by type without help. Consider the following class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package examples;
|
||||
|
||||
@@ -148,7 +148,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package examples
|
||||
|
||||
@@ -159,12 +159,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[discrete]
|
||||
[[beans-factory-ctor-arguments-type]]
|
||||
==== Constructor argument type matching
|
||||
|
||||
.[[beans-factory-ctor-arguments-type]]Constructor argument type matching
|
||||
--
|
||||
In the preceding scenario, the container can use type matching with simple types if
|
||||
you explicitly specify the type of the constructor argument via the `type` attribute,
|
||||
you explicitly specify the type of the constructor argument by using the `type` attribute,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -174,11 +172,10 @@ as the following example shows:
|
||||
<constructor-arg type="java.lang.String" value="42"/>
|
||||
</bean>
|
||||
----
|
||||
--
|
||||
|
||||
[discrete]
|
||||
[[beans-factory-ctor-arguments-index]]
|
||||
==== Constructor argument index
|
||||
|
||||
.[[beans-factory-ctor-arguments-index]]Constructor argument index
|
||||
--
|
||||
You can use the `index` attribute to specify explicitly the index of constructor arguments,
|
||||
as the following example shows:
|
||||
|
||||
@@ -194,11 +191,10 @@ In addition to resolving the ambiguity of multiple simple values, specifying an
|
||||
resolves ambiguity where a constructor has two arguments of the same type.
|
||||
|
||||
NOTE: The index is 0-based.
|
||||
--
|
||||
|
||||
[discrete]
|
||||
[[beans-factory-ctor-arguments-name]]
|
||||
==== Constructor argument name
|
||||
|
||||
.[[beans-factory-ctor-arguments-name]]Constructor argument name
|
||||
--
|
||||
You can also use the constructor parameter name for value disambiguation, as the following
|
||||
example shows:
|
||||
|
||||
@@ -211,8 +207,8 @@ example shows:
|
||||
----
|
||||
|
||||
Keep in mind that, to make this work out of the box, your code must be compiled with the
|
||||
`-parameters` flag enabled so that Spring can look up the parameter name from the constructor.
|
||||
If you cannot or do not want to compile your code with the `-parameters` flag, you can use the
|
||||
debug flag enabled so that Spring can look up the parameter name from the constructor.
|
||||
If you cannot or do not want to compile your code with the debug flag, you can use the
|
||||
https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.html[@ConstructorProperties]
|
||||
JDK annotation to explicitly name your constructor arguments. The sample class would
|
||||
then have to look as follows:
|
||||
@@ -221,7 +217,7 @@ then have to look as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package examples;
|
||||
|
||||
@@ -239,7 +235,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package examples
|
||||
|
||||
@@ -248,6 +244,7 @@ Kotlin::
|
||||
constructor(val years: Int, val ultimateAnswer: String)
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
[[beans-setter-injection]]
|
||||
@@ -265,7 +262,7 @@ on container specific interfaces, base classes, or annotations.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -283,7 +280,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -440,7 +437,7 @@ The following example shows the corresponding `ExampleBean` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ExampleBean {
|
||||
|
||||
@@ -466,7 +463,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ExampleBean {
|
||||
lateinit var beanOne: AnotherBean
|
||||
@@ -503,7 +500,7 @@ The following example shows the corresponding `ExampleBean` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ExampleBean {
|
||||
|
||||
@@ -524,7 +521,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ExampleBean(
|
||||
private val beanOne: AnotherBean,
|
||||
@@ -557,7 +554,7 @@ The following example shows the corresponding `ExampleBean` class:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ExampleBean {
|
||||
|
||||
@@ -581,7 +578,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ExampleBean private constructor() {
|
||||
companion object {
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
If a bean is a dependency of another bean, that usually means that one bean is set as a
|
||||
property of another. Typically you accomplish this with the
|
||||
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
|
||||
in XML-based metadata or through xref:core/beans/dependencies/factory-autowire.adoc[autowiring].
|
||||
|
||||
However, sometimes dependencies between beans are less direct. An example is when a static
|
||||
initializer in a class needs to be triggered, such as for database driver registration.
|
||||
The `depends-on` attribute or `@DependsOn` annotation can explicitly force one or more beans
|
||||
to be initialized before the bean using this element is initialized. The following example
|
||||
uses the `depends-on` attribute to express a dependency on a single bean:
|
||||
in XML-based configuration metadata. However, sometimes dependencies between beans are
|
||||
less direct. An example is when a static initializer in a class needs to be triggered,
|
||||
such as for database driver registration. The `depends-on` attribute can explicitly force
|
||||
one or more beans to be initialized before the bean using this element is initialized.
|
||||
The following example uses the `depends-on` attribute to express a dependency on a single
|
||||
bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -33,10 +32,10 @@ delimiters):
|
||||
----
|
||||
|
||||
NOTE: The `depends-on` attribute can specify both an initialization-time dependency and,
|
||||
in the case of xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton]
|
||||
beans only, a corresponding destruction-time dependency. Dependent beans that define a
|
||||
`depends-on` relationship with a given bean are destroyed first, prior to the given bean
|
||||
itself being destroyed. Thus, `depends-on` can also control shutdown order.
|
||||
in the case of xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton] beans only, a corresponding
|
||||
destruction-time dependency. Dependent beans that define a `depends-on` relationship
|
||||
with a given bean are destroyed first, prior to the given bean itself being destroyed.
|
||||
Thus, `depends-on` can also control shutdown order.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,25 +10,33 @@ pre-instantiation of a singleton bean by marking the bean definition as being
|
||||
lazy-initialized. A lazy-initialized bean tells the IoC container to create a bean
|
||||
instance when it is first requested, rather than at startup.
|
||||
|
||||
This behavior is controlled by the `@Lazy` annotation or in XML the `lazy-init` attribute on the `<bean/>` element, as
|
||||
the following example shows:
|
||||
In XML, this behavior is controlled by the `lazy-init` attribute on the `<bean/>`
|
||||
element, as the following example shows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
|
||||
<bean name="not.lazy" class="com.something.AnotherBean"/>
|
||||
----
|
||||
|
||||
When the preceding configuration is consumed by an `ApplicationContext`, the `lazy` bean
|
||||
is not eagerly pre-instantiated when the `ApplicationContext` starts,
|
||||
whereas the `notLazy` one is eagerly pre-instantiated.
|
||||
whereas the `not.lazy` bean is eagerly pre-instantiated.
|
||||
|
||||
However, when a lazy-initialized bean is a dependency of a singleton bean that is
|
||||
not lazy-initialized, the `ApplicationContext` creates the lazy-initialized bean at
|
||||
startup, because it must satisfy the singleton's dependencies. The lazy-initialized bean
|
||||
is injected into a singleton bean elsewhere that is not lazy-initialized.
|
||||
|
||||
You can also control lazy-initialization for a set of beans by using the `@Lazy` annotation on your `@Configuration`
|
||||
annotated class or in XML using the `default-lazy-init` attribute on the `<beans/>` element, as the following example
|
||||
shows:
|
||||
You can also control lazy-initialization at the container level by using the
|
||||
`default-lazy-init` attribute on the `<beans/>` element, as the following example shows:
|
||||
|
||||
include-code::./LazyConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans default-lazy-init="true">
|
||||
<!-- no beans will be pre-instantiated... -->
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
+12
-12
@@ -21,7 +21,7 @@ shows this approach:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package fiona.apple;
|
||||
|
||||
@@ -60,7 +60,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package fiona.apple
|
||||
|
||||
@@ -137,7 +137,7 @@ the reworked example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package fiona.apple;
|
||||
|
||||
@@ -160,7 +160,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package fiona.apple
|
||||
|
||||
@@ -220,7 +220,7 @@ method through the `@Lookup` annotation, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public abstract class CommandManager {
|
||||
|
||||
@@ -237,7 +237,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
abstract class CommandManager {
|
||||
|
||||
@@ -260,7 +260,7 @@ declared return type of the lookup method:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public abstract class CommandManager {
|
||||
|
||||
@@ -277,7 +277,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
abstract class CommandManager {
|
||||
|
||||
@@ -324,7 +324,7 @@ the following class, which has a method called `computeValue` that we want to ov
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MyValueCalculator {
|
||||
|
||||
@@ -338,7 +338,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MyValueCalculator {
|
||||
|
||||
@@ -358,7 +358,7 @@ interface provides the new method definition, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
/**
|
||||
* meant to be used to override the existing computeValue(String)
|
||||
@@ -377,7 +377,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
/**
|
||||
* meant to be used to override the existing computeValue(String)
|
||||
|
||||
+9
-9
@@ -102,8 +102,8 @@ following snippet:
|
||||
----
|
||||
<bean id="theTargetBean" class="..." />
|
||||
|
||||
<bean id="theClientBean" class="...">
|
||||
<property name="targetName" ref="theTargetBean"/>
|
||||
<bean id="client" class="...">
|
||||
<property name="targetName" value="theTargetBean"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
@@ -354,7 +354,7 @@ The following Java class and bean definition show how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SomeClass {
|
||||
|
||||
@@ -368,7 +368,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SomeClass {
|
||||
lateinit var accounts: Map<String, Float>
|
||||
@@ -418,14 +418,14 @@ The preceding example is equivalent to the following Java code:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
exampleBean.setEmail("");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
exampleBean.email = ""
|
||||
----
|
||||
@@ -449,14 +449,14 @@ The preceding configuration is equivalent to the following Java code:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
exampleBean.setEmail(null);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
exampleBean.email = null
|
||||
----
|
||||
@@ -582,7 +582,7 @@ it needs to be declared in the XML file even though it is not defined in an XSD
|
||||
(it exists inside the Spring core).
|
||||
|
||||
For the rare cases where the constructor argument names are not available (usually if
|
||||
the bytecode was compiled without the `-parameters` flag), you can fall back to the
|
||||
the bytecode was compiled without debugging information), you can use fallback to the
|
||||
argument indexes, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
|
||||
@@ -43,7 +43,7 @@ Consider the first use case in a practical application that requires a
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
@@ -57,7 +57,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
@@ -79,7 +79,7 @@ now looks like the following listing:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean(destroyMethod = "")
|
||||
public DataSource dataSource() throws Exception {
|
||||
@@ -90,7 +90,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Bean(destroyMethod = "")
|
||||
fun dataSource(): DataSource {
|
||||
@@ -128,7 +128,7 @@ can rewrite the `dataSource` configuration as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@Profile("development")
|
||||
@@ -147,7 +147,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@Profile("development")
|
||||
@@ -171,7 +171,7 @@ Kotlin::
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@Profile("production")
|
||||
@@ -188,7 +188,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@Profile("production")
|
||||
@@ -233,7 +233,7 @@ of creating a custom composed annotation. The following example defines a custom
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -244,7 +244,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -272,7 +272,7 @@ the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -300,7 +300,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -454,7 +454,7 @@ it programmatically against the `Environment` API which is available through an
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.getEnvironment().setActiveProfiles("development");
|
||||
@@ -464,7 +464,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val ctx = AnnotationConfigApplicationContext().apply {
|
||||
environment.setActiveProfiles("development")
|
||||
@@ -491,14 +491,14 @@ activates multiple profiles:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
|
||||
----
|
||||
@@ -523,7 +523,7 @@ the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@Profile("default")
|
||||
@@ -541,7 +541,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@Profile("default")
|
||||
@@ -578,7 +578,7 @@ hierarchy of property sources. Consider the following listing:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ApplicationContext ctx = new GenericApplicationContext();
|
||||
Environment env = ctx.getEnvironment();
|
||||
@@ -588,7 +588,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val ctx = GenericApplicationContext()
|
||||
val env = ctx.environment
|
||||
@@ -643,7 +643,7 @@ current `Environment`. The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ConfigurableApplicationContext ctx = new GenericApplicationContext();
|
||||
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
|
||||
@@ -652,7 +652,7 @@ sources.addFirst(new MyPropertySource());
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val ctx = GenericApplicationContext()
|
||||
val sources = ctx.environment.propertySources
|
||||
@@ -684,7 +684,7 @@ a call to `testBean.getName()` returns `myTestBean`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@PropertySource("classpath:/com/myco/app.properties")
|
||||
@@ -704,7 +704,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@PropertySource("classpath:/com/myco/app.properties")
|
||||
@@ -729,7 +729,7 @@ environment, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
|
||||
@@ -749,7 +749,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
|
||||
|
||||
@@ -123,7 +123,7 @@ The following listing shows the custom `BeanPostProcessor` implementation class
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package scripting;
|
||||
|
||||
@@ -145,7 +145,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package scripting
|
||||
|
||||
@@ -205,7 +205,7 @@ The following Java application runs the preceding code and configuration:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
@@ -224,7 +224,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ no-argument signature. With Java configuration, you can use the `initMethod` att
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ExampleBean {
|
||||
|
||||
@@ -84,7 +84,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ExampleBean {
|
||||
|
||||
@@ -107,7 +107,7 @@ The preceding example has almost exactly the same effect as the following exampl
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class AnotherExampleBean implements InitializingBean {
|
||||
|
||||
@@ -120,7 +120,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class AnotherExampleBean : InitializingBean {
|
||||
|
||||
@@ -144,7 +144,7 @@ based on the given configuration but no further activity with external bean acce
|
||||
Otherwise there is a risk for an initialization deadlock.
|
||||
|
||||
For a scenario where expensive post-initialization activity is to be triggered,
|
||||
for example, asynchronous database preparation steps, your bean should either implement
|
||||
e.g. asynchronous database preparation steps, your bean should either implement
|
||||
`SmartInitializingSingleton.afterSingletonsInstantiated()` or rely on the context
|
||||
refresh event: implementing `ApplicationListener<ContextRefreshedEvent>` or
|
||||
declaring its annotation equivalent `@EventListener(ContextRefreshedEvent.class)`.
|
||||
@@ -187,7 +187,7 @@ xref:core/beans/java/bean-annotation.adoc#beans-java-lifecycle-callbacks[Receivi
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ExampleBean {
|
||||
|
||||
@@ -199,7 +199,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ExampleBean {
|
||||
|
||||
@@ -221,7 +221,7 @@ The preceding definition has almost exactly the same effect as the following def
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class AnotherExampleBean implements DisposableBean {
|
||||
|
||||
@@ -234,7 +234,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class AnotherExampleBean : DisposableBean {
|
||||
|
||||
@@ -295,7 +295,7 @@ following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class DefaultBlogService implements BlogService {
|
||||
|
||||
@@ -316,7 +316,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class DefaultBlogService : BlogService {
|
||||
|
||||
@@ -551,7 +551,7 @@ declared on the `ConfigurableApplicationContext` interface, as the following exa
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
@@ -573,7 +573,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext
|
||||
|
||||
@@ -607,7 +607,7 @@ bean creation phase and its subsequent initial publication, they need to be decl
|
||||
`volatile` or guarded by a common lock whenever accessed.
|
||||
|
||||
Note that concurrent access to such configuration state in singleton bean instances,
|
||||
for example, for controller instances or repository instances, is perfectly thread-safe after
|
||||
e.g. for controller instances or repository instances, is perfectly thread-safe after
|
||||
such safe initial publication from the container side. This includes common singleton
|
||||
`FactoryBean` instances which are processed within the general singleton lock as well.
|
||||
|
||||
@@ -617,7 +617,7 @@ structures (or in `volatile` fields for simple cases) as per common Java guideli
|
||||
|
||||
Deeper `Lifecycle` integration as shown above involves runtime-mutable state such as
|
||||
a `runnable` field which will have to be declared as `volatile`. While the common
|
||||
lifecycle callbacks follow a certain order, for example, a start callback is guaranteed to
|
||||
lifecycle callbacks follow a certain order, e.g. a start callback is guaranteed to
|
||||
only happen after full initialization and a stop callback only after an initial start,
|
||||
there is a special case with the common stop before destroy arrangement: It is strongly
|
||||
recommended that the internal state in any such bean also allows for an immediate
|
||||
|
||||
@@ -251,7 +251,7 @@ to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@RequestScope
|
||||
@Component
|
||||
@@ -262,7 +262,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@RequestScope
|
||||
@Component
|
||||
@@ -301,7 +301,7 @@ When using annotation-driven components or Java configuration, you can use the
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@SessionScope
|
||||
@Component
|
||||
@@ -312,7 +312,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@SessionScope
|
||||
@Component
|
||||
@@ -350,7 +350,7 @@ following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@ApplicationScope
|
||||
@Component
|
||||
@@ -361,7 +361,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@ApplicationScope
|
||||
@Component
|
||||
@@ -584,14 +584,14 @@ underlying scope:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Object get(String name, ObjectFactory<?> objectFactory)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun get(name: String, objectFactory: ObjectFactory<*>): Any
|
||||
----
|
||||
@@ -606,14 +606,14 @@ the underlying scope:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Object remove(String name)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun remove(name: String): Any
|
||||
----
|
||||
@@ -626,14 +626,14 @@ destroyed or when the specified object in the scope is destroyed:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
void registerDestructionCallback(String name, Runnable destructionCallback)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
|
||||
----
|
||||
@@ -648,14 +648,14 @@ The following method obtains the conversation identifier for the underlying scop
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
String getConversationId()
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun getConversationId(): String
|
||||
----
|
||||
@@ -677,14 +677,14 @@ method to register a new `Scope` with the Spring container:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
void registerScope(String scopeName, Scope scope);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun registerScope(scopeName: String, scope: Scope)
|
||||
----
|
||||
@@ -710,7 +710,7 @@ implementations.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Scope threadScope = new SimpleThreadScope();
|
||||
beanFactory.registerScope("thread", threadScope);
|
||||
@@ -718,7 +718,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val threadScope = SimpleThreadScope()
|
||||
beanFactory.registerScope("thread", threadScope)
|
||||
|
||||
@@ -19,7 +19,7 @@ The simplest possible `@Configuration` class reads as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -33,7 +33,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@@ -25,7 +25,7 @@ the method name. The following example shows a `@Bean` method declaration:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -39,7 +39,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -75,7 +75,7 @@ configurations by implementing interfaces with bean definitions on default metho
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public interface BaseConfig {
|
||||
|
||||
@@ -99,7 +99,7 @@ return type, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -113,7 +113,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -153,7 +153,7 @@ parameter, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -167,7 +167,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -211,7 +211,7 @@ on the `bean` element, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class BeanOne {
|
||||
|
||||
@@ -244,7 +244,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class BeanOne {
|
||||
|
||||
@@ -291,7 +291,7 @@ The following example shows how to prevent an automatic destruction callback for
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean(destroyMethod = "")
|
||||
public DataSource dataSource() throws NamingException {
|
||||
@@ -301,7 +301,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Bean(destroyMethod = "")
|
||||
fun dataSource(): DataSource {
|
||||
@@ -326,7 +326,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -344,7 +344,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -382,7 +382,7 @@ as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class MyConfiguration {
|
||||
@@ -397,7 +397,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MyConfiguration {
|
||||
@@ -431,7 +431,7 @@ it resembles the following:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// an HTTP Session-scoped bean exposed as a proxy
|
||||
@Bean
|
||||
@@ -451,7 +451,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// an HTTP Session-scoped bean exposed as a proxy
|
||||
@Bean
|
||||
@@ -479,7 +479,7 @@ as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -493,7 +493,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -517,7 +517,7 @@ The following example shows how to set a number of aliases for a bean:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -531,7 +531,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -559,7 +559,7 @@ annotation, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -574,7 +574,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
+59
-119
@@ -16,7 +16,7 @@ another configuration class, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class ConfigA {
|
||||
@@ -40,7 +40,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class ConfigA {
|
||||
@@ -67,7 +67,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
|
||||
@@ -80,7 +80,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -116,14 +116,14 @@ the configuration model, in that references to other beans must be valid Java sy
|
||||
Fortunately, solving this problem is simple. As
|
||||
xref:core/beans/java/bean-annotation.adoc#beans-java-dependencies[we already discussed],
|
||||
a `@Bean` method can have an arbitrary number of parameters that describe the bean
|
||||
dependencies. Consider the following more realistic scenario with several `@Configuration`
|
||||
dependencies. Consider the following more real-world scenario with several `@Configuration`
|
||||
classes, each depending on beans declared in the others:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class ServiceConfig {
|
||||
@@ -163,7 +163,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -219,7 +219,7 @@ parameter-based injection, as in the preceding example.
|
||||
Avoid access to locally defined beans within a `@PostConstruct` method on the same configuration
|
||||
class. This effectively leads to a circular reference since non-static `@Bean` methods semantically
|
||||
require a fully initialized configuration class instance to be called on. With circular references
|
||||
disallowed (for example, in Spring Boot 2.6+), this may trigger a `BeanCurrentlyInCreationException`.
|
||||
disallowed (e.g. in Spring Boot 2.6+), this may trigger a `BeanCurrentlyInCreationException`.
|
||||
|
||||
Also, be particularly careful with `BeanPostProcessor` and `BeanFactoryPostProcessor` definitions
|
||||
through `@Bean`. Those should usually be declared as `static @Bean` methods, not triggering the
|
||||
@@ -234,7 +234,7 @@ The following example shows how one bean can be autowired to another bean:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class ServiceConfig {
|
||||
@@ -283,7 +283,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -331,10 +331,8 @@ TIP: Constructor injection in `@Configuration` classes is only supported as of S
|
||||
Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
|
||||
bean defines only one constructor.
|
||||
|
||||
[discrete]
|
||||
[[beans-java-injecting-imported-beans-fq]]
|
||||
==== Fully-qualifying imported beans for ease of navigation
|
||||
|
||||
.[[beans-java-injecting-imported-beans-fq]]Fully-qualifying imported beans for ease of navigation
|
||||
--
|
||||
In the preceding scenario, using `@Autowired` works well and provides the desired
|
||||
modularity, but determining exactly where the autowired bean definitions are declared is
|
||||
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
|
||||
@@ -353,7 +351,7 @@ configuration classes themselves. The following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class ServiceConfig {
|
||||
@@ -371,7 +369,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class ServiceConfig {
|
||||
@@ -397,7 +395,7 @@ abstract class-based `@Configuration` classes. Consider the following example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class ServiceConfig {
|
||||
@@ -447,7 +445,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -503,48 +501,12 @@ Now `ServiceConfig` is loosely coupled with respect to the concrete
|
||||
get a type hierarchy of `RepositoryConfig` implementations. In this
|
||||
way, navigating `@Configuration` classes and their dependencies becomes no different
|
||||
than the usual process of navigating interface-based code.
|
||||
--
|
||||
|
||||
|
||||
[[beans-java-startup]]
|
||||
== Influencing the Startup of `@Bean`-defined Singletons
|
||||
|
||||
If you want to influence the startup creation order of certain singleton beans, consider
|
||||
declaring some of them as `@Lazy` for creation on first access instead of on startup.
|
||||
|
||||
`@DependsOn` forces certain other beans to be initialized first, making sure that
|
||||
the specified beans are created before the current bean, beyond what the latter's
|
||||
direct dependencies imply.
|
||||
|
||||
[[beans-java-startup-background]]
|
||||
=== Background Initialization
|
||||
|
||||
As of 6.2, there is a background initialization option: `@Bean(bootstrap=BACKGROUND)`
|
||||
allows for singling out specific beans for background initialization, covering the
|
||||
entire bean creation step for each such bean on context startup.
|
||||
|
||||
Dependent beans with non-lazy injection points automatically wait for the bean instance
|
||||
to be completed. All regular background initializations are forced to complete at the end
|
||||
of context startup. Only beans additionally marked as `@Lazy` are allowed to be completed
|
||||
later (up until the first actual access).
|
||||
|
||||
Background initialization typically goes together with `@Lazy` (or `ObjectProvider`)
|
||||
injection points in dependent beans. Otherwise, the main bootstrap thread is going to
|
||||
block when an actual background-initialized bean instance needs to be injected early.
|
||||
|
||||
This form of concurrent startup applies to individual beans: if such a bean depends on
|
||||
other beans, they need to have been initialized already, either simply through being
|
||||
declared earlier or through `@DependsOn` which enforces initialization in the main
|
||||
bootstrap thread before background initialization for the affected bean is triggered.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `bootstrapExecutor` bean of type `Executor` must be declared for background
|
||||
bootstrapping to be actually active. Otherwise, the background markers will be ignored at
|
||||
runtime.
|
||||
|
||||
The bootstrap executor may be a bounded executor just for startup purposes or a shared
|
||||
thread pool which serves for other purposes as well.
|
||||
====
|
||||
TIP: If you want to influence the startup creation order of certain beans, consider
|
||||
declaring some of them as `@Lazy` (for creation on first access instead of on startup)
|
||||
or as `@DependsOn` certain other beans (making sure that specific other beans are
|
||||
created before the current bean, beyond what the latter's direct dependencies imply).
|
||||
|
||||
|
||||
[[beans-java-conditional]]
|
||||
@@ -570,7 +532,7 @@ method that returns `true` or `false`. For example, the following listing shows
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Override
|
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
@@ -578,7 +540,7 @@ Java::
|
||||
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
|
||||
if (attrs != null) {
|
||||
for (Object value : attrs.get("value")) {
|
||||
if (context.getEnvironment().matchesProfiles((String[]) value)) {
|
||||
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -590,14 +552,14 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
|
||||
// Read the @Profile annotation attributes
|
||||
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
|
||||
if (attrs != null) {
|
||||
for (value in attrs["value"]!!) {
|
||||
if (context.environment.matchesProfiles(*value as Array<String>)) {
|
||||
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -632,24 +594,22 @@ that uses Spring XML, it is easier to create `@Configuration` classes on an
|
||||
as-needed basis and include them from the existing XML files. Later in this section, we cover the
|
||||
options for using `@Configuration` classes in this kind of "`XML-centric`" situation.
|
||||
|
||||
[discrete]
|
||||
[[beans-java-combining-xml-centric-declare-as-bean]]
|
||||
==== Declaring `@Configuration` classes as plain Spring `<bean/>` elements
|
||||
|
||||
Remember that `@Configuration` classes are ultimately bean definitions in the container.
|
||||
In this series of examples, we create a `@Configuration` class named `AppConfig` and
|
||||
.[[beans-java-combining-xml-centric-declare-as-bean]]Declaring `@Configuration` classes as plain Spring `<bean/>` elements
|
||||
--
|
||||
Remember that `@Configuration` classes are ultimately bean definitions in the
|
||||
container. In this series examples, we create a `@Configuration` class named `AppConfig` and
|
||||
include it within `system-test-config.xml` as a `<bean/>` definition. Because
|
||||
`<context:annotation-config/>` is switched on, the container recognizes the
|
||||
`@Configuration` annotation and processes the `@Bean` methods declared in `AppConfig`
|
||||
properly.
|
||||
|
||||
The following example shows the `AppConfig` configuration class in Java and Kotlin:
|
||||
The following example shows an ordinary configuration class in Java:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -664,14 +624,14 @@ Java::
|
||||
|
||||
@Bean
|
||||
public TransferService transferService() {
|
||||
return new TransferServiceImpl(accountRepository());
|
||||
return new TransferService(accountRepository());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -697,7 +657,6 @@ The following example shows part of a sample `system-test-config.xml` file:
|
||||
<beans>
|
||||
<!-- enable processing of annotations such as @Autowired and @Configuration -->
|
||||
<context:annotation-config/>
|
||||
|
||||
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
|
||||
|
||||
<bean class="com.acme.AppConfig"/>
|
||||
@@ -723,7 +682,7 @@ jdbc.password=
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
|
||||
@@ -734,7 +693,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
|
||||
@@ -744,20 +703,20 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: In the `system-test-config.xml` file, the `AppConfig` `<bean/>` does not declare an `id`
|
||||
attribute. While it would be acceptable to do so, it is unnecessary, given that no other bean
|
||||
|
||||
NOTE: In `system-test-config.xml` file, the `AppConfig` `<bean/>` does not declare an `id`
|
||||
element. While it would be acceptable to do so, it is unnecessary, given that no other bean
|
||||
ever refers to it, and it is unlikely to be explicitly fetched from the container by name.
|
||||
Similarly, the `DataSource` bean is only ever autowired by type, so an explicit bean `id`
|
||||
is not strictly required.
|
||||
--
|
||||
|
||||
[discrete]
|
||||
[[beans-java-combining-xml-centric-component-scan]]
|
||||
==== Using <context:component-scan/> to pick up `@Configuration` classes
|
||||
|
||||
.[[beans-java-combining-xml-centric-component-scan]] Using <context:component-scan/> to pick up `@Configuration` classes
|
||||
--
|
||||
Because `@Configuration` is meta-annotated with `@Component`, `@Configuration`-annotated
|
||||
classes are automatically candidates for component scanning. Using the same scenario as
|
||||
described in the previous example, we can redefine `system-test-config.xml` to take
|
||||
advantage of component-scanning. Note that, in this case, we need not explicitly declare
|
||||
described in the previous example, we can redefine `system-test-config.xml` to take advantage of component-scanning.
|
||||
Note that, in this case, we need not explicitly declare
|
||||
`<context:annotation-config/>`, because `<context:component-scan/>` enables the same
|
||||
functionality.
|
||||
|
||||
@@ -768,7 +727,6 @@ The following example shows the modified `system-test-config.xml` file:
|
||||
<beans>
|
||||
<!-- picks up and registers AppConfig as a bean definition -->
|
||||
<context:component-scan base-package="com.acme"/>
|
||||
|
||||
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
|
||||
|
||||
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
@@ -778,23 +736,25 @@ The following example shows the modified `system-test-config.xml` file:
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
--
|
||||
|
||||
[[beans-java-combining-java-centric]]
|
||||
=== `@Configuration` Class-centric Use of XML with `@ImportResource`
|
||||
|
||||
In applications where `@Configuration` classes are the primary mechanism for configuring
|
||||
the container, it may still be necessary to use at least some XML. In such scenarios, you
|
||||
can use `@ImportResource` and define only as much XML as you need. Doing so achieves a
|
||||
"`Java-centric`" approach to configuring the container and keeps XML to a bare minimum.
|
||||
The following example (which includes a configuration class, an XML file that defines a
|
||||
bean, a properties file, and the `main()` method) shows how to use the `@ImportResource`
|
||||
annotation to achieve "`Java-centric`" configuration that uses XML as needed:
|
||||
the container, it is still likely necessary to use at least some XML. In these
|
||||
scenarios, you can use `@ImportResource` and define only as much XML as you need. Doing
|
||||
so achieves a "`Java-centric`" approach to configuring the container and keeps XML to a
|
||||
bare minimum. The following example (which includes a configuration class, an XML file
|
||||
that defines a bean, a properties file, and the `main` class) shows how to use
|
||||
the `@ImportResource` annotation to achieve "`Java-centric`" configuration that uses XML
|
||||
as needed:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ImportResource("classpath:/com/acme/properties-config.xml")
|
||||
@@ -813,23 +773,12 @@ Java::
|
||||
public DataSource dataSource() {
|
||||
return new DriverManagerDataSource(url, username, password);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AccountRepository accountRepository(DataSource dataSource) {
|
||||
return new JdbcAccountRepository(dataSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TransferService transferService(AccountRepository accountRepository) {
|
||||
return new TransferServiceImpl(accountRepository);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ImportResource("classpath:/com/acme/properties-config.xml")
|
||||
@@ -848,32 +797,21 @@ Kotlin::
|
||||
fun dataSource(): DataSource {
|
||||
return DriverManagerDataSource(url, username, password)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun accountRepository(dataSource: DataSource): AccountRepository {
|
||||
return JdbcAccountRepository(dataSource)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun transferService(accountRepository: AccountRepository): TransferService {
|
||||
return TransferServiceImpl(accountRepository)
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
.properties-config.xml
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
properties-config.xml
|
||||
<beans>
|
||||
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
.jdbc.properties
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
jdbc.properties
|
||||
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
|
||||
jdbc.username=sa
|
||||
jdbc.password=
|
||||
@@ -883,7 +821,7 @@ jdbc.password=
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
|
||||
@@ -894,7 +832,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -906,3 +844,5 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ as having one bean method call another, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -36,7 +36,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
@@ -72,7 +72,7 @@ following example shows how to use lookup method injection:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public abstract class CommandManager {
|
||||
public Object process(Object commandState) {
|
||||
@@ -90,7 +90,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
abstract class CommandManager {
|
||||
fun process(commandState: Any): Any {
|
||||
@@ -115,7 +115,7 @@ the abstract `createCommand()` method is overridden in such a way that it looks
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@@ -139,7 +139,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@@ -172,7 +172,7 @@ Consider the following example, which shows a `@Bean` annotated method being cal
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@@ -200,7 +200,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@@ -27,7 +27,7 @@ XML-free usage of the Spring container, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
|
||||
@@ -38,7 +38,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -58,7 +58,7 @@ as input to the constructor, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
|
||||
@@ -69,7 +69,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -97,7 +97,7 @@ example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
@@ -111,7 +111,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
@@ -136,7 +136,7 @@ To enable component scanning, you can annotate your `@Configuration` class as fo
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "com.acme") // <1>
|
||||
@@ -148,7 +148,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["com.acme"]) // <1>
|
||||
@@ -183,7 +183,7 @@ following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static void main(String[] args) {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
@@ -195,7 +195,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun main() {
|
||||
val ctx = AnnotationConfigApplicationContext()
|
||||
|
||||
@@ -33,7 +33,7 @@ Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@@ -55,7 +55,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
@@ -83,7 +83,7 @@ preceding example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
@@ -106,7 +106,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
@@ -131,7 +131,7 @@ you should use the `@Named` annotation, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -151,7 +151,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -190,7 +190,7 @@ a `required` attribute. The following pair of examples show how to use `@Inject`
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@@ -203,7 +203,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -225,7 +225,7 @@ as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -246,7 +246,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -269,7 +269,7 @@ It is very common to use `@Component` without specifying a name for the componen
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -290,7 +290,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -313,7 +313,7 @@ exact same way as when you use Spring annotations, as the following example show
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example")
|
||||
@@ -324,7 +324,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"])
|
||||
|
||||
@@ -29,7 +29,7 @@ a `DataBuffer` implementation and that does not involve allocation.
|
||||
|
||||
Note that WebFlux applications do not create a `DataBufferFactory` directly but instead
|
||||
access it through the `ServerHttpResponse` or the `ClientHttpRequest` on the client side.
|
||||
The type of factory depends on the underlying client or server, for example,
|
||||
The type of factory depends on the underlying client or server, e.g.
|
||||
`NettyDataBufferFactory` for Reactor Netty, `DefaultDataBufferFactory` for others.
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ to use the convenience methods in `DataBufferUtils` that apply release or retain
|
||||
|
||||
`DataBufferUtils` offers a number of utility methods to operate on data buffers:
|
||||
|
||||
* Join a stream of data buffers into a single buffer possibly with zero copy, for example, via
|
||||
* Join a stream of data buffers into a single buffer possibly with zero copy, e.g. via
|
||||
composite buffers, if that's supported by the underlying byte buffer API.
|
||||
* Turn `InputStream` or NIO `Channel` into `Flux<DataBuffer>`, and vice versa a
|
||||
`Publisher<DataBuffer>` into `OutputStream` or NIO `Channel`.
|
||||
@@ -144,7 +144,7 @@ a serialization error occurs while populating the buffer with data. For example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
DataBuffer buffer = factory.allocateBuffer();
|
||||
boolean release = true;
|
||||
@@ -162,7 +162,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val buffer = factory.allocateBuffer()
|
||||
var release = true
|
||||
|
||||
@@ -1,34 +1,219 @@
|
||||
[[expressions-beandef]]
|
||||
= Expressions in Bean Definitions
|
||||
|
||||
You can use SpEL expressions with configuration metadata for defining bean instances. In both
|
||||
cases, the syntax to define the expression is of the form `#{ <expression string> }`.
|
||||
You can use SpEL expressions with XML-based or annotation-based configuration metadata for
|
||||
defining `BeanDefinition` instances. In both cases, the syntax to define the expression is of the
|
||||
form `#{ <expression string> }`.
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-xml-based]]
|
||||
== XML Configuration
|
||||
|
||||
A property or constructor argument value can be set by using expressions, as the following
|
||||
example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
All beans in the application context are available as predefined variables with their
|
||||
common bean name. This includes standard context beans such as `environment` (of type
|
||||
`org.springframework.core.env.Environment`) as well as `systemProperties` and
|
||||
`systemEnvironment` (of type `Map<String, Object>`) for access to the runtime environment.
|
||||
|
||||
To specify a default value, you can place the `@Value` annotation on fields, methods,
|
||||
and method or constructor parameters (or XML equivalent).
|
||||
The following example shows access to the `systemProperties` bean as a SpEL variable:
|
||||
|
||||
The following example sets the default value of a field:
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
|
||||
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
|
||||
|
||||
include-code::./FieldValueTestBean[tag=snippet,indent=0]
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that you do not have to prefix the predefined variable with the `#` symbol here.
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
|
||||
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
|
||||
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-annotation-based]]
|
||||
== Annotation Configuration
|
||||
|
||||
To specify a default value, you can place the `@Value` annotation on fields, methods,
|
||||
and method or constructor parameters.
|
||||
|
||||
The following example sets the default value of a field:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class FieldValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
private String defaultLocale;
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class FieldValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the equivalent but on a property setter method:
|
||||
|
||||
include-code::./PropertyValueTestBean[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class PropertyValueTestBean {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class PropertyValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Autowired methods and constructors can also use the `@Value` annotation, as the following
|
||||
examples show:
|
||||
|
||||
include-code::./SimpleMovieLister[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
private String defaultLocale;
|
||||
|
||||
@Autowired
|
||||
public void configure(MovieFinder movieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
|
||||
this.movieFinder = movieFinder;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
private lateinit var movieFinder: MovieFinder
|
||||
private lateinit var defaultLocale: String
|
||||
|
||||
@Autowired
|
||||
fun configure(movieFinder: MovieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
|
||||
this.movieFinder = movieFinder
|
||||
this.defaultLocale = defaultLocale
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
private CustomerPreferenceDao customerPreferenceDao;
|
||||
|
||||
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") String defaultLocale) {
|
||||
this.customerPreferenceDao = customerPreferenceDao;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
include-code::./MovieRecommender[tag=snippet,indent=0]
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
include-code::./ShapeGuess[tag=snippet,indent=0]
|
||||
|
||||
@@ -12,7 +12,7 @@ expression, `Hello World`.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp = parser.parseExpression("'Hello World'"); // <1>
|
||||
@@ -22,7 +22,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val exp = parser.parseExpression("'Hello World'") // <1>
|
||||
@@ -51,7 +51,7 @@ literal, `Hello World`.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); // <1>
|
||||
@@ -61,7 +61,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val exp = parser.parseExpression("'Hello World'.concat('!')") // <1>
|
||||
@@ -77,7 +77,7 @@ string literal, `Hello World`.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@@ -89,7 +89,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
@@ -110,7 +110,7 @@ The following example shows how to use dot notation to get the length of a strin
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@@ -122,7 +122,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
@@ -140,7 +140,7 @@ example shows.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // <1>
|
||||
@@ -150,7 +150,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val exp = parser.parseExpression("new String('hello world').toUpperCase()") // <1>
|
||||
@@ -173,7 +173,7 @@ reference the `name` property in a boolean expression.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Create and set a calendar
|
||||
GregorianCalendar c = new GregorianCalendar();
|
||||
@@ -195,7 +195,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Create and set a calendar
|
||||
val c = GregorianCalendar()
|
||||
@@ -222,38 +222,29 @@ Kotlin::
|
||||
[[expressions-evaluation-context]]
|
||||
== Understanding `EvaluationContext`
|
||||
|
||||
The `EvaluationContext` API is used when evaluating an expression to resolve properties,
|
||||
methods, or fields and to help perform type conversion. Spring provides two
|
||||
The `EvaluationContext` interface is used when evaluating an expression to resolve
|
||||
properties, methods, or fields and to help perform type conversion. Spring provides two
|
||||
implementations.
|
||||
|
||||
`SimpleEvaluationContext`::
|
||||
Exposes a subset of essential SpEL language features and configuration options, for
|
||||
categories of expressions that do not require the full extent of the SpEL language
|
||||
syntax and should be meaningfully restricted. Examples include but are not limited to
|
||||
data binding expressions and property-based filters.
|
||||
* `SimpleEvaluationContext`: Exposes a subset of essential SpEL language features and
|
||||
configuration options, for categories of expressions that do not require the full extent
|
||||
of the SpEL language syntax and should be meaningfully restricted. Examples include but
|
||||
are not limited to data binding expressions and property-based filters.
|
||||
|
||||
`StandardEvaluationContext`::
|
||||
Exposes the full set of SpEL language features and configuration options. You can use
|
||||
it to specify a default root object and to configure every available evaluation-related
|
||||
strategy.
|
||||
* `StandardEvaluationContext`: Exposes the full set of SpEL language features and
|
||||
configuration options. You can use it to specify a default root object and to configure
|
||||
every available evaluation-related strategy.
|
||||
|
||||
`SimpleEvaluationContext` is designed to support only a subset of the SpEL language
|
||||
syntax. For example, it excludes Java type references, constructors, and bean references.
|
||||
It also requires you to explicitly choose the level of support for properties and methods
|
||||
in expressions. When creating a `SimpleEvaluationContext` you need to choose the level of
|
||||
support that you need for data binding in SpEL expressions:
|
||||
`SimpleEvaluationContext` is designed to support only a subset of the SpEL language syntax.
|
||||
It excludes Java type references, constructors, and bean references. It also requires
|
||||
you to explicitly choose the level of support for properties and methods in expressions.
|
||||
By default, the `create()` static factory method enables only read access to properties.
|
||||
You can also obtain a builder to configure the exact level of support needed, targeting
|
||||
one or some combination of the following.
|
||||
|
||||
* Data binding for read-only access
|
||||
* Data binding for read and write access
|
||||
* A custom `PropertyAccessor` (typically not reflection-based), potentially combined with
|
||||
a `DataBindingPropertyAccessor`
|
||||
|
||||
Conveniently, `SimpleEvaluationContext.forReadOnlyDataBinding()` enables read-only access
|
||||
to properties via `DataBindingPropertyAccessor`. Similarly,
|
||||
`SimpleEvaluationContext.forReadWriteDataBinding()` enables read and write access to
|
||||
properties. Alternatively, configure custom accessors via
|
||||
`SimpleEvaluationContext.forPropertyAccessors(...)`, potentially disable assignment, and
|
||||
optionally activate method resolution and/or a type converter through the builder.
|
||||
* Custom `PropertyAccessor` only (no reflection)
|
||||
* Data binding properties for read-only access
|
||||
* Data binding properties for read and write
|
||||
|
||||
|
||||
[[expressions-type-conversion]]
|
||||
@@ -275,7 +266,7 @@ being placed in it. The following example shows how to do so.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class Simple {
|
||||
public List<Boolean> booleanList = new ArrayList<>();
|
||||
@@ -296,7 +287,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class Simple {
|
||||
var booleanList: MutableList<Boolean> = ArrayList()
|
||||
@@ -323,23 +314,23 @@ Kotlin::
|
||||
It is possible to configure the SpEL expression parser by using a parser configuration
|
||||
object (`org.springframework.expression.spel.SpelParserConfiguration`). The configuration
|
||||
object controls the behavior of some of the expression components. For example, if you
|
||||
index into a collection and the element at the specified index is `null`, SpEL can
|
||||
automatically create the element. This is useful when using expressions made up of a
|
||||
chain of property references. Similarly, if you index into a collection and specify an
|
||||
index that is greater than the current size of the collection, SpEL can automatically
|
||||
grow the collection to accommodate that index. In order to add an element at the
|
||||
index into an array or collection and the element at the specified index is `null`, SpEL
|
||||
can automatically create the element. This is useful when using expressions made up of a
|
||||
chain of property references. If you index into an array or list and specify an index
|
||||
that is beyond the end of the current size of the array or list, SpEL can automatically
|
||||
grow the array or list to accommodate that index. In order to add an element at the
|
||||
specified index, SpEL will try to create the element using the element type's default
|
||||
constructor before setting the specified value. If the element type does not have a
|
||||
default constructor, `null` will be added to the collection. If there is no built-in
|
||||
converter or custom converter that knows how to set the value, `null` will remain in the
|
||||
collection at the specified index. The following example demonstrates how to
|
||||
automatically grow a `List`.
|
||||
default constructor, `null` will be added to the array or list. If there is no built-in
|
||||
or custom converter that knows how to set the value, `null` will remain in the array or
|
||||
list at the specified index. The following example demonstrates how to automatically grow
|
||||
the list.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class Demo {
|
||||
public List<String> list;
|
||||
@@ -364,7 +355,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class Demo {
|
||||
var list: List<String>? = null
|
||||
@@ -444,27 +435,18 @@ component. This section discusses both of these options.
|
||||
The compiler can operate in one of three modes, which are captured in the
|
||||
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows.
|
||||
|
||||
`OFF` ::
|
||||
The compiler is switched off, and all expressions will be evaluated in _interpreted_
|
||||
mode. This is the default mode.
|
||||
`IMMEDIATE` ::
|
||||
In immediate mode, expressions are compiled as soon as possible, typically after the
|
||||
first interpreted evaluation. If evaluation of the compiled expression fails (for
|
||||
example, due to a type changing, as described earlier), the caller of the expression
|
||||
evaluation receives an exception. If the types of various expression elements change
|
||||
over time, consider switching to `MIXED` mode or turning off the compiler.
|
||||
`MIXED` ::
|
||||
In mixed mode, expression evaluation silently switches between _interpreted_ and
|
||||
_compiled_ over time. After some number of successful interpreted runs, the expression
|
||||
gets compiled. If evaluation of the compiled expression fails (for example, due to a
|
||||
type changing), that failure will be caught internally, and the system will switch back
|
||||
to interpreted mode for the given expression. Basically, the exception that the caller
|
||||
receives in `IMMEDIATE` mode is instead handled internally. Sometime later, the
|
||||
compiler may generate another compiled form and switch to it. This cycle of switching
|
||||
between interpreted and compiled mode will continue until the system determines that it
|
||||
does not make sense to continue trying — for example, when a certain failure threshold
|
||||
has been reached — at which point the system will permanently switch to interpreted
|
||||
mode for the given expression.
|
||||
* `OFF` (default): The compiler is switched off.
|
||||
* `IMMEDIATE`: In immediate mode, the expressions are compiled as soon as possible. This
|
||||
is typically after the first interpreted evaluation. If the compiled expression fails
|
||||
(typically due to a type changing, as described earlier), the caller of the expression
|
||||
evaluation receives an exception.
|
||||
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and
|
||||
compiled mode over time. After some number of interpreted runs, they switch to compiled
|
||||
form and, if something goes wrong with the compiled form (such as a type changing, as
|
||||
described earlier), the expression automatically switches back to interpreted form
|
||||
again. Sometime later, it may generate another compiled form and switch to it.
|
||||
Basically, the exception that the user gets in `IMMEDIATE` mode is instead handled
|
||||
internally.
|
||||
|
||||
`IMMEDIATE` mode exists because `MIXED` mode could cause issues for expressions that
|
||||
have side effects. If a compiled expression blows up after partially succeeding, it
|
||||
@@ -479,7 +461,7 @@ following example shows how to do so.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
|
||||
this.getClass().getClassLoader());
|
||||
@@ -495,7 +477,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
|
||||
this.javaClass.classLoader)
|
||||
@@ -534,11 +516,10 @@ following kinds of expressions cannot be compiled.
|
||||
|
||||
* Expressions involving assignment
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using custom resolvers or accessors
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
* Expressions using bean references
|
||||
|
||||
Compilation of additional kinds of expressions may be supported in the future.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ This section lists the classes used in the examples throughout this chapter.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
|
||||
@@ -84,7 +84,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
@@ -103,7 +103,7 @@ Kotlin::
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
|
||||
@@ -141,7 +141,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
@@ -155,7 +155,7 @@ Kotlin::
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
|
||||
@@ -200,7 +200,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ to have the array populated at construction time. The following example shows ho
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
|
||||
|
||||
@@ -21,7 +21,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
|
||||
|
||||
|
||||
+18
-26
@@ -1,73 +1,65 @@
|
||||
[[expressions-bean-references]]
|
||||
= Bean References
|
||||
|
||||
If the evaluation context has been configured with a bean resolver, you can look up beans
|
||||
from an expression by using the `@` symbol as a prefix. The following example shows how
|
||||
If the evaluation context has been configured with a bean resolver, you can
|
||||
look up beans from an expression by using the `@` symbol. The following example shows how
|
||||
to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setBeanResolver(new MyBeanResolver());
|
||||
|
||||
// This will end up calling resolve(context, "someBean") on MyBeanResolver
|
||||
// during evaluation.
|
||||
Object bean = parser.parseExpression("@someBean").getValue(context);
|
||||
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
|
||||
Object bean = parser.parseExpression("@something").getValue(context);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = StandardEvaluationContext()
|
||||
context.setBeanResolver(MyBeanResolver())
|
||||
|
||||
// This will end up calling resolve(context, "someBean") on MyBeanResolver
|
||||
// during evaluation.
|
||||
val bean = parser.parseExpression("@someBean").getValue(context)
|
||||
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
|
||||
val bean = parser.parseExpression("@something").getValue(context)
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If a bean name contains a dot (`.`) or other special characters, you must provide the
|
||||
name of the bean as a _string literal_ – for example, `@'order.service'`.
|
||||
====
|
||||
|
||||
To access a factory bean itself, you should instead prefix the bean name with an `&`
|
||||
symbol. The following example shows how to do so:
|
||||
To access a factory bean itself, you should instead prefix the bean name with an `&` symbol.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setBeanResolver(new MyBeanResolver());
|
||||
|
||||
// This will end up calling resolve(context, "&someFactoryBean") on
|
||||
// MyBeanResolver during evaluation.
|
||||
Object factoryBean = parser.parseExpression("&someFactoryBean").getValue(context);
|
||||
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
|
||||
Object bean = parser.parseExpression("&foo").getValue(context);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = StandardEvaluationContext()
|
||||
context.setBeanResolver(MyBeanResolver())
|
||||
|
||||
// This will end up calling resolve(context, "&someFactoryBean") on
|
||||
// MyBeanResolver during evaluation.
|
||||
val factoryBean = parser.parseExpression("&someFactoryBean").getValue(context)
|
||||
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
|
||||
val bean = parser.parseExpression("&foo").getValue(context)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ list. The following example uses projection to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
@@ -20,7 +20,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ selection lets us easily get a list of Serbian inventors, as the following examp
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
List<Inventor> list = (List<Inventor>) parser.parseExpression(
|
||||
"members.?[nationality == 'Serbian']").getValue(societyContext);
|
||||
@@ -20,7 +20,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val list = parser.parseExpression(
|
||||
"members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
|
||||
@@ -41,14 +41,14 @@ than 27:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val newMap = parser.parseExpression("#map.?[value < 27]").getValue() as Map
|
||||
----
|
||||
|
||||
@@ -3,40 +3,39 @@
|
||||
|
||||
You can invoke constructors by using the `new` operator. You should use the fully
|
||||
qualified class name for all types except those located in the `java.lang` package
|
||||
(`Integer`, `Float`, `String`, and so on).
|
||||
xref:core/expressions/language-ref/varargs.adoc[Varargs] are also supported.
|
||||
|
||||
The following example shows how to use the `new` operator to invoke constructors.
|
||||
(`Integer`, `Float`, `String`, and so on). The following example shows how to use the
|
||||
`new` operator to invoke constructors:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Inventor einstein = parser.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
Inventor einstein = p.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
.getValue(Inventor.class);
|
||||
|
||||
// create new Inventor instance within the add() method of List
|
||||
parser.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
|
||||
.getValue(societyContext);
|
||||
p.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor(
|
||||
'Albert Einstein', 'German'))").getValue(societyContext);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val einstein = parser.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
val einstein = p.parseExpression(
|
||||
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
|
||||
.getValue(Inventor::class.java)
|
||||
|
||||
// create new Inventor instance within the add() method of List
|
||||
parser.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
|
||||
p.parseExpression(
|
||||
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
|
||||
.getValue(societyContext)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
= Functions
|
||||
|
||||
You can extend SpEL by registering user-defined functions that can be called within
|
||||
expressions by using the `#functionName(...)` syntax, and like with standard method
|
||||
invocations, xref:core/expressions/language-ref/varargs.adoc[varargs] are also supported
|
||||
for function invocations.
|
||||
|
||||
Functions can be registered as _variables_ in `EvaluationContext` implementations via the
|
||||
`setVariable()` method.
|
||||
expressions by using the `#functionName(...)` syntax. Functions can be registered as
|
||||
variables in `EvaluationContext` implementations via the `setVariable()` method.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -30,7 +26,7 @@ reflection using a `java.lang.reflect.Method`:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Method method = ...;
|
||||
|
||||
@@ -40,7 +36,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val method: Method = ...
|
||||
|
||||
@@ -55,7 +51,7 @@ For example, consider the following utility method that reverses a string:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public abstract class StringUtils {
|
||||
|
||||
@@ -67,7 +63,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun reverseString(input: String): String {
|
||||
return StringBuilder(input).reverse().toString()
|
||||
@@ -81,7 +77,7 @@ You can register and use the preceding method, as the following example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@@ -96,7 +92,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
@@ -114,9 +110,8 @@ potentially more efficient use cases if the `MethodHandle` target and parameters
|
||||
been fully bound prior to registration; however, partially bound handles are also
|
||||
supported.
|
||||
|
||||
Consider the `String#formatted(Object...)` instance method, which produces a message
|
||||
according to a template and a variable number of arguments
|
||||
(xref:core/expressions/language-ref/varargs.adoc[varargs]).
|
||||
Consider the `String#formatted(String, Object...)` instance method, which produces a
|
||||
message according to a template and a variable number of arguments.
|
||||
|
||||
You can register and use the `formatted` method as a `MethodHandle`, as the following
|
||||
example shows:
|
||||
@@ -125,7 +120,7 @@ example shows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
@@ -141,7 +136,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
@@ -156,16 +151,16 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
As mentioned above, binding a `MethodHandle` and registering the bound `MethodHandle` is
|
||||
also supported. This is likely to be more performant if both the target and all the
|
||||
arguments are bound. In that case no arguments are necessary in the SpEL expression, as
|
||||
the following example shows:
|
||||
As hinted above, binding a `MethodHandle` and registering the bound `MethodHandle` is also
|
||||
supported. This is likely to be more performant if both the target and all the arguments
|
||||
are bound. In that case no arguments are necessary in the SpEL expression, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
@@ -173,10 +168,9 @@ Java::
|
||||
String template = "This is a %s message with %s words: <%s>";
|
||||
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
|
||||
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
|
||||
MethodType.methodType(String.class, Object[].class))
|
||||
MethodType.methodType(String.class, Object[].class))
|
||||
.bindTo(template)
|
||||
// Here we have to provide the arguments in a single array binding:
|
||||
.bindTo(varargs);
|
||||
.bindTo(varargs); //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh);
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
@@ -186,7 +180,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
@@ -195,10 +189,9 @@ Kotlin::
|
||||
val varargs = arrayOf("prerecorded", 3, "Oh Hello World!", "ignored")
|
||||
|
||||
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
.bindTo(template)
|
||||
// Here we have to provide the arguments in a single array binding:
|
||||
.bindTo(varargs)
|
||||
.bindTo(varargs) //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh)
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
@@ -208,3 +201,4 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ You can directly express lists in an expression by using `{}` notation.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to a Java list containing the four numbers
|
||||
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
|
||||
@@ -17,7 +17,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to a Java list containing the four numbers
|
||||
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>
|
||||
|
||||
@@ -8,7 +8,7 @@ following example shows how to do so:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to a Java map containing the two entries
|
||||
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
|
||||
@@ -18,7 +18,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
// evaluates to a Java map containing the two entries
|
||||
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, *>
|
||||
|
||||
@@ -53,7 +53,7 @@ method.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@@ -75,7 +75,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
[[expressions-methods]]
|
||||
= Methods
|
||||
|
||||
You can invoke methods by using the typical Java programming syntax. You can also invoke
|
||||
methods directly on literals such as strings or numbers.
|
||||
xref:core/expressions/language-ref/varargs.adoc[Varargs] are supported as well.
|
||||
|
||||
The following examples show how to invoke methods.
|
||||
You can invoke methods by using typical Java programming syntax. You can also invoke methods
|
||||
on literals. Variable arguments are also supported. The following examples show how to
|
||||
invoke methods:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// string literal, evaluates to "bc"
|
||||
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
|
||||
@@ -23,7 +21,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// string literal, evaluates to "bc"
|
||||
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)
|
||||
|
||||
@@ -19,7 +19,7 @@ The following example shows how to use the Elvis operator:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@@ -29,7 +29,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
@@ -48,7 +48,7 @@ The following listing shows a more complex example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
@@ -64,7 +64,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
|
||||
+13
-72
@@ -1,7 +1,7 @@
|
||||
[[expressions-operator-safe-navigation]]
|
||||
= Safe Navigation Operator
|
||||
|
||||
The safe navigation operator (`?.`) is used to avoid a `NullPointerException` and comes
|
||||
The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
|
||||
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
|
||||
language. Typically, when you have a reference to an object, you might need to verify
|
||||
that it is not `null` before accessing methods or properties of the object. To avoid
|
||||
@@ -27,7 +27,7 @@ The following example shows how to use the safe navigation operator for property
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
@@ -50,7 +50,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
@@ -81,65 +81,6 @@ For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
|
||||
`max(int, int)` method will be invoked on the `#calculator`.
|
||||
====
|
||||
|
||||
[[expressions-operator-safe-navigation-indexing]]
|
||||
== Safe Index Access
|
||||
|
||||
Since Spring Framework 6.2, the Spring Expression Language supports safe navigation for
|
||||
indexing into the following types of structures.
|
||||
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-arrays-and-collections[arrays and collections]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-strings[strings]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-maps[maps]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-objects[objects]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-custom[custom]
|
||||
|
||||
The following example shows how to use the safe navigation operator for indexing into
|
||||
a list (`?.[]`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
EvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
var inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a null `members` list
|
||||
======
|
||||
|
||||
[[expressions-operator-safe-navigation-selection-and-projection]]
|
||||
== Safe Collection Selection and Projection
|
||||
@@ -161,7 +102,7 @@ selection (`?.?`).
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
@@ -182,7 +123,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
@@ -209,7 +150,7 @@ collections (`?.^`).
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
@@ -231,7 +172,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
@@ -260,7 +201,7 @@ collections (`?.$`).
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
@@ -282,7 +223,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
@@ -310,7 +251,7 @@ projection (`?.!`).
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
@@ -331,7 +272,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
@@ -380,7 +321,7 @@ evaluates to `null` instead of throwing an exception.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
@@ -401,7 +342,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
|
||||
+4
-4
@@ -8,7 +8,7 @@ the expression. The following listing shows a minimal example:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
String falseString = parser.parseExpression(
|
||||
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
|
||||
@@ -16,7 +16,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val falseString = parser.parseExpression(
|
||||
"false ? 'trueExp' : 'falseExp'").getValue(String::class.java)
|
||||
@@ -30,7 +30,7 @@ realistic example follows:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
parser.parseExpression("name").setValue(societyContext, "IEEE");
|
||||
societyContext.setVariable("queryName", "Nikola Tesla");
|
||||
@@ -45,7 +45,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
parser.parseExpression("name").setValue(societyContext, "IEEE")
|
||||
societyContext.setVariable("queryName", "Nikola Tesla")
|
||||
|
||||
@@ -24,7 +24,7 @@ The following listing shows a few examples of relational operators:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to true
|
||||
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
|
||||
@@ -41,7 +41,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to true
|
||||
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
|
||||
@@ -89,7 +89,7 @@ shows examples of all three:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
boolean result;
|
||||
|
||||
@@ -128,7 +128,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to true
|
||||
var result = parser.parseExpression(
|
||||
@@ -195,7 +195,7 @@ The following example shows how to use the logical operators:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// -- AND --
|
||||
|
||||
@@ -228,7 +228,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// -- AND --
|
||||
|
||||
@@ -277,7 +277,7 @@ The following example shows the `String` operators in use:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// -- Concatenation --
|
||||
|
||||
@@ -300,7 +300,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// -- Concatenation --
|
||||
|
||||
@@ -358,7 +358,7 @@ The following example shows the mathematical operators in use:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Inventor inventor = new Inventor();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
@@ -424,7 +424,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val inventor = Inventor()
|
||||
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
|
||||
@@ -501,7 +501,7 @@ listing shows both ways to use the assignment operator:
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Inventor inventor = new Inventor();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
@@ -515,7 +515,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val inventor = Inventor()
|
||||
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
|
||||
@@ -541,7 +541,7 @@ For example, if we want to overload the `ADD` operator to allow two lists to be
|
||||
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
|
||||
follows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
pubic class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@@ -576,7 +576,7 @@ as demonstrated in the following example.
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setOperatorOverloader(new ListConcatenation());
|
||||
@@ -587,7 +587,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
StandardEvaluationContext context = StandardEvaluationContext()
|
||||
context.setOperatorOverloader(ListConcatenation())
|
||||
|
||||
+32
-254
@@ -1,47 +1,31 @@
|
||||
[[expressions-properties-arrays]]
|
||||
= Properties, Arrays, Lists, Maps, and Indexers
|
||||
|
||||
The Spring Expression Language provides support for navigating object graphs and indexing
|
||||
into various structures.
|
||||
|
||||
NOTE: Numerical index values are zero-based, such as when accessing the n^th^ element of
|
||||
an array in Java.
|
||||
|
||||
TIP: See the xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
|
||||
section for details on how to navigate object graphs and index into various structures
|
||||
using the null-safe operator.
|
||||
|
||||
[[expressions-property-navigation]]
|
||||
== Property Navigation
|
||||
|
||||
You can navigate property references within an object graph by using a period to indicate
|
||||
a nested property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the
|
||||
xref:core/expressions/example-classes.adoc[Classes used in the examples] section. To
|
||||
navigate _down_ the object graph and get Tesla's year of birth and Pupin's city of birth,
|
||||
we use the following expressions:
|
||||
Navigating with property references is easy. To do so, use a period to indicate a nested
|
||||
property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the xref:core/expressions/example-classes.adoc[Classes used in the examples]
|
||||
section. To navigate "down" the object graph and get Tesla's year of birth and
|
||||
Pupin's city of birth, we use the following expressions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to 1856
|
||||
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to 1856
|
||||
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
|
||||
----
|
||||
======
|
||||
@@ -55,26 +39,14 @@ method invocations -- for example, `getPlaceOfBirth().getCity()` instead of
|
||||
`placeOfBirth.city`.
|
||||
====
|
||||
|
||||
[[expressions-indexing-arrays-and-collections]]
|
||||
== Indexing into Arrays and Collections
|
||||
|
||||
The n^th^ element of an array or collection (for example, a `Set` or `List`) can be
|
||||
obtained by using square bracket notation, as the following example shows.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If the indexed collection is a `java.util.List`, the n^th^ element will be accessed
|
||||
directly via `list.get(n)`.
|
||||
|
||||
For any other type of `Collection`, the n^th^ element will be accessed by iterating over
|
||||
the collection using its `Iterator` and returning the n^th^ element encountered.
|
||||
====
|
||||
The contents of arrays and lists are obtained by using square bracket notation, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
@@ -91,8 +63,7 @@ Java::
|
||||
String name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String.class);
|
||||
|
||||
// List and Array Indexing
|
||||
|
||||
// List and Array navigation
|
||||
// evaluates to "Wireless communication"
|
||||
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String.class);
|
||||
@@ -100,7 +71,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
@@ -117,248 +88,55 @@ Kotlin::
|
||||
val name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String::class.java)
|
||||
|
||||
// List and Array Indexing
|
||||
|
||||
// List and Array navigation
|
||||
// evaluates to "Wireless communication"
|
||||
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-strings]]
|
||||
== Indexing into Strings
|
||||
|
||||
The n^th^ character of a string can be obtained by specifying the index within square
|
||||
brackets, as demonstrated in the following example.
|
||||
|
||||
NOTE: The n^th^ character of a string will evaluate to a `java.lang.String`, not a
|
||||
`java.lang.Character`.
|
||||
The contents of maps are obtained by specifying the literal key value within the
|
||||
brackets. In the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
String character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String.class);
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Officer's Dictionary
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
val character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-maps]]
|
||||
== Indexing into Maps
|
||||
|
||||
The contents of maps are obtained by specifying the key value within square brackets. In
|
||||
the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals such as `'president'`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Officer's Map
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor.class);
|
||||
Inventor pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor.class);
|
||||
|
||||
// evaluates to "Idvor"
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String.class);
|
||||
|
||||
String countryExpression = "officers['advisors'][0].placeOfBirth.country";
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String.class);
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia");
|
||||
|
||||
// evaluates to "Croatia"
|
||||
String country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String.class);
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Officer's Map
|
||||
// Officer's Dictionary
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
val pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor::class.java)
|
||||
val pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor::class.java)
|
||||
|
||||
// evaluates to "Idvor"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String::class.java)
|
||||
|
||||
val countryExpression = "officers['advisors'][0].placeOfBirth.country"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String::class.java)
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia")
|
||||
|
||||
// evaluates to "Croatia"
|
||||
val country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String::class.java)
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia")
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-objects]]
|
||||
== Indexing into Objects
|
||||
|
||||
A property of an object can be obtained by specifying the name of the property within
|
||||
square brackets. This is analogous to accessing the value of a map based on its key. The
|
||||
following example demonstrates how to _index_ into an object to retrieve a specific
|
||||
property.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
Inventor tesla = new Inventor("Nikola Tesla");
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
val tesla = Inventor("Nikola Tesla")
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
val name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-custom]]
|
||||
== Indexing into Custom Structures
|
||||
|
||||
Since Spring Framework 6.2, the Spring Expression Language supports indexing into custom
|
||||
structures by allowing developers to implement and register an `IndexAccessor` with the
|
||||
`EvaluationContext`. If you would like to support
|
||||
xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation] of
|
||||
expressions that rely on a custom index accessor, that index accessor must implement the
|
||||
`CompilableIndexAccessor` SPI.
|
||||
|
||||
To support common use cases, Spring provides a built-in `ReflectiveIndexAccessor` which
|
||||
is a flexible `IndexAccessor` that uses reflection to read from and optionally write to
|
||||
an indexed structure of a target object. The indexed structure can be accessed through a
|
||||
`public` read-method (when being read) or a `public` write-method (when being written).
|
||||
The relationship between the read-method and write-method is based on a convention that
|
||||
is applicable for typical implementations of indexed structures.
|
||||
|
||||
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
|
||||
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
|
||||
to bytecode for read access. Note, however, that the configured read-method must be
|
||||
invokable via a `public` class or `public` interface for compilation to succeed.
|
||||
|
||||
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
|
||||
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
|
||||
a `FruitMap` within a SpEL expression, you will need to register an `IndexAccessor`.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
package example;
|
||||
|
||||
public enum Color {
|
||||
RED, ORANGE, YELLOW
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class FruitMap {
|
||||
|
||||
private final Map<Color, String> map = new HashMap<>();
|
||||
|
||||
public FruitMap() {
|
||||
this.map.put(Color.RED, "cherry");
|
||||
this.map.put(Color.ORANGE, "orange");
|
||||
this.map.put(Color.YELLOW, "banana");
|
||||
}
|
||||
|
||||
public String getFruit(Color color) {
|
||||
return this.map.get(color);
|
||||
}
|
||||
|
||||
public void setFruit(Color color, String fruit) {
|
||||
this.map.put(color, fruit);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
A read-only `IndexAccessor` for `FruitMap` can be created via `new
|
||||
ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit")`. With that accessor
|
||||
registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
|
||||
expression `#fruitMap[T(example.Color).RED]` will evaluate to `"cherry"`.
|
||||
|
||||
A read-write `IndexAccessor` for `FruitMap` can be created via `new
|
||||
ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit", "setFruit")`. With that
|
||||
accessor registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
|
||||
expression `#fruitMap[T(example.Color).RED] = 'strawberry'` can be used to change the
|
||||
fruit mapping for the color red from `"cherry"` to `"strawberry"`.
|
||||
|
||||
The following example demonstrates how to register a `ReflectiveIndexAccessor` to index
|
||||
into a `FruitMap` and then index into the `FruitMap` within a SpEL expression.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Create a ReflectiveIndexAccessor for FruitMap
|
||||
IndexAccessor fruitMapAccessor = new ReflectiveIndexAccessor(
|
||||
FruitMap.class, Color.class, "getFruit", "setFruit");
|
||||
|
||||
// Register the IndexAccessor for FruitMap
|
||||
context.addIndexAccessor(fruitMapAccessor);
|
||||
|
||||
// Register the fruitMap variable
|
||||
context.setVariable("fruitMap", new FruitMap());
|
||||
|
||||
// evaluates to "cherry"
|
||||
String fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
|
||||
.getValue(context, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Create a ReflectiveIndexAccessor for FruitMap
|
||||
val fruitMapAccessor = ReflectiveIndexAccessor(
|
||||
FruitMap::class.java, Color::class.java, "getFruit", "setFruit")
|
||||
|
||||
// Register the IndexAccessor for FruitMap
|
||||
context.addIndexAccessor(fruitMapAccessor)
|
||||
|
||||
// Register the fruitMap variable
|
||||
context.setVariable("fruitMap", FruitMap())
|
||||
|
||||
// evaluates to "cherry"
|
||||
val fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user