mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e35a12209 | |||
| 317a1f9909 | |||
| de6601fdac | |||
| 47dc1c4d93 | |||
| d8c77934ff | |||
| 99fbce1254 | |||
| d1e69a9677 | |||
| 8dc888e1b8 | |||
| 131f94fbc5 | |||
| d4f4c69318 | |||
| 74ab6625ac | |||
| 7c27183a4f | |||
| 526f67cd83 | |||
| 88f081b1cd | |||
| 5806a23bcf | |||
| 507399d9a8 | |||
| cd9a430869 | |||
| 0a933787a6 | |||
| 2bbea84830 | |||
| b9517b8ed7 | |||
| f41786b6af | |||
| 2d10d513e1 | |||
| c83fb69d9b | |||
| 566a42bec9 | |||
| b426ef2d1b | |||
| 728466dce0 | |||
| 455cb766a9 | |||
| c77993acac | |||
| f3d0f9427a | |||
| 688975e327 | |||
| b619e69130 | |||
| 972ba739aa | |||
| f87b5ee326 | |||
| 7768cee7a0 | |||
| 9042682b56 | |||
| 2d81a9fe9b | |||
| a3f7179ab3 | |||
| 50ef3b0a29 | |||
| 1c7e79026f | |||
| c8735efb9d | |||
| 999bbe3959 | |||
| acab61f9a7 | |||
| bfa81290b3 | |||
| 2c7ed5e5f1 | |||
| 474d520182 | |||
| 50bffe7ddc | |||
| 6d849bd9b0 | |||
| 8334388e20 | |||
| 757b713f22 | |||
| a065563484 | |||
| 6162d89042 | |||
| 5c537db2cc | |||
| 849553dc8e | |||
| 7240a5a669 | |||
| b00c387775 | |||
| 8396c071af | |||
| bb35e9f11c | |||
| a0319b1f91 | |||
| 5973a17253 | |||
| ed7243259b | |||
| a391db2ef5 | |||
| 957202523c | |||
| a3c9166da7 | |||
| 0c3dd8cb00 | |||
| 56d8ec25e8 | |||
| fb9db146f2 | |||
| dcf98bc55c | |||
| b9cd06a487 | |||
| 91ff706e8c | |||
| 103057de2a | |||
| b6d1e88563 | |||
| af1e9da3d7 | |||
| bc5e395a42 | |||
| 1ec3cb4d5f | |||
| 7b7126ae3d | |||
| 9f1332c716 | |||
| 2ff93f4207 | |||
| 3a6c7786a1 | |||
| 539a098f8c | |||
| ecfd78ff93 | |||
| 1e0a85368e | |||
| c49d5dc860 | |||
| 58af70f2e3 | |||
| c6e73b5d1d | |||
| f531cc9fdf | |||
| 1977f31821 | |||
| cf3ed229de | |||
| 154dad6587 | |||
| fd8b4d5936 | |||
| 13defc09f9 | |||
| 9e512315ab | |||
| c750efb0a6 | |||
| 0033edad45 | |||
| 183cd4c8cd | |||
| 6bd0ed8e79 | |||
| dcb7922a24 | |||
| 05a9cc26ae | |||
| 05beb4ad4d | |||
| 5236b35687 | |||
| 7482ccc18f | |||
| b3fb9f4e31 | |||
| add3b1a7f4 | |||
| 476aae5ce8 | |||
| be68a777b6 | |||
| 9a0bfd7306 | |||
| 91a0c28fa9 | |||
| c0e8a36592 | |||
| 836289315d | |||
| 08b77dd0ad | |||
| 5eb16a6a17 | |||
| d4705804cc | |||
| 23625ee698 | |||
| c89c4ac614 | |||
| 2155e9fb25 | |||
| 24df35c55a | |||
| 3b1fa369b4 | |||
| 221adf14a4 | |||
| ad849fb3a2 | |||
| 8041a09268 | |||
| 97b9517918 | |||
| e79d8e35a2 | |||
| 96503a2ea6 | |||
| 8803d3c5dc | |||
| 5b4728f0bf | |||
| 1fd1d8d629 | |||
| afc40d7b93 | |||
| 3eb1df0ac0 | |||
| 2aa3b0a77e | |||
| 0008106adb | |||
| 8f6d44a86b | |||
| 874498272f | |||
| 2144813bad | |||
| 67a92306f7 | |||
| bc0731891b | |||
| 3faa7cac4d | |||
| 3121daf553 | |||
| 821165488e | |||
| 3b556ba0c0 | |||
| b25f98374b | |||
| c2f7cd3401 | |||
| 22d2810ed0 | |||
| b39055f293 | |||
| 140933400d | |||
| e7a5452a14 | |||
| ab96576e67 | |||
| 19b080b73f | |||
| f9b4fba97a | |||
| 37e26e0377 | |||
| 85c47a73dd | |||
| ab93020263 | |||
| a4134663a6 | |||
| a4b1155ca6 | |||
| 1bdd8337c6 | |||
| 7a0ea14452 | |||
| 59025c5b96 | |||
| e5de8b9bc6 | |||
| e146e809e5 | |||
| ff62e7355f | |||
| 4e97013595 | |||
| bd10b7ae06 | |||
| e4288170c8 | |||
| 8545a759a7 | |||
| f94645de17 | |||
| 8553f97df1 | |||
| 030dace2be | |||
| 5af1c9b487 | |||
| f3ed04c9d7 | |||
| 46ee944acc | |||
| 6be1c29fda | |||
| 3fa56db88b |
@@ -19,7 +19,7 @@ inputs:
|
||||
java-version:
|
||||
description: 'Java version to compile and test with'
|
||||
required: false
|
||||
default: '25'
|
||||
default: '17'
|
||||
publish:
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
required: false
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":warning: Attention Required"
|
||||
labels:
|
||||
- "for: upgrade-attention"
|
||||
summary:
|
||||
mode: "member-comment"
|
||||
config:
|
||||
prefix: "Attention Required:"
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
|
||||
@@ -19,7 +19,7 @@ inputs:
|
||||
java-version:
|
||||
description: 'Java version to use for the build'
|
||||
required: false
|
||||
default: '25'
|
||||
default: '17'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -30,7 +30,6 @@ runs:
|
||||
java-version: |
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
25
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
with:
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
require:
|
||||
members: false
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Build and Deploy Snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 7.0.x
|
||||
- 6.2.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: 'spring-framework-7.0.x'
|
||||
build-name: 'spring-framework-6.2.x'
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
name: CI
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
name: Release Milestone
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.0.0-M[1-9]
|
||||
- v7.0.0-RC[1-9]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-stage-release:
|
||||
name: Build and Stage Release
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-staging-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
sync-to-maven-central:
|
||||
name: Sync to Maven Central
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
central-token-password: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
|
||||
central-token-username: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
|
||||
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
promote-release:
|
||||
name: Promote Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- sync-to-maven-central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- promote-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
milestone: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
pre-release: true
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -2,7 +2,7 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.0.[0-9]+
|
||||
- v6.2.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.2.x', '7.0.x', 'main' ]
|
||||
branch: [ '6.1.x' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
|
||||
+1
-3
@@ -9,7 +9,6 @@ ivy-cache
|
||||
argfile*
|
||||
activemq-data/
|
||||
classes/
|
||||
.cursor/
|
||||
|
||||
# Log files
|
||||
jxl.log
|
||||
@@ -39,7 +38,7 @@ bin
|
||||
.springBeans
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
|
||||
# IntelliJ IDEA artifacts and output dirs
|
||||
# IDEA artifacts and output dirs
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
@@ -55,4 +54,3 @@ atlassian-ide-plugin.xml
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
/.kotlin/
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=25-librca
|
||||
java=17.0.13-librca
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ source code into your IDE.
|
||||
The wiki pages
|
||||
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
|
||||
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
|
||||
define the source file coding standards we use along with some IntelliJ editor settings we customize.
|
||||
define the source file coding standards we use along with some IDEA editor settings we customize.
|
||||
|
||||
### Reference Docs
|
||||
|
||||
|
||||
+38
-19
@@ -1,12 +1,15 @@
|
||||
plugins {
|
||||
id 'io.freefair.aspectj' version '8.13.1' apply false
|
||||
id 'io.freefair.aspectj' version '8.4' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka'
|
||||
id 'org.jetbrains.dokka' version '1.9.20'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'com.gradleup.shadow' version "9.2.2" apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'io.spring.nullability' version '0.0.11' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -21,6 +24,13 @@ configure(allprojects) { project ->
|
||||
group = "org.springframework"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = "https://repo.spring.io/milestone"
|
||||
content {
|
||||
// Netty 5 optional support
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
if (version.contains('-')) {
|
||||
maven { url = "https://repo.spring.io/milestone" }
|
||||
}
|
||||
@@ -54,6 +64,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
apply plugin: "java"
|
||||
apply plugin: "java-test-fixtures"
|
||||
apply plugin: 'org.springframework.build.conventions'
|
||||
apply from: "${rootDir}/gradle/toolchains.gradle"
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
|
||||
dependencies {
|
||||
@@ -61,27 +72,35 @@ configure([rootProject] + javaProjects) { project ->
|
||||
testImplementation("org.junit.platform:junit-platform-suite")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk") {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl")
|
||||
// JSR-305 only used for non-required meta-annotations
|
||||
compileOnly("com.google.code.findbugs:jsr305")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/7.2/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/5.6/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/core/release/api/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.0.3/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.hibernate.org/orm/5.6/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
|
||||
//"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
|
||||
// JakartaEE equivalents in the jakarta.annotation package.
|
||||
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
||||
+1
-31
@@ -9,18 +9,7 @@ The `org.springframework.build.conventions` plugin applies all conventions to th
|
||||
|
||||
* Configuring the Java compiler, see `JavaConventions`
|
||||
* Configuring the Kotlin compiler, see `KotlinConventions`
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
* Configuring the ArchUnit rules for the project, see `org.springframework.build.architecture.ArchitectureRules`
|
||||
|
||||
This plugin also provides a DSL extension to optionally enable Java preview features for
|
||||
compiling and testing sources in a module. This can be applied with the following in a
|
||||
module build file:
|
||||
|
||||
```groovy
|
||||
springFramework {
|
||||
enableJavaPreviewFeatures = true
|
||||
}
|
||||
```
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
|
||||
|
||||
## Build Plugins
|
||||
@@ -33,25 +22,6 @@ but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
### MultiRelease Jar
|
||||
|
||||
The `org.springframework.build.multiReleaseJar` plugin configures the project with MultiRelease JAR support.
|
||||
It creates a new SourceSet and dedicated tasks for each Java variant considered.
|
||||
This can be configured with the DSL, by setting a list of Java variants to configure:
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
|
||||
multiRelease {
|
||||
releaseVersions 21, 24
|
||||
}
|
||||
```
|
||||
|
||||
Note, Java classes will be compiled with the toolchain pre-configured by the project, assuming that its
|
||||
Java language version is equal or higher than all variants we consider. Each compilation task will only
|
||||
set the "-release" compilation option accordingly to produce the expected bytecode version.
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
|
||||
+2
-22
@@ -20,24 +20,14 @@ ext {
|
||||
dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.1.0"
|
||||
implementation "com.tngtech.archunit:archunit:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
|
||||
testImplementation("org.assertj:assertj-core:${assertjVersion}")
|
||||
testImplementation(platform("org.junit:junit-bom:${junitVersion}"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
architecturePlugin {
|
||||
id = "org.springframework.architecture"
|
||||
implementationClass = "org.springframework.build.architecture.ArchitecturePlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
@@ -46,10 +36,6 @@ gradlePlugin {
|
||||
id = "org.springframework.build.localdev"
|
||||
implementationClass = "org.springframework.build.dev.LocalDevelopmentPlugin"
|
||||
}
|
||||
multiReleasePlugin {
|
||||
id = "org.springframework.build.multiReleaseJar"
|
||||
implementationClass = "org.springframework.build.multirelease.MultiReleaseJarPlugin"
|
||||
}
|
||||
optionalDependenciesPlugin {
|
||||
id = "org.springframework.build.optional-dependencies"
|
||||
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
|
||||
@@ -60,9 +46,3 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jar.dependsOn check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<module name="com.puppycrawl.tools.checkstyle.Checker">
|
||||
|
||||
<!-- Root Checks -->
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringHeaderCheck">
|
||||
@@ -12,15 +12,16 @@
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck"/>
|
||||
|
||||
<!-- TreeWalker Checks -->
|
||||
<module name="TreeWalker">
|
||||
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
|
||||
|
||||
<!-- Imports -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
<module name="RedundantImport"/>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck">
|
||||
<property name="processJavadoc" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Modifiers -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck"/>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
</module>
|
||||
@@ -1,4 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
assertjVersion=3.27.3
|
||||
javaFormatVersion=0.0.43
|
||||
junitVersion=5.12.2
|
||||
|
||||
@@ -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("13.4.2");
|
||||
checkstyle.setToolVersion("10.26.1");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -63,8 +63,8 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(NoHttpPlugin.class);
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**", "**/.classpath",
|
||||
"**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**", "buildSrc/build/**");
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -21,15 +21,12 @@ import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
|
||||
import org.springframework.build.architecture.ArchitecturePlugin;
|
||||
|
||||
/**
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
* Conventions are applied in response to various plugins being applied.
|
||||
*
|
||||
* <p>When the {@link JavaBasePlugin} is applied, the conventions in {@link CheckstyleConventions},
|
||||
* {@link TestConventions} and {@link JavaConventions} are applied.
|
||||
* The {@link ArchitecturePlugin} plugin is also applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* are applied.
|
||||
*
|
||||
@@ -39,8 +36,6 @@ public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getExtensions().create("springFramework", SpringFrameworkExtension.class);
|
||||
new ArchitecturePlugin().apply(project);
|
||||
new CheckstyleConventions().apply(project);
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
@@ -26,6 +27,7 @@ import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JvmVendorSpec;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
|
||||
@@ -40,21 +42,8 @@ public class JavaConventions {
|
||||
|
||||
private static final List<String> TEST_COMPILER_ARGS;
|
||||
|
||||
/**
|
||||
* The Java version we should use as the JVM baseline for building the project.
|
||||
* <p>NOTE: If you update this value, you should also update the value used in
|
||||
* the {@code javadoc} task in {@code framework-api.gradle}.
|
||||
*/
|
||||
private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(25);
|
||||
|
||||
/**
|
||||
* The Java version we should use as the baseline for the compiled bytecode
|
||||
* (the "-release" compiler argument).
|
||||
*/
|
||||
private static final JavaLanguageVersion DEFAULT_RELEASE_VERSION = JavaLanguageVersion.of(17);
|
||||
|
||||
static {
|
||||
List<String> commonCompilerArgs = List.of(
|
||||
List<String> commonCompilerArgs = Arrays.asList(
|
||||
"-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann",
|
||||
"-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides",
|
||||
"-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options",
|
||||
@@ -62,74 +51,43 @@ public class JavaConventions {
|
||||
);
|
||||
COMPILER_ARGS = new ArrayList<>();
|
||||
COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
COMPILER_ARGS.addAll(List.of(
|
||||
COMPILER_ARGS.addAll(Arrays.asList(
|
||||
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
|
||||
"-Xlint:unchecked", "-Werror"
|
||||
));
|
||||
TEST_COMPILER_ARGS = new ArrayList<>();
|
||||
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
TEST_COMPILER_ARGS.addAll(List.of("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||
TEST_COMPILER_ARGS.addAll(Arrays.asList("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||
"-Xlint:-deprecation", "-Xlint:-unchecked"));
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> {
|
||||
applyToolchainConventions(project);
|
||||
applyJavaCompileConventions(project);
|
||||
});
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> applyJavaCompileConventions(project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the Toolchain support for the project.
|
||||
* @param project the current project
|
||||
*/
|
||||
private static void applyToolchainConventions(Project project) {
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getLanguageVersion().set(DEFAULT_LANGUAGE_VERSION);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the common Java compiler options for main sources, test fixture sources, and
|
||||
* Applies the common Java compiler options for main sources, test fixture sources, and
|
||||
* test sources.
|
||||
* @param project the current project
|
||||
*/
|
||||
private void applyJavaCompileConventions(Project project) {
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
|
||||
toolchain.getLanguageVersion().set(JavaLanguageVersion.of(17));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* We should pick the {@link #DEFAULT_RELEASE_VERSION} for all compiled classes,
|
||||
* unless the current task is compiling multi-release JAR code with a higher version.
|
||||
*/
|
||||
private void setJavaRelease(JavaCompile task) {
|
||||
int defaultVersion = DEFAULT_RELEASE_VERSION.asInt();
|
||||
int releaseVersion = defaultVersion;
|
||||
int compilerVersion = task.getJavaCompiler().get().getMetadata().getLanguageVersion().asInt();
|
||||
for (int version = defaultVersion ; version <= compilerVersion ; version++) {
|
||||
if (task.getName().contains("Java" + version)) {
|
||||
releaseVersion = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
task.getOptions().getRelease().set(releaseVersion);
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,78 +16,33 @@
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.jetbrains.dokka.gradle.DokkaExtension;
|
||||
import org.jetbrains.dokka.gradle.DokkaPlugin;
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class KotlinConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm", plugin -> {
|
||||
project.getTasks().withType(KotlinCompile.class, this::configure);
|
||||
if (project.getLayout().getProjectDirectory().dir("src/main/kotlin").getAsFile().exists()) {
|
||||
project.getPlugins().apply(DokkaPlugin.class);
|
||||
project.getExtensions().configure(DokkaExtension.class, dokka -> configure(project, dokka));
|
||||
project.project(":framework-api").getDependencies().add("dokka", project);
|
||||
}
|
||||
});
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm",
|
||||
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
|
||||
}
|
||||
|
||||
private void configure(KotlinCompile compile) {
|
||||
compile.compilerOptions(options -> {
|
||||
options.getApiVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getLanguageVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getJvmTarget().set(JvmTarget.JVM_17);
|
||||
options.getJavaParameters().set(true);
|
||||
options.getAllWarningsAsErrors().set(true);
|
||||
options.getFreeCompilerArgs().addAll(
|
||||
"-Xsuppress-version-warnings",
|
||||
"-Xjsr305=strict", // For dependencies using JSR 305
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void configure(Project project, DokkaExtension dokka) {
|
||||
dokka.getDokkaSourceSets().forEach(sourceSet -> {
|
||||
sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin"));
|
||||
sourceSet.getClasspath()
|
||||
.from(project.getExtensions()
|
||||
.getByType(SourceSetContainer.class)
|
||||
.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
|
||||
.getOutput());
|
||||
var externalDocumentationLinks = sourceSet.getExternalDocumentationLinks();
|
||||
var springVersion = project.getVersion();
|
||||
externalDocumentationLinks.register("spring-framework", spec -> {
|
||||
spec.url("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/");
|
||||
spec.packageListUrl("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/element-list");
|
||||
});
|
||||
externalDocumentationLinks.register("reactor-core", spec ->
|
||||
spec.url("https://projectreactor.io/docs/core/release/api/"));
|
||||
externalDocumentationLinks.register("reactive-streams", spec ->
|
||||
spec.url("https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/"));
|
||||
externalDocumentationLinks.register("kotlinx-coroutines", spec ->
|
||||
spec.url("https://kotlinlang.org/api/kotlinx.coroutines/"));
|
||||
externalDocumentationLinks.register("hamcrest", spec ->
|
||||
spec.url("https://javadoc.io/doc/org.hamcrest/hamcrest/2.1/"));
|
||||
externalDocumentationLinks.register("jakarta-servlet", spec -> {
|
||||
spec.url("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/");
|
||||
spec.packageListUrl("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list");
|
||||
});
|
||||
externalDocumentationLinks.register("rsocket-core", spec ->
|
||||
spec.url("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"));
|
||||
});
|
||||
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
|
||||
kotlinOptions.setApiVersion("1.7");
|
||||
kotlinOptions.setLanguageVersion("1.7");
|
||||
kotlinOptions.setJvmTarget("17");
|
||||
kotlinOptions.setJavaParameters(true);
|
||||
kotlinOptions.setAllWarningsAsErrors(true);
|
||||
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
|
||||
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
|
||||
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.process.CommandLineArgumentProvider;
|
||||
|
||||
public class SpringFrameworkExtension {
|
||||
|
||||
private final Property<Boolean> enableJavaPreviewFeatures;
|
||||
|
||||
public SpringFrameworkExtension(Project project) {
|
||||
this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class);
|
||||
project.getTasks().withType(JavaCompile.class).configureEach(javaCompile ->
|
||||
javaCompile.getOptions().getCompilerArgumentProviders().add(asArgumentProvider()));
|
||||
project.getTasks().withType(Test.class).configureEach(test ->
|
||||
test.getJvmArgumentProviders().add(asArgumentProvider()));
|
||||
|
||||
}
|
||||
|
||||
public Property<Boolean> getEnableJavaPreviewFeatures() {
|
||||
return this.enableJavaPreviewFeatures;
|
||||
}
|
||||
|
||||
private CommandLineArgumentProvider asArgumentProvider() {
|
||||
return () -> {
|
||||
if (getEnableJavaPreviewFeatures().getOrElse(false)) {
|
||||
return List.of("--enable-preview");
|
||||
}
|
||||
return Collections.emptyList();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.api.tasks.testing.TestFrameworkOptions;
|
||||
@@ -26,16 +26,12 @@ import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
|
||||
import org.gradle.testretry.TestRetryPlugin;
|
||||
import org.gradle.testretry.TestRetryTaskExtension;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
|
||||
* plugin is applied:
|
||||
* <ul>
|
||||
* <li>The {@link TestRetryPlugin Test Retry} plugin is applied so that flaky tests
|
||||
* are retried 3 times when running on the CI server.
|
||||
* <li>Common test properties are configured
|
||||
* <li>The ByteBuddy Java agent is configured on test tasks.
|
||||
* </ul>
|
||||
*
|
||||
* @author Brian Clozel
|
||||
@@ -49,7 +45,6 @@ class TestConventions {
|
||||
}
|
||||
|
||||
private void configureTestConventions(Project project) {
|
||||
configureByteBuddyAgent(project);
|
||||
project.getTasks().withType(Test.class,
|
||||
test -> {
|
||||
configureTests(project, test);
|
||||
@@ -68,7 +63,9 @@ class TestConventions {
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
"io.netty.leakDetection.level", "paranoid",
|
||||
"junit.platform.discovery.issue.severity.critical", "INFO"
|
||||
"io.netty5.leakDetectionLevel", "paranoid",
|
||||
"io.netty5.leakDetection.targetRecords", "32",
|
||||
"io.netty5.buffer.lifecycleTracingEnabled", "true"
|
||||
));
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
@@ -80,20 +77,6 @@ class TestConventions {
|
||||
);
|
||||
}
|
||||
|
||||
private void configureByteBuddyAgent(Project project) {
|
||||
if (project.hasProperty("byteBuddyVersion")) {
|
||||
String byteBuddyVersion = (String) project.getProperties().get("byteBuddyVersion");
|
||||
Configuration byteBuddyAgentConfig = project.getConfigurations().create("byteBuddyAgentConfig");
|
||||
byteBuddyAgentConfig.setTransitive(false);
|
||||
Dependency byteBuddyAgent = project.getDependencies().create("net.bytebuddy:byte-buddy-agent:" + byteBuddyVersion);
|
||||
byteBuddyAgentConfig.getDependencies().add(byteBuddyAgent);
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().withType(Test.class, test -> test
|
||||
.jvmArgs("-javaagent:" + byteBuddyAgentConfig.getAsPath()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.EvaluationResult;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.SkipWhenEmpty;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import static org.springframework.build.architecture.ArchitectureRules.allPackagesShouldBeFreeOfTangles;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.classesShouldNotImportForbiddenTypes;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.javaClassesShouldNotImportKotlinAnnotations;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToLowerCaseWithoutLocale;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToUpperCaseWithoutLocale;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks for architecture problems.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public abstract class ArchitectureCheck extends DefaultTask {
|
||||
|
||||
private FileCollection classes;
|
||||
|
||||
public ArchitectureCheck() {
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
getProhibitObjectsRequireNonNull().convention(true);
|
||||
getRules().addAll(classesShouldNotImportForbiddenTypes(),
|
||||
javaClassesShouldNotImportKotlinAnnotations(),
|
||||
allPackagesShouldBeFreeOfTangles(),
|
||||
noClassesShouldCallStringToLowerCaseWithoutLocale(),
|
||||
noClassesShouldCallStringToUpperCaseWithoutLocale());
|
||||
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void checkArchitecture() throws IOException {
|
||||
JavaClasses javaClasses = new ClassFileImporter()
|
||||
.importPaths(this.classes.getFiles().stream().map(File::toPath).toList());
|
||||
List<EvaluationResult> violations = getRules().get()
|
||||
.stream()
|
||||
.map((rule) -> rule.evaluate(javaClasses))
|
||||
.filter(EvaluationResult::hasViolation)
|
||||
.toList();
|
||||
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
||||
outputFile.getParentFile().mkdirs();
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder report = new StringBuilder();
|
||||
for (EvaluationResult violation : violations) {
|
||||
report.append(violation.getFailureReport());
|
||||
report.append(String.format("%n"));
|
||||
}
|
||||
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
throw new GradleException("Architecture check failed. See '" + outputFile + "' for details.");
|
||||
}
|
||||
else {
|
||||
outputFile.createNewFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void setClasses(FileCollection classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public FileCollection getClasses() {
|
||||
return this.classes;
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
@SkipWhenEmpty
|
||||
@IgnoreEmptyDirectories
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
final FileTree getInputClasses() {
|
||||
return this.classes.getAsFileTree();
|
||||
}
|
||||
|
||||
@Optional
|
||||
@InputFiles
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract DirectoryProperty getResourcesDirectory();
|
||||
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDirectory();
|
||||
|
||||
@Internal
|
||||
public abstract ListProperty<ArchRule> getRules();
|
||||
|
||||
@Internal
|
||||
public abstract Property<Boolean> getProhibitObjectsRequireNonNull();
|
||||
|
||||
@Input
|
||||
// The rules themselves can't be an input as they aren't serializable so we use
|
||||
// their descriptions instead
|
||||
abstract ListProperty<String> getRuleDescriptions();
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
/**
|
||||
* {@link Plugin} for verifying a project's architecture.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ArchitecturePlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerTasks(project));
|
||||
}
|
||||
|
||||
private void registerTasks(Project project) {
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
List<TaskProvider<ArchitectureCheck>> architectureChecks = new ArrayList<>();
|
||||
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
|
||||
if (sourceSet.getName().contains("test")) {
|
||||
// skip test source sets.
|
||||
continue;
|
||||
}
|
||||
TaskProvider<ArchitectureCheck> checkArchitecture = project.getTasks()
|
||||
.register(taskName(sourceSet), ArchitectureCheck.class,
|
||||
(task) -> {
|
||||
task.setClasses(sourceSet.getOutput().getClassesDirs());
|
||||
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
|
||||
task.dependsOn(sourceSet.getProcessResourcesTaskName());
|
||||
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
|
||||
+ " source set.");
|
||||
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
});
|
||||
architectureChecks.add(checkArchitecture);
|
||||
}
|
||||
if (!architectureChecks.isEmpty()) {
|
||||
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
|
||||
checkTask.configure((check) -> check.dependsOn(architectureChecks));
|
||||
}
|
||||
}
|
||||
|
||||
private static String taskName(SourceSet sourceSet) {
|
||||
return "checkArchitecture"
|
||||
+ sourceSet.getName().substring(0, 1).toUpperCase()
|
||||
+ sourceSet.getName().substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import com.tngtech.archunit.base.DescribedPredicate;
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
|
||||
import com.tngtech.archunit.library.dependencies.SliceAssignment;
|
||||
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
|
||||
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||
import java.util.List;
|
||||
|
||||
abstract class ArchitectureRules {
|
||||
|
||||
static ArchRule allPackagesShouldBeFreeOfTangles() {
|
||||
return SlicesRuleDefinition.slices()
|
||||
.assignedFrom(new SpringSlices()).should().beFreeOfCycles();
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToLowerCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toLowerCase")
|
||||
.because("String.toLowerCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToUpperCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toUpperCase")
|
||||
.because("String.toUpperCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule classesShouldNotImportForbiddenTypes() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("reactor.core.support.Assert")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.slf4j.LoggerFactory")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.NonNull")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.Nullable");
|
||||
}
|
||||
|
||||
static ArchRule javaClassesShouldNotImportKotlinAnnotations() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.that(new DescribedPredicate<JavaClass>("is not a Kotlin class") {
|
||||
@Override
|
||||
public boolean test(JavaClass javaClass) {
|
||||
return javaClass.getSourceCodeLocation()
|
||||
.getSourceFileName().endsWith(".java");
|
||||
}
|
||||
}
|
||||
)
|
||||
.should().dependOnClassesThat()
|
||||
.resideInAnyPackage("org.jetbrains.annotations..")
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
static class SpringSlices implements SliceAssignment {
|
||||
|
||||
private final List<String> ignoredPackages = List.of("org.springframework.asm",
|
||||
"org.springframework.cglib",
|
||||
"org.springframework.javapoet",
|
||||
"org.springframework.objenesis");
|
||||
|
||||
@Override
|
||||
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
|
||||
|
||||
String packageName = javaClass.getPackageName();
|
||||
for (String ignoredPackage : ignoredPackages) {
|
||||
if (packageName.startsWith(ignoredPackage)) {
|
||||
return SliceIdentifier.ignore();
|
||||
}
|
||||
}
|
||||
return SliceIdentifier.of("spring framework");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Spring Framework Slices";
|
||||
}
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.attributes.LibraryElements;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
/**
|
||||
* @author Cedric Champeau
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public abstract class MultiReleaseExtension {
|
||||
private final TaskContainer tasks;
|
||||
private final SourceSetContainer sourceSets;
|
||||
private final DependencyHandler dependencies;
|
||||
private final ObjectFactory objects;
|
||||
private final ConfigurationContainer configurations;
|
||||
|
||||
@Inject
|
||||
public MultiReleaseExtension(SourceSetContainer sourceSets,
|
||||
ConfigurationContainer configurations,
|
||||
TaskContainer tasks,
|
||||
DependencyHandler dependencies,
|
||||
ObjectFactory objectFactory) {
|
||||
this.sourceSets = sourceSets;
|
||||
this.configurations = configurations;
|
||||
this.tasks = tasks;
|
||||
this.dependencies = dependencies;
|
||||
this.objects = objectFactory;
|
||||
}
|
||||
|
||||
public void releaseVersions(int... javaVersions) {
|
||||
releaseVersions("src/main/", "src/test/", javaVersions);
|
||||
}
|
||||
|
||||
private void releaseVersions(String mainSourceDirectory, String testSourceDirectory, int... javaVersions) {
|
||||
for (int javaVersion : javaVersions) {
|
||||
addLanguageVersion(javaVersion, mainSourceDirectory, testSourceDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private void addLanguageVersion(int javaVersion, String mainSourceDirectory, String testSourceDirectory) {
|
||||
String javaN = "java" + javaVersion;
|
||||
|
||||
SourceSet langSourceSet = sourceSets.create(javaN, srcSet -> srcSet.getJava().srcDir(mainSourceDirectory + javaN));
|
||||
SourceSet testSourceSet = sourceSets.create(javaN + "Test", srcSet -> srcSet.getJava().srcDir(testSourceDirectory + javaN));
|
||||
SourceSet sharedSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||
SourceSet sharedTestSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME);
|
||||
|
||||
FileCollection mainClasses = objects.fileCollection().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs());
|
||||
dependencies.add(javaN + "Implementation", mainClasses);
|
||||
|
||||
tasks.named(langSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||
task.getOptions().getRelease().set(javaVersion)
|
||||
);
|
||||
tasks.named(testSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||
task.getOptions().getRelease().set(javaVersion)
|
||||
);
|
||||
|
||||
TaskProvider<Test> testTask = createTestTask(javaVersion, testSourceSet, sharedTestSourceSet, langSourceSet, sharedSourceSet);
|
||||
tasks.named("check", task -> task.dependsOn(testTask));
|
||||
|
||||
configureMultiReleaseJar(javaVersion, langSourceSet);
|
||||
}
|
||||
|
||||
private TaskProvider<Test> createTestTask(int javaVersion, SourceSet testSourceSet, SourceSet sharedTestSourceSet, SourceSet langSourceSet, SourceSet sharedSourceSet) {
|
||||
Configuration testImplementation = configurations.getByName(testSourceSet.getImplementationConfigurationName());
|
||||
testImplementation.extendsFrom(configurations.getByName(sharedTestSourceSet.getImplementationConfigurationName()));
|
||||
Configuration testCompileOnly = configurations.getByName(testSourceSet.getCompileOnlyConfigurationName());
|
||||
testCompileOnly.extendsFrom(configurations.getByName(sharedTestSourceSet.getCompileOnlyConfigurationName()));
|
||||
testCompileOnly.getDependencies().add(dependencies.create(langSourceSet.getOutput().getClassesDirs()));
|
||||
testCompileOnly.getDependencies().add(dependencies.create(sharedSourceSet.getOutput().getClassesDirs()));
|
||||
|
||||
Configuration testRuntimeClasspath = configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName());
|
||||
// so here's the deal. MRjars are JARs! Which means that to execute tests, we need
|
||||
// the JAR on classpath, not just classes + resources as Gradle usually does
|
||||
testRuntimeClasspath.getAttributes()
|
||||
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
|
||||
|
||||
TaskProvider<Test> testTask = tasks.register("java" + javaVersion + "Test", Test.class, test -> {
|
||||
test.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
|
||||
ConfigurableFileCollection testClassesDirs = objects.fileCollection();
|
||||
testClassesDirs.from(testSourceSet.getOutput());
|
||||
testClassesDirs.from(sharedTestSourceSet.getOutput());
|
||||
test.setTestClassesDirs(testClassesDirs);
|
||||
ConfigurableFileCollection classpath = objects.fileCollection();
|
||||
// must put the MRJar first on classpath
|
||||
classpath.from(tasks.named("jar"));
|
||||
// then we put the specific test sourceset tests, so that we can override
|
||||
// the shared versions
|
||||
classpath.from(testSourceSet.getOutput());
|
||||
|
||||
// then we add the shared tests
|
||||
classpath.from(sharedTestSourceSet.getRuntimeClasspath());
|
||||
test.setClasspath(classpath);
|
||||
});
|
||||
return testTask;
|
||||
}
|
||||
|
||||
private void configureMultiReleaseJar(int version, SourceSet languageSourceSet) {
|
||||
tasks.named("jar", Jar.class, jar -> {
|
||||
jar.into("META-INF/versions/" + version, s -> s.from(languageSourceSet.getOutput()));
|
||||
Attributes attributes = jar.getManifest().getAttributes();
|
||||
attributes.put("Multi-Release", "true");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.plugins.ExtensionContainer;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
/**
|
||||
* A plugin which adds support for building multi-release jars
|
||||
* with Gradle.
|
||||
* @author Cedric Champeau
|
||||
* @author Brian Clozel
|
||||
* @see <a href="https://github.com/melix/mrjar-gradle-plugin">original project</a>
|
||||
*/
|
||||
public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||
|
||||
public static String VALIDATE_JAR_TASK_NAME = "validateMultiReleaseJar";
|
||||
|
||||
@Inject
|
||||
protected JavaToolchainService getToolchains() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply(JavaPlugin.class);
|
||||
ExtensionContainer extensions = project.getExtensions();
|
||||
JavaPluginExtension javaPluginExtension = extensions.getByType(JavaPluginExtension.class);
|
||||
ConfigurationContainer configurations = project.getConfigurations();
|
||||
TaskContainer tasks = project.getTasks();
|
||||
DependencyHandler dependencies = project.getDependencies();
|
||||
ObjectFactory objects = project.getObjects();
|
||||
extensions.create("multiRelease", MultiReleaseExtension.class,
|
||||
javaPluginExtension.getSourceSets(),
|
||||
configurations,
|
||||
tasks,
|
||||
dependencies,
|
||||
objects);
|
||||
|
||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
}
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.CacheableTask;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@CacheableTask
|
||||
public abstract class MultiReleaseJarValidateTask extends JavaExec {
|
||||
|
||||
|
||||
public MultiReleaseJarValidateTask() {
|
||||
getMainModule().set("jdk.jartool");
|
||||
getArgumentProviders().add(() -> List.of("--validate", "--file", getJar().get().getAsFile().getAbsolutePath()));
|
||||
}
|
||||
|
||||
@Inject
|
||||
protected abstract JavaToolchainService getJavaToolchainService();
|
||||
|
||||
@InputFile
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract RegularFileProperty getJar();
|
||||
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class ShadowSource extends DefaultTask {
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
public DirectoryProperty getOutputDirectory() {
|
||||
DirectoryProperty getOutputDirectory() {
|
||||
return this.outputDirectory;
|
||||
}
|
||||
|
||||
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link MultiReleaseJarPlugin}
|
||||
*/
|
||||
public class MultiReleaseJarPluginTests {
|
||||
|
||||
private File projectDir;
|
||||
|
||||
private File buildFile;
|
||||
|
||||
private File propertiesFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
this.buildFile = new File(this.projectDir, "build.gradle");
|
||||
this.propertiesFile = new File(this.projectDir, "gradle.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureSourceSets() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
multiRelease { releaseVersions 21, 24 }
|
||||
task printSourceSets {
|
||||
doLast {
|
||||
sourceSets.all { println it.name }
|
||||
}
|
||||
}
|
||||
""");
|
||||
BuildResult buildResult = runGradle("printSourceSets");
|
||||
assertThat(buildResult.getOutput()).contains("main", "test", "java21", "java21Test", "java24", "java24Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureToolchainReleaseVersion() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
multiRelease { releaseVersions 21 }
|
||||
task printReleaseVersion {
|
||||
doLast {
|
||||
tasks.all { println it.name }
|
||||
tasks.named("compileJava21Java") {
|
||||
println "compileJava21Java releaseVersion: ${it.options.release.get()}"
|
||||
}
|
||||
tasks.named("compileJava21TestJava") {
|
||||
println "compileJava21TestJava releaseVersion: ${it.options.release.get()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
BuildResult buildResult = runGradle("printReleaseVersion");
|
||||
assertThat(buildResult.getOutput()).contains("compileJava21Java releaseVersion: 21")
|
||||
.contains("compileJava21TestJava releaseVersion: 21");
|
||||
}
|
||||
|
||||
@Test
|
||||
void packageInJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
version = '1.2.3'
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {}
|
||||
""");
|
||||
BuildResult buildResult = runGradle("assemble");
|
||||
File file = new File(this.projectDir, "/build/libs/" + this.projectDir.getName() + "-1.2.3.jar");
|
||||
assertThat(file).exists();
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
Attributes mainAttributes = jar.getManifest().getMainAttributes();
|
||||
assertThat(mainAttributes.getValue("Multi-Release")).isEqualTo("true");
|
||||
|
||||
assertThat(jar.entries().asIterator()).toIterable()
|
||||
.anyMatch(entry -> entry.getName().equals("META-INF/versions/17/Main.class"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
|
||||
void validateJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
version = '1.2.3'
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.release = 11
|
||||
}
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeGradleProperties("""
|
||||
org.gradle.jvmargs=-Duser.language=en
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {
|
||||
|
||||
public void method() {}
|
||||
|
||||
}
|
||||
""");
|
||||
writeClass("src/main/java", "Main.java", """
|
||||
public class Main {}
|
||||
""");
|
||||
assertThatThrownBy(() ->runGradle("validateMultiReleaseJar"))
|
||||
.isInstanceOf(UnexpectedBuildFailure.class)
|
||||
.hasMessageContaining("entry: META-INF/versions/17/Main.class, contains a class with different api from earlier version");
|
||||
}
|
||||
|
||||
private void writeBuildFile(String buildContent) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
|
||||
out.print(buildContent);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGradleProperties(String properties) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
|
||||
out.print(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClass(String path, String fileName, String fileContent) throws IOException {
|
||||
Path folder = this.projectDir.toPath().resolve(path);
|
||||
Files.createDirectories(folder);
|
||||
Path filePath = folder.resolve(fileName);
|
||||
Files.createFile(filePath);
|
||||
Files.writeString(filePath, fileContent);
|
||||
}
|
||||
|
||||
private BuildResult runGradle(String... args) {
|
||||
return GradleRunner.create().withProjectDir(this.projectDir).withArguments(args).withPluginClasspath().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
plugins {
|
||||
id 'java-platform'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
id 'org.jetbrains.dokka'
|
||||
}
|
||||
|
||||
description = "Spring Framework API Docs"
|
||||
@@ -20,12 +19,7 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
def springAspectsOutput = project(":spring-aspects").sourceSets.main.output
|
||||
javadoc {
|
||||
javadocTool.set(javaToolchains.javadocToolFor({
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}))
|
||||
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
@@ -40,35 +34,35 @@ javadoc {
|
||||
links(rootProject.ext.javadocLinks)
|
||||
// Check for 'syntax' and 'reference' during linting.
|
||||
addBooleanOption('Xdoclint:syntax,reference', true)
|
||||
// Change modularity mismatch from warn to info.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
addStringOption("-link-modularity-mismatch", "info")
|
||||
// Fail build on Javadoc warnings.
|
||||
addBooleanOption('Werror', true)
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
}
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
classpath += files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
project(":spring-aspects").sourceSets.main.output
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
}
|
||||
|
||||
dokka {
|
||||
moduleName = "spring-framework"
|
||||
dokkaPublications.html {
|
||||
outputDirectory = project.java.docsDir.dir("kdoc-api")
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
/**
|
||||
* Produce KDoc for all Spring Framework modules in "build/docs/kdoc"
|
||||
*/
|
||||
rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
dependsOn {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.java.docsDir.dir("kdoc-api").get().asFile)
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all Java docs (javadoc & kdoc) into a single archive
|
||||
*/
|
||||
tasks.register('docsZip', Zip) {
|
||||
dependsOn = ['javadoc', 'dokkaGenerate']
|
||||
dependsOn = ['javadoc', rootProject.tasks.dokkaHtmlMultiModule]
|
||||
group = "distribution"
|
||||
description = "Builds -${archiveClassifier} archive containing api and reference " +
|
||||
"for deployment at https://docs.spring.io/spring-framework/docs/."
|
||||
@@ -81,7 +75,7 @@ tasks.register('docsZip', Zip) {
|
||||
from(javadoc) {
|
||||
into "javadoc-api"
|
||||
}
|
||||
from(project.java.docsDir.dir("kdoc-api")) {
|
||||
from(rootProject.tasks.dokkaHtmlMultiModule.outputDirectory) {
|
||||
into "kdoc-api"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ content:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: include all releases from 6.2.0 to 9.*.*.
|
||||
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
|
||||
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
@@ -38,4 +38,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
|
||||
|
||||
@@ -31,7 +31,7 @@ asciidoc:
|
||||
spring-org: 'spring-projects'
|
||||
spring-github-org: "https://github.com/{spring-org}"
|
||||
spring-framework-github: "https://github.com/{spring-org}/spring-framework"
|
||||
spring-framework-code: '{spring-framework-github}/tree/7.0.x'
|
||||
spring-framework-code: '{spring-framework-github}/tree/main'
|
||||
spring-framework-issues: '{spring-framework-github}/issues'
|
||||
spring-framework-wiki: '{spring-framework-github}/wiki'
|
||||
# Docs
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
|
||||
|
||||
plugins {
|
||||
id 'kotlin'
|
||||
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
|
||||
@@ -15,15 +12,11 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
node {
|
||||
version = '24.15.0'
|
||||
}
|
||||
|
||||
tasks.named("generateAntoraYml") {
|
||||
asciidocAttributes = project.provider( {
|
||||
return ["spring-version": project.version ]
|
||||
@@ -48,16 +41,6 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
// To avoid a redeclaration error with Kotlin compiler and set the JVM target
|
||||
tasks.withType(KotlinCompilationTask.class).configureEach {
|
||||
javaSources.from = []
|
||||
compilerOptions.jvmTarget = JvmTarget.JVM_17
|
||||
compilerOptions.freeCompilerArgs.addAll(
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-aspects"))
|
||||
implementation(project(":spring-context"))
|
||||
@@ -71,10 +54,10 @@ dependencies {
|
||||
implementation(project(":spring-webmvc"))
|
||||
implementation(project(":spring-websocket"))
|
||||
|
||||
implementation("com.github.ben-manes.caffeine:caffeine")
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
implementation("com.mchange:c3p0:0.9.5.5")
|
||||
implementation("com.oracle.database.jdbc:ojdbc11")
|
||||
implementation("io.micrometer:context-propagation")
|
||||
implementation("io.projectreactor.netty:reactor-netty-http")
|
||||
implementation("jakarta.jms:jakarta.jms-api")
|
||||
implementation("jakarta.servlet:jakarta.servlet-api")
|
||||
@@ -84,15 +67,9 @@ dependencies {
|
||||
implementation("javax.cache:cache-api")
|
||||
implementation("org.apache.activemq:activemq-ra:6.1.2")
|
||||
implementation("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
implementation("org.apache.groovy:groovy-templates")
|
||||
implementation("org.aspectj:aspectjweaver")
|
||||
implementation("org.assertj:assertj-core")
|
||||
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
implementation("org.freemarker:freemarker")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
implementation("tools.jackson.core:jackson-databind")
|
||||
implementation("tools.jackson.dataformat:jackson-dataformat-xml")
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 63 KiB |
@@ -17,7 +17,6 @@
|
||||
class="st5"
|
||||
id="svg5499"
|
||||
version="1.1"
|
||||
font-family="Helvetica, Arial, sans-serif"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mvc-splitted-contexts.svg"
|
||||
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
|
||||
@@ -37,7 +36,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path9164" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -48,7 +47,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8836" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -59,7 +58,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8520" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -70,7 +69,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8216" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -81,7 +80,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7924" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -92,7 +91,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7644" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -103,7 +102,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7375" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -114,7 +113,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7119" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -125,7 +124,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6875" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -136,7 +135,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6643" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -147,7 +146,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6423" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -158,7 +157,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6215" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -169,7 +168,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6019" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -180,7 +179,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5835" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -191,7 +190,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5663" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -202,7 +201,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5503" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -213,7 +212,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5355" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -224,7 +223,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5219" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -235,7 +234,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5095" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -246,7 +245,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path4983" /></marker><marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
@@ -257,7 +256,7 @@
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"><path
|
||||
id="path7394"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
@@ -268,7 +267,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8123"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
@@ -280,7 +279,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path8035"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -291,7 +290,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7959"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -302,7 +301,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7376"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
@@ -312,7 +311,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7388"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
@@ -324,7 +323,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7370"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
@@ -353,9 +352,9 @@
|
||||
v:viewMarkup="false" /><style
|
||||
type="text/css"
|
||||
id="style5501"><![CDATA[
|
||||
.st1 {fill:#969696;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#F8F9FA;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#333333;font-family:Helvetica, Arial, sans-serif;font-size:2.50001em;font-weight:bold}
|
||||
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
|
||||
.st4 {font-size:0.333333em;font-weight:normal}
|
||||
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]></style><g
|
||||
@@ -364,22 +363,20 @@
|
||||
v:groupContext="foregroundPage"
|
||||
id="g5503"
|
||||
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6599"
|
||||
width="382.68423"
|
||||
height="146.09897"
|
||||
x="87.884865"
|
||||
y="148.26482"
|
||||
rx="8" /><v:userDefs><v:ud
|
||||
y="148.26482" /><v:userDefs><v:ud
|
||||
v:nameU="SchemeName"
|
||||
v:val="VT4(Default)" /></v:userDefs><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5725"
|
||||
width="464.31128"
|
||||
height="374.11411"
|
||||
x="41.684383"
|
||||
y="112.3262"
|
||||
rx="8" /><title
|
||||
y="112.3262" /><title
|
||||
id="title5505">Page-1</title><v:pageProperties
|
||||
v:drawingScale="0.0393701"
|
||||
v:pageScale="0.0393701"
|
||||
@@ -388,16 +385,15 @@
|
||||
v:shadowOffsetY="-8.50394" /><v:layer
|
||||
v:name="Connector"
|
||||
v:index="0" /><rect
|
||||
style="fill:#F8F9FA;fill-opacity:1;fill-rule:evenodd;stroke:#6db33f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5715"
|
||||
width="322.8194"
|
||||
height="43.63184"
|
||||
x="119.95335"
|
||||
y="-135.66222"
|
||||
transform="scale(1,-1)"
|
||||
rx="8" /><text
|
||||
transform="scale(1,-1)" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
id="text5717"
|
||||
@@ -405,12 +401,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5719"
|
||||
x="172.843"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">DispatcherServlet</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
id="text6589"
|
||||
@@ -418,12 +414,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6591"
|
||||
x="106.770851"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">Servlet WebApplicationContext</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
id="text6593"
|
||||
@@ -433,39 +429,36 @@
|
||||
id="tspan6595"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555">(containing controllers, view resolvers,</tspan><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="260.00443"
|
||||
y="212.83057"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan6597">and other web-related beans)</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6620"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="114.52653"
|
||||
y="-259.43161"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="scale(1,-1)" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6622"
|
||||
width="87.843979"
|
||||
height="36.697304"
|
||||
x="223.39864"
|
||||
y="-287.19809"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-264.81918"
|
||||
x="117.92834"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6614"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
id="text6616"
|
||||
@@ -473,20 +466,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6618"
|
||||
x="124.24728"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
style="font-size:11.53426838px;fill:#333333">Controllers</tspan></text>
|
||||
style="font-size:11.53426838px">Controllers</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
style="font-size:11.53426838px"
|
||||
y="282.70709"
|
||||
x="221.61203"
|
||||
x="219.61203"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">ViewResolver</tspan></text>
|
||||
<rect
|
||||
@@ -496,10 +489,9 @@
|
||||
height="36.577778"
|
||||
width="114.4539"
|
||||
id="rect6628"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
id="text6630"
|
||||
@@ -507,27 +499,26 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6632"
|
||||
x="332.51276"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
style="font-size:11.53426838px;fill:#333333">HandlerMapping</tspan></text>
|
||||
style="font-size:11.53426838px">HandlerMapping</tspan></text>
|
||||
<rect
|
||||
y="338.69724"
|
||||
x="87.803261"
|
||||
height="121.5683"
|
||||
width="382.84744"
|
||||
id="rect6634"
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="8" /><text
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6636"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
|
||||
y="376.61673"
|
||||
x="113.61351"
|
||||
x="108.61351"
|
||||
id="tspan6638"
|
||||
sodipodi:role="line">Root WebApplicationContext</tspan></text>
|
||||
<text
|
||||
@@ -536,10 +527,10 @@
|
||||
id="text6640"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan6644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
|
||||
@@ -550,47 +541,43 @@
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6648"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6650"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="167.73116"
|
||||
y="-445.45563"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><text
|
||||
transform="scale(1,-1)" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6652"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
style="font-size:11.53426838px"
|
||||
y="448.55054"
|
||||
x="178.87148"
|
||||
x="175.87148"
|
||||
id="tspan6654"
|
||||
sodipodi:role="line">Services</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6656"
|
||||
width="86.393044"
|
||||
height="36.704323"
|
||||
x="306.86328"
|
||||
y="-439.60837"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-444.99475"
|
||||
x="310.26624"
|
||||
height="36.701977"
|
||||
width="86.876686"
|
||||
id="rect6658"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
id="text6660"
|
||||
@@ -598,20 +585,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6662"
|
||||
x="308.30771"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
style="font-size:11.53426838px;fill:#333333">Repositories</tspan></text>
|
||||
style="font-size:11.53426838px">Repositories</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
|
||||
id="path7643"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
style="fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
|
||||
id="path7645"
|
||||
inkscape:connector-curvature="0" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
id="text4963"
|
||||
@@ -620,6 +607,6 @@
|
||||
sodipodi:role="line"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan4965">Delegates if no bean found</tspan></text>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
@@ -32,7 +32,6 @@
|
||||
**** xref:core/beans/java/bean-annotation.adoc[]
|
||||
**** xref:core/beans/java/configuration-annotation.adoc[]
|
||||
**** xref:core/beans/java/composing-configuration-classes.adoc[]
|
||||
**** xref:core/beans/java/programmatic-bean-registration.adoc[]
|
||||
*** xref:core/beans/environment.adoc[]
|
||||
*** xref:core/beans/context-load-time-weaver.adoc[]
|
||||
*** xref:core/beans/context-introduction.adoc[]
|
||||
@@ -40,8 +39,8 @@
|
||||
** xref:core/resources.adoc[]
|
||||
** xref:core/validation.adoc[]
|
||||
*** xref:core/validation/validator.adoc[]
|
||||
*** xref:core/validation/data-binding.adoc[]
|
||||
*** xref:core/validation/error-code-resolution.adoc[]
|
||||
*** xref:core/validation/beans-beans.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/convert.adoc[]
|
||||
*** xref:core/validation/format.adoc[]
|
||||
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
|
||||
@@ -100,9 +99,9 @@
|
||||
*** xref:core/aop-api/autoproxy.adoc[]
|
||||
*** xref:core/aop-api/targetsource.adoc[]
|
||||
*** xref:core/aop-api/extensibility.adoc[]
|
||||
** xref:core/resilience.adoc[]
|
||||
** xref:core/null-safety.adoc[]
|
||||
** xref:core/databuffer-codec.adoc[]
|
||||
** xref:core/spring-jcl.adoc[]
|
||||
** xref:core/aot.adoc[]
|
||||
** xref:core/appendix.adoc[]
|
||||
*** xref:core/appendix/xsd-schemas.adoc[]
|
||||
@@ -162,6 +161,7 @@
|
||||
**** xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/viewresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/localeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/themeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
|
||||
*** xref:web/webmvc/filters.adoc[]
|
||||
@@ -197,10 +197,7 @@
|
||||
*** xref:web/webmvc-functional.adoc[]
|
||||
*** xref:web/webmvc/mvc-uri-building.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-async.adoc[]
|
||||
*** xref:web/webmvc/mvc-range.adoc[]
|
||||
*** xref:web/webmvc/mvc-data-binding.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc-versioning.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webmvc/mvc-security.adoc[]
|
||||
*** xref:web/webmvc/mvc-caching.adoc[]
|
||||
@@ -229,7 +226,6 @@
|
||||
**** xref:web/webmvc/mvc-config/static-resources.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/default-servlet-handler.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/path-matching.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/api-version.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-java.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-xml.adoc[]
|
||||
*** xref:web/webmvc/mvc-http2.adoc[]
|
||||
@@ -262,6 +258,7 @@
|
||||
**** xref:web/websocket/stomp/configuration-performance.adoc[]
|
||||
**** xref:web/websocket/stomp/stats.adoc[]
|
||||
**** xref:web/websocket/stomp/testing.adoc[]
|
||||
** xref:web/integration.adoc[]
|
||||
* xref:web-reactive.adoc[]
|
||||
** xref:web/webflux.adoc[]
|
||||
*** xref:web/webflux/new-framework.adoc[]
|
||||
@@ -295,10 +292,7 @@
|
||||
**** xref:web/webflux/controller/ann-advice.adoc[]
|
||||
*** xref:web/webflux-functional.adoc[]
|
||||
*** xref:web/webflux/uri-building.adoc[]
|
||||
*** xref:web/webflux/range.adoc[]
|
||||
*** xref:web/webflux/data-binding.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux-versioning.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webflux/security.adoc[]
|
||||
*** xref:web/webflux/caching.adoc[]
|
||||
@@ -315,7 +309,7 @@
|
||||
*** xref:web/webflux-webclient/client-context.adoc[]
|
||||
*** xref:web/webflux-webclient/client-synchronous.adoc[]
|
||||
*** xref:web/webflux-webclient/client-testing.adoc[]
|
||||
** xref:web/webflux-http-service-client.adoc[]
|
||||
** xref:web/webflux-http-interface-client.adoc[]
|
||||
** xref:web/webflux-websocket.adoc[]
|
||||
** xref:web/webflux-test.adoc[]
|
||||
** xref:rsocket.adoc[]
|
||||
@@ -335,7 +329,6 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
@@ -346,7 +339,6 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
@@ -358,7 +350,6 @@
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/resttestclient.adoc[]
|
||||
** xref:testing/mockmvc.adoc[]
|
||||
*** xref:testing/mockmvc/overview.adoc[]
|
||||
*** xref:testing/mockmvc/setup-options.adoc[]
|
||||
@@ -442,8 +433,8 @@
|
||||
*** xref:integration/cache/plug.adoc[]
|
||||
*** xref:integration/cache/specific-config.adoc[]
|
||||
** xref:integration/observability.adoc[]
|
||||
** xref:integration/aot-cache.adoc[]
|
||||
** xref:integration/checkpoint-restore.adoc[]
|
||||
** xref:integration/cds.adoc[]
|
||||
** xref:integration/appendix.adoc[]
|
||||
* xref:languages.adoc[]
|
||||
** xref:languages/kotlin.adoc[]
|
||||
@@ -452,13 +443,14 @@
|
||||
*** xref:languages/kotlin/null-safety.adoc[]
|
||||
*** xref:languages/kotlin/classes-interfaces.adoc[]
|
||||
*** xref:languages/kotlin/annotations.adoc[]
|
||||
*** xref:languages/kotlin/bean-registration-dsl.adoc[]
|
||||
*** xref:languages/kotlin/bean-definition-dsl.adoc[]
|
||||
*** xref:languages/kotlin/web.adoc[]
|
||||
*** xref:languages/kotlin/coroutines.adoc[]
|
||||
*** xref:languages/kotlin/spring-projects-in.adoc[]
|
||||
*** xref:languages/kotlin/getting-started.adoc[]
|
||||
*** xref:languages/kotlin/resources.adoc[]
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
|
||||
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
|
||||
|
||||
@@ -74,11 +74,6 @@ expressions used in XML bean definitions, `@Value`, etc.
|
||||
| The mode to use when compiling expressions for the
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
|
||||
|
||||
| `spring.expression.maxOperations`
|
||||
| The default maximum number of operations permitted during
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
expression evaluation.
|
||||
|
||||
| `spring.getenv.ignore`
|
||||
| Instructs Spring to ignore operating system environment variables if a Spring
|
||||
`Environment` property -- for example, a placeholder in a configuration String -- isn't
|
||||
@@ -86,11 +81,6 @@ resolvable otherwise. See
|
||||
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.http.response.flush.enabled`
|
||||
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
|
||||
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
|
||||
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
@@ -134,20 +124,11 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int
|
||||
| The maximum size of the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
|
||||
|
||||
| `spring.test.context.cache.pause`
|
||||
| The pause mode for the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context Pausing].
|
||||
|
||||
| `spring.test.context.failure.threshold`
|
||||
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
|
||||
in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
|
||||
|
||||
| `spring.test.extension.context.scope`
|
||||
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
|
||||
class hierarchies. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
|
||||
@@ -87,12 +87,14 @@ A crucial difference between Spring pooling and SLSB pooling is that Spring pool
|
||||
be applied to any POJO. As with Spring in general, this service can be applied in a
|
||||
non-invasive way.
|
||||
|
||||
Spring provides support for Commons Pool 2, which provides a
|
||||
Spring provides support for Commons Pool 2.2, which provides a
|
||||
fairly efficient pooling implementation. You need the `commons-pool` Jar on your
|
||||
application's classpath to use this feature. You can also subclass
|
||||
`org.springframework.aop.target.AbstractPoolingTargetSource` to support any other
|
||||
pooling API.
|
||||
|
||||
NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.
|
||||
|
||||
The following listing shows an example configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
|
||||
@@ -727,7 +727,7 @@ of determining parameter names, an exception will be thrown.
|
||||
parameter names. This discoverer is only used if such APIs are present on the classpath.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach.
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{spring-framework-api}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
|
||||
@@ -28,10 +28,6 @@ you can do so. However, you should consider the following issues:
|
||||
deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
|
||||
[[aop-forcing-proxy-types]]
|
||||
== Forcing Specific AOP Proxy Types
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
of the `<aop:config>` element to true, as follows:
|
||||
|
||||
@@ -64,24 +60,6 @@ To be clear, using `proxy-target-class="true"` on `<tx:annotation-driven/>`,
|
||||
proxies _for all three of them_.
|
||||
====
|
||||
|
||||
`@EnableAspectJAutoProxy`, `@EnableTransactionManagement` and related configuration
|
||||
annotations offer a corresponding `proxyTargetClass` attribute. These are collapsed
|
||||
into a single unified auto-proxy creator too, effectively applying the _strongest_
|
||||
proxy settings at runtime. As of 7.0, this applies to individual proxy processors
|
||||
as well, for example `@EnableAsync`, consistently participating in unified global
|
||||
default settings for all auto-proxying attempts in a given application.
|
||||
|
||||
The global default proxy type may differ between setups. While the core framework
|
||||
suggests interface-based proxies by default, Spring Boot may - depending on
|
||||
configuration properties - enable class-based proxies by default.
|
||||
|
||||
As of 7.0, forcing a specific proxy type for individual beans is possible through
|
||||
the `@Proxyable` annotation on a given `@Bean` method or `@Component` class, with
|
||||
`@Proxyable(INTERFACES)` or `@Proxyable(TARGET_CLASS)` overriding any globally
|
||||
configured default. For very specific purposes, you may even specify the proxy
|
||||
interface(s) to use through `@Proxyable(interfaces=...)`, limiting the exposure
|
||||
to selected interfaces rather than all interfaces that the target bean implements.
|
||||
|
||||
|
||||
[[aop-understanding-aop-proxies]]
|
||||
== Understanding AOP Proxies
|
||||
|
||||
@@ -378,7 +378,7 @@ The container also supports creating a bean with {spring-framework-api}++/beans/
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
. Bypassing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
@@ -19,6 +19,10 @@ its behavior changes.
|
||||
| Initialization of `SmartInitializingSingleton` beans.
|
||||
| `beanName` the name of the bean.
|
||||
|
||||
| `spring.context.annotated-bean-reader.create`
|
||||
| Creation of the `AnnotatedBeanDefinitionReader`.
|
||||
|
|
||||
|
||||
| `spring.context.base-packages.scan`
|
||||
| Scanning of base packages.
|
||||
| `packages` array of base packages for scanning.
|
||||
|
||||
+1
-1
@@ -274,7 +274,7 @@ Kotlin::
|
||||
Next, you can provide the information for the candidate bean definitions. You can add
|
||||
`<qualifier/>` tags as sub-elements of the `<bean/>` tag and then specify the `type` and
|
||||
`value` to match your custom qualifier annotations. The type is matched against the
|
||||
fully-qualified class name of the annotation. Alternatively, as a convenience if no risk of
|
||||
fully-qualified class name of the annotation. Alternately, as a convenience if no risk of
|
||||
conflicting names exists, you can use the short class name. The following example
|
||||
demonstrates both approaches:
|
||||
|
||||
|
||||
@@ -37,18 +37,18 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
[NOTE]
|
||||
====
|
||||
An `@Autowired` annotation on such a constructor is not necessary if the target bean
|
||||
defines only one constructor. However, if several constructors are available and there is
|
||||
no primary or default constructor, at least one of the constructors must be annotated
|
||||
with `@Autowired` in order to instruct the container which one to use. See the discussion
|
||||
on xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution]
|
||||
for details.
|
||||
As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is no longer
|
||||
necessary if the target bean defines only one constructor to begin with. However, if
|
||||
several constructors are available and there is no primary/default constructor, at least
|
||||
one of the constructors must be annotated with `@Autowired` in order to instruct the
|
||||
container which one to use. See the discussion on
|
||||
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution] for details.
|
||||
====
|
||||
|
||||
You can apply the `@Autowired` annotation to _traditional_ setter methods, as the
|
||||
following example shows:
|
||||
You can also apply the `@Autowired` annotation to _traditional_ setter methods,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -84,8 +84,8 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can apply `@Autowired` to methods with arbitrary names and multiple arguments, as the
|
||||
following example shows:
|
||||
You can also apply the annotation to methods with arbitrary names and multiple
|
||||
arguments, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -176,15 +176,14 @@ Kotlin::
|
||||
====
|
||||
Make sure that your target components (for example, `MovieCatalog` or `CustomerPreferenceDao`)
|
||||
are consistently declared by the type that you use for your `@Autowired`-annotated
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at
|
||||
runtime.
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at runtime.
|
||||
|
||||
For XML-defined beans or component classes found via classpath scanning, the container
|
||||
usually knows the concrete type up front. However, for `@Bean` factory methods, you need
|
||||
to make sure that the declared return type is sufficiently expressive. For components
|
||||
that implement several interfaces or for components potentially referred to by their
|
||||
implementation type, declare the most specific return type on your factory method (at
|
||||
least as specific as required by the injection points referring to your bean).
|
||||
implementation type, consider declaring the most specific return type on your factory
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
.[[beans-autowired-annotation-self-injection]]Self Injection
|
||||
@@ -314,8 +313,8 @@ for each type.
|
||||
====
|
||||
|
||||
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
|
||||
The map values are all beans of the expected type, and the keys are the corresponding
|
||||
bean names, as the following example shows:
|
||||
The map values contain all beans of the expected type, and the keys contain the
|
||||
corresponding bean names, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -433,7 +432,7 @@ annotated constructor does not have to be public.
|
||||
====
|
||||
|
||||
Alternatively, you can express the non-required nature of a particular dependency
|
||||
through Java's `java.util.Optional`, as the following example shows:
|
||||
through Java 8's `java.util.Optional`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -447,8 +446,8 @@ through Java's `java.util.Optional`, as the following example shows:
|
||||
----
|
||||
|
||||
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
|
||||
for example, `org.jspecify.annotations.Nullable` from JSpecify) or just leverage Kotlin's
|
||||
built-in null-safety support:
|
||||
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
|
||||
null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -479,6 +478,13 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
|
||||
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
|
||||
detects type-level annotations and consistently declares JSpecify in its own codebase.
|
||||
====
|
||||
|
||||
You can also use `@Autowired` for interfaces that are well-known resolvable
|
||||
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
|
||||
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
|
||||
@@ -523,6 +529,5 @@ class MovieRecommender {
|
||||
The `@Autowired`, `@Inject`, `@Value`, and `@Resource` annotations are handled by Spring
|
||||
`BeanPostProcessor` implementations. This means that you cannot apply these annotations
|
||||
within your own `BeanPostProcessor` or `BeanFactoryPostProcessor` types (if any).
|
||||
|
||||
These types must be 'wired up' explicitly by using XML or a Spring `@Bean` method.
|
||||
====
|
||||
|
||||
@@ -202,17 +202,18 @@ another file or files. The following example shows how to do so:
|
||||
<beans>
|
||||
<import resource="services.xml"/>
|
||||
<import resource="resources/messageSource.xml"/>
|
||||
<import resource="/resources/themeSource.xml"/>
|
||||
|
||||
<bean id="bean1" class="..."/>
|
||||
<bean id="bean2" class="..."/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
In the preceding example, external bean definitions are loaded from the files
|
||||
`services.xml` and `messageSource.xml`. All location paths are
|
||||
In the preceding example, external bean definitions are loaded from three files:
|
||||
`services.xml`, `messageSource.xml`, and `themeSource.xml`. All location paths are
|
||||
relative to the definition file doing the importing, so `services.xml` must be in the
|
||||
same directory or classpath location as the file doing the importing, while
|
||||
`messageSource.xml` must be in a `resources` location below the
|
||||
`messageSource.xml` and `themeSource.xml` must be in a `resources` location below the
|
||||
location of the importing file. As you can see, a leading slash is ignored. However, given
|
||||
that these paths are relative, it is better form not to use the slash at all. The
|
||||
contents of the files being imported, including the top level `<beans/>` element, must
|
||||
@@ -239,6 +240,40 @@ The namespace itself provides the import directive feature. Further
|
||||
configuration features beyond plain bean definitions are available in a selection
|
||||
of XML namespaces provided by Spring -- for example, the `context` and `util` namespaces.
|
||||
|
||||
[[beans-factory-groovy]]
|
||||
=== The Groovy Bean Definition DSL
|
||||
|
||||
As a further example for externalized configuration metadata, bean definitions can also
|
||||
be expressed in Spring's Groovy Bean Definition DSL, as known from the Grails framework.
|
||||
Typically, such configuration live in a ".groovy" file with the structure shown in the
|
||||
following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
beans {
|
||||
dataSource(BasicDataSource) {
|
||||
driverClassName = "org.hsqldb.jdbcDriver"
|
||||
url = "jdbc:hsqldb:mem:grailsDB"
|
||||
username = "sa"
|
||||
password = ""
|
||||
settings = [mynew:"setting"]
|
||||
}
|
||||
sessionFactory(SessionFactory) {
|
||||
dataSource = dataSource
|
||||
}
|
||||
myService(MyService) {
|
||||
nestedBean = { AnotherBean bean ->
|
||||
dataSource = dataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This configuration style is largely equivalent to XML bean definitions and even
|
||||
supports Spring's XML configuration namespaces. It also allows for importing XML
|
||||
bean definition files through an `importBeans` directive.
|
||||
|
||||
|
||||
[[beans-factory-client]]
|
||||
== Using the Container
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ annotations. Even in those examples, however, the "base" bean definitions are ex
|
||||
defined in the XML file, while the annotations drive only the dependency injection.
|
||||
|
||||
This section describes an option for implicitly detecting the candidate components by
|
||||
scanning the classpath. Candidate components are classes that match against filter
|
||||
scanning the classpath. Candidate components are classes that match against a filter
|
||||
criteria and have a corresponding bean definition registered with the container.
|
||||
This removes the need to use XML to perform bean registration. Instead, you can use
|
||||
annotations (for example, `@Component`), AspectJ type expressions, or your own
|
||||
@@ -70,7 +70,7 @@ Java::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -85,7 +85,7 @@ Kotlin::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
======
|
||||
|
||||
You can also combine meta-annotations to create "`composed annotations`". For example,
|
||||
@@ -97,7 +97,7 @@ meta-annotations to allow customization. This can be particularly useful when yo
|
||||
want to only expose a subset of the meta-annotation's attributes. For example, Spring's
|
||||
`@SessionScope` annotation hard codes the scope name to `session` but still allows
|
||||
customization of the `proxyMode`. The following listing shows the definition of the
|
||||
`@SessionScope` annotation:
|
||||
`SessionScope` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -211,7 +211,7 @@ Java::
|
||||
@Service
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private final MovieFinder movieFinder;
|
||||
private MovieFinder movieFinder;
|
||||
|
||||
public SimpleMovieLister(MovieFinder movieFinder) {
|
||||
this.movieFinder = movieFinder;
|
||||
@@ -251,11 +251,11 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
To autodetect these classes and register the corresponding beans, you need to add
|
||||
`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute is
|
||||
configured with a common parent package for the two classes. Alternatively, you can
|
||||
specify a comma-, semicolon-, or space-separated list that includes the parent package
|
||||
of each class.
|
||||
`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute
|
||||
is a common parent package for the two classes. (Alternatively, you can specify a
|
||||
comma- or semicolon- or space-separated list that includes the parent package of each class.)
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -282,10 +282,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
TIP: For brevity, the preceding example could have used the implicit `value` attribute of
|
||||
the annotation instead: `@ComponentScan("org.example")`
|
||||
NOTE: For brevity, the preceding example could have used the `value` attribute of the
|
||||
annotation (that is, `@ComponentScan("org.example")`).
|
||||
|
||||
The following example uses XML configuration:
|
||||
The following alternative uses XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -325,68 +325,16 @@ sure that they are 'opened' (that is, that they use an `opens` declaration inste
|
||||
|
||||
Furthermore, the `AutowiredAnnotationBeanPostProcessor` and
|
||||
`CommonAnnotationBeanPostProcessor` are both implicitly included when you use the
|
||||
`<context:component-scan>` element. That means that the two components are autodetected
|
||||
and wired together -- all without any bean configuration metadata provided in XML.
|
||||
component-scan element. That means that the two components are autodetected and
|
||||
wired together -- all without any bean configuration metadata provided in XML.
|
||||
|
||||
NOTE: You can disable the registration of `AutowiredAnnotationBeanPostProcessor` and
|
||||
`CommonAnnotationBeanPostProcessor` by including the `annotation-config` attribute
|
||||
with a value of `false`.
|
||||
|
||||
|
||||
[[beans-scanning-placeholders-and-patterns]]
|
||||
=== Property Placeholders and Ant-style Patterns
|
||||
|
||||
The `basePackages` and `value` attributes in `@ComponentScan` support `${...}` property
|
||||
placeholders which are resolved against the `Environment` as well as Ant-style package
|
||||
patterns such as `"org.example.+++**+++"`.
|
||||
|
||||
In addition, multiple packages or patterns may be specified, either separately or within
|
||||
a single String — for example, `{"org.example.config", "org.example.service.+++**+++"}`
|
||||
or `"org.example.config, org.example.service.+++**+++"`.
|
||||
|
||||
The following example specifies the `app.scan.packages` property placeholder for the
|
||||
implicit `value` attribute in `@ComponentScan`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("${app.scan.packages}") // <1>
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(["\${app.scan.packages}"]) // <1>
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
|
||||
======
|
||||
|
||||
The following listing represents a properties file which defines the `app.scan.packages`
|
||||
property. In the preceding example, it is assumed that this properties file has been
|
||||
registered with the `Environment` – for example, via `@PropertySource` or a similar
|
||||
mechanism.
|
||||
|
||||
[source,properties,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
app.scan.packages=org.example.config, org.example.service.**
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-filters]]
|
||||
=== Using Filters to Customize Scanning
|
||||
== Using Filters to Customize Scanning
|
||||
|
||||
By default, classes annotated with `@Component`, `@Repository`, `@Service`, `@Controller`,
|
||||
`@Configuration`, or a custom annotation that itself is annotated with `@Component` are
|
||||
@@ -423,8 +371,8 @@ The following table describes the filtering options:
|
||||
| A custom implementation of the `org.springframework.core.type.TypeFilter` interface.
|
||||
|===
|
||||
|
||||
The following example shows `@ComponentScan` configuration that excludes all
|
||||
`@Repository` annotations and includes "`Stub`" repositories instead:
|
||||
The following example shows the configuration ignoring all `@Repository` annotations
|
||||
and using "`stub`" repositories instead:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -476,367 +424,6 @@ annotated or meta-annotated with `@Component`, `@Repository`, `@Service`, `@Cont
|
||||
`@RestController`, or `@Configuration`.
|
||||
|
||||
|
||||
[[beans-scanning-name-generator]]
|
||||
=== Naming Autodetected Components
|
||||
|
||||
When a component is autodetected as part of the scanning process, its bean name is
|
||||
generated by the `BeanNameGenerator` strategy known to that scanner.
|
||||
|
||||
By default, the `AnnotationBeanNameGenerator` is used. For Spring
|
||||
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
|
||||
if you supply a name via the annotation's `value` attribute that name will be used as
|
||||
the name in the corresponding bean definition. This convention also applies when the
|
||||
`@jakarta.inject.Named` annotation is used instead of Spring stereotype annotations.
|
||||
|
||||
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
|
||||
the bean name is no longer required to be `value`. Custom stereotype annotations can
|
||||
declare an attribute with a different name (such as `name`) and annotate that attribute
|
||||
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
|
||||
declaration of `ControllerAdvice#name()` for a concrete example.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
|
||||
and will be removed in a future version of the framework. Consequently, custom stereotype
|
||||
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
|
||||
in `@Component`. See the source code declaration of `Repository#value()` and
|
||||
`ControllerAdvice#name()` for concrete examples.
|
||||
====
|
||||
|
||||
If an explicit bean name cannot be derived from such an annotation or for any other
|
||||
detected component (such as those discovered by custom filters), the default bean name
|
||||
generator returns the uncapitalized non-qualified class name. For example, if the
|
||||
following component classes were detected, the names would be `myMovieLister` and
|
||||
`movieFinderImpl`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
public class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement either the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`] or
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following examples show.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example"
|
||||
name-generator="org.example.MyNameGenerator" />
|
||||
</beans>
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you run into naming conflicts due to multiple autodetected components having the same
|
||||
non-qualified class name (for example, classes with identical names but residing in
|
||||
different packages), you can configure a `BeanNameGenerator` that defaults to the
|
||||
fully-qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` can be used for such purposes.
|
||||
|
||||
As of Spring Framework 7.0, if you encounter naming conflicts among `@Bean` methods in
|
||||
`@Configuration` classes, you can alternatively configure a
|
||||
`ConfigurationBeanNameGenerator` that generates unique bean names for `@Bean` methods.
|
||||
The `FullyQualifiedConfigurationBeanNameGenerator` can be used to generate
|
||||
fully-qualified default bean names for `@Bean` methods without an explicit `name`
|
||||
attribute — for example, `com.example.MyConfig.myBean` for an `@Bean` method named
|
||||
`myBean()` declared in `@Configuration` class `com.example.MyConfig`.
|
||||
|
||||
The `FullyQualifiedAnnotationBeanNameGenerator` and
|
||||
`FullyQualifiedConfigurationBeanNameGenerator` both reside in the
|
||||
`org.springframework.context.annotation` package.
|
||||
====
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
|
||||
[[beans-scanning-scope-resolver]]
|
||||
=== Providing a Scope for Autodetected Components
|
||||
|
||||
As with Spring-managed components in general, the default and most common scope for
|
||||
autodetected components is `singleton`. However, sometimes you need a different scope
|
||||
that can be specified by the `@Scope` annotation. You can provide the name of the
|
||||
scope within the annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Scope` annotations are only introspected on the concrete bean class (for annotated
|
||||
components) or the factory method (for `@Bean` methods). In contrast to XML bean
|
||||
definitions, there is no notion of bean definition inheritance, and inheritance
|
||||
hierarchies at the class level are irrelevant for metadata purposes.
|
||||
|
||||
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes].
|
||||
As with the pre-built annotations for those scopes, you may also compose your own scoping
|
||||
annotations by using Spring's meta-annotation approach: for example, a custom annotation
|
||||
meta-annotated with `@Scope("prototype")`, possibly also declaring a custom scoped-proxy mode.
|
||||
|
||||
NOTE: To provide a custom strategy for scope resolution rather than relying on the
|
||||
annotation-based approach, you can implement the
|
||||
{spring-framework-api}/context/annotation/ScopeMetadataResolver.html[`ScopeMetadataResolver`]
|
||||
interface. Be sure to include a default no-arg constructor. Then you can provide the
|
||||
fully qualified class name when configuring the scanner, as the following example of both
|
||||
an annotation and a bean definition shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
When using certain non-singleton scopes, it may be necessary to generate proxies for the
|
||||
scoped objects. The reasoning is described in
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
For this purpose, a scoped-proxy attribute is available on the component-scan
|
||||
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
|
||||
the following configuration results in standard JDK dynamic proxies:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-qualifiers]]
|
||||
=== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
metadata was provided on the candidate bean definitions by using the `qualifier` or `meta`
|
||||
child elements of the `bean` element in the XML. When relying upon classpath scanning for
|
||||
auto-detection of components, you can provide the qualifier metadata with type-level
|
||||
annotations on the candidate class. The following three examples demonstrate this
|
||||
technique:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
class ActionMovieCatalog : MovieCatalog
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
class ActionMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
public class CachingMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
class CachingMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As with most annotation-based alternatives, keep in mind that the annotation metadata is
|
||||
bound to the class definition itself, while the use of XML allows for multiple beans
|
||||
of the same type to provide variations in their qualifier metadata, because that
|
||||
metadata is provided per-instance rather than per-class.
|
||||
|
||||
|
||||
[[beans-factorybeans-annotations]]
|
||||
== Defining Bean Metadata within Components
|
||||
|
||||
@@ -1059,10 +646,10 @@ methods anywhere. However, regular `@Bean` methods in `@Configuration` classes n
|
||||
to be overridable -- that is, they must not be declared as `private` or `final`.
|
||||
|
||||
`@Bean` methods are also discovered on base classes of a given component or
|
||||
configuration class, as well as on Java default methods declared in interfaces
|
||||
configuration class, as well as on Java 8 default methods declared in interfaces
|
||||
implemented by the component or configuration class. This allows for a lot of
|
||||
flexibility in composing complex configuration arrangements, with even multiple
|
||||
inheritance being possible through Java default methods.
|
||||
inheritance being possible through Java 8 default methods as of Spring 4.2.
|
||||
|
||||
Finally, a single class may hold multiple `@Bean` methods for the same
|
||||
bean, as an arrangement of multiple factory methods to use depending on available
|
||||
@@ -1071,3 +658,352 @@ constructor or factory method in other configuration scenarios: The variant with
|
||||
the largest number of satisfiable dependencies is picked at construction time,
|
||||
analogous to how the container selects between multiple `@Autowired` constructors.
|
||||
====
|
||||
|
||||
|
||||
[[beans-scanning-name-generator]]
|
||||
== Naming Autodetected Components
|
||||
|
||||
When a component is autodetected as part of the scanning process, its bean name is
|
||||
generated by the `BeanNameGenerator` strategy known to that scanner.
|
||||
|
||||
By default, the `AnnotationBeanNameGenerator` is used. For Spring
|
||||
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
|
||||
if you supply a name via the annotation's `value` attribute that name will be used as
|
||||
the name in the corresponding bean definition. This convention also applies when the
|
||||
following JSR-250 and JSR-330 annotations are used instead of Spring stereotype
|
||||
annotations: `@jakarta.annotation.ManagedBean`, `@javax.annotation.ManagedBean`,
|
||||
`@jakarta.inject.Named`, and `@javax.inject.Named`.
|
||||
|
||||
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
|
||||
the bean name is no longer required to be `value`. Custom stereotype annotations can
|
||||
declare an attribute with a different name (such as `name`) and annotate that attribute
|
||||
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
|
||||
declaration of `ControllerAdvice#name()` for a concrete example.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
|
||||
and will be removed in a future version of the framework. Consequently, custom stereotype
|
||||
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
|
||||
in `@Component`. See the source code declaration of `Repository#value()` and
|
||||
`ControllerAdvice#name()` for concrete examples.
|
||||
====
|
||||
|
||||
If an explicit bean name cannot be derived from such an annotation or for any other
|
||||
detected component (such as those discovered by custom filters), the default bean name
|
||||
generator returns the uncapitalized non-qualified class name. For example, if the
|
||||
following component classes were detected, the names would be `myMovieLister` and
|
||||
`movieFinderImpl`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
public class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following example annotation
|
||||
and bean definition show.
|
||||
|
||||
TIP: If you run into naming conflicts due to multiple autodetected components having the
|
||||
same non-qualified class name (i.e., classes with identical names but residing in
|
||||
different packages), you may need to configure a `BeanNameGenerator` that defaults to the
|
||||
fully qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` located in package
|
||||
`org.springframework.context.annotation` can be used for such purposes.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example"
|
||||
name-generator="org.example.MyNameGenerator" />
|
||||
</beans>
|
||||
----
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
|
||||
[[beans-scanning-scope-resolver]]
|
||||
== Providing a Scope for Autodetected Components
|
||||
|
||||
As with Spring-managed components in general, the default and most common scope for
|
||||
autodetected components is `singleton`. However, sometimes you need a different scope
|
||||
that can be specified by the `@Scope` annotation. You can provide the name of the
|
||||
scope within the annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Scope` annotations are only introspected on the concrete bean class (for annotated
|
||||
components) or the factory method (for `@Bean` methods). In contrast to XML bean
|
||||
definitions, there is no notion of bean definition inheritance, and inheritance
|
||||
hierarchies at the class level are irrelevant for metadata purposes.
|
||||
|
||||
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes].
|
||||
As with the pre-built annotations for those scopes, you may also compose your own scoping
|
||||
annotations by using Spring's meta-annotation approach: for example, a custom annotation
|
||||
meta-annotated with `@Scope("prototype")`, possibly also declaring a custom scoped-proxy mode.
|
||||
|
||||
NOTE: To provide a custom strategy for scope resolution rather than relying on the
|
||||
annotation-based approach, you can implement the
|
||||
{spring-framework-api}/context/annotation/ScopeMetadataResolver.html[`ScopeMetadataResolver`]
|
||||
interface. Be sure to include a default no-arg constructor. Then you can provide the
|
||||
fully qualified class name when configuring the scanner, as the following example of both
|
||||
an annotation and a bean definition shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
When using certain non-singleton scopes, it may be necessary to generate proxies for the
|
||||
scoped objects. The reasoning is described in
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
For this purpose, a scoped-proxy attribute is available on the component-scan
|
||||
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
|
||||
the following configuration results in standard JDK dynamic proxies:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-qualifiers]]
|
||||
== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
metadata was provided on the candidate bean definitions by using the `qualifier` or `meta`
|
||||
child elements of the `bean` element in the XML. When relying upon classpath scanning for
|
||||
auto-detection of components, you can provide the qualifier metadata with type-level
|
||||
annotations on the candidate class. The following three examples demonstrate this
|
||||
technique:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
class ActionMovieCatalog : MovieCatalog
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
class ActionMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
public class CachingMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
class CachingMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As with most annotation-based alternatives, keep in mind that the annotation metadata is
|
||||
bound to the class definition itself, while the use of XML allows for multiple beans
|
||||
of the same type to provide variations in their qualifier metadata, because that
|
||||
metadata is provided per-instance rather than per-class.
|
||||
|
||||
@@ -575,7 +575,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honor that and will not register the method to listen to events.
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
|
||||
|
||||
The method signature once again declares the event type to which it listens,
|
||||
but, this time, with a flexible name and without implementing a specific listener interface.
|
||||
@@ -933,12 +933,13 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
try (StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
}
|
||||
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
// end the current step
|
||||
scanPackages.end();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -946,12 +947,13 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
try (val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
}
|
||||
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages)
|
||||
// end the current step
|
||||
scanPackages.end()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ the `@Bean` factory method in favor of any pre-declared constructor on the bean
|
||||
****
|
||||
|
||||
NOTE: We acknowledge that overriding beans in test scenarios is convenient, and there is
|
||||
explicit support for this. Please refer to
|
||||
explicit support for this as of Spring Framework 6.2. Please refer to
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc[this section] for more details.
|
||||
|
||||
|
||||
|
||||
@@ -248,7 +248,8 @@ Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via
|
||||
Notice how the `InstantiationTracingBeanPostProcessor` is merely defined. It does not
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as with any
|
||||
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
|
||||
script.)
|
||||
script. The Spring dynamic language support is detailed in the chapter entitled
|
||||
xref:languages/dynamic.adoc[Dynamic Language Support].)
|
||||
|
||||
The following Java application runs the preceding code and configuration:
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
`@Bean` is a method-level annotation and a direct analog of the XML `<bean/>` element.
|
||||
The annotation supports some of the attributes offered by `<bean/>`, such as:
|
||||
|
||||
* xref:core/beans/definition.adoc#beans-beanname[name]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-initializingbean[init-method]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-disposablebean[destroy-method]
|
||||
* xref:core/beans/dependencies/factory-autowire.adoc[autowiring]
|
||||
* `name`.
|
||||
|
||||
You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
`@Component`-annotated class.
|
||||
@@ -17,10 +17,9 @@ You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
== Declaring a Bean
|
||||
|
||||
To declare a bean, you can annotate a method with the `@Bean` annotation. You use this
|
||||
method to register a bean definition within an `ApplicationContext` of the type specified
|
||||
by the method's return type. By default, the bean name is the same as the method name
|
||||
(unless a different xref:#beans-java-customizing-bean-naming[bean name generator] is
|
||||
configured). The following example shows a `@Bean` method declaration:
|
||||
method to register a bean definition within an `ApplicationContext` of the type
|
||||
specified as the method's return value. By default, the bean name is the same as
|
||||
the method name. The following example shows a `@Bean` method declaration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -127,7 +126,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
However, this limits the visibility for advanced type prediction to the specified
|
||||
However, this limits the visibility for advance type prediction to the specified
|
||||
interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`)
|
||||
known to the container only once the affected singleton bean has been instantiated.
|
||||
Non-lazy singleton beans get instantiated according to their declaration order,
|
||||
@@ -474,15 +473,8 @@ Kotlin::
|
||||
== Customizing Bean Naming
|
||||
|
||||
By default, configuration classes use a `@Bean` method's name as the name of the
|
||||
resulting bean. However, as of Spring Framework 7.0, you can change this default strategy
|
||||
by configuring a custom
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
when bootstrapping the context or configuring component scanning. For example,
|
||||
{spring-framework-api}/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.html[`FullyQualifiedConfigurationBeanNameGenerator`]
|
||||
can be used to generate fully-qualified default bean names for `@Bean` methods without an
|
||||
explicit `name` attribute. For individual `@Bean` methods, the default or
|
||||
generator-derived name can be overridden with the `name` attribute, as the following
|
||||
example shows:
|
||||
resulting bean. This functionality can be overridden, however, with the `name` attribute,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -513,7 +505,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Bean("myThing")` is equivalent to `@Bean(name = "myThing")`.
|
||||
|
||||
[[beans-java-bean-aliasing]]
|
||||
== Bean Aliasing
|
||||
|
||||
+3
-15
@@ -327,8 +327,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
TIP: Note that there is no need to specify `@Autowired` if the target bean defines
|
||||
only one constructor.
|
||||
TIP: Constructor injection in `@Configuration` classes is only supported as of Spring
|
||||
Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
|
||||
bean defines only one constructor.
|
||||
|
||||
[discrete]
|
||||
[[beans-java-injecting-imported-beans-fq]]
|
||||
@@ -610,19 +611,6 @@ Kotlin::
|
||||
See the {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`]
|
||||
javadoc for more detail.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `@Conditional` annotation declared on an enclosing `@Configuration` class is only
|
||||
applied to the registration of a nested `@Configuration` class if the nested class is
|
||||
reached through the parser's recursion from its enclosing class, or via `@Import`. If a
|
||||
nested class is discovered independently of its enclosing class — for example, via
|
||||
`@ComponentScan` or by directly registering it against the application context — it is
|
||||
processed using only its own `@Conditional` annotations. Thus, if you wish to ensure that
|
||||
the same `@Conditional` annotations apply in such scenarios, you must redeclare the
|
||||
relevant annotations on the nested class, or extract them into a composed annotation
|
||||
which you apply to both the enclosing class and the nested class.
|
||||
====
|
||||
|
||||
|
||||
[[beans-java-combining]]
|
||||
== Combining Java and XML Configuration
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
[[beans-java-programmatic-registration]]
|
||||
= Programmatic Bean Registration
|
||||
|
||||
As of Spring Framework 7, a first-class support for programmatic bean registration is
|
||||
provided via the {spring-framework-api}/beans/factory/BeanRegistrar.html[`BeanRegistrar`]
|
||||
interface that can be implemented to register beans programmatically in a flexible and
|
||||
efficient way.
|
||||
|
||||
Those bean registrar implementations are typically imported with an `@Import` annotation
|
||||
on `@Configuration` classes.
|
||||
|
||||
include-code::./MyConfiguration[tag=snippet,indent=0]
|
||||
|
||||
NOTE: You can leverage type-level conditional annotations ({spring-framework-api}/context/annotation/Conditional.html[`@Conditional`],
|
||||
but also other variants) to conditionally import the related bean registrars.
|
||||
|
||||
The bean registrar implementation uses {spring-framework-api}/beans/factory/BeanRegistry.html[`BeanRegistry`] and
|
||||
{spring-framework-api}/core/env/Environment.html[`Environment`] APIs to register beans programmatically in a concise
|
||||
and flexible way. For example, it allows custom registration through an `if` expression, a
|
||||
`for` loop, etc.
|
||||
|
||||
include-code::./MyBeanRegistrar[tag=snippet,indent=0]
|
||||
|
||||
NOTE: Bean registrars are supported with xref:core/aot.adoc[Ahead of Time Optimizations],
|
||||
either on the JVM or with GraalVM native images, including when instance suppliers are used.
|
||||
@@ -1,17 +1,16 @@
|
||||
[[beans-standard-annotations]]
|
||||
= Using JSR-330 Standard Annotations
|
||||
= Using JSR 330 Standard Annotations
|
||||
|
||||
Spring offers support for JSR-330 standard _Dependency Injection_ annotations which are
|
||||
available in the `jakarta.inject` package. These annotations may optionally be used as
|
||||
alternatives to Spring annotations.
|
||||
|
||||
To use them, you need to have the relevant jar in your classpath. For example, the
|
||||
`jakarta.inject` artifact is available in the standard Maven repository
|
||||
(`https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/`),
|
||||
Spring offers support for JSR-330 standard annotations (Dependency Injection). Those
|
||||
annotations are scanned in the same way as the Spring annotations. To use them, you need
|
||||
to have the relevant jars in your classpath.
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
If you use Maven, you can add the following dependency to your `pom.xml` file.
|
||||
If you use Maven, the `jakarta.inject` artifact is available in the standard Maven
|
||||
repository (
|
||||
https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/[https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/]).
|
||||
You can add the following dependency to your file pom.xml:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -27,14 +26,13 @@ If you use Maven, you can add the following dependency to your `pom.xml` file.
|
||||
[[beans-inject-named]]
|
||||
== Dependency Injection with `@Inject` and `@Named`
|
||||
|
||||
Instead of using `@Autowired` for dependency injection, you may optionally choose to use
|
||||
`@jakarta.inject.Inject` as follows.
|
||||
Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@@ -56,7 +54,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
@@ -74,19 +72,17 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level, and
|
||||
constructor-argument level.
|
||||
|
||||
Furthermore, as an alternative to Spring's `ObjectProvider` mechanism, you may choose to
|
||||
declare your injection point as a `jakarta.inject.Provider`, allowing for on-demand
|
||||
access to beans of shorter scopes or lazy access to other beans through a
|
||||
`Provider.get()` call. The following example offers a variant of the preceding example.
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level
|
||||
and constructor-argument level. Furthermore, you may declare your injection point as a
|
||||
`Provider`, allowing for on-demand access to beans of shorter scopes or lazy access to
|
||||
other beans through a `Provider.get()` call. The following example offers a variant of the
|
||||
preceding example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
@@ -109,10 +105,9 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Provider
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -128,15 +123,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
If you would like to use a qualified name for the dependency that should be injected, you
|
||||
may choose to use the `@Named` annotation as an alternative to Spring's `@Qualifier`
|
||||
support, as the following example shows.
|
||||
If you would like to use a qualified name for the dependency that should be injected,
|
||||
you should use the `@Named` annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -156,7 +150,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -176,15 +170,12 @@ Kotlin::
|
||||
======
|
||||
|
||||
As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have a
|
||||
`required` attribute. The following examples show how to use `@Inject` with `Optional`,
|
||||
`@Nullable`, and Kotlin's built-in support for nullable types.
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have
|
||||
a `required` attribute. The following pair of examples show how to use `@Inject` and
|
||||
`@Nullable`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -198,11 +189,8 @@ As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -214,10 +202,8 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -228,21 +214,21 @@ Kotlin::
|
||||
|
||||
|
||||
[[beans-named]]
|
||||
== `@Named`: Standard Equivalent to the `@Component` Annotation
|
||||
== `@Named` and `@ManagedBean`: Standard Equivalents to the `@Component` Annotation
|
||||
|
||||
Instead of `@Component` or other Spring stereotype annotations, you may optionally choose
|
||||
to use `@jakarta.inject.Named`, as the following example shows.
|
||||
Instead of `@Component`, you can use `@jakarta.inject.Named` or `jakarta.annotation.ManagedBean`,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Named("movieListener")
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
@@ -258,12 +244,12 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
|
||||
@Named("movieListener")
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -274,15 +260,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
It is very common to use `@Component` or other Spring stereotype annotations without
|
||||
specifying an explicit name for the component, and `@Named` can be used in a similar
|
||||
fashion, as the following example shows.
|
||||
It is very common to use `@Component` without specifying a name for the component.
|
||||
`@Named` can be used in a similar fashion, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -303,7 +288,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -319,14 +304,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
When you use `@Named`, you can use component scanning in the exact same way as when you
|
||||
use Spring annotations, as the following example shows.
|
||||
When you use `@Named` or `@ManagedBean`, you can use component scanning in the
|
||||
exact same way as when you use Spring annotations, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example")
|
||||
@@ -337,7 +322,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"])
|
||||
@@ -347,106 +332,55 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` annotation is not composable. You
|
||||
should use Spring's stereotype model for building custom component annotations.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you work with legacy systems that still use `@javax.inject.Named` or
|
||||
`@javax.annotation.ManagedBean` for components (note the `javax` package namespace), you
|
||||
can explicitly configure component scanning to include those annotation types, as shown
|
||||
in the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = "org.example",
|
||||
includeFilters = @Filter({
|
||||
javax.inject.Named.class,
|
||||
javax.annotation.ManagedBean.class
|
||||
})
|
||||
)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = ["org.example"],
|
||||
includeFilters = [Filter([
|
||||
javax.inject.Named::class,
|
||||
javax.annotation.ManagedBean::class
|
||||
])]
|
||||
)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In addition, if you would like for the `value` attributes in `@javax.inject.Named` and
|
||||
`@javax.annotation.ManagedBean` to be used as component names, you need to override the
|
||||
`isStereotypeWithNameValue(...)` method in `AnnotationBeanNameGenerator` to add explicit
|
||||
support for `javax.annotation.ManagedBean` and `javax.inject.Named` and register your
|
||||
custom `AnnotationBeanNameGenerator` via the `nameGenerator` attribute in
|
||||
`@ComponentScan`.
|
||||
====
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `@ManagedBean`
|
||||
annotations are not composable. You should use Spring's stereotype model for building
|
||||
custom component annotations.
|
||||
|
||||
|
||||
[[beans-standard-annotations-limitations]]
|
||||
== Limitations of JSR-330 Standard Annotations
|
||||
|
||||
When you work with JSR-330 standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows.
|
||||
When you work with standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows:
|
||||
|
||||
[[annotations-comparison]]
|
||||
.Spring component model versus JSR-330 variants
|
||||
.Spring component model elements versus JSR-330 variants
|
||||
|===
|
||||
| Spring | JSR-330 | JSR-330 restrictions / comments
|
||||
| Spring| jakarta.inject.*| jakarta.inject restrictions / comments
|
||||
|
||||
| `@Autowired`
|
||||
| `@Inject`
|
||||
| `@Inject` has no `required` attribute. Can be used with Java's `Optional` instead.
|
||||
| @Autowired
|
||||
| @Inject
|
||||
| `@Inject` has no 'required' attribute. Can be used with Java 8's `Optional` instead.
|
||||
|
||||
| `@Component`
|
||||
| `@Named`
|
||||
| @Component
|
||||
| @Named / @ManagedBean
|
||||
| JSR-330 does not provide a composable model, only a way to identify named components.
|
||||
|
||||
| `@Scope("singleton")`
|
||||
| `@Singleton`
|
||||
| @Scope("singleton")
|
||||
| @Singleton
|
||||
| The JSR-330 default scope is like Spring's `prototype`. However, in order to keep it
|
||||
consistent with Spring's general defaults, a JSR-330 bean declared in the Spring
|
||||
container is a `singleton` by default. In order to use a scope other than `singleton`,
|
||||
you should use Spring's `@Scope` annotation. `jakarta.inject` also provides a
|
||||
`jakarta.inject.Scope` annotation; however, this one is only intended to be used
|
||||
`jakarta.inject.Scope` annotation: however, this one is only intended to be used
|
||||
for creating custom annotations.
|
||||
|
||||
| `@Qualifier`
|
||||
| `@Qualifier` / `@Named`
|
||||
| @Qualifier
|
||||
| @Qualifier / @Named
|
||||
| `jakarta.inject.Qualifier` is just a meta-annotation for building custom qualifiers.
|
||||
Concrete `String` qualifiers (like Spring's `@Qualifier` with a value) can be associated
|
||||
through `jakarta.inject.Named`.
|
||||
|
||||
| `@Value`
|
||||
| @Value
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| `@Lazy`
|
||||
| @Lazy
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| `ObjectFactory`
|
||||
| `Provider`
|
||||
| ObjectFactory
|
||||
| Provider
|
||||
| `jakarta.inject.Provider` is a direct alternative to Spring's `ObjectFactory`,
|
||||
only with a shorter `get()` method name. It can also be used in combination with
|
||||
Spring's `@Autowired` or with non-annotated constructors and setter methods.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
Java NIO provides `ByteBuffer` but many libraries build their own byte buffer API on top,
|
||||
especially for network operations where reusing buffers and/or using direct buffers is
|
||||
beneficial for performance. For example Netty has the `ByteBuf` hierarchy,
|
||||
Jetty uses pooled byte buffers with a callback to be released, and so on.
|
||||
beneficial for performance. For example Netty has the `ByteBuf` hierarchy, Undertow uses
|
||||
XNIO, Jetty uses pooled byte buffers with a callback to be released, and so on.
|
||||
The `spring-core` module provides a set of abstractions to work with various byte buffer
|
||||
APIs as follows:
|
||||
|
||||
|
||||
@@ -395,16 +395,6 @@ set a JVM system property or Spring property named `spring.context.expression.ma
|
||||
to the maximum expression length needed by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
Similarly, the number of operations performed during the evaluation of a SpEL expression
|
||||
cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If
|
||||
you create a `SpelExpressionParser` programmatically (the recommend approach), you can
|
||||
specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that
|
||||
you provide to the `SpelExpressionParser`. If you are not able to configure an explicit
|
||||
value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system
|
||||
property or Spring property named `spring.expression.maxOperations` to the maximum number
|
||||
of operations required by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
|
||||
[[expressions-spel-compilation]]
|
||||
== SpEL Compilation
|
||||
@@ -541,7 +531,6 @@ following kinds of expressions cannot be compiled.
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using `Optional` with the null-safe or Elvis operator
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
* Expressions using bean references
|
||||
|
||||
@@ -2,12 +2,4 @@
|
||||
= Language Reference
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Expression Language (SpEL) expressions are composed of a sequence of tokens such
|
||||
as literals, operators, method invocations, and so forth.
|
||||
|
||||
Whitespace can be used freely between tokens to format and improve the readability of
|
||||
expressions. Specifically, the `\s` (space), `\t` (tab), `\r` (carriage return), and `\n`
|
||||
(newline) characters are all valid separators between tokens. However, whitespace is
|
||||
ignored by the expression parser unless it is part of a string literal.
|
||||
|
||||
The following sections describe the features and syntax of SpEL.
|
||||
This section describes how the Spring Expression Language works.
|
||||
|
||||
+19
-33
@@ -1,19 +1,19 @@
|
||||
[[expressions-operator-elvis]]
|
||||
= The Elvis Operator
|
||||
|
||||
The Elvis operator (`?:`) is a shortening of the ternary operator syntax and is used in
|
||||
the https://www.groovy-lang.org/operators.html#_elvis_operator[Groovy] language. With the
|
||||
ternary operator syntax, you often have to repeat a variable twice, as the following Java
|
||||
example shows:
|
||||
The Elvis operator is a shortening of the ternary operator syntax and is used in the
|
||||
https://www.groovy-lang.org/operators.html#_elvis_operator[Groovy] language.
|
||||
With the ternary operator syntax, you usually have to repeat a variable twice, as the
|
||||
following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String name = "Elvis Presley";
|
||||
String displayName = (name != null ? name : "Unknown");
|
||||
----
|
||||
|
||||
Instead, you can use the Elvis operator (named for the resemblance to Elvis' hair style).
|
||||
The following example shows how to use the Elvis operator in a SpEL expression:
|
||||
The following example shows how to use the Elvis operator:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -23,7 +23,7 @@ Java::
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
String name = parser.parseExpression("name ?: 'Unknown'").getValue(new Inventor(), String.class);
|
||||
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
|
||||
System.out.println(name); // 'Unknown'
|
||||
----
|
||||
|
||||
@@ -33,29 +33,14 @@ Kotlin::
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
val name = parser.parseExpression("name ?: 'Unknown'").getValue(Inventor(), String::class.java)
|
||||
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
|
||||
println(name) // 'Unknown'
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The SpEL Elvis operator also treats an _empty_ String like a `null` object. Thus, the
|
||||
original Java example is only close to emulating the semantics of the operator: it would
|
||||
need to use `name != null && !name.isEmpty()` as the predicate to be compatible with the
|
||||
semantics of the SpEL Elvis operator.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
As of Spring Framework 7.0, the SpEL Elvis operator supports `java.util.Optional` with
|
||||
transparent unwrapping semantics.
|
||||
|
||||
For example, given the expression `A ?: B`, if `A` is `null` or an _empty_ `Optional`,
|
||||
the expression evaluates to `B`. However, if `A` is a non-empty `Optional` the expression
|
||||
evaluates to the object contained in the `Optional`, thereby effectively unwrapping the
|
||||
`Optional` which correlates to `A.get()`.
|
||||
====
|
||||
NOTE: The SpEL Elvis operator also checks for _empty_ Strings in addition to `null` objects.
|
||||
The original snippet is thus only close to emulating the semantics of the operator (it would need an
|
||||
additional `!name.isEmpty()` check).
|
||||
|
||||
The following listing shows a more complex example:
|
||||
|
||||
@@ -69,11 +54,11 @@ Java::
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
|
||||
String name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
System.out.println(name); // Nikola Tesla
|
||||
|
||||
tesla.setName("");
|
||||
name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
System.out.println(name); // Elvis Presley
|
||||
----
|
||||
|
||||
@@ -85,16 +70,16 @@ Kotlin::
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
|
||||
val tesla = Inventor("Nikola Tesla", "Serbian")
|
||||
var name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
println(name) // Nikola Tesla
|
||||
|
||||
tesla.setName("")
|
||||
name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
println(name) // Elvis Presley
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
[NOTE]
|
||||
=====
|
||||
You can use the Elvis operator to apply default values in expressions. The following
|
||||
example shows how to use the Elvis operator in a `@Value` expression:
|
||||
@@ -104,6 +89,7 @@ example shows how to use the Elvis operator in a `@Value` expression:
|
||||
@Value("#{systemProperties['pop3.port'] ?: 25}")
|
||||
----
|
||||
|
||||
This will inject the value of the system property named `pop3.port` if it is defined or
|
||||
`25` if the property is not defined.
|
||||
This will inject a system property `pop3.port` if it is defined or 25 if not.
|
||||
=====
|
||||
|
||||
|
||||
|
||||
+1
-44
@@ -252,6 +252,7 @@ Kotlin::
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to use the "null-safe select last" operator for
|
||||
collections (`?.$`).
|
||||
|
||||
@@ -350,50 +351,6 @@ Kotlin::
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
======
|
||||
|
||||
[[expressions-operator-safe-navigation-optional]]
|
||||
== Null-safe Operations on `Optional`
|
||||
|
||||
As of Spring Framework 7.0, null-safe operations are supported on instances of
|
||||
`java.util.Optional` with transparent unwrapping semantics.
|
||||
|
||||
Specifically, when a null-safe operator is applied to an _empty_ `Optional`, it will be
|
||||
treated as if the `Optional` were `null`, and the subsequent operation will evaluate to
|
||||
`null`. However, if a null-safe operator is applied to a non-empty `Optional`, the
|
||||
subsequent operation will be applied to the object contained in the `Optional`, thereby
|
||||
effectively unwrapping the `Optional`.
|
||||
|
||||
For example, if `user` is of type `Optional<User>`, the expression `user?.name` will
|
||||
evaluate to `null` if `user` is either `null` or an _empty_ `Optional` and will otherwise
|
||||
evaluate to the `name` of the `user`, effectively `user.get().getName()` or
|
||||
`user.get().name` for property or field access, respectively.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Invocations of methods defined in the `Optional` API are still supported on an _empty_
|
||||
`Optional`. For example, if `name` is of type `Optional<String>`, the expression
|
||||
`name?.orElse('Unknown')` will evaluate to `"Unknown"` if `name` is an empty `Optional`
|
||||
and will otherwise evaluate to the `String` contained in the `Optional` if `name` is a
|
||||
non-empty `Optional`, effectively `name.get()`.
|
||||
====
|
||||
|
||||
// NOTE: ⁠ is the Unicode Character 'WORD JOINER', which prevents undesired line wraps.
|
||||
|
||||
Similarly, if `names` is of type `Optional<List<String>>`, the expression
|
||||
`names?.?⁠[#this.length > 5]` will evaluate to `null` if `names` is `null` or an _empty_
|
||||
`Optional` and will otherwise evaluate to a sequence containing the names whose lengths
|
||||
are greater than 5, effectively
|
||||
`names.get().stream().filter(s -> s.length() > 5).toList()`.
|
||||
|
||||
The same semantics apply to all of the null-safe operators mentioned previously in this
|
||||
chapter.
|
||||
|
||||
For further details and examples, consult the javadoc for the following operators.
|
||||
|
||||
* {spring-framework-api}/expression/spel/ast/PropertyOrFieldReference.html[`PropertyOrFieldReference`]
|
||||
* {spring-framework-api}/expression/spel/ast/MethodReference.html[`MethodReference`]
|
||||
* {spring-framework-api}/expression/spel/ast/Indexer.html[`Indexer`]
|
||||
* {spring-framework-api}/expression/spel/ast/Selection.html[`Selection`]
|
||||
* {spring-framework-api}/expression/spel/ast/Projection.html[`Projection`]
|
||||
|
||||
[[expressions-operator-safe-navigation-compound-expressions]]
|
||||
== Null-safe Operations in Compound Expressions
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ is applicable for typical implementations of indexed structures.
|
||||
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
|
||||
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
|
||||
to bytecode for read access. Note, however, that the configured read-method must be
|
||||
invocable via a `public` class or `public` interface for compilation to succeed.
|
||||
invokable via a `public` class or `public` interface for compilation to succeed.
|
||||
|
||||
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
|
||||
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
|
||||
|
||||
@@ -1,216 +1,53 @@
|
||||
[[null-safety]]
|
||||
= Null-safety
|
||||
|
||||
Although Java does not let you express nullness markers with its type system yet, the Spring Framework codebase is
|
||||
annotated with https://jspecify.dev/docs/start-here/[JSpecify] annotations to declare the nullability of its APIs,
|
||||
fields, and related type usages. Reading the https://jspecify.dev/docs/user-guide/[JSpecify user guide] is highly
|
||||
recommended in order to get familiar with those annotations and semantics.
|
||||
Although Java does not let you express null-safety with its type system, the Spring Framework
|
||||
provides the following annotations in the `org.springframework.lang` package to let you
|
||||
declare nullability of APIs and fields:
|
||||
|
||||
The primary goal of this null-safety arrangement is to prevent a `NullPointerException` from being thrown at
|
||||
runtime via build time checks and to use explicit nullability as a way to express the possible absence of value.
|
||||
It is useful in Java by leveraging nullability checkers such as https://github.com/uber/NullAway[NullAway] or IDEs
|
||||
supporting JSpecify annotations such as IntelliJ IDEA and Eclipse (the latter requiring manual configuration). In Kotlin,
|
||||
JSpecify annotations are automatically translated to {kotlin-docs}/null-safety.html[Kotlin's null safety].
|
||||
* {spring-framework-api}/lang/Nullable.html[`@Nullable`]: Annotation to indicate that a
|
||||
specific parameter, return value, or field can be `null`.
|
||||
* {spring-framework-api}/lang/NonNull.html[`@NonNull`]: Annotation to indicate that a specific
|
||||
parameter, return value, or field cannot be `null` (not needed on parameters, return values,
|
||||
and fields where `@NonNullApi` and `@NonNullFields` apply, respectively).
|
||||
* {spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`]: Annotation at the package level
|
||||
that declares non-null as the default semantics for parameters and return values.
|
||||
* {spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`]: Annotation at the package
|
||||
level that declares non-null as the default semantics for fields.
|
||||
|
||||
The {spring-framework-api}/core/Nullness.html[`Nullness` Spring API] can be used at runtime to detect the
|
||||
nullness of a type usage, a field, a method return type, or a parameter. It provides full support for
|
||||
JSpecify annotations, Kotlin null safety, and Java primitive types, as well as a pragmatic check on any
|
||||
`@Nullable` annotation (regardless of the package).
|
||||
The Spring Framework itself leverages these annotations, but they can also be used in any
|
||||
Spring-based Java project to declare null-safe APIs and optionally null-safe fields.
|
||||
Nullability declarations for generic type arguments, varargs, and array elements are not supported yet.
|
||||
Nullability declarations are expected to be fine-tuned between Spring Framework releases,
|
||||
including minor ones. Nullability of types used inside method bodies is outside the
|
||||
scope of this feature.
|
||||
|
||||
NOTE: Other common libraries such as Reactor and Spring Data provide null-safe APIs that
|
||||
use a similar nullability arrangement, delivering a consistent overall experience for
|
||||
Spring application developers.
|
||||
|
||||
|
||||
[[null-safety-libraries]]
|
||||
== Annotating libraries with JSpecify annotations
|
||||
[[use-cases]]
|
||||
== Use cases
|
||||
|
||||
As of Spring Framework 7, the Spring Framework codebase leverages JSpecify annotations to expose null-safe APIs
|
||||
and to check the consistency of those nullability declarations with https://github.com/uber/NullAway[NullAway]
|
||||
as part of its build. It is recommended for each library depending on Spring Framework and Spring portfolio projects,
|
||||
as well as other libraries related to the Spring ecosystem (Reactor, Micrometer, and Spring community projects),
|
||||
to do the same.
|
||||
In addition to providing an explicit declaration for Spring Framework API nullability,
|
||||
these annotations can be used by an IDE (such as IDEA or Eclipse) to provide useful
|
||||
warnings related to null-safety in order to avoid `NullPointerException` at runtime.
|
||||
|
||||
They are also used to make Spring APIs null-safe in Kotlin projects, since Kotlin natively
|
||||
supports {kotlin-docs}/null-safety.html[null-safety]. More details
|
||||
are available in the xref:languages/kotlin/null-safety.adoc[Kotlin support documentation].
|
||||
|
||||
|
||||
[[null-safety-applications]]
|
||||
== Leveraging JSpecify annotations in Spring applications
|
||||
[[jsr-305-meta-annotations]]
|
||||
== JSR-305 meta-annotations
|
||||
|
||||
Developing applications with IDEs that support nullness annotations will provide warnings in Java and errors in
|
||||
Kotlin when the nullability contracts are not honored, allowing Spring application developers to refine their
|
||||
null handling to prevent a `NullPointerException` from being thrown at runtime.
|
||||
Spring annotations are meta-annotated with {JSR}305[JSR 305]
|
||||
annotations (a dormant but widespread JSR). JSR-305 meta-annotations let tooling vendors
|
||||
like IDEA or Kotlin provide null-safety support in a generic way, without having to
|
||||
hard-code support for Spring annotations.
|
||||
|
||||
Optionally, Spring application developers can annotate their codebase and use build plugins like
|
||||
https://github.com/uber/NullAway[NullAway] to enforce null-safety at the application level during build time.
|
||||
|
||||
[[null-safety-guidelines]]
|
||||
== Guidelines
|
||||
|
||||
The purpose of this section is to share some proposed guidelines for explicitly specifying the nullability of
|
||||
Spring-related libraries or applications.
|
||||
|
||||
[[null-safety-guidelines-jspecify]]
|
||||
=== JSpecify
|
||||
|
||||
==== Defaults to non-null
|
||||
|
||||
A key point to understand is that the nullness of types is unknown by default in Java and that non-null type usage
|
||||
is by far more frequent than nullable usage. In order to keep codebases readable, we typically want to define by
|
||||
default that type usage is non-null unless marked as nullable for a specific scope. This is exactly the purpose
|
||||
of https://jspecify.dev/docs/api/org/jspecify/annotations/NullMarked.html[`@NullMarked`] which is typically set
|
||||
in Spring projects at the package level via a `package-info.java` file, for example:
|
||||
|
||||
[source,java,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
----
|
||||
@NullMarked
|
||||
package org.springframework.core;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
----
|
||||
|
||||
==== Explicit nullability
|
||||
|
||||
In `@NullMarked` code, nullable type usage is defined explicitly with
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/Nullable.html[`@Nullable`].
|
||||
|
||||
A key difference between JSpecify `@Nullable` / `@NonNull` annotations and most other variants is that the JSpecify
|
||||
annotations are meta-annotated with `@Target(ElementType.TYPE_USE)`, so they apply only to type usage. This impacts
|
||||
where such annotations should be placed, either to comply with
|
||||
https://docs.oracle.com/javase/specs/jls/se17/html/jls-9.html#jls-9.7.4[related Java specifications] or to follow code
|
||||
style best practices. From a style perspective, it is recommended to embrace the type-use nature of those annotations
|
||||
by placing them on the same line as and immediately preceding the annotated type.
|
||||
|
||||
For example, for a field:
|
||||
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
private @Nullable String fileEncoding;
|
||||
----
|
||||
|
||||
Or for method parameters and method return types:
|
||||
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
public @Nullable String buildMessage(@Nullable String message,
|
||||
@Nullable Throwable cause) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
When overriding a method, JSpecify annotations are not inherited from the original
|
||||
method. That means the JSpecify annotations should be copied to the overriding method if
|
||||
you want to override the implementation and keep the same nullability semantics.
|
||||
====
|
||||
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/NonNull.html[`@NonNull`] and
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/NullUnmarked.html[`@NullUnmarked`] should rarely be needed for
|
||||
typical use cases.
|
||||
|
||||
==== Arrays and varargs
|
||||
|
||||
With arrays and varargs, you need to be able to differentiate the nullness of the elements from the nullness of
|
||||
the array itself. Pay attention to the syntax
|
||||
https://docs.oracle.com/javase/specs/jls/se17/html/jls-9.html#jls-9.7.4[defined by the Java specification] which may be
|
||||
initially surprising. For example, in `@NullMarked` code:
|
||||
|
||||
- `@Nullable Object[] array` means individual elements can be `null` but the array itself cannot.
|
||||
- `Object @Nullable [] array` means individual elements cannot be `null` but the array itself can.
|
||||
- `@Nullable Object @Nullable [] array` means both individual elements and the array can be `null`.
|
||||
|
||||
==== Generics
|
||||
|
||||
JSpecify annotations apply to generics as well. For example, in `@NullMarked` code:
|
||||
|
||||
- `List<String>` means a list of non-null elements (equivalent of `List<@NonNull String>`)
|
||||
- `List<@Nullable String>` means a list of nullable elements
|
||||
|
||||
Things are a bit more complicated when you are declaring generic types or generic methods. See the related
|
||||
https://jspecify.dev/docs/user-guide/#generics[JSpecify generics documentation] for more details.
|
||||
|
||||
WARNING: The nullability of generic types and generic methods
|
||||
https://github.com/uber/NullAway/issues?q=is%3Aissue+is%3Aopen+label%3Ajspecify[is not yet fully supported by NullAway].
|
||||
|
||||
==== Nested and fully qualified types
|
||||
|
||||
The Java specification also enforces that annotations defined with `@Target(ElementType.TYPE_USE)` – like JSpecify's
|
||||
`@Nullable` annotation – must be declared after the last dot (`.`) within inner or fully qualified type names:
|
||||
|
||||
- `Cache.@Nullable ValueWrapper`
|
||||
- `jakarta.validation.@Nullable Validator`
|
||||
|
||||
|
||||
[[null-safety-guidelines-nullaway]]
|
||||
=== NullAway
|
||||
|
||||
==== Configuration
|
||||
|
||||
The recommended configuration is:
|
||||
|
||||
- `NullAway:OnlyNullMarked=true` in order to perform nullability checks only for packages annotated with `@NullMarked`.
|
||||
- `NullAway:CustomContractAnnotations=org.springframework.lang.Contract` which makes NullAway aware of the
|
||||
{spring-framework-api}/lang/Contract.html[@Contract] annotation in the `org.springframework.lang` package which
|
||||
can be used to express complementary semantics to avoid irrelevant warnings in your codebase.
|
||||
|
||||
A good example of the benefits of a `@Contract` declaration can be seen with
|
||||
{spring-framework-api}/util/Assert.html#notNull(java.lang.Object,java.lang.String)[`Assert.notNull()`]
|
||||
which is annotated with `@Contract("null, _ -> fail")`. With that contract declaration, NullAway will understand
|
||||
that the value passed as a parameter cannot be null after a successful invocation of `Assert.notNull()`.
|
||||
|
||||
Optionally, it is possible to set `NullAway:JSpecifyMode=true` to enable
|
||||
https://github.com/uber/NullAway/wiki/JSpecify-Support[checks on the full JSpecify semantics], including annotations on
|
||||
arrays, varargs, and generics. Be aware that this mode is
|
||||
https://github.com/uber/NullAway/issues?q=is%3Aissue+is%3Aopen+label%3Ajspecify[still under development] and requires
|
||||
JDK 22 or later (typically combined with the `--release` Java compiler flag to configure the
|
||||
expected baseline). It is recommended to enable the JSpecify mode only as a second step, after making sure the codebase
|
||||
generates no warning with the recommended configuration mentioned previously in this section.
|
||||
|
||||
==== Warnings suppression
|
||||
|
||||
There are a few valid use cases where NullAway will incorrectly detect nullability problems. In such cases,
|
||||
it is recommended to suppress related warnings and to document the reason:
|
||||
|
||||
- `@SuppressWarnings("NullAway.Init")` at field, constructor, or class level can be used to avoid unnecessary warnings
|
||||
due to the lazy initialization of fields – for example, due to a class implementing
|
||||
{spring-framework-api}/beans/factory/InitializingBean.html[`InitializingBean`].
|
||||
- `@SuppressWarnings("NullAway") // Dataflow analysis limitation` can be used when NullAway dataflow analysis is not
|
||||
able to detect that the path involving a nullability problem will never happen.
|
||||
- `@SuppressWarnings("NullAway") // Lambda` can be used when NullAway does not take into account assertions performed
|
||||
outside of a lambda for the code path within the lambda.
|
||||
- `@SuppressWarnings("NullAway") // Reflection` can be used for some reflection operations that are known to return
|
||||
non-null values even if that cannot be expressed by the API.
|
||||
- `@SuppressWarnings("NullAway") // Well-known map keys` can be used when `Map#get` invocations are performed with keys
|
||||
that are known to be present and when non-null related values have been inserted previously.
|
||||
- `@SuppressWarnings("NullAway") // Overridden method does not define nullability` can be used when the superclass does
|
||||
not define nullability (typically when the superclass comes from an external dependency).
|
||||
- `@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075` can be used when NullAway is not able to detect type variable nullness in generic methods.
|
||||
|
||||
|
||||
[[null-safety-migrating]]
|
||||
== Migrating from Spring null-safety annotations
|
||||
|
||||
Spring null-safety annotations {spring-framework-api}/lang/Nullable.html[`@Nullable`],
|
||||
{spring-framework-api}/lang/NonNull.html[`@NonNull`],
|
||||
{spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`], and
|
||||
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package were
|
||||
introduced in Spring Framework 5 when JSpecify did not exist, and the best option at that time was to leverage
|
||||
meta-annotations from JSR 305 (a dormant but widespread JSR). They are deprecated as of Spring Framework 7 in favor of
|
||||
https://jspecify.dev/docs/start-here/[JSpecify] annotations, which provide significant enhancements such as properly
|
||||
defined specifications, a canonical dependency with no split-package issues, better tooling, better Kotlin integration,
|
||||
and the capability to specify nullability more precisely for more use cases.
|
||||
|
||||
A key difference is that Spring's deprecated null-safety annotations, which follow JSR 305 semantics, apply to fields,
|
||||
parameters, and return values; while JSpecify annotations apply to type usage. This subtle difference
|
||||
is pretty significant in practice, since it allows developers to differentiate between the nullness of elements and the
|
||||
nullness of arrays/varargs as well as to define the nullness of generic types.
|
||||
|
||||
That means array and varargs null-safety declarations have to be updated to keep the same semantics. For example
|
||||
`@Nullable Object[] array` with Spring annotations needs to be changed to `Object @Nullable [] array` with JSpecify
|
||||
annotations. The same applies to varargs.
|
||||
|
||||
It is also recommended to move field and return value annotations closer to the type and on the same line, for example:
|
||||
|
||||
- For fields, instead of `@Nullable private String field` with Spring annotations, use `private @Nullable String field`
|
||||
with JSpecify annotations.
|
||||
- For method return types, instead of `@Nullable public String method()` with Spring annotations, use
|
||||
`public @Nullable String method()` with JSpecify annotations.
|
||||
|
||||
Also, with JSpecify, you do not need to specify `@NonNull` when overriding a type usage annotated with `@Nullable`
|
||||
in the super method to "undo" the nullable declaration in null-marked code. Just declare it unannotated, and the
|
||||
null-marked defaults will apply (type usage is considered non-null unless explicitly annotated as nullable).
|
||||
It is neither necessary nor recommended to add a JSR-305 dependency to the project classpath to
|
||||
take advantage of Spring's null-safe APIs. Only projects such as Spring-based libraries that use
|
||||
null-safety annotations in their codebase should add `com.google.code.findbugs:jsr305:3.0.2`
|
||||
with `compileOnly` Gradle configuration or Maven `provided` scope to avoid compiler warnings.
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
[[resilience]]
|
||||
= Resilience Features
|
||||
|
||||
As of 7.0, the core Spring Framework includes common resilience features, in particular
|
||||
<<resilience-annotations-retryable>> and <<resilience-annotations-concurrencylimit>>
|
||||
annotations for method invocations as well as <<resilience-programmatic-retry,
|
||||
programmatic retry support>>.
|
||||
|
||||
|
||||
[[resilience-annotations-retryable]]
|
||||
== `@Retryable`
|
||||
|
||||
{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`] is an annotation
|
||||
that specifies retry characteristics for an individual method (with the annotation
|
||||
declared at the method level), or for all proxy-invoked methods in a given class hierarchy
|
||||
(with the annotation declared at the type level).
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
----
|
||||
|
||||
By default, the method invocation will be retried for any exception thrown: with at most
|
||||
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
|
||||
between attempts. If all attempts have failed and the retry policy has been exhausted,
|
||||
the last original exception from the target method will be propagated to the caller.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `@Retryable` method will be invoked at least once and retried at most `maxRetries`
|
||||
times, where `maxRetries` is the maximum number of retry attempts. Specifically,
|
||||
`total attempts = 1 initial attempt + maxRetries attempts`.
|
||||
|
||||
For example, if `maxRetries` is set to `4`, the `@Retryable` method will be invoked at
|
||||
least once and at most 5 times.
|
||||
====
|
||||
|
||||
This can be specifically adapted for every method if necessary — for example, by narrowing
|
||||
the exceptions to retry via the `includes` and `excludes` attributes. The supplied
|
||||
exception types will be matched against an exception thrown by a failed invocation as well
|
||||
as nested causes.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(MessageDeliveryException.class)
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: `@Retryable(MessageDeliveryException.class)` is a shortcut for
|
||||
`@Retryable(includes{nbsp}={nbsp}MessageDeliveryException.class)`.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
For advanced use cases, you can specify a custom `MethodRetryPredicate` via the
|
||||
`predicate` attribute in `@Retryable`, and the predicate will be used to determine whether
|
||||
to retry a failed method invocation based on a `Method` and a given `Throwable` – for
|
||||
example, by checking the message of the `Throwable`.
|
||||
|
||||
Custom predicates can be combined with `includes` and `excludes`; however, custom
|
||||
predicates will always be applied after `includes` and `excludes` have been applied.
|
||||
====
|
||||
|
||||
Or for 4 retry attempts and an exponential back-off strategy with a bit of jitter:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(
|
||||
includes = MessageDeliveryException.class,
|
||||
maxRetries = 4,
|
||||
delay = 100,
|
||||
jitter = 10,
|
||||
multiplier = 2,
|
||||
maxDelay = 1000)
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
----
|
||||
|
||||
Last but not least, `@Retryable` also works for reactive methods with a reactive return
|
||||
type, decorating the pipeline with Reactor's retry capabilities:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(maxRetries = 4, delay = 100)
|
||||
public Mono<Void> sendNotification() {
|
||||
return Mono.from(...); // <1>
|
||||
}
|
||||
----
|
||||
<1> This raw `Mono` will get decorated with a retry spec.
|
||||
|
||||
For details on the various characteristics, see the available annotation attributes in
|
||||
{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
|
||||
|
||||
TIP: Several attributes in `@Retryable` have `String` variants that provide property
|
||||
placeholder and SpEL support, as an alternative to the specifically typed annotation
|
||||
attributes used in the above examples.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
During `@Retryable` processing, Spring publishes a `MethodRetryEvent` for every exception
|
||||
coming out of the target method. This can be used to track/log all original exceptions
|
||||
whereas the caller of the `@Retryable` method will only ever see the last exception.
|
||||
====
|
||||
|
||||
|
||||
[[resilience-annotations-concurrencylimit]]
|
||||
== `@ConcurrencyLimit`
|
||||
|
||||
{spring-framework-api}/resilience/annotation/ConcurrencyLimit.html[`@ConcurrencyLimit`] is
|
||||
an annotation that specifies a concurrency limit for an individual method (with the
|
||||
annotation declared at the method level), or for all proxy-invoked methods in a given
|
||||
class hierarchy (with the annotation declared at the type level).
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ConcurrencyLimit(10)
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
----
|
||||
|
||||
This is meant to protect the target resource from being accessed from too many threads at
|
||||
the same time, similar to the effect of a pool size limit for a thread pool or a
|
||||
connection pool that blocks access if its limit is reached.
|
||||
|
||||
You may optionally set the limit to `1`, effectively locking access to the target bean
|
||||
instance:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ConcurrencyLimit(1)
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
----
|
||||
|
||||
Such limiting is particularly useful with Virtual Threads where there is generally no
|
||||
thread pool limit in place. For asynchronous tasks, this can be constrained on
|
||||
{spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`].
|
||||
For synchronous invocations, this annotation provides equivalent behavior through
|
||||
{spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`]
|
||||
which has been available since Spring Framework 1.0 for programmatic use with the AOP
|
||||
framework.
|
||||
|
||||
TIP: `@ConcurrencyLimit` also has a `limitString` attribute that provides property
|
||||
placeholder and SpEL support, as an alternative to the `int` based examples above.
|
||||
|
||||
|
||||
[[resilience-annotations-configuration]]
|
||||
== Enabling Resilient Methods
|
||||
|
||||
Like many of Spring's core annotation-based features, `@Retryable` and `@ConcurrencyLimit`
|
||||
are designed as metadata that you can choose to honor or ignore. The most convenient way
|
||||
to enable processing of the resilience annotations is to declare
|
||||
{spring-framework-api}/resilience/annotation/EnableResilientMethods.html[`@EnableResilientMethods`]
|
||||
on a corresponding `@Configuration` class.
|
||||
|
||||
Alternatively, these annotations can be individually enabled by defining a
|
||||
`RetryAnnotationBeanPostProcessor` or a `ConcurrencyLimitBeanPostProcessor` bean in the
|
||||
context.
|
||||
|
||||
|
||||
[[resilience-programmatic-retry]]
|
||||
== Programmatic Retry Support
|
||||
|
||||
In contrast to <<resilience-annotations-retryable>> which provides a declarative approach
|
||||
for specifying retry semantics for methods within beans registered in the
|
||||
`ApplicationContext`,
|
||||
{spring-framework-api}/core/retry/RetryTemplate.html[`RetryTemplate`] provides a
|
||||
programmatic API for retrying arbitrary blocks of code.
|
||||
|
||||
Specifically, a `RetryTemplate` executes and potentially retries a
|
||||
{spring-framework-api}/core/retry/Retryable.html[`Retryable`] operation based on a
|
||||
configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`].
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(); // <1>
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Implicitly uses `RetryPolicy.withDefaults()`.
|
||||
|
||||
By default, a retryable operation will be retried for any exception thrown: with at most
|
||||
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
|
||||
between attempts.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A retryable operation will be executed at least once and retried at most `maxRetries`
|
||||
times, where `maxRetries` is the maximum number of retry attempts. Specifically,
|
||||
`total attempts = 1 initial attempt + maxRetries attempts`.
|
||||
|
||||
For example, if `maxRetries` is set to `4`, the retryable operation will be invoked at
|
||||
least once and at most 5 times.
|
||||
====
|
||||
|
||||
If you only need to customize the number of retry attempts, you can use the
|
||||
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1>
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Explicitly uses `RetryPolicy.withMaxRetries(4)`.
|
||||
|
||||
If you need to narrow the types of exceptions to retry, that can be achieved via the
|
||||
`includes()` and `excludes()` builder methods. The supplied exception types will be
|
||||
matched against an exception thrown by a failed operation as well as nested causes.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.includes(MessageDeliveryException.class) // <1>
|
||||
.excludes(...) // <2>
|
||||
.build();
|
||||
|
||||
var retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Specify one or more exception types to include.
|
||||
<2> Specify one or more exception types to exclude.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
For advanced use cases, you can specify a custom `Predicate<Throwable>` via the
|
||||
`predicate()` method in the `RetryPolicy.Builder`, and the predicate will be used to
|
||||
determine whether to retry a failed operation based on a given `Throwable` – for example,
|
||||
by checking the message of the `Throwable`.
|
||||
|
||||
Custom predicates can be combined with `includes` and `excludes`; however, custom
|
||||
predicates will always be applied after `includes` and `excludes` have been applied.
|
||||
====
|
||||
|
||||
The following example demonstrates how to configure a `RetryPolicy` with 4 retry attempts
|
||||
and an exponential back-off strategy with a bit of jitter.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.includes(MessageDeliveryException.class)
|
||||
.maxRetries(4)
|
||||
.delay(Duration.ofMillis(100))
|
||||
.jitter(Duration.ofMillis(10))
|
||||
.multiplier(2)
|
||||
.maxDelay(Duration.ofSeconds(1))
|
||||
.build();
|
||||
|
||||
var retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Although the factory methods and builder API for `RetryPolicy` cover most common
|
||||
configuration scenarios, you can implement a custom `RetryPolicy` for complete control
|
||||
over the types of exceptions that should trigger a retry as well as the
|
||||
{spring-framework-api}/util/backoff/BackOff.html[`BackOff`] strategy to use. Note that
|
||||
you can also configure a customized `BackOff` strategy via the `backOff()` method in
|
||||
the `RetryPolicy.Builder`.
|
||||
====
|
||||
|
||||
Note that the examples above apply a pattern similar to `@Retryable` method invocations
|
||||
where the last original exception will be propagated to the caller, using the `invoke`
|
||||
variants on `RetryTemplate` which are available with and without a return value.
|
||||
The callback may throw unchecked exceptions, the last one of which is exposed for
|
||||
direct handling on the caller side:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
var result = retryTemplate.invoke(() -> {
|
||||
jmsClient.destination("notifications").send(...);
|
||||
return "result";
|
||||
});
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
`RetryTemplate` instances are very light and can be created on the fly,
|
||||
potentially with a specific retry policy to use for a given invocation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
new RetryTemplate(RetryPolicy.withMaxRetries(4)).invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
For deeper interaction, you may use RetryTemplate's `execute` method. The caller will
|
||||
have to handle the checked `RetryException` thrown by `RetryTemplate`, exposing the
|
||||
outcome of all attempts:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
var result = retryTemplate.execute(() -> {
|
||||
jmsClient.destination("notifications").send(...);
|
||||
return "result";
|
||||
});
|
||||
}
|
||||
catch (RetryException ex) {
|
||||
// ex.getExceptions() / ex.getLastException() ...
|
||||
}
|
||||
----
|
||||
|
||||
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
|
||||
with a `RetryTemplate` to react to key retry steps (before or after a retry attempt etc.)
|
||||
or simply to every invocation attempt, being able to track all exceptions coming out of
|
||||
the callback and all retry outcomes (exhaustion, interruption, timeout). This is
|
||||
particularly useful when using `invoke` where no retry state other than the last
|
||||
original exception is exposed otherwise:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryListener(new RetryListener() {
|
||||
@Override
|
||||
public void onRetryableExecution(RetryPolicy retryPolicy, Retryable<?> retryable, RetryState retryState) {
|
||||
...
|
||||
}
|
||||
});
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
|
||||
You can also compose multiple listeners via a
|
||||
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
|
||||
@@ -0,0 +1,47 @@
|
||||
[[spring-jcl]]
|
||||
= Logging
|
||||
|
||||
Spring comes with its own Commons Logging bridge implemented
|
||||
in the `spring-jcl` module. The implementation checks for the presence of the Log4j 2.x
|
||||
API and the SLF4J 1.7 API in the classpath and uses the first one of those found as the
|
||||
logging implementation, falling back to the Java platform's core logging facilities (also
|
||||
known as _JUL_ or `java.util.logging`) if neither Log4j 2.x nor SLF4J is available.
|
||||
|
||||
Put Log4j 2.x or Logback (or another SLF4J provider) in your classpath, without any extra
|
||||
bridges, and let the framework auto-adapt to your choice. For further information see the
|
||||
{spring-boot-docs-ref}/features/logging.html[Spring
|
||||
Boot Logging Reference Documentation].
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Spring's Commons Logging variant is only meant to be used for infrastructure logging
|
||||
purposes in the core framework and in extensions.
|
||||
|
||||
For logging needs within application code, prefer direct use of Log4j 2.x, SLF4J, or JUL.
|
||||
====
|
||||
|
||||
A `Log` implementation may be retrieved via `org.apache.commons.logging.LogFactory` as in
|
||||
the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MyBean {
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyBean {
|
||||
private val log = LogFactory.getLog(javaClass)
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
+32
-28
@@ -1,20 +1,20 @@
|
||||
[[data-binding]]
|
||||
[[beans-binding]]
|
||||
= Data Binding
|
||||
|
||||
Data binding is useful for binding user input to a target object where user input is a map
|
||||
with property paths as keys, following xref:data-binding-conventions[JavaBeans conventions].
|
||||
with property paths as keys, following xref:beans-beans-conventions[JavaBeans conventions].
|
||||
`DataBinder` is the main class that supports this, and it provides two ways to bind user
|
||||
input:
|
||||
|
||||
- xref:data-binding-constructor-binding[Constructor binding] - bind user input to a
|
||||
public data constructor, looking up constructor argument values in the user input.
|
||||
- xref:data-binding-property-binding[Property binding] - bind user input to setters,
|
||||
matching keys from the user input to properties of the target object structure.
|
||||
- xref:beans-constructor-binding[Constructor binding] - bind user input to a public data
|
||||
constructor, looking up constructor argument values in the user input.
|
||||
- xref:beans-beans[Property binding] - bind user input to setters, matching keys from the
|
||||
user input to properties of the target object structure.
|
||||
|
||||
You can apply both constructor and property binding or only one.
|
||||
|
||||
|
||||
[[data-binding-constructor-binding]]
|
||||
[[beans-constructor-binding]]
|
||||
== Constructor Binding
|
||||
|
||||
To use constructor binding:
|
||||
@@ -32,7 +32,7 @@ WebFlux support a custom name mapping through the `@BindParam` annotation on con
|
||||
parameters or fields if present. If necessary, you can also configure a `NameResolver` on
|
||||
`DataBinder` to customize the argument name to use.
|
||||
|
||||
xref:data-binding-conventions[Type conversion] is applied as needed to convert user input.
|
||||
xref:beans-beans-conventions[Type conversion] is applied as needed to convert user input.
|
||||
If the constructor parameter is an object, it is constructed recursively in the same
|
||||
manner, but through a nested property path. That means constructor binding creates both
|
||||
the target object and any objects it contains.
|
||||
@@ -46,7 +46,9 @@ If the target is created successfully, then `target` is set to the created insta
|
||||
after the call to `construct`.
|
||||
|
||||
|
||||
[[data-binding-property-binding]]
|
||||
|
||||
|
||||
[[beans-beans]]
|
||||
== Property Binding with `BeanWrapper`
|
||||
|
||||
The `org.springframework.beans` package adheres to the JavaBeans standard.
|
||||
@@ -72,14 +74,15 @@ The way the `BeanWrapper` works is partly indicated by its name: it wraps a bean
|
||||
perform actions on that bean, such as setting and retrieving properties.
|
||||
|
||||
|
||||
[[data-binding-conventions]]
|
||||
|
||||
[[beans-beans-conventions]]
|
||||
=== Setting and Getting Basic and Nested Properties
|
||||
|
||||
Setting and getting properties is done through the `setPropertyValue` and
|
||||
`getPropertyValue` overloaded method variants of `BeanWrapper`. See their Javadoc for
|
||||
details. The below table shows some examples of these conventions:
|
||||
|
||||
[[data-binding-conventions-properties-tbl]]
|
||||
[[beans-beans-conventions-properties-tbl]]
|
||||
.Examples of properties
|
||||
|===
|
||||
| Expression| Explanation
|
||||
@@ -103,7 +106,7 @@ details. The below table shows some examples of these conventions:
|
||||
(This next section is not vitally important to you if you do not plan to work with
|
||||
the `BeanWrapper` directly. If you use only the `DataBinder` and the `BeanFactory`
|
||||
and their default implementations, you should skip ahead to the
|
||||
xref:core/validation/data-binding.adoc#data-binding-conversion[section on `PropertyEditors`].)
|
||||
xref:core/validation/beans-beans.adoc#beans-beans-conversion[section on `PropertyEditors`].)
|
||||
|
||||
The following two example classes use the `BeanWrapper` to get and set
|
||||
properties:
|
||||
@@ -236,8 +239,9 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[data-binding-conversion]]
|
||||
== ``PropertyEditor``s
|
||||
|
||||
[[beans-beans-conversion]]
|
||||
== ``PropertyEditor``'s
|
||||
|
||||
Spring uses the concept of a `PropertyEditor` to effect the conversion between an
|
||||
`Object` and a `String`. It can be handy
|
||||
@@ -268,7 +272,7 @@ package. Most, (but not all, as indicated in the following table) are, by defaul
|
||||
still register your own variant to override the default one. The following table describes
|
||||
the various `PropertyEditor` implementations that Spring provides:
|
||||
|
||||
[[data-binding-property-editors-tbl]]
|
||||
[[beans-beans-property-editors-tbl]]
|
||||
.Built-in `PropertyEditor` Implementations
|
||||
[cols="30%,70%"]
|
||||
|===
|
||||
@@ -347,10 +351,10 @@ recognized and used as the `PropertyEditor` for `Something`-typed properties.
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com
|
||||
└── example
|
||||
└── things
|
||||
├── *Something*
|
||||
└── *SomethingEditor* // the PropertyEditor for the Something class
|
||||
chank
|
||||
pop
|
||||
Something
|
||||
SomethingEditor // the PropertyEditor for the Something class
|
||||
----
|
||||
|
||||
Note that you can also use the standard `BeanInfo` JavaBeans mechanism here as well
|
||||
@@ -362,10 +366,10 @@ following example uses the `BeanInfo` mechanism to explicitly register one or mo
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com
|
||||
└── example
|
||||
└── things
|
||||
├── *Something*
|
||||
└── *SomethingBeanInfo* // the BeanInfo for the Something class
|
||||
chank
|
||||
pop
|
||||
Something
|
||||
SomethingBeanInfo // the BeanInfo for the Something class
|
||||
----
|
||||
|
||||
The following Java source code for the referenced `SomethingBeanInfo` class
|
||||
@@ -422,8 +426,8 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[data-binding-conversion-customeditor-registration]]
|
||||
=== Custom ``PropertyEditor``s
|
||||
[[beans-beans-conversion-customeditor-registration]]
|
||||
=== Custom ``PropertyEditor``'s
|
||||
|
||||
When setting bean properties as string values, a Spring IoC container ultimately uses
|
||||
standard JavaBeans `PropertyEditor` implementations to convert these strings to the complex type of the
|
||||
@@ -447,7 +451,7 @@ where it can be automatically detected and applied.
|
||||
Note that all bean factories and application contexts automatically use a number of
|
||||
built-in property editors, through their use of a `BeanWrapper` to
|
||||
handle property conversions. The standard property editors that the `BeanWrapper`
|
||||
registers are listed in the xref:core/validation/data-binding.adoc#data-binding-conversion[previous section].
|
||||
registers are listed in the xref:core/validation/beans-beans.adoc#beans-beans-conversion[previous section].
|
||||
Additionally, ``ApplicationContext``s also override or add additional editors to handle
|
||||
resource lookups in a manner appropriate to the specific application context type.
|
||||
|
||||
@@ -565,7 +569,7 @@ Finally, the following example shows how to use `CustomEditorConfigurer` to regi
|
||||
</bean>
|
||||
----
|
||||
|
||||
[[data-binding-conversion-customeditor-registration-per]]
|
||||
[[beans-beans-conversion-customeditor-registration-per]]
|
||||
=== `PropertyEditorRegistrar`
|
||||
|
||||
Another mechanism for registering property editors with the Spring container is to
|
||||
@@ -576,7 +580,7 @@ You can write a corresponding registrar and reuse it in each case.
|
||||
`PropertyEditorRegistry`, an interface that is implemented by the Spring `BeanWrapper`
|
||||
(and `DataBinder`). `PropertyEditorRegistrar` instances are particularly convenient
|
||||
when used in conjunction with `CustomEditorConfigurer` (described
|
||||
xref:core/validation/data-binding.adoc#data-binding-conversion-customeditor-registration[here]), which exposes a property
|
||||
xref:core/validation/beans-beans.adoc#beans-beans-conversion-customeditor-registration[here]), which exposes a property
|
||||
called `setPropertyEditorRegistrars(..)`. `PropertyEditorRegistrar` instances added
|
||||
to a `CustomEditorConfigurer` in this fashion can easily be shared with `DataBinder` and
|
||||
Spring MVC controllers. Furthermore, it avoids the need for synchronization on custom
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
[[validation-error-code-resolution]]
|
||||
= Resolving Error Codes to Error Messages
|
||||
[[validation-conversion]]
|
||||
= Resolving Codes to Error Messages
|
||||
|
||||
We covered data binding and validation. This section covers outputting messages that correspond
|
||||
We covered databinding and validation. This section covers outputting messages that correspond
|
||||
to validation errors. In the example shown in the xref:core/validation/validator.adoc[preceding section],
|
||||
we rejected the `name` and `age` fields. If we want to output the error messages by using a
|
||||
`MessageSource`, we can do so using the error code we provide when rejecting the field
|
||||
@@ -310,11 +310,9 @@ Java::
|
||||
|
||||
List<Integer> input = ...
|
||||
cs.convert(input,
|
||||
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class)), // <1>
|
||||
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class))); // <2>
|
||||
TypeDescriptor.forObject(input), // List<Integer> type descriptor
|
||||
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
|
||||
----
|
||||
<1> `List<Integer>` type descriptor
|
||||
<2> `List<String>` type descriptor
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -324,11 +322,9 @@ Kotlin::
|
||||
|
||||
val input: List<Integer> = ...
|
||||
cs.convert(input,
|
||||
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(Integer::class.java)), // <1>
|
||||
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java))) // <2>
|
||||
TypeDescriptor.forObject(input), // List<Integer> type descriptor
|
||||
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))
|
||||
----
|
||||
<1> `List<Integer>` type descriptor
|
||||
<2> `List<String>` type descriptor
|
||||
======
|
||||
|
||||
Note that `DefaultConversionService` automatically registers converters that are
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[[validator]]
|
||||
= Validation Using Spring's Validator Interface
|
||||
= Validation by Using Spring's Validator Interface
|
||||
|
||||
Spring features a `Validator` interface that you can use to validate objects. The
|
||||
`Validator` interface works by using an `Errors` object so that, while validating,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[orm-hibernate]]
|
||||
= Hibernate
|
||||
|
||||
We start with a coverage of https://hibernate.org/[Hibernate] in a Spring environment,
|
||||
We start with a coverage of https://hibernate.org/[Hibernate 5] in a Spring environment,
|
||||
using it to demonstrate the approach that Spring takes towards integrating OR mappers.
|
||||
This section covers many issues in detail and shows different variations of DAO
|
||||
implementations and transaction demarcation. Most of these patterns can be directly
|
||||
@@ -10,12 +10,13 @@ cover the other ORM technologies and show brief examples.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 7.0, Spring requires Hibernate ORM 7.x for Spring's
|
||||
`HibernateJpaVendorAdapter`.
|
||||
As of Spring Framework 6.0, Spring requires Hibernate ORM 5.5+ for Spring's
|
||||
`HibernateJpaVendorAdapter` as well as for a native Hibernate `SessionFactory` setup.
|
||||
We recommend Hibernate ORM 5.6 as the last feature branch in that Hibernate generation.
|
||||
|
||||
The `org.springframework.orm.jpa.hibernate` package supersedes the former `orm.hibernate5`:
|
||||
now for use with Hibernate ORM 7.x, tightly integrated with `HibernateJpaVendorAdapter`
|
||||
as well as supporting Hibernate's native `SessionFactory.getCurrentSession()` style.
|
||||
Hibernate ORM 6.x is primarily supported as a JPA provider (`HibernateJpaVendorAdapter`).
|
||||
Plain `SessionFactory` setup with the `orm.hibernate5` package is tolerated for migration
|
||||
purposes. We recommend Hibernate ORM 6.x with JPA-style setup for new development projects.
|
||||
====
|
||||
|
||||
|
||||
@@ -42,7 +43,7 @@ JDBC `DataSource` and a Hibernate `SessionFactory` on top of it:
|
||||
<property name="password" value=""/>
|
||||
</bean>
|
||||
|
||||
<bean id="mySessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="myDataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
@@ -270,7 +271,7 @@ processing at runtime. The following example shows how to do so:
|
||||
<!-- SessionFactory, DataSource, etc. omitted -->
|
||||
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager">
|
||||
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="sessionFactory"/>
|
||||
</bean>
|
||||
|
||||
@@ -300,7 +301,7 @@ and an example for a business method implementation:
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="myTxManager" class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager">
|
||||
<bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="mySessionFactory"/>
|
||||
</bean>
|
||||
|
||||
|
||||
+42
-13
@@ -8,7 +8,7 @@ danger of undue coupling, because code that is meant to be used transactionally
|
||||
almost always deployed that way anyway.
|
||||
|
||||
NOTE: The standard `jakarta.transaction.Transactional` annotation is also supported as
|
||||
a drop-in replacement for Spring's own annotation. Please refer to the JTA documentation
|
||||
a drop-in replacement to Spring's own annotation. Please refer to the JTA documentation
|
||||
for more details.
|
||||
|
||||
The ease-of-use afforded by the use of the `@Transactional` annotation is best
|
||||
@@ -89,16 +89,47 @@ annotation in a `@Configuration` class. See the
|
||||
{spring-framework-api}/transaction/annotation/EnableTransactionManagement.html[javadoc]
|
||||
for full details.
|
||||
|
||||
The following example shows the configuration needed to enable annotation-driven transaction management:
|
||||
In XML configuration, the `<tx:annotation-driven/>` tag provides similar convenience:
|
||||
|
||||
include-code::./AppConfig[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- from the file 'context.xml' -->
|
||||
<?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:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
TIP: In programmatic configuration, the `@EnableTransactionManagement` annotation uses any
|
||||
`TransactionManager` bean in the context. In XML configuration, you can omit
|
||||
the `transaction-manager` attribute in the `<tx:annotation-driven/>` tag if the bean
|
||||
name of the `TransactionManager` that you want to wire in has the name `transactionManager`.
|
||||
If the `TransactionManager` bean has any other name, you have to use the
|
||||
`transaction-manager` attribute explicitly, as in the preceding example.
|
||||
<!-- this is the service object that we want to make transactional -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- enable the configuration of transactional behavior based on annotations -->
|
||||
<!-- a TransactionManager is still required -->
|
||||
<tx:annotation-driven transaction-manager="txManager"/> <1>
|
||||
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<!-- (this dependency is defined somewhere else) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
<1> The line that makes the bean instance transactional.
|
||||
|
||||
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>`
|
||||
tag if the bean name of the `TransactionManager` that you want to wire in has the name
|
||||
`transactionManager`. If the `TransactionManager` bean that you want to dependency-inject
|
||||
has any other name, you have to use the `transaction-manager` attribute, as in the
|
||||
preceding example.
|
||||
|
||||
Reactive transactional methods use reactive return types in contrast to imperative
|
||||
programming arrangements as the following listing shows:
|
||||
@@ -203,10 +234,8 @@ on an interface, a class definition, or a method on a class. However, the mere p
|
||||
of the `@Transactional` annotation is not enough to activate the transactional behavior.
|
||||
The `@Transactional` annotation is merely metadata that can be consumed by corresponding
|
||||
runtime infrastructure which uses that metadata to configure the appropriate beans with
|
||||
transactional behavior. In the preceding examples that use programmatic configuration,
|
||||
the `@EnableTransactionManagement` annotation switches on actual transaction management
|
||||
at runtime. Whereas, in the preceding example that uses XML configuration, the
|
||||
`<tx:annotation-driven/>` element switches on actual transaction management at runtime.
|
||||
transactional behavior. In the preceding example, the `<tx:annotation-driven/>` element
|
||||
switches on actual transaction management at runtime.
|
||||
|
||||
TIP: The Spring team recommends that you annotate methods of concrete classes with the
|
||||
`@Transactional` annotation, rather than relying on annotated methods in interfaces,
|
||||
|
||||
+5
-7
@@ -86,13 +86,11 @@ rollback rules may be configured via the `rollbackFor`/`noRollbackFor` and
|
||||
`rollbackForClassName`/`noRollbackForClassName` attributes, which allow rules to be
|
||||
defined based on exception types or patterns, respectively.
|
||||
|
||||
When a rollback rule is defined with an exception type – for example, via `rollbackFor` –
|
||||
that type will be used to match against the type of a thrown exception. Specifically,
|
||||
given a configured exception type `C`, a thrown exception of type `T` will be considered
|
||||
a match against `C` if `T` is equal to `C` or a subclass of `C`. This provides type
|
||||
safety and avoids any unintentional matches that may occur when using a pattern. For
|
||||
example, a value of `jakarta.servlet.ServletException.class` will only match thrown
|
||||
exceptions of type `jakarta.servlet.ServletException` and its subclasses.
|
||||
When a rollback rule is defined with an exception type, that type will be used to match
|
||||
against the type of a thrown exception and its super types, providing type safety and
|
||||
avoiding any unintentional matches that may occur when using a pattern. For example, a
|
||||
value of `jakarta.servlet.ServletException.class` will only match thrown exceptions of
|
||||
type `jakarta.servlet.ServletException` and its subclasses.
|
||||
|
||||
When a rollback rule is defined with an exception pattern, the pattern can be a fully
|
||||
qualified class name or a substring of a fully qualified class name for an exception type
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ where all the underlying resources have to participate in the service-level tran
|
||||
|
||||
NOTE: By default, a participating transaction joins the characteristics of the outer scope,
|
||||
silently ignoring the local isolation level, timeout value, or read-only flag (if any).
|
||||
Consider switching the `validateExistingTransaction` flag to `true` on your transaction
|
||||
Consider switching the `validateExistingTransactions` flag to `true` on your transaction
|
||||
manager if you want isolation level declarations to be rejected when participating in
|
||||
an existing transaction with a different isolation level. This non-lenient mode also
|
||||
rejects read-only mismatches (that is, an inner read-write transaction that tries to participate
|
||||
|
||||
@@ -212,7 +212,7 @@ example declares `sessionFactory` and `txManager` beans:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
@@ -226,7 +226,7 @@ example declares `sessionFactory` and `txManager` beans:
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="txManager" class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager">
|
||||
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="sessionFactory"/>
|
||||
</bean>
|
||||
----
|
||||
@@ -238,7 +238,7 @@ transaction coordinator and possibly also its connection release mode configurat
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
@@ -262,7 +262,7 @@ for enforcing the same defaults:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
[[aot-cache]]
|
||||
= JVM AOT Cache
|
||||
:page-aliases: integration/class-data-sharing.adoc
|
||||
:page-aliases: integration/cds.adoc
|
||||
|
||||
The ahead-of-time cache is a JVM feature introduced in Java 24 via
|
||||
https://openjdk.org/jeps/483[JEP 483] that can help reduce the startup time and memory
|
||||
footprint of Java applications. AOT cache is a natural evolution of
|
||||
https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[Class Data Sharing (CDS)].
|
||||
Spring Framework supports both CDS and AOT cache, and it is recommended that you use the
|
||||
latter if available in the JVM version you are using (Java 24+).
|
||||
|
||||
To use this feature, an AOT cache should be created for the particular classpath of the
|
||||
application. It is possible to create this cache on the deployed instance, or during a
|
||||
training run performed for example when packaging the application thanks to a hook-point
|
||||
provided by the Spring Framework to ease such use case. Once the cache is available, users
|
||||
should opt in to use it via a JVM flag.
|
||||
|
||||
NOTE: If you are using Spring Boot, it is highly recommended to leverage its
|
||||
{spring-boot-docs-ref}/packaging/efficient.html#packaging.efficient.unpacking[executable JAR unpacking support]
|
||||
which is designed to fulfill the class loading requirements of both the AOT cache and CDS.
|
||||
|
||||
== Creating the cache
|
||||
|
||||
An AOT cache can typically be created when the application exits. The Spring Framework
|
||||
provides a mode of operation where the process can exit automatically once the
|
||||
`ApplicationContext` has refreshed. In this mode, all non-lazy initialized singletons
|
||||
have been instantiated, and `InitializingBean#afterPropertiesSet` callbacks have been
|
||||
invoked; but the lifecycle has not started, and the `ContextRefreshedEvent` has not yet
|
||||
been published.
|
||||
|
||||
To create the cache during the training run, it is possible to specify the `-Dspring.context.exit=onRefresh`
|
||||
JVM flag to start and then exit your Spring application once the
|
||||
`ApplicationContext` has refreshed:
|
||||
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
|
||||
AOT cache (Java 25+)::
|
||||
+
|
||||
[source,bash,subs="verbatim,quotes"]
|
||||
----
|
||||
java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar ...
|
||||
----
|
||||
|
||||
AOT cache (Java 24)::
|
||||
+
|
||||
[source,bash,subs="verbatim,quotes"]
|
||||
----
|
||||
# Both commands need to be run with the same classpath
|
||||
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -Dspring.context.exit=onRefresh ...
|
||||
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot ...
|
||||
----
|
||||
|
||||
CDS::
|
||||
+
|
||||
[source,bash,subs="verbatim,quotes"]
|
||||
----
|
||||
# To create a CDS archive, your JDK/JRE must have a base image
|
||||
java -XX:ArchiveClassesAtExit=app.jsa -Dspring.context.exit=onRefresh ...
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
NOTE: With Java 25+, AOT cache stores, among other things, the
|
||||
https://openjdk.org/jeps/515[method profiling information]. Therefore, to benefit of this capability,
|
||||
it is recommended to create an AOT cache for an application that experienced a portion of a
|
||||
production-like workflow instead of using the `-Dspring.context.exit=onRefresh` flag which designed to
|
||||
optimize only the startup of your application.
|
||||
|
||||
== Using the cache
|
||||
|
||||
Once the cache file has been created, you can use it to start your application faster:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
AOT cache::
|
||||
+
|
||||
[source,bash,subs="verbatim"]
|
||||
----
|
||||
# With the same classpath (or a superset) tan the training run
|
||||
java -XX:AOTCache=app.aot ...
|
||||
----
|
||||
|
||||
CDS::
|
||||
+
|
||||
[source,bash,subs="verbatim"]
|
||||
----
|
||||
# With the same classpath (or a superset) tan the training run
|
||||
java -XX:SharedArchiveFile=app.jsa ...
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
Pay attention to the logs and the startup time to check if the AOT cache is used successfully.
|
||||
To figure out how effective the cache is, you can enable class loading logs by adding
|
||||
an extra attribute: `-Xlog:class+load:file=aot-cache.log`. This creates an `aot-cache.log` with
|
||||
every attempt to load a class and its source. Classes that are loaded from the cache should have
|
||||
a "shared objects file" source, as shown in the following example:
|
||||
|
||||
[source,shell,subs="verbatim"]
|
||||
----
|
||||
[0.151s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file
|
||||
[0.151s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file
|
||||
[0.151s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file
|
||||
[0.151s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file
|
||||
[0.151s][info][class,load] org.springframework.context.MessageSource source: shared objects file
|
||||
----
|
||||
|
||||
If the AOT cache cannot be enabled or if you have a large number of classes that are not loaded from
|
||||
the cache, make sure that the following conditions are fulfilled when creating and using the cache:
|
||||
|
||||
- The very same JVM must be used.
|
||||
- The classpath must be specified as a JAR or a list of JARs, and avoid the usage of directories and `*` wildcard characters.
|
||||
- The timestamps of the JARs must be preserved.
|
||||
- When using the cache, the classpath must be the same as the one used to create it, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but will not be cached).
|
||||
+1
-1
@@ -35,7 +35,7 @@ xref:integration/cache/store-configuration.adoc#cache-store-configuration-jsr107
|
||||
[[cache-store-configuration-caffeine]]
|
||||
== Caffeine Cache
|
||||
|
||||
Caffeine is a rewrite of Guava's cache, and its implementation is located in the
|
||||
Caffeine is a Java 8 rewrite of Guava's cache, and its implementation is located in the
|
||||
`org.springframework.cache.caffeine` package and provides access to several features
|
||||
of Caffeine.
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
[[cds]]
|
||||
= CDS
|
||||
:page-aliases: integration/class-data-sharing.adoc
|
||||
|
||||
Class Data Sharing (CDS) is a https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[JVM feature]
|
||||
that can help reduce the startup time and memory footprint of Java applications.
|
||||
|
||||
To use this feature, a CDS archive should be created for the particular classpath of the
|
||||
application. The Spring Framework provides a hook-point to ease the creation of the
|
||||
archive. Once the archive is available, users should opt in to use it via a JVM flag.
|
||||
|
||||
|
||||
== Creating the CDS Archive
|
||||
|
||||
A CDS archive for an application can be created when the application exits. The Spring
|
||||
Framework provides a mode of operation where the process can exit automatically once the
|
||||
`ApplicationContext` has refreshed. In this mode, all non-lazy initialized singletons
|
||||
have been instantiated, and `InitializingBean#afterPropertiesSet` callbacks have been
|
||||
invoked; but the lifecycle has not started, and the `ContextRefreshedEvent` has not yet
|
||||
been published.
|
||||
|
||||
To create the archive, two additional JVM flags must be specified:
|
||||
|
||||
* `-XX:ArchiveClassesAtExit=application.jsa`: creates the CDS archive on exit
|
||||
* `-Dspring.context.exit=onRefresh`: starts and then immediately exits your Spring
|
||||
application as described above
|
||||
|
||||
To create a CDS archive, your JDK/JRE must have a base image. If you add the flags above to
|
||||
your startup script, you may get a warning that looks like this:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
----
|
||||
-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded. Run with -Xlog:cds for more info.
|
||||
----
|
||||
|
||||
The base CDS archive is usually provided out-of-the-box, but can also be created if needed by issuing the following
|
||||
command:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
----
|
||||
$ java -Xshare:dump
|
||||
----
|
||||
|
||||
|
||||
== Using the Archive
|
||||
|
||||
Once the archive is available, add `-XX:SharedArchiveFile=application.jsa` to your startup
|
||||
script to use it, assuming an `application.jsa` file in the working directory.
|
||||
|
||||
To check if the CDS cache is effective, you can use (for testing purposes only, not in production) `-Xshare:on` which
|
||||
prints an error message and exits if CDS can't be enabled.
|
||||
|
||||
To figure out how effective the cache is, you can enable class loading logs by adding
|
||||
an extra attribute: `-Xlog:class+load:file=cds.log`. This creates a `cds.log` with every
|
||||
attempt to load a class and its source. Classes that are loaded from the cache should have
|
||||
a "shared objects file" source, as shown in the following example:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
----
|
||||
[0.064s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file (top)
|
||||
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
|
||||
----
|
||||
|
||||
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
|
||||
the following conditions are fulfilled when creating and using the archive:
|
||||
|
||||
- The very same JVM must be used.
|
||||
- The classpath must be specified as a list of JARs, and avoid the usage of directories and `*` wildcard characters.
|
||||
- The timestamps of the JARs must be preserved.
|
||||
- When using the archive, the classpath must be the same than the one used to create the archive, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but won't be cached).
|
||||
@@ -11,7 +11,9 @@ Spring Framework's email support:
|
||||
* The https://jakartaee.github.io/mail-api/[Jakarta Mail] library
|
||||
|
||||
This library is freely available on the web -- for example, in Maven Central as
|
||||
`org.eclipse.angus:angus-mail`.
|
||||
`com.sun.mail:jakarta.mail`. Please make sure to use the latest 2.x version (which uses
|
||||
the `jakarta.mail` package namespace) rather than Jakarta Mail 1.6.x (which uses the
|
||||
`javax.mail` package namespace).
|
||||
****
|
||||
|
||||
The Spring Framework provides a helpful utility library for sending email that shields
|
||||
|
||||
@@ -26,7 +26,7 @@ behind the scenes for each annotated method, by using a `JmsListenerContainerFac
|
||||
Such a container is not registered against the application context but can be easily
|
||||
located for management purposes by using the `JmsListenerEndpointRegistry` bean.
|
||||
|
||||
TIP: `@JmsListener` is a repeatable annotation, so you can associate
|
||||
TIP: `@JmsListener` is a repeatable annotation on Java 8, so you can associate
|
||||
several JMS destinations with the same method by adding additional `@JmsListener`
|
||||
declarations to it.
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ This describes how to receive messages with JMS in Spring.
|
||||
[[jms-receiving-sync]]
|
||||
== Synchronous Receipt
|
||||
|
||||
While JMS is typically associated with asynchronous processing, you can consume messages
|
||||
synchronously. The `receive(..)` methods on `JmsTemplate` and `JmsClient` provide this
|
||||
While JMS is typically associated with asynchronous processing, you can
|
||||
consume messages synchronously. The overloaded `receive(..)` methods provide this
|
||||
functionality. During a synchronous receive, the calling thread blocks until a message
|
||||
becomes available. This can be a dangerous operation, since the calling thread can
|
||||
potentially be blocked indefinitely. The `receiveTimeout` property specifies how long
|
||||
|
||||
@@ -9,7 +9,39 @@ that takes no destination argument uses the default destination.
|
||||
The following example uses the `MessageCreator` callback to create a text message from the
|
||||
supplied `Session` object:
|
||||
|
||||
include-code::./JmsQueueSender[]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.JMSException;
|
||||
import jakarta.jms.Message;
|
||||
import jakarta.jms.Queue;
|
||||
import jakarta.jms.Session;
|
||||
|
||||
import org.springframework.jms.core.MessageCreator;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
|
||||
public class JmsQueueSender {
|
||||
|
||||
private JmsTemplate jmsTemplate;
|
||||
private Queue queue;
|
||||
|
||||
public void setConnectionFactory(ConnectionFactory cf) {
|
||||
this.jmsTemplate = new JmsTemplate(cf);
|
||||
}
|
||||
|
||||
public void setQueue(Queue queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
public void simpleSend() {
|
||||
this.jmsTemplate.send(this.queue, new MessageCreator() {
|
||||
public Message createMessage(Session session) throws JMSException {
|
||||
return session.createTextMessage("hello queue world");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In the preceding example, the `JmsTemplate` is constructed by passing a reference to a
|
||||
`ConnectionFactory`. As an alternative, a zero-argument constructor and
|
||||
@@ -52,7 +84,21 @@ gives you access to the message after it has been converted but before it is sen
|
||||
following example shows how to modify a message header and a property after a
|
||||
`java.util.Map` is converted to a message:
|
||||
|
||||
include-code::./JmsSenderWithConversion[]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public void sendWithConversion() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("Name", "Mark");
|
||||
map.put("Age", new Integer(47));
|
||||
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
|
||||
public Message postProcessMessage(Message message) throws JMSException {
|
||||
message.setIntProperty("AccountID", 1234);
|
||||
message.setJMSCorrelationID("123-00001");
|
||||
return message;
|
||||
}
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
This results in a message of the following form:
|
||||
|
||||
@@ -74,11 +120,6 @@ MapMessage={
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: This JMS-specific `org.springframework.jms.support.converter.MessageConverter`
|
||||
arrangement operates on JMS message types and is responsible for immediate conversion
|
||||
to `jakarta.jms.TextMessage`, `jakarta.jms.BytesMessage`, etc. For a contract supporting
|
||||
generic message payloads, use `org.springframework.messaging.converter.MessageConverter`
|
||||
with `JmsMessagingTemplate` or preferably `JmsClient` as your central delegate instead.
|
||||
|
||||
[[jms-sending-callbacks]]
|
||||
== Using `SessionCallback` and `ProducerCallback` on `JmsTemplate`
|
||||
@@ -88,21 +129,3 @@ want to perform multiple operations on a JMS `Session` or `MessageProducer`. The
|
||||
`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` /
|
||||
`MessageProducer` pair, respectively. The `execute()` methods on `JmsTemplate` run
|
||||
these callback methods.
|
||||
|
||||
|
||||
[[jms-sending-jmsclient]]
|
||||
== Sending a Message with `JmsClient`
|
||||
|
||||
include-code::./JmsClientSample[]
|
||||
|
||||
|
||||
[[jms-sending-postprocessor]]
|
||||
== Post-processing outgoing messages
|
||||
|
||||
Applications often need to intercept messages before they are sent out, for example to add message properties to all outgoing messages.
|
||||
The `org.springframework.messaging.core.MessagePostProcessor` based on the spring-messaging `Message` can do that,
|
||||
when configured on the `JmsClient`. It will be used for all outgoing messages sent with the `send` and `sendAndReceive` methods.
|
||||
|
||||
Here is an example of an interceptor adding a "tenantId" property to all outgoing messages.
|
||||
|
||||
include-code::./JmsClientWithPostProcessor[]
|
||||
|
||||
@@ -4,20 +4,13 @@
|
||||
This section describes how to use Spring's JMS components.
|
||||
|
||||
|
||||
[[jms-jmstemplate-jmsclient]]
|
||||
== `JmsTemplate` and `JmsClient`
|
||||
[[jms-jmstemplate]]
|
||||
== Using `JmsTemplate`
|
||||
|
||||
The `JmsTemplate` class is the central class in the JMS core package. It simplifies the
|
||||
use of JMS, since it handles the creation and release of resources when sending or
|
||||
synchronously receiving messages.
|
||||
|
||||
`JmsClient` is a new API variant in Spring Framework 7.0, following the design of
|
||||
`JdbcClient` and co. `JmsClient` builds on `JmsTemplate` for straightforward send
|
||||
and receive operations with customization options per operation.
|
||||
|
||||
[[jms-jmstemplate]]
|
||||
=== Using `JmsTemplate`
|
||||
|
||||
Code that uses the `JmsTemplate` needs only to implement callback interfaces that give them
|
||||
a clearly defined high-level contract. The `MessageCreator` callback interface creates a
|
||||
message when given a `Session` provided by the calling code in `JmsTemplate`. To
|
||||
@@ -50,23 +43,10 @@ and then safely inject this shared reference into multiple collaborators. To be
|
||||
clear, the `JmsTemplate` is stateful, in that it maintains a reference to a
|
||||
`ConnectionFactory`, but this state is not conversational state.
|
||||
|
||||
[[jms-jmsclient]]
|
||||
=== Using `JmsClient`
|
||||
|
||||
As of Spring Framework 4.1, `JmsMessagingTemplate` is built on top of `JmsTemplate`
|
||||
and provides an integration with the Spring's common messaging abstraction -- that is,
|
||||
handling `org.springframework.messaging.Message` for sending and receiving,
|
||||
throwing `org.springframework.messaging.MessagingException` and with payload conversion
|
||||
going through `org.springframework.messaging.converter.MessageConverter` (with many
|
||||
common converter implementations available).
|
||||
|
||||
As of Spring Framework 7.0, a fluent API called `JmsClient` is available. This provides
|
||||
customizable operations around `org.springframework.messaging.Message` and throwing
|
||||
`org.springframework.messaging.MessagingException`, similar to `JmsMessagingTemplate`,
|
||||
as well as integration with `org.springframework.messaging.converter.MessageConverter`.
|
||||
A `JmsClient can either be created for a given `ConnectionFactory` or for a given
|
||||
`JmsTemplate`, in the latter case reusing its settings by default. See
|
||||
{spring-framework-api}/jms/core/JmsClient.html[`JmsClient`] for usage examples.
|
||||
and provides an integration with the messaging abstraction -- that is,
|
||||
`org.springframework.messaging.Message`. This lets you create the message to
|
||||
send in a generic manner.
|
||||
|
||||
|
||||
[[jms-connections]]
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
[[observability]]
|
||||
= Observability Support
|
||||
|
||||
With https://docs.micrometer.io/micrometer/reference/concepts.html[Micrometer], developers can instrument libraries and applications for metrics (timers, gauges, counters)
|
||||
that collect statistics about their runtime behavior. Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
https://docs.micrometer.io/tracing/reference/[Micrometer can also produce traces], giving you a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Micrometer defines an {micrometer-docs}/observation.html[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Each observation will produce:
|
||||
|
||||
* https://docs.micrometer.io/micrometer/reference/observation/components.html#micrometer-observation-default-meter-handler[several metrics - a timer, a long task timer and many counters]
|
||||
* a https://docs.micrometer.io/tracing/reference/glossary.html[span for the current trace]
|
||||
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||
You can learn more about {spring-boot-docs-ref}/actuator/observability.html[configuring the observability infrastructure in Spring Boot].
|
||||
@@ -194,13 +189,13 @@ This observation uses the `io.micrometer.jakarta9.instrument.jms.DefaultJmsProce
|
||||
[[observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
|
||||
HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications,
|
||||
or `"http.server.request.duration"` if using the OpenTelemetry convention.
|
||||
HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
|
||||
[[observability.http-server.servlet]]
|
||||
=== Servlet applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
||||
It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the Servlet filter.
|
||||
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and xref:web/webmvc/mvc-ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
|
||||
@@ -212,11 +207,6 @@ NOTE: Because the instrumentation is done at the Servlet Filter level, the obser
|
||||
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
|
||||
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside the scope of this project.
|
||||
|
||||
[[observability.http-server.servlet.default]]
|
||||
==== Default Semantic Convention
|
||||
|
||||
It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
@@ -238,16 +228,6 @@ By default, the following `KeyValues` are created:
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
[[observability.http-server.servlet.otel]]
|
||||
==== OpenTelemetry Semantic Convention
|
||||
|
||||
An OpenTelemetry variant is available with `org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention`, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This variant complies with the https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/http/http-metrics.md[OpenTelemetry Semantic Conventions for HTTP Metrics (v1.36.0)]
|
||||
and the https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/http/http-spans.md[OpenTelemetry Semantic Conventions for HTTP Spans (v1.36.0)].
|
||||
|
||||
|
||||
[[observability.http-server.reactive]]
|
||||
=== Reactive applications
|
||||
|
||||
|
||||
@@ -3,34 +3,26 @@
|
||||
|
||||
The Spring Framework provides the following choices for making calls to REST endpoints:
|
||||
|
||||
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] -- synchronous client with a fluent API
|
||||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] -- non-blocking, reactive client with fluent API
|
||||
* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] -- synchronous client with template method API, now deprecated in favor of `RestClient`
|
||||
* xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service Clients] -- annotated interface backed by generated proxy
|
||||
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] - synchronous client with a fluent API.
|
||||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] - non-blocking, reactive client with fluent API.
|
||||
* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] - synchronous client with template method API.
|
||||
* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] - annotated interface with generated, dynamic proxy implementation.
|
||||
|
||||
|
||||
[[rest-restclient]]
|
||||
== `RestClient`
|
||||
|
||||
`RestClient` is a synchronous HTTP client that provides a fluent API to perform requests.
|
||||
It serves as an abstraction over HTTP libraries, and handles conversion of HTTP request and response content to and from higher level Java objects.
|
||||
The `RestClient` is a synchronous HTTP client that offers a modern, fluent API.
|
||||
It offers an abstraction over HTTP libraries that allows for convenient conversion from a Java object to an HTTP request, and the creation of objects from an HTTP response.
|
||||
|
||||
=== Create a `RestClient`
|
||||
=== Creating a `RestClient`
|
||||
|
||||
`RestClient` has static `create` shortcut methods.
|
||||
It also exposes a `builder()` with further options:
|
||||
The `RestClient` is created using one of the static `create` methods.
|
||||
You can also use `builder()` to get a builder with further options, such as specifying which HTTP library to use (see <<rest-request-factories>>) and which message converters to use (see <<rest-message-conversion>>), setting a default URI, default path variables, default request headers, or `uriBuilderFactory`, or registering interceptors and initializers.
|
||||
|
||||
- select the HTTP library to use, see <<rest-request-factories>>
|
||||
- configure message converters, see <<rest-message-conversion>>
|
||||
- set a baseUrl
|
||||
- set default request headers, cookies, path variables, API version
|
||||
- configure an `ApiVersionInserter`
|
||||
- register interceptors
|
||||
- register request initializers
|
||||
Once created (or built), the `RestClient` can be used safely by multiple threads.
|
||||
|
||||
Once created, a `RestClient` is safe to use in multiple threads.
|
||||
|
||||
The below shows how to create or build a `RestClient`:
|
||||
The following sample shows how to create a default `RestClient`, and how to build a custom one.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -47,8 +39,6 @@ Java::
|
||||
.defaultUriVariables(Map.of("variable", "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.defaultVersion("1.2")
|
||||
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build();
|
||||
@@ -67,25 +57,23 @@ Kotlin::
|
||||
.defaultUriVariables(mapOf("variable" to "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.defaultVersion("1.2")
|
||||
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
=== Use the `RestClient`
|
||||
=== Using the `RestClient`
|
||||
|
||||
To perform an HTTP request, first specify the HTTP method to use.
|
||||
Use the convenience methods like `get()`, `head()`, `post()`, and others, or `method(HttpMethod)`.
|
||||
When making an HTTP request with the `RestClient`, the first thing to specify is which HTTP method to use.
|
||||
This can be done with `method(HttpMethod)` or with the convenience methods `get()`, `head()`, `post()`, and so on.
|
||||
|
||||
==== Request URL
|
||||
|
||||
Next, specify the request URI with the `uri` methods.
|
||||
This is optional, and you can skip this step if you configured a baseUrl through the builder.
|
||||
Next, the request URI can be specified with the `uri` methods.
|
||||
This step is optional and can be skipped if the `RestClient` is configured with a default URI.
|
||||
The URL is typically specified as a `String`, with optional URI template variables.
|
||||
The following shows how to perform a request:
|
||||
The following example configures a GET request to `https://example.com/orders/42`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -120,7 +108,6 @@ For more details on working with and encoding URIs, see xref:web/webmvc/mvc-uri-
|
||||
|
||||
If necessary, the HTTP request can be manipulated by adding request headers with `header(String, String)`, `headers(Consumer<HttpHeaders>`, or with the convenience methods `accept(MediaType...)`, `acceptCharset(Charset...)` and so on.
|
||||
For HTTP requests that can contain a body (`POST`, `PUT`, and `PATCH`), additional methods are available: `contentType(MediaType)`, and `contentLength(long)`.
|
||||
You can set an API version for the request if the client is configured with `ApiVersionInserter`.
|
||||
|
||||
The request body itself can be set by `body(Object)`, which internally uses <<rest-message-conversion>>.
|
||||
Alternatively, the request body can be set using a `ParameterizedTypeReference`, allowing you to use generics.
|
||||
@@ -466,7 +453,7 @@ synchronous, asynchronous, and streaming scenarios.
|
||||
* Non-blocking I/O
|
||||
* Reactive Streams back pressure
|
||||
* High concurrency with fewer hardware resources
|
||||
* Functional-style, fluent API that takes advantage of lambda expressions
|
||||
* Functional-style, fluent API that takes advantage of Java 8 lambdas
|
||||
* Synchronous and asynchronous interactions
|
||||
* Streaming up to or streaming down from a server
|
||||
|
||||
@@ -479,8 +466,7 @@ See xref:web/webflux-webclient.adoc[WebClient] for more details.
|
||||
The `RestTemplate` provides a high-level API over HTTP client libraries in the form of a classic Spring Template class.
|
||||
It exposes the following groups of overloaded methods:
|
||||
|
||||
WARNING: As of Spring Framework 7.0, `RestTemplate` is deprecated in favor of `RestClient` and will be removed in a future version,
|
||||
please use the xref:integration/rest-clients.adoc#migrating-to-restclient["Migrating to RestClient"] guide.
|
||||
NOTE: The xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] offers a more modern API for synchronous HTTP access.
|
||||
For asynchronous and streaming scenarios, consider the reactive xref:web/webflux-webclient.adoc[WebClient].
|
||||
|
||||
[[rest-overview-of-resttemplate-methods-tbl]]
|
||||
@@ -548,26 +534,10 @@ See the xref:integration/observability.adoc#http-client.resttemplate[RestTemplat
|
||||
Objects passed into and returned from `RestTemplate` methods are converted to and from HTTP messages
|
||||
with the help of an `HttpMessageConverter`, see <<rest-message-conversion>>.
|
||||
|
||||
[[migrating-to-restclient]]
|
||||
=== Migrating to `RestClient`
|
||||
|
||||
Applications can adopt `RestClient` in a gradual fashion, first focusing on API usage and then on infrastructure setup.
|
||||
You can consider the following steps:
|
||||
|
||||
1. Create one or more `RestClient` from existing `RestTemplate` instances, like: `RestClient restClient = RestClient.create(restTemplate)`.
|
||||
Gradually replace `RestTemplate` usage in your application, component by component, by focusing first on issuing requests.
|
||||
See the table below for API equivalents.
|
||||
2. Once all client requests go through `RestClient` instances, you can now work on replicating your existing
|
||||
`RestTemplate` instance creations by using `RestClient.Builder`. Because `RestTemplate` and `RestClient`
|
||||
share the same infrastructure, you can reuse custom `ClientHttpRequestFactory` or `ClientHttpRequestInterceptor`
|
||||
in your setup. See xref:integration/rest-clients.adoc#rest-restclient[the `RestClient` builder API].
|
||||
|
||||
If no other library is available on the classpath, `RestClient` will choose the `JdkClientHttpRequestFactory`
|
||||
powered by the modern JDK `HttpClient`, whereas `RestTemplate` would pick the `SimpleClientHttpRequestFactory` that
|
||||
uses `HttpURLConnection`. This can explain subtle behavior difference at runtime at the HTTP level.
|
||||
|
||||
=== Migrating from `RestTemplate` to `RestClient`
|
||||
|
||||
The following table shows `RestClient` equivalents for `RestTemplate` methods.
|
||||
It can be used to migrate from the latter to the former.
|
||||
|
||||
.RestClient equivalents for RestTemplate methods
|
||||
[cols="1,1", options="header"]
|
||||
@@ -871,24 +841,17 @@ The following table shows `RestClient` equivalents for `RestTemplate` methods.
|
||||
|
||||
|===
|
||||
|
||||
`RestClient` and `RestTemplate` instances share the same behavior when it comes to throwing exceptions
|
||||
(with the `RestClientException` type being at the top of the hierarchy).
|
||||
When `RestTemplate` consistently throws `HttpClientErrorException` for "4xx" response statues,
|
||||
`RestClient` allows for more flexibility with custom xref:integration/rest-clients.adoc#rest-http-service-client-exceptions["status handlers"].
|
||||
|
||||
[[rest-http-interface]]
|
||||
== HTTP Interface
|
||||
|
||||
[[rest-http-service-client]]
|
||||
== HTTP Service Clients
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with
|
||||
`@HttpExchange` methods. You can pass such an interface to `HttpServiceProxyFactory`
|
||||
to create a proxy which performs requests through an HTTP client such as `RestClient`
|
||||
or `WebClient`. You can also implement the interface from an `@Controller` for server
|
||||
request handling.
|
||||
|
||||
You can define an HTTP Service as a Java interface with `@HttpExchange` methods, and use
|
||||
`HttpServiceProxyFactory` to create a client proxy from it for remote access over HTTP via
|
||||
`RestClient`, `WebClient`, or `RestTemplate`. On the server side, an `@Controller` class
|
||||
can implement the same interface to handle requests with
|
||||
xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[@HttpExchange]
|
||||
controller methods.
|
||||
|
||||
|
||||
First, create the Java interface:
|
||||
Start by creating the interface with `@HttpExchange` methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -902,7 +865,43 @@ First, create the Java interface:
|
||||
}
|
||||
----
|
||||
|
||||
Optionally, use `@HttpExchange` at the type level to declare common attributes for all methods:
|
||||
Now you can create a proxy that performs requests when methods are called.
|
||||
|
||||
For `RestClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
For `WebClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
For `RestTemplate`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/"));
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
`@HttpExchange` is supported at the type level where it applies to all methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -919,52 +918,15 @@ Optionally, use `@HttpExchange` at the type level to declare common attributes f
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
Next, configure the client and create the `HttpServiceProxyFactory`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Using RestClient...
|
||||
|
||||
RestClient restClient = RestClient.create("...");
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
|
||||
// or WebClient...
|
||||
|
||||
WebClient webClient = WebClient.create("...");
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
|
||||
// or RestTemplate...
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
Now, you're ready to create client proxies:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
// Use service methods for remote calls...
|
||||
----
|
||||
|
||||
HTTP service clients is a powerful and expressive choice for remote access over HTTP.
|
||||
It allows one team to own the knowledge of how a REST API works, what parts are relevant
|
||||
to a client application, what input and output types to create, what endpoint method
|
||||
signatures are needed, what Javadoc to have, and so on. The resulting Java API guides and
|
||||
is ready to use.
|
||||
|
||||
|
||||
[[rest-http-service-client-method-parameters]]
|
||||
[[rest-http-interface-method-parameters]]
|
||||
=== Method Parameters
|
||||
|
||||
`@HttpExchange` methods support flexible method signatures with the following inputs:
|
||||
Annotated, HTTP exchange methods support flexible method signatures with the following
|
||||
method parameters:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method parameter | Description
|
||||
| Method argument | Description
|
||||
|
||||
| `URI`
|
||||
| Dynamically set the URL for the request, overriding the annotation's `url` attribute.
|
||||
@@ -1025,32 +987,28 @@ Method parameters cannot be `null` unless the `required` attribute (where availa
|
||||
parameter annotation) is set to `false`, or the parameter is marked optional as determined by
|
||||
{spring-framework-api}/core/MethodParameter.html#isOptional()[`MethodParameter#isOptional`].
|
||||
|
||||
`RestClientAdapter` provides additional support for a method parameter of type
|
||||
`StreamingHttpOutputMessage.Body` that allows sending the request body by writing to an
|
||||
`OutputStream`.
|
||||
[[rest-http-interface.custom-resolver]]
|
||||
=== Custom argument resolver
|
||||
|
||||
[[rest-http-service-client.custom-resolver]]
|
||||
=== Custom Arguments
|
||||
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters.
|
||||
This would take over the entire HTTP request and not improve the semantics of the interface.
|
||||
Instead of adding many method parameters, developers can combine them into a custom type
|
||||
and configure a dedicated `HttpServiceArgumentResolver` implementation.
|
||||
|
||||
You can configure a custom `HttpServiceArgumentResolver`. The example interface below
|
||||
uses a custom `Search` method parameter type:
|
||||
In the following HTTP interface, we are using a custom `Search` type as a parameter:
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=httpserviceclient,indent=0]
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
|
||||
|
||||
A custom argument resolver could be implemented like this:
|
||||
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type
|
||||
and writes its data in the outgoing HTTP request.
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0]
|
||||
|
||||
To configure the custom argument resolver:
|
||||
Finally, we can use this argument resolver during the setup and use our HTTP interface.
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0]
|
||||
|
||||
TIP: By default, `RequestEntity` is not supported as a method parameter, instead encouraging
|
||||
the use of more fine-grained method parameters for individual parts of the request.
|
||||
|
||||
|
||||
|
||||
[[rest-http-service-client-return-values]]
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
The supported return values depend on the underlying client.
|
||||
@@ -1122,206 +1080,63 @@ depends on how the underlying HTTP client is configured. You can set a `blockTim
|
||||
value on the adapter level as well, but we recommend relying on timeout settings of the
|
||||
underlying HTTP client, which operates at a lower level and provides more control.
|
||||
|
||||
`RestClientAdapter` provides supports additional support for a return value of type
|
||||
`InputStream` or `ResponseEntity<InputStream>` that provides access to the raw response
|
||||
body content.
|
||||
|
||||
[[rest-http-service-client-exceptions]]
|
||||
[[rest-http-interface-exceptions]]
|
||||
=== Error Handling
|
||||
|
||||
To customize error handling for HTTP Service client proxies, you can configure the
|
||||
underlying client as needed. By default, clients raise an exception for 4xx and 5xx HTTP
|
||||
status codes. To customize this, register a response status handler that applies to all
|
||||
responses performed through the client as follows:
|
||||
To customize error response handling, you need to configure the underlying HTTP client.
|
||||
|
||||
For `RestClient`:
|
||||
|
||||
By default, `RestClient` raises `RestClientException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register a response status handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// For RestClient
|
||||
RestClient restClient = RestClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
|
||||
.build();
|
||||
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
|
||||
// or for WebClient...
|
||||
WebClient webClient = WebClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
|
||||
.build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
|
||||
// or for RestTemplate...
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(myErrorHandler);
|
||||
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options such as suppressing error status codes, see the reference
|
||||
documentation for each client, as well as the Javadoc of `defaultStatusHandler` in
|
||||
`RestClient.Builder` or `WebClient.Builder`, and the `setErrorHandler` of `RestTemplate`.
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `RestClient.Builder`.
|
||||
|
||||
For `WebClient`:
|
||||
|
||||
|
||||
[[rest-http-service-client-adapter-decorator]]
|
||||
=== Decorating the Adapter
|
||||
|
||||
`HttpExchangeAdapter` and `ReactorHttpExchangeAdapter` are contracts that decouple HTTP
|
||||
Interface client infrastructure from the details of invoking the underlying
|
||||
client. There are adapter implementations for `RestClient`, `WebClient`, and
|
||||
`RestTemplate`.
|
||||
|
||||
Occasionally, it may be useful to intercept client invocations through a decorator
|
||||
configurable in the `HttpServiceProxyFactory.Builder`. For example, you can apply
|
||||
built-in decorators to suppress 404 exceptions and return a `ResponseEntity` with
|
||||
`NOT_FOUND` and a `null` body:
|
||||
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register a response status handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// For RestClient
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restClientAdapter)
|
||||
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
|
||||
WebClient webClient = WebClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
|
||||
.build();
|
||||
|
||||
// or for WebClient...
|
||||
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builderFor(webClientAdapter)
|
||||
.exchangeAdapterDecorator(NotFoundWebClientAdapterDecorator::new)
|
||||
.build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `WebClient.Builder`.
|
||||
|
||||
For `RestTemplate`:
|
||||
|
||||
[[rest-http-service-client-group-config]]
|
||||
=== HTTP Service Groups
|
||||
|
||||
It's trivial to create client proxies with `HttpServiceProxyFactory`, but to have them
|
||||
declared as beans leads to repetitive configuration. You may also have multiple
|
||||
target hosts, and therefore multiple clients to configure, and even more client proxy
|
||||
beans to create.
|
||||
|
||||
To make it easier to work with interface clients at scale the Spring Framework provides
|
||||
dedicated configuration support. It lets applications focus on identifying HTTP Services
|
||||
by group, and customizing the client for each group, while the framework transparently
|
||||
creates a registry of client proxies, and declares each proxy as a bean.
|
||||
|
||||
An HTTP Service group is simply a set of interfaces that share the same client setup and
|
||||
`HttpServiceProxyFactory` instance to create proxies. Typically, that means one group per
|
||||
host, but you can have more than one group for the same target host in case the
|
||||
underlying client needs to be configured differently.
|
||||
|
||||
One way to declare HTTP Service groups is via `@ImportHttpServices` annotations in
|
||||
`@Configuration` classes as shown below:
|
||||
By default, `RestTemplate` raises `RestClientException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register an error handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class}) // <1>
|
||||
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) // <2>
|
||||
public class ClientConfig {
|
||||
}
|
||||
|
||||
----
|
||||
<1> Manually list interfaces for group "echo"
|
||||
<2> Detect interfaces for group "greeting" under a base package
|
||||
|
||||
It is also possible to declare groups programmatically by creating an HTTP Service
|
||||
registrar and then importing it:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MyHttpServiceRegistrar extends AbstractHttpServiceRegistrar { // <1>
|
||||
|
||||
@Override
|
||||
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
|
||||
registry.forGroup("echo").register(EchoServiceA.class, EchoServiceB.class); // <2>
|
||||
registry.forGroup("greeting").detectInBasePackages(GreetServiceA.class); // <3>
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(MyHttpServiceRegistrar.class) // <4>
|
||||
public class ClientConfig {
|
||||
}
|
||||
|
||||
----
|
||||
<1> Create extension class of `AbstractHttpServiceRegistrar`
|
||||
<2> Manually list interfaces for group "echo"
|
||||
<3> Detect interfaces for group "greeting" under a base package
|
||||
<4> Import the registrar
|
||||
|
||||
TIP: You can mix and match `@ImportHttpService` annotations with programmatic registrars,
|
||||
and you can spread the imports across multiple configuration classes. All imports
|
||||
contribute collaboratively the same, shared `HttpServiceProxyRegistry` instance.
|
||||
|
||||
Once HTTP Service groups are declared, add an `HttpServiceGroupConfigurer` bean to
|
||||
customize the client for each group. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class})
|
||||
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class)
|
||||
public class ClientConfig {
|
||||
|
||||
@Bean
|
||||
public RestClientHttpServiceGroupConfigurer groupConfigurer() {
|
||||
return groups -> {
|
||||
// configure client for group "echo"
|
||||
groups.filterByName("echo").forEachClient((group, clientBuilder) -> ...);
|
||||
|
||||
// configure the clients for all groups
|
||||
groups.forEachClient((group, clientBuilder) -> ...);
|
||||
|
||||
// configure client and proxy factory for each group
|
||||
groups.forEachGroup((group, clientBuilder, factoryBuilder) -> ...);
|
||||
};
|
||||
}
|
||||
}
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(myErrorHandler);
|
||||
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
TIP: Spring Boot uses an `HttpServiceGroupConfigurer` to add support for client properties
|
||||
by HTTP Service group, Spring Security to add OAuth support, and Spring Cloud to add load
|
||||
balancing.
|
||||
|
||||
As a result of the above, each client proxy is available as a bean that you can
|
||||
conveniently autowire by type:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
public class EchoController {
|
||||
|
||||
private final EchoService echoService;
|
||||
|
||||
public EchoController(EchoService echoService) {
|
||||
this.echoService = echoService;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
However, if there are multiple client proxies of the same type, e.g. the same interface
|
||||
in multiple groups, then there is no unique bean of that type, and you cannot autowire by
|
||||
type only. For such cases, you can work directly with the `HttpServiceProxyRegistry` that
|
||||
holds all proxies, and obtain the ones you need by group:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
public class EchoController {
|
||||
|
||||
private final EchoService echoService1;
|
||||
|
||||
private final EchoService echoService2;
|
||||
|
||||
public EchoController(HttpServiceProxyRegistry registry) {
|
||||
this.echoService1 = registry.getClient("echo1", EchoService.class); // <1>
|
||||
this.echoService2 = registry.getClient("echo2", EchoService.class); // <2>
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Access the `EchoService` client proxy for group "echo1"
|
||||
<2> Access the `EchoService` client proxy for group "echo2"
|
||||
For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and
|
||||
the `ResponseErrorHandler` hierarchy.
|
||||
|
||||
@@ -533,7 +533,8 @@ that returns a value:
|
||||
----
|
||||
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return
|
||||
type but also `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
type but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of
|
||||
Spring 4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
the asynchronous task and for immediate composition with further processing steps.
|
||||
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as `@PostConstruct`.
|
||||
|
||||
@@ -0,0 +1,831 @@
|
||||
[[dynamic-language]]
|
||||
= Dynamic Language Support
|
||||
|
||||
Spring provides comprehensive support for using classes and objects that have been
|
||||
defined by using a dynamic language (such as Groovy) with Spring. This support lets
|
||||
you write any number of classes in a supported dynamic language and have the Spring
|
||||
container transparently instantiate, configure, and dependency inject the resulting
|
||||
objects.
|
||||
|
||||
Spring's scripting support primarily targets Groovy and BeanShell. Beyond those
|
||||
specifically supported languages, the JSR-223 scripting mechanism is supported
|
||||
for integration with any JSR-223 capable language provider (as of Spring 4.2),
|
||||
for example, JRuby.
|
||||
|
||||
You can find fully working examples of where this dynamic language support can be
|
||||
immediately useful in xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios].
|
||||
|
||||
|
||||
[[dynamic-language-a-first-example]]
|
||||
== A First Example
|
||||
|
||||
The bulk of this chapter is concerned with describing the dynamic language support in
|
||||
detail. Before diving into all of the ins and outs of the dynamic language support,
|
||||
we look at a quick example of a bean defined in a dynamic language. The dynamic
|
||||
language for this first bean is Groovy. (The basis of this example was taken from the
|
||||
Spring test suite. If you want to see equivalent examples in any of the other
|
||||
supported languages, take a look at the source code).
|
||||
|
||||
The next example shows the `Messenger` interface, which the Groovy bean is going to
|
||||
implement. Note that this interface is defined in plain Java. Dependent objects that
|
||||
are injected with a reference to the `Messenger` do not know that the underlying
|
||||
implementation is a Groovy script. The following listing shows the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public interface Messenger {
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
----
|
||||
|
||||
The following example defines a class that has a dependency on the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public class DefaultBookingService implements BookingService {
|
||||
|
||||
private Messenger messenger;
|
||||
|
||||
public void setMessenger(Messenger messenger) {
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
public void processBooking() {
|
||||
// use the injected Messenger object...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following example implements the `Messenger` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
----
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// Import the Messenger interface (written in Java) that is to be implemented
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// Define the implementation in Groovy in file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
String message
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
To use the custom dynamic language tags to define dynamic-language-backed beans, you
|
||||
need to have the XML Schema preamble at the top of your Spring XML configuration file.
|
||||
You also need to use a Spring `ApplicationContext` implementation as your IoC
|
||||
container. Using the dynamic-language-backed beans with a plain `BeanFactory`
|
||||
implementation is supported, but you have to manage the plumbing of the Spring internals
|
||||
to do so.
|
||||
|
||||
For more information on schema-based configuration, see xref:languages/dynamic.adoc#xsd-schemas-lang[XML Schema-based Configuration]
|
||||
.
|
||||
====
|
||||
|
||||
Finally, the following example shows the bean definitions that effect the injection of the
|
||||
Groovy-defined `Messenger` implementation into an instance of the
|
||||
`DefaultBookingService` class:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
||||
|
||||
<!-- this is the bean definition for the Groovy-backed Messenger implementation -->
|
||||
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy">
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
</lang:groovy>
|
||||
|
||||
<!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger -->
|
||||
<bean id="bookingService" class="x.y.DefaultBookingService">
|
||||
<property name="messenger" ref="messenger" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The `bookingService` bean (a `DefaultBookingService`) can now use its private `messenger`
|
||||
member variable as normal, because the `Messenger` instance that was injected into it is
|
||||
a `Messenger` instance. There is nothing special going on here -- just plain Java and
|
||||
plain Groovy.
|
||||
|
||||
Hopefully, the preceding XML snippet is self-explanatory, but do not worry unduly if it is not.
|
||||
Keep reading for the in-depth detail on the whys and wherefores of the preceding configuration.
|
||||
|
||||
|
||||
[[dynamic-language-beans]]
|
||||
== Defining Beans that Are Backed by Dynamic Languages
|
||||
|
||||
This section describes exactly how you define Spring-managed beans in any of the
|
||||
supported dynamic languages.
|
||||
|
||||
Note that this chapter does not attempt to explain the syntax and idioms of the supported
|
||||
dynamic languages. For example, if you want to use Groovy to write certain of the classes
|
||||
in your application, we assume that you already know Groovy. If you need further details
|
||||
about the dynamic languages themselves, see
|
||||
xref:languages/dynamic.adoc#dynamic-language-resources[Further Resources] at the end of
|
||||
this chapter.
|
||||
|
||||
[[dynamic-language-beans-concepts]]
|
||||
=== Common Concepts
|
||||
|
||||
The steps involved in using dynamic-language-backed beans are as follows:
|
||||
|
||||
. Write the test for the dynamic language source code (naturally).
|
||||
. Then write the dynamic language source code itself.
|
||||
. Define your dynamic-language-backed beans by using the appropriate `<lang:language/>`
|
||||
element in the XML configuration (you can define such beans programmatically by
|
||||
using the Spring API, although you will have to consult the source code for
|
||||
directions on how to do this, as this chapter does not cover this type of advanced configuration).
|
||||
Note that this is an iterative step. You need at least one bean definition for each dynamic
|
||||
language source file (although multiple bean definitions can reference the same source file).
|
||||
|
||||
The first two steps (testing and writing your dynamic language source files) are beyond
|
||||
the scope of this chapter. See the language specification and reference manual
|
||||
for your chosen dynamic language and crack on with developing your dynamic language
|
||||
source files. You first want to read the rest of this chapter, though, as
|
||||
Spring's dynamic language support does make some (small) assumptions about the contents
|
||||
of your dynamic language source files.
|
||||
|
||||
[[dynamic-language-beans-concepts-xml-language-element]]
|
||||
==== The <lang:language/> element
|
||||
|
||||
The final step in the list in the xref:languages/dynamic.adoc#dynamic-language-beans-concepts[preceding section]
|
||||
involves defining dynamic-language-backed bean definitions, one for each bean that you
|
||||
want to configure (this is no different from normal JavaBean configuration). However,
|
||||
instead of specifying the fully qualified class name of the class that is to be
|
||||
instantiated and configured by the container, you can use the `<lang:language/>`
|
||||
element to define the dynamic language-backed bean.
|
||||
|
||||
Each of the supported languages has a corresponding `<lang:language/>` element:
|
||||
|
||||
* `<lang:groovy/>` (Groovy)
|
||||
* `<lang:bsh/>` (BeanShell)
|
||||
* `<lang:std/>` (JSR-223, for example, with JRuby)
|
||||
|
||||
The exact attributes and child elements that are available for configuration depends on
|
||||
exactly which language the bean has been defined in (the language-specific sections
|
||||
later in this chapter detail this).
|
||||
|
||||
[[dynamic-language-refreshable-beans]]
|
||||
==== Refreshable Beans
|
||||
|
||||
One of the (and perhaps the single) most compelling value adds of the dynamic language
|
||||
support in Spring is the "`refreshable bean`" feature.
|
||||
|
||||
A refreshable bean is a dynamic-language-backed bean. With a small amount of
|
||||
configuration, a dynamic-language-backed bean can monitor changes in its underlying
|
||||
source file resource and then reload itself when the dynamic language source file is
|
||||
changed (for example, when you edit and save changes to the file on the file system).
|
||||
|
||||
This lets you deploy any number of dynamic language source files as part of an
|
||||
application, configure the Spring container to create beans backed by dynamic
|
||||
language source files (using the mechanisms described in this chapter), and (later,
|
||||
as requirements change or some other external factor comes into play) edit a dynamic
|
||||
language source file and have any change they make be reflected in the bean that is
|
||||
backed by the changed dynamic language source file. There is no need to shut down a
|
||||
running application (or redeploy in the case of a web application). The
|
||||
dynamic-language-backed bean so amended picks up the new state and logic from the
|
||||
changed dynamic language source file.
|
||||
|
||||
NOTE: This feature is off by default.
|
||||
|
||||
Now we can take a look at an example to see how easy it is to start using refreshable
|
||||
beans. To turn on the refreshable beans feature, you have to specify exactly one
|
||||
additional attribute on the `<lang:language/>` element of your bean definition. So,
|
||||
if we stick with xref:languages/dynamic.adoc#dynamic-language-a-first-example[the example] from earlier in
|
||||
this chapter, the following example shows what we would change in the Spring XML
|
||||
configuration to effect refreshable beans:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<!-- this bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute -->
|
||||
<lang:groovy id="messenger"
|
||||
refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks -->
|
||||
script-source="classpath:Messenger.groovy">
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
</lang:groovy>
|
||||
|
||||
<bean id="bookingService" class="x.y.DefaultBookingService">
|
||||
<property name="messenger" ref="messenger" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
That really is all you have to do. The `refresh-check-delay` attribute defined on the
|
||||
`messenger` bean definition is the number of milliseconds after which the bean is
|
||||
refreshed with any changes made to the underlying dynamic language source file.
|
||||
You can turn off the refresh behavior by assigning a negative value to the
|
||||
`refresh-check-delay` attribute. Remember that, by default, the refresh behavior is
|
||||
disabled. If you do not want the refresh behavior, do not define the attribute.
|
||||
|
||||
If we then run the following application, we can exercise the refreshable feature.
|
||||
(Please excuse the "`jumping-through-hoops-to-pause-the-execution`" shenanigans
|
||||
in this next slice of code.) The `System.in.read()` call is only there so that the
|
||||
execution of the program pauses while you (the developer in this scenario) go off
|
||||
and edit the underlying dynamic language source file so that the refresh triggers
|
||||
on the dynamic-language-backed bean when the program resumes execution.
|
||||
|
||||
The following listing shows this sample application:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.scripting.Messenger;
|
||||
|
||||
public final class Boot {
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
||||
Messenger messenger = (Messenger) ctx.getBean("messenger");
|
||||
System.out.println(messenger.getMessage());
|
||||
// pause execution while I go off and make changes to the source file...
|
||||
System.in.read();
|
||||
System.out.println(messenger.getMessage());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Assume then, for the purposes of this example, that all calls to the `getMessage()`
|
||||
method of `Messenger` implementations have to be changed such that the message is
|
||||
surrounded by quotation marks. The following listing shows the changes that you
|
||||
(the developer) should make to the `Messenger.groovy` source file when the
|
||||
execution of the program is paused:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting
|
||||
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
private String message = "Bingo"
|
||||
|
||||
public String getMessage() {
|
||||
// change the implementation to surround the message in quotes
|
||||
return "'" + this.message + "'"
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
When the program runs, the output before the input pause will be `I Can Do The Frug`.
|
||||
After the change to the source file is made and saved and the program resumes execution,
|
||||
the result of calling the `getMessage()` method on the dynamic-language-backed
|
||||
`Messenger` implementation is `'I Can Do The Frug'` (notice the inclusion of the
|
||||
additional quotation marks).
|
||||
|
||||
Changes to a script do not trigger a refresh if the changes occur within the window of
|
||||
the `refresh-check-delay` value. Changes to the script are not actually picked up until
|
||||
a method is called on the dynamic-language-backed bean. It is only when a method is
|
||||
called on a dynamic-language-backed bean that it checks to see if its underlying script
|
||||
source has changed. Any exceptions that relate to refreshing the script (such as
|
||||
encountering a compilation error or finding that the script file has been deleted)
|
||||
results in a fatal exception being propagated to the calling code.
|
||||
|
||||
The refreshable bean behavior described earlier does not apply to dynamic language
|
||||
source files defined with the `<lang:inline-script/>` element notation (see
|
||||
xref:languages/dynamic.adoc#dynamic-language-beans-inline[Inline Dynamic Language Source Files]).
|
||||
Additionally, it applies only to beans where changes to the underlying source file can
|
||||
actually be detected (for example, by code that checks the last modified date of a
|
||||
dynamic language source file that exists on the file system).
|
||||
|
||||
[[dynamic-language-beans-inline]]
|
||||
==== Inline Dynamic Language Source Files
|
||||
|
||||
The dynamic language support can also cater to dynamic language source files that are
|
||||
embedded directly in Spring bean definitions. More specifically, the
|
||||
`<lang:inline-script/>` element lets you define dynamic language source immediately
|
||||
inside a Spring configuration file. An example might clarify how the inline script
|
||||
feature works:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:groovy id="messenger">
|
||||
<lang:inline-script>
|
||||
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
class GroovyMessenger implements Messenger {
|
||||
String message
|
||||
}
|
||||
|
||||
</lang:inline-script>
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
</lang:groovy>
|
||||
----
|
||||
|
||||
If we put to one side the issues surrounding whether it is good practice to define
|
||||
dynamic language source inside a Spring configuration file, the `<lang:inline-script/>`
|
||||
element can be useful in some scenarios. For instance, we might want to quickly add a
|
||||
Spring `Validator` implementation to a Spring MVC `Controller`. This is but a moment's
|
||||
work using inline source. (See
|
||||
xref:languages/dynamic.adoc#dynamic-language-scenarios-validators[Scripted Validators]
|
||||
for such an example.)
|
||||
|
||||
[[dynamic-language-beans-ctor-injection]]
|
||||
==== Understanding Constructor Injection in the Context of Dynamic-language-backed Beans
|
||||
|
||||
There is one very important thing to be aware of with regard to Spring's dynamic
|
||||
language support. Namely, you can not (currently) supply constructor arguments
|
||||
to dynamic-language-backed beans (and, hence, constructor-injection is not available for
|
||||
dynamic-language-backed beans). In the interests of making this special handling of
|
||||
constructors and properties 100% clear, the following mixture of code and configuration
|
||||
does not work:
|
||||
|
||||
.An approach that cannot work
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// from the file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
GroovyMessenger() {}
|
||||
|
||||
// this constructor is not available for Constructor Injection
|
||||
GroovyMessenger(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
String message
|
||||
|
||||
String anotherMessage
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:groovy id="badMessenger"
|
||||
script-source="classpath:Messenger.groovy">
|
||||
<!-- this next constructor argument will not be injected into the GroovyMessenger -->
|
||||
<!-- in fact, this isn't even allowed according to the schema -->
|
||||
<constructor-arg value="This will not work" />
|
||||
|
||||
<!-- only property values are injected into the dynamic-language-backed object -->
|
||||
<lang:property name="anotherMessage" value="Passed straight through to the dynamic-language-backed object" />
|
||||
|
||||
</lang>
|
||||
----
|
||||
|
||||
In practice this limitation is not as significant as it first appears, since setter
|
||||
injection is the injection style favored by the overwhelming majority of developers
|
||||
(we leave the discussion as to whether that is a good thing to another day).
|
||||
|
||||
[[dynamic-language-beans-groovy]]
|
||||
=== Groovy Beans
|
||||
|
||||
This section describes how to use beans defined in Groovy in Spring.
|
||||
|
||||
The Groovy homepage includes the following description:
|
||||
|
||||
"`Groovy is an agile dynamic language for the Java 2 Platform that has many of the
|
||||
features that people like so much in languages like Python, Ruby and Smalltalk, making
|
||||
them available to Java developers using a Java-like syntax.`"
|
||||
|
||||
If you have read this chapter straight from the top, you have already
|
||||
xref:languages/dynamic.adoc#dynamic-language-a-first-example[seen an example] of a Groovy-dynamic-language-backed
|
||||
bean. Now consider another example (again using an example from the Spring test suite):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public interface Calculator {
|
||||
|
||||
int add(int x, int y);
|
||||
}
|
||||
----
|
||||
|
||||
The following example implements the `Calculator` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// from the file 'calculator.groovy'
|
||||
class GroovyCalculator implements Calculator {
|
||||
|
||||
int add(int x, int y) {
|
||||
x + y
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following bean definition uses the calculator defined in Groovy:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- from the file 'beans.xml' -->
|
||||
<beans>
|
||||
<lang:groovy id="calculator" script-source="classpath:calculator.groovy"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
Finally, the following small application exercises the preceding configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
||||
Calculator calc = ctx.getBean("calculator", Calculator.class);
|
||||
System.out.println(calc.add(2, 8));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The resulting output from running the above program is (unsurprisingly) `10`.
|
||||
(For more interesting examples, see the dynamic language showcase project for a more
|
||||
complex example or see the examples xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios] later in this chapter).
|
||||
|
||||
You must not define more than one class per Groovy source file. While this is perfectly
|
||||
legal in Groovy, it is (arguably) a bad practice. In the interests of a consistent
|
||||
approach, you should (in the opinion of the Spring team) respect the standard Java
|
||||
conventions of one (public) class per source file.
|
||||
|
||||
[[dynamic-language-beans-groovy-customizer]]
|
||||
==== Customizing Groovy Objects by Using a Callback
|
||||
|
||||
The `GroovyObjectCustomizer` interface is a callback that lets you hook additional
|
||||
creation logic into the process of creating a Groovy-backed bean. For example,
|
||||
implementations of this interface could invoke any required initialization methods,
|
||||
set some default property values, or specify a custom `MetaClass`. The following listing
|
||||
shows the `GroovyObjectCustomizer` interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface GroovyObjectCustomizer {
|
||||
|
||||
void customize(GroovyObject goo);
|
||||
}
|
||||
----
|
||||
|
||||
The Spring Framework instantiates an instance of your Groovy-backed bean and then
|
||||
passes the created `GroovyObject` to the specified `GroovyObjectCustomizer` (if one
|
||||
has been defined). You can do whatever you like with the supplied `GroovyObject`
|
||||
reference. We expect that most people want to set a custom `MetaClass` with this
|
||||
callback, and the following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer {
|
||||
|
||||
public void customize(GroovyObject goo) {
|
||||
DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) {
|
||||
|
||||
public Object invokeMethod(Object object, String methodName, Object[] arguments) {
|
||||
System.out.println("Invoking '" + methodName + "'.");
|
||||
return super.invokeMethod(object, methodName, arguments);
|
||||
}
|
||||
};
|
||||
metaClass.initialize();
|
||||
goo.setMetaClass(metaClass);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
A full discussion of meta-programming in Groovy is beyond the scope of the Spring
|
||||
reference manual. See the relevant section of the Groovy reference manual or do a
|
||||
search online. Plenty of articles address this topic. Actually, making use of a
|
||||
`GroovyObjectCustomizer` is easy if you use the Spring namespace support, as the
|
||||
following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- define the GroovyObjectCustomizer just like any other bean -->
|
||||
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
|
||||
|
||||
<!-- ... and plug it into the desired Groovy bean via the 'customizer-ref' attribute -->
|
||||
<lang:groovy id="calculator"
|
||||
script-source="classpath:org/springframework/scripting/groovy/Calculator.groovy"
|
||||
customizer-ref="tracingCustomizer"/>
|
||||
----
|
||||
|
||||
If you do not use the Spring namespace support, you can still use the
|
||||
`GroovyObjectCustomizer` functionality, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="calculator" class="org.springframework.scripting.groovy.GroovyScriptFactory">
|
||||
<constructor-arg value="classpath:org/springframework/scripting/groovy/Calculator.groovy"/>
|
||||
<!-- define the GroovyObjectCustomizer (as an inner bean) -->
|
||||
<constructor-arg>
|
||||
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
|
||||
----
|
||||
|
||||
NOTE: You may also specify a Groovy `CompilationCustomizer` (such as an `ImportCustomizer`)
|
||||
or even a full Groovy `CompilerConfiguration` object in the same place as Spring's
|
||||
`GroovyObjectCustomizer`. Furthermore, you may set a common `GroovyClassLoader` with custom
|
||||
configuration for your beans at the `ConfigurableApplicationContext.setClassLoader` level;
|
||||
this also leads to shared `GroovyClassLoader` usage and is therefore recommendable in case of
|
||||
a large number of scripted beans (avoiding an isolated `GroovyClassLoader` instance per bean).
|
||||
|
||||
[[dynamic-language-beans-bsh]]
|
||||
=== BeanShell Beans
|
||||
|
||||
This section describes how to use BeanShell beans in Spring.
|
||||
|
||||
The https://beanshell.github.io/intro.html[BeanShell homepage] includes the following
|
||||
description:
|
||||
|
||||
----
|
||||
BeanShell is a small, free, embeddable Java source interpreter with dynamic language
|
||||
features, written in Java. BeanShell dynamically runs standard Java syntax and
|
||||
extends it with common scripting conveniences such as loose types, commands, and method
|
||||
closures like those in Perl and JavaScript.
|
||||
----
|
||||
|
||||
In contrast to Groovy, BeanShell-backed bean definitions require some (small) additional
|
||||
configuration. The implementation of the BeanShell dynamic language support in Spring is
|
||||
interesting, because Spring creates a JDK dynamic proxy that implements all of the
|
||||
interfaces that are specified in the `script-interfaces` attribute value of the
|
||||
`<lang:bsh>` element (this is why you must supply at least one interface in the value
|
||||
of the attribute, and, consequently, program to interfaces when you use BeanShell-backed
|
||||
beans). This means that every method call on a BeanShell-backed object goes through the
|
||||
JDK dynamic proxy invocation mechanism.
|
||||
|
||||
Now we can show a fully working example of using a BeanShell-based bean that implements
|
||||
the `Messenger` interface that was defined earlier in this chapter. We again show the
|
||||
definition of the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public interface Messenger {
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the BeanShell "`implementation`" (we use the term loosely here)
|
||||
of the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String message;
|
||||
|
||||
String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
void setMessage(String aMessage) {
|
||||
message = aMessage;
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the Spring XML that defines an "`instance`" of the above
|
||||
"`class`" (again, we use these terms very loosely here):
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh"
|
||||
script-interfaces="org.springframework.scripting.Messenger">
|
||||
|
||||
<lang:property name="message" value="Hello World!" />
|
||||
</lang:bsh>
|
||||
----
|
||||
|
||||
See xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios] for some scenarios
|
||||
where you might want to use BeanShell-based beans.
|
||||
|
||||
|
||||
[[dynamic-language-scenarios]]
|
||||
== Scenarios
|
||||
|
||||
The possible scenarios where defining Spring managed beans in a scripting language would
|
||||
be beneficial are many and varied. This section describes two possible use cases for the
|
||||
dynamic language support in Spring.
|
||||
|
||||
[[dynamic-language-scenarios-controllers]]
|
||||
=== Scripted Spring MVC Controllers
|
||||
|
||||
One group of classes that can benefit from using dynamic-language-backed beans is that
|
||||
of Spring MVC controllers. In pure Spring MVC applications, the navigational flow
|
||||
through a web application is, to a large extent, determined by code encapsulated within
|
||||
your Spring MVC controllers. As the navigational flow and other presentation layer logic
|
||||
of a web application needs to be updated to respond to support issues or changing
|
||||
business requirements, it may well be easier to effect any such required changes by
|
||||
editing one or more dynamic language source files and seeing those changes being
|
||||
immediately reflected in the state of a running application.
|
||||
|
||||
Remember that, in the lightweight architectural model espoused by projects such as
|
||||
Spring, you typically aim to have a really thin presentation layer, with all
|
||||
the meaty business logic of an application being contained in the domain and service
|
||||
layer classes. Developing Spring MVC controllers as dynamic-language-backed beans lets
|
||||
you change presentation layer logic by editing and saving text files. Any
|
||||
changes to such dynamic language source files is (depending on the configuration)
|
||||
automatically reflected in the beans that are backed by dynamic language source files.
|
||||
|
||||
NOTE: To effect this automatic "`pickup`" of any changes to dynamic-language-backed
|
||||
beans, you have to enable the "`refreshable beans`" functionality. See
|
||||
xref:languages/dynamic.adoc#dynamic-language-refreshable-beans[Refreshable Beans] for a full treatment of this feature.
|
||||
|
||||
The following example shows an `org.springframework.web.servlet.mvc.Controller` implemented
|
||||
by using the Groovy dynamic language:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.showcase.fortune.web
|
||||
|
||||
import org.springframework.showcase.fortune.service.FortuneService
|
||||
import org.springframework.showcase.fortune.domain.Fortune
|
||||
import org.springframework.web.servlet.ModelAndView
|
||||
import org.springframework.web.servlet.mvc.Controller
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
|
||||
// from the file '/WEB-INF/groovy/FortuneController.groovy'
|
||||
class FortuneController implements Controller {
|
||||
|
||||
@Property FortuneService fortuneService
|
||||
|
||||
ModelAndView handleRequest(HttpServletRequest request,
|
||||
HttpServletResponse httpServletResponse) {
|
||||
return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:groovy id="fortune"
|
||||
refresh-check-delay="3000"
|
||||
script-source="/WEB-INF/groovy/FortuneController.groovy">
|
||||
<lang:property name="fortuneService" ref="fortuneService"/>
|
||||
</lang:groovy>
|
||||
----
|
||||
|
||||
[[dynamic-language-scenarios-validators]]
|
||||
=== Scripted Validators
|
||||
|
||||
Another area of application development with Spring that may benefit from the
|
||||
flexibility afforded by dynamic-language-backed beans is that of validation. It can
|
||||
be easier to express complex validation logic by using a loosely typed dynamic language
|
||||
(that may also have support for inline regular expressions) as opposed to regular Java.
|
||||
|
||||
Again, developing validators as dynamic-language-backed beans lets you change
|
||||
validation logic by editing and saving a simple text file. Any such changes is
|
||||
(depending on the configuration) automatically reflected in the execution of a
|
||||
running application and would not require the restart of an application.
|
||||
|
||||
NOTE: To effect the automatic "`pickup`" of any changes to dynamic-language-backed
|
||||
beans, you have to enable the 'refreshable beans' feature. See
|
||||
xref:languages/dynamic.adoc#dynamic-language-refreshable-beans[Refreshable Beans]
|
||||
for a full and detailed treatment of this feature.
|
||||
|
||||
The following example shows a Spring `org.springframework.validation.Validator`
|
||||
implemented by using the Groovy dynamic language (see
|
||||
xref:core/validation/validator.adoc[Validation using Spring’s Validator interface]
|
||||
for a discussion of the `Validator` interface):
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.validation.Validator
|
||||
import org.springframework.validation.Errors
|
||||
import org.springframework.beans.TestBean
|
||||
|
||||
class TestBeanValidator implements Validator {
|
||||
|
||||
boolean supports(Class clazz) {
|
||||
return TestBean.class.isAssignableFrom(clazz)
|
||||
}
|
||||
|
||||
void validate(Object bean, Errors errors) {
|
||||
if(bean.name?.trim()?.size() > 0) {
|
||||
return
|
||||
}
|
||||
errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[dynamic-language-final-notes]]
|
||||
== Additional Details
|
||||
|
||||
This last section contains some additional details related to the dynamic language support.
|
||||
|
||||
[[dynamic-language-final-notes-aop]]
|
||||
=== AOP -- Advising Scripted Beans
|
||||
|
||||
You can use the Spring AOP framework to advise scripted beans. The Spring AOP
|
||||
framework actually is unaware that a bean that is being advised might be a scripted
|
||||
bean, so all of the AOP use cases and functionality that you use (or aim to use)
|
||||
work with scripted beans. When you advise scripted beans, you cannot use class-based
|
||||
proxies. You must use xref:core/aop/proxying.adoc[interface-based proxies].
|
||||
|
||||
You are not limited to advising scripted beans. You can also write aspects themselves
|
||||
in a supported dynamic language and use such beans to advise other Spring beans.
|
||||
This really would be an advanced use of the dynamic language support though.
|
||||
|
||||
[[dynamic-language-final-notes-scopes]]
|
||||
=== Scoping
|
||||
|
||||
In case it is not immediately obvious, scripted beans can be scoped in the same way as
|
||||
any other bean. The `scope` attribute on the various `<lang:language/>` elements lets
|
||||
you control the scope of the underlying scripted bean, as it does with a regular
|
||||
bean. (The default scope is xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton],
|
||||
as it is with "`regular`" beans.)
|
||||
|
||||
The following example uses the `scope` attribute to define a Groovy bean scoped as
|
||||
a xref:core/beans/factory-scopes.adoc#beans-factory-scopes-prototype[prototype]:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
||||
|
||||
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy" scope="prototype">
|
||||
<lang:property name="message" value="I Can Do The RoboCop" />
|
||||
</lang:groovy>
|
||||
|
||||
<bean id="bookingService" class="x.y.DefaultBookingService">
|
||||
<property name="messenger" ref="messenger" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
See xref:core/beans/factory-scopes.adoc[Bean Scopes] in
|
||||
xref:web/webmvc-view/mvc-xslt.adoc#mvc-view-xslt-beandefs[The IoC Container]
|
||||
for a full discussion of the scoping support in the Spring Framework.
|
||||
|
||||
[[xsd-schemas-lang]]
|
||||
=== The `lang` XML schema
|
||||
|
||||
The `lang` elements in Spring XML configuration deal with exposing objects that have been
|
||||
written in a dynamic language (such as Groovy or BeanShell) as beans in the Spring container.
|
||||
|
||||
These elements (and the dynamic language support) are comprehensively covered in
|
||||
xref:languages/dynamic.adoc[Dynamic Language Support]. See that section
|
||||
for full details on this support and the `lang` elements.
|
||||
|
||||
To use the elements in the `lang` schema, you need to have the following preamble at the
|
||||
top of your Spring XML configuration file. The text in the following snippet references
|
||||
the correct schema so that the tags in the `lang` namespace are available to you:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[dynamic-language-resources]]
|
||||
== Further Resources
|
||||
|
||||
The following links go to further resources about the various dynamic languages referenced
|
||||
in this chapter:
|
||||
|
||||
* The https://www.groovy-lang.org/[Groovy] homepage
|
||||
* The https://beanshell.github.io/intro.html[BeanShell] homepage
|
||||
* The https://www.jruby.org[JRuby] homepage
|
||||
@@ -6,37 +6,9 @@ Groovy is a powerful, optionally typed, and dynamic language, with static-typing
|
||||
compilation capabilities. It offers a concise syntax and integrates smoothly with any
|
||||
existing Java application.
|
||||
|
||||
[[beans-factory-groovy]]
|
||||
== The Groovy Bean Definition DSL
|
||||
|
||||
The Spring Framework provides a dedicated `ApplicationContext` that supports a Groovy-based
|
||||
Bean Definition DSL, as known from the Grails framework.
|
||||
|
||||
Typically, such configuration live in a ".groovy" file with the structure shown in the
|
||||
following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
beans {
|
||||
dataSource(BasicDataSource) {
|
||||
driverClassName = "org.hsqldb.jdbcDriver"
|
||||
url = "jdbc:hsqldb:mem:grailsDB"
|
||||
username = "sa"
|
||||
password = ""
|
||||
settings = [mynew:"setting"]
|
||||
}
|
||||
sessionFactory(SessionFactory) {
|
||||
dataSource = dataSource
|
||||
}
|
||||
myService(MyService) {
|
||||
nestedBean = { AnotherBean bean ->
|
||||
dataSource = dataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This configuration style is largely equivalent to XML bean definitions and even
|
||||
supports Spring's XML configuration namespaces. It also allows for importing XML
|
||||
bean definition files through an `importBeans` directive.
|
||||
Bean Definition DSL. For more details, see
|
||||
xref:core/beans/basics.adoc#beans-factory-groovy[The Groovy Bean Definition DSL].
|
||||
|
||||
Further support for Groovy, including beans written in Groovy, refreshable script beans,
|
||||
and more is available in xref:languages/dynamic.adoc[Dynamic Language Support].
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
[[kotlin-bean-definition-dsl]]
|
||||
= Bean Definition DSL
|
||||
|
||||
Spring Framework supports registering beans in a functional way by using lambdas
|
||||
as an alternative to XML or Java configuration (`@Configuration` and `@Bean`). In a nutshell,
|
||||
it lets you register beans with a lambda that acts as a `FactoryBean`.
|
||||
This mechanism is very efficient, as it does not require any reflection or CGLIB proxies.
|
||||
|
||||
In Java, you can, for example, write the following:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
class Foo {}
|
||||
|
||||
class Bar {
|
||||
private final Foo foo;
|
||||
public Bar(Foo foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
}
|
||||
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBean(Foo.class);
|
||||
context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class)));
|
||||
----
|
||||
|
||||
In Kotlin, with reified type parameters and `GenericApplicationContext` Kotlin extensions,
|
||||
you can instead write the following:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
class Foo
|
||||
|
||||
class Bar(private val foo: Foo)
|
||||
|
||||
val context = GenericApplicationContext().apply {
|
||||
registerBean<Foo>()
|
||||
registerBean { Bar(it.getBean()) }
|
||||
}
|
||||
----
|
||||
|
||||
When the class `Bar` has a single constructor, you can even just specify the bean class,
|
||||
the constructor parameters will be autowired by type:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
val context = GenericApplicationContext().apply {
|
||||
registerBean<Foo>()
|
||||
registerBean<Bar>()
|
||||
}
|
||||
----
|
||||
|
||||
In order to allow a more declarative approach and cleaner syntax, Spring Framework provides
|
||||
a {spring-framework-api-kdoc}/spring-context/org.springframework.context.support/-bean-definition-dsl/index.html[Kotlin bean definition DSL]
|
||||
It declares an `ApplicationContextInitializer` through a clean declarative API,
|
||||
which lets you deal with profiles and `Environment` for customizing
|
||||
how beans are registered.
|
||||
|
||||
In the following example notice that:
|
||||
|
||||
* Type inference usually allows to avoid specifying the type for bean references like `ref("bazBean")`
|
||||
* It is possible to use Kotlin top level functions to declare beans using callable references like `bean(::myRouter)` in this example
|
||||
* When specifying `bean<Bar>()` or `bean(::myRouter)`, parameters are autowired by type
|
||||
* The `FooBar` bean will be registered only if the `foobar` profile is active
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
class Foo
|
||||
class Bar(private val foo: Foo)
|
||||
class Baz(var message: String = "")
|
||||
class FooBar(private val baz: Baz)
|
||||
|
||||
val myBeans = beans {
|
||||
bean<Foo>()
|
||||
bean<Bar>()
|
||||
bean("bazBean") {
|
||||
Baz().apply {
|
||||
message = "Hello world"
|
||||
}
|
||||
}
|
||||
profile("foobar") {
|
||||
bean { FooBar(ref("bazBean")) }
|
||||
}
|
||||
bean(::myRouter)
|
||||
}
|
||||
|
||||
fun myRouter(foo: Foo, bar: Bar, baz: Baz) = router {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: This DSL is programmatic, meaning it allows custom registration logic of beans
|
||||
through an `if` expression, a `for` loop, or any other Kotlin constructs.
|
||||
|
||||
You can then use this `beans()` function to register beans on the application context,
|
||||
as the following example shows:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
val context = GenericApplicationContext().apply {
|
||||
myBeans.initialize(this)
|
||||
refresh()
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Spring Boot is based on JavaConfig and
|
||||
{spring-boot-issues}/8115[does not yet provide specific support for functional bean definition],
|
||||
but you can experimentally use functional bean definitions through Spring Boot's `ApplicationContextInitializer` support.
|
||||
See {stackoverflow-questions}/45935931/how-to-use-functional-bean-definition-kotlin-dsl-with-spring-boot-and-spring-w/46033685#46033685[this Stack Overflow answer]
|
||||
for more details and up-to-date information. See also the experimental Kofu DSL developed in {spring-github-org}-experimental/spring-fu[Spring Fu incubator].
|
||||
@@ -1,4 +0,0 @@
|
||||
[[kotlin-bean-registration-dsl]]
|
||||
= Bean Registration DSL
|
||||
|
||||
See xref:core/beans/java/programmatic-bean-registration.adoc[Programmatic Bean Registration].
|
||||
@@ -3,15 +3,13 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
The Spring Framework supports various Kotlin constructs, such as instantiating Kotlin classes
|
||||
through primary constructors, data binding for immutable classes, and optional parameters
|
||||
with default values for functions.
|
||||
through primary constructors, immutable classes data binding, and function optional parameters
|
||||
with default values.
|
||||
|
||||
Kotlin parameter names are recognized through a dedicated `KotlinReflectionParameterNameDiscoverer`,
|
||||
which allows finding interface method parameter names without requiring the Java `-parameters`
|
||||
compiler flag to be enabled during compilation.
|
||||
|
||||
TIP: For completeness, we nevertheless recommend running the Kotlin compiler with its
|
||||
`-java-parameters` flag for standard Java parameter exposure.
|
||||
which allows finding interface method parameter names without requiring the Java 8 `-parameters`
|
||||
compiler flag to be enabled during compilation. (For completeness, we nevertheless recommend
|
||||
running the Kotlin compiler with its `-java-parameters` flag for standard Java parameter exposure.)
|
||||
|
||||
You can declare configuration classes as
|
||||
{kotlin-docs}/nested-classes.html[top level or nested but not inner],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[[coroutines]]
|
||||
= Coroutines
|
||||
|
||||
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are instances of
|
||||
suspendable computations allowing to write non-blocking code in an imperative way. On language side,
|
||||
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are Kotlin
|
||||
lightweight threads allowing to write non-blocking code in an imperative way. On language side,
|
||||
suspending functions provides an abstraction for asynchronous operations while on library side
|
||||
{kotlin-github-org}/kotlinx.coroutines[kotlinx.coroutines] provides functions like
|
||||
{kotlin-coroutines-api}/kotlinx-coroutines-core/kotlinx.coroutines/async.html[`async { }`]
|
||||
@@ -250,29 +250,3 @@ For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[coroutines.propagation]]
|
||||
== Context Propagation
|
||||
|
||||
Spring applications are xref:integration/observability.adoc[instrumented with Micrometer for Observability support].
|
||||
For tracing support, the current observation is propagated through a `ThreadLocal` for blocking code,
|
||||
or the Reactor `Context` for reactive pipelines. But the current observation also needs to be made available
|
||||
in the execution context of a suspended function. Without that, the current "traceId" will not be automatically
|
||||
prepended to logged statements from coroutines.
|
||||
|
||||
The {spring-framework-api-kdoc}/spring-core/org.springframework.core/-propagation-context-element/index.html[`PropagationContextElement`] operator generally ensures that the
|
||||
{micrometer-context-propagation-docs}/[Micrometer Context Propagation library] works with Kotlin Coroutines.
|
||||
|
||||
It requires the `io.micrometer:context-propagation` dependency and optionally the
|
||||
`org.jetbrains.kotlinx:kotlinx-coroutines-reactor` one. Automatic context propagation via
|
||||
`CoroutinesUtils#invokeSuspendingFunction` (used by Spring to adapt Coroutines to Reactor `Flux` or `Mono`) can be
|
||||
enabled by invoking `Hooks.enableAutomaticContextPropagation()`.
|
||||
|
||||
Applications can also use `PropagationContextElement` explicitly to augment the `CoroutineContext`
|
||||
with the context propagation mechanism:
|
||||
|
||||
include-code::./ContextPropagationSample[tag=context,indent=0]
|
||||
|
||||
Here, assuming that Micrometer Tracing is configured, the resulting logging statement will show the current "traceId"
|
||||
and unlock better observability for your application.
|
||||
|
||||
|
||||
@@ -5,11 +5,30 @@ One of Kotlin's key features is {kotlin-docs}/null-safety.html[null-safety],
|
||||
which cleanly deals with `null` values at compile time rather than bumping into the famous
|
||||
`NullPointerException` at runtime. This makes applications safer through nullability
|
||||
declarations and expressing "`value or no value`" semantics without paying the cost of wrappers, such as `Optional`.
|
||||
Kotlin allows using functional constructs with nullable values. See this
|
||||
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].
|
||||
(Kotlin allows using functional constructs with nullable values. See this
|
||||
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].)
|
||||
|
||||
Although Java does not let you express null-safety in its type-system, the Spring Framework
|
||||
provides xref:core/null-safety.adoc[null-safety of the whole Spring Framework API]
|
||||
via tooling-friendly https://jspecify.dev/[JSpecify] annotations.
|
||||
provides xref:languages/kotlin/null-safety.adoc[null-safety of the whole Spring Framework API]
|
||||
via tooling-friendly annotations declared in the `org.springframework.lang` package.
|
||||
By default, types from Java APIs used in Kotlin are recognized as
|
||||
{kotlin-docs}/java-interop.html#null-safety-and-platform-types[platform types],
|
||||
for which null-checks are relaxed.
|
||||
{kotlin-docs}/java-interop.html#jsr-305-support[Kotlin support for JSR-305 annotations]
|
||||
and Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers,
|
||||
with the advantage of dealing with `null`-related issues at compile time.
|
||||
|
||||
As of Kotlin 2.1, Kotlin enforces strict handling of nullability annotations from `org.jspecify.annotations` package.
|
||||
NOTE: Libraries such as Reactor or Spring Data provide null-safe APIs to leverage this feature.
|
||||
|
||||
You can configure JSR-305 checks by adding the `-Xjsr305` compiler flag with the following
|
||||
options: `-Xjsr305={strict|warn|ignore}`.
|
||||
|
||||
For kotlin versions 1.1+, the default behavior is the same as `-Xjsr305=warn`.
|
||||
The `strict` value is required to have Spring Framework API null-safety taken into account
|
||||
in Kotlin types inferred from Spring API but should be used with the knowledge that Spring
|
||||
API nullability declaration could evolve even between minor releases and that more checks may
|
||||
be added in the future.
|
||||
|
||||
NOTE: Generic type arguments, varargs, and array elements nullability are not supported yet,
|
||||
but should be in an upcoming release. See {kotlin-github-org}/KEEP/issues/79[this discussion]
|
||||
for up-to-date information.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= Requirements
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Framework supports Kotlin 2.2+ and requires
|
||||
Spring Framework supports Kotlin 1.7+ and requires
|
||||
https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`]
|
||||
and https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`]
|
||||
to be present on the classpath. They are provided by default if you bootstrap a Kotlin project on
|
||||
|
||||
@@ -319,7 +319,7 @@ progresses.
|
||||
== Testing
|
||||
|
||||
This section addresses testing with the combination of Kotlin and Spring Framework.
|
||||
The recommended testing framework is https://junit.org/[JUnit] along with
|
||||
The recommended testing framework is https://junit.org/[JUnit Jupiter] along with
|
||||
https://mockk.io/[Mockk] for mocking.
|
||||
|
||||
[TIP]
|
||||
@@ -337,7 +337,7 @@ NOTE: If you are using Spring Boot, see
|
||||
=== Constructor injection
|
||||
|
||||
As described in the xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[dedicated section],
|
||||
JUnit Jupiter allows constructor injection of beans which is pretty useful with Kotlin
|
||||
JUnit Jupiter (JUnit 5) allows constructor injection of beans which is pretty useful with Kotlin
|
||||
in order to use `val` instead of `lateinit var`. You can use
|
||||
{spring-framework-api}/test/context/TestConstructor.html[`@TestConstructor(autowireMode = AutowireMode.ALL)`]
|
||||
to enable autowiring for all parameters.
|
||||
@@ -360,7 +360,7 @@ file with a `spring.test.constructor.autowire.mode = all` property.
|
||||
[[per_class-lifecycle]]
|
||||
=== `PER_CLASS` Lifecycle
|
||||
|
||||
With JUnit Jupiter, Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
||||
With JUnit Jupiter (JUnit 5), Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
||||
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
|
||||
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
|
||||
|
||||
@@ -403,8 +403,8 @@ class IntegrationTests {
|
||||
[[specification-like-tests]]
|
||||
=== Specification-like Tests
|
||||
|
||||
You can create specification-like tests with Kotlin and JUnit Jupiter's `@Nested` test
|
||||
class support. The following example shows how to do so:
|
||||
You can create specification-like tests with JUnit 5 and Kotlin.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
|
||||
@@ -72,13 +72,61 @@ idiomatic Kotlin API and to allow better discoverability (no usage of static met
|
||||
----
|
||||
|
||||
|
||||
[[kotlin-script-templates]]
|
||||
== Kotlin Script Templates
|
||||
|
||||
Spring Framework provides a
|
||||
{spring-framework-api}/web/servlet/view/script/ScriptTemplateView.html[`ScriptTemplateView`]
|
||||
which supports {JSR}223[JSR-223] to render templates by using script engines.
|
||||
|
||||
By leveraging `scripting-jsr223` dependencies, it
|
||||
is possible to use such feature to render Kotlin-based templates with
|
||||
{kotlin-github-org}/kotlinx.html[kotlinx.html] DSL or Kotlin multiline interpolated `String`.
|
||||
|
||||
`build.gradle.kts`
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
dependencies {
|
||||
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
|
||||
}
|
||||
----
|
||||
|
||||
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans.
|
||||
|
||||
`KotlinScriptConfiguration.kt`
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
class KotlinScriptConfiguration {
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "kotlin"
|
||||
setScripts("scripts/render.kts")
|
||||
renderFunction = "render"
|
||||
isSharedEngine = false
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
|
||||
setPrefix("templates/")
|
||||
setSuffix(".kts")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
|
||||
project for more details.
|
||||
|
||||
|
||||
[[kotlin-multiplatform-serialization]]
|
||||
== Kotlin multiplatform serialization
|
||||
|
||||
{kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
|
||||
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The builtin support currently targets CBOR, JSON,
|
||||
and ProtoBuf formats.
|
||||
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The built-in support currently targets CBOR, JSON, and ProtoBuf formats.
|
||||
|
||||
To enable it, follow {kotlin-github-org}/kotlinx.serialization#setup[those instructions] to add the related dependencies
|
||||
and plugin. With Spring MVC and WebFlux, Kotlin serialization is configured by default if it is in the classpath and
|
||||
other variants like Jackson are not. If needed, configure the converters or codecs manually.
|
||||
To enable it, follow {kotlin-github-org}/kotlinx.serialization#setup[those instructions] to add the related dependency and plugin.
|
||||
With Spring MVC and WebFlux, both Kotlin serialization and Jackson will be configured by default if they are in the classpath since
|
||||
Kotlin serialization is designed to serialize only Kotlin classes annotated with `@Serializable`.
|
||||
With Spring Messaging (RSocket), make sure that neither Jackson, GSON or JSONB are in the classpath if you want automatic configuration,
|
||||
if Jackson is needed configure `KotlinSerializationJsonMessageConverter` manually.
|
||||
|
||||
@@ -73,7 +73,7 @@ As of Spring Framework 6.0, Spring has been upgraded to the Jakarta EE 9 level
|
||||
traditional `javax` packages. With EE 9 as the minimum and EE 10 supported already,
|
||||
Spring is prepared to provide out-of-the-box support for the further evolution of
|
||||
the Jakarta EE APIs. Spring Framework 6.0 is fully compatible with Tomcat 10.1,
|
||||
Jetty 11 as web servers, and also with Hibernate ORM 6.1.
|
||||
Jetty 11 and Undertow 2.3 as web servers, and also with Hibernate ORM 6.1.
|
||||
|
||||
Over time, the role of Java/Jakarta EE in application development has evolved. In the
|
||||
early days of J2EE and Spring, applications were created to be deployed to an application
|
||||
|
||||
@@ -237,8 +237,8 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RSocketStrategies strategies = RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.build();
|
||||
|
||||
RSocketRequester requester = RSocketRequester.builder()
|
||||
@@ -251,8 +251,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val strategies = RSocketStrategies.builder()
|
||||
.encoders { it.add(JacksonCborEncoder()) }
|
||||
.decoders { it.add(JacksonCborDecoder()) }
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.build()
|
||||
|
||||
val requester = RSocketRequester.builder()
|
||||
@@ -681,8 +681,8 @@ Java::
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.routeMatcher(new PathPatternRouteMatcher())
|
||||
.build();
|
||||
}
|
||||
@@ -703,8 +703,8 @@ Kotlin::
|
||||
|
||||
@Bean
|
||||
fun rsocketStrategies() = RSocketStrategies.builder()
|
||||
.encoders { it.add(JacksonCborEncoder()) }
|
||||
.decoders { it.add(JacksonCborDecoder()) }
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.routeMatcher(PathPatternRouteMatcher())
|
||||
.build()
|
||||
}
|
||||
|
||||
+5
-56
@@ -2,10 +2,9 @@
|
||||
= Spring JUnit Jupiter Testing Annotations
|
||||
|
||||
The following annotations are supported when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and the JUnit Jupiter testing framework:
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`] and JUnit Jupiter
|
||||
(that is, the programming model in JUnit 5):
|
||||
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitwebconfig[`@SpringJUnitWebConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
|
||||
@@ -15,56 +14,6 @@ and the JUnit Jupiter testing framework:
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
|
||||
|
||||
[[integration-testing-annotations-springextensionconfig]]
|
||||
== `@SpringExtensionConfig`
|
||||
|
||||
`@SpringExtensionConfig` is a type-level annotation that can be used to configure the
|
||||
behavior of the `SpringExtension`.
|
||||
|
||||
As of Spring Framework 7.0, the `SpringExtension` is configured to use a test-method
|
||||
scoped `ExtensionContext`, which enables consistent dependency injection into fields and
|
||||
constructors from the `ApplicationContext` for the current test method in a `@Nested`
|
||||
test class hierarchy. However, if a third-party `TestExecutionListener` is not compatible
|
||||
with the semantics associated with a test-method scoped extension context — or if a
|
||||
developer wishes to switch to test-class scoped semantics — the `SpringExtension` can be
|
||||
configured to use a test-class scoped `ExtensionContext` by annotating a top-level test
|
||||
class with `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
|
||||
|
||||
Alternatively, you can change the global default by setting the
|
||||
`spring.test.extension.context.scope` property to `test_class`. The property is resolved
|
||||
first via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism
|
||||
which also supports JVM system properties — for example,
|
||||
`-Dspring.test.extension.context.scope=test_class`. If the Spring property has not been
|
||||
set, the `SpringExtension` will attempt to resolve the property as a
|
||||
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter]
|
||||
as a fallback mechanism. If the property has not been set via either of those mechanisms,
|
||||
the `SpringExtension` will use a test-method scoped extension context by default. Note,
|
||||
however, that a `@SpringExtensionConfig` declaration always takes precedence over this
|
||||
property.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If a test class uses JUnit Jupiter's `@TestInstance(Lifecycle.PER_CLASS)` semantics, the
|
||||
`SpringExtension` will always use a test-class scoped `ExtensionContext`, and
|
||||
configuration via `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)` or
|
||||
the `spring.test.extension.context.scope` property will have no effect for that test
|
||||
class.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This annotation is currently only applicable to `@Nested` test class hierarchies and
|
||||
should be applied to the top-level enclosing class of a `@Nested` test class hierarchy.
|
||||
Consequently, there is no need to declare this annotation on a test class that does not
|
||||
contain `@Nested` test classes.
|
||||
|
||||
In addition,
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
|
||||
does not apply to this annotation. `@SpringExtensionConfig` will always be detected
|
||||
within a `@Nested` test class hierarchy, effectively disregarding any
|
||||
`@NestedTestConfiguration(OVERRIDE)` declarations.
|
||||
====
|
||||
|
||||
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
|
||||
== `@SpringJUnitConfig`
|
||||
|
||||
@@ -225,9 +174,9 @@ the parameters of a test class constructor are autowired from components in the
|
||||
|
||||
If `@TestConstructor` is not present or meta-present on a test class, the default _test
|
||||
constructor autowire mode_ will be used. See the tip below for details on how to change
|
||||
the default mode. Note, however, that a local declaration of `@Autowired` or
|
||||
`@jakarta.inject.Inject` on a constructor takes precedence over both `@TestConstructor`
|
||||
and the default mode.
|
||||
the default mode. Note, however, that a local declaration of `@Autowired`,
|
||||
`@jakarta.inject.Inject`, or `@javax.inject.Inject` on a constructor takes precedence
|
||||
over both `@TestConstructor` and the default mode.
|
||||
|
||||
.Changing the default test constructor autowire mode
|
||||
[TIP]
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
[[integration-testing-annotations-junit4]]
|
||||
= Spring JUnit 4 Testing Annotations
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
JUnit 4 support is deprecated since Spring Framework 7.0 in favor of the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and JUnit Jupiter.
|
||||
====
|
||||
|
||||
The following annotations are supported only when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner],
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules], or
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
= Meta-Annotation Support for Testing
|
||||
|
||||
You can use most test-related annotations as
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to
|
||||
create custom composed annotations and reduce configuration duplication across a test
|
||||
suite.
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to create custom composed
|
||||
annotations and reduce configuration duplication across a test suite.
|
||||
|
||||
For example, you can use each of the following as a meta-annotation in conjunction with
|
||||
the xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
You can use each of the following as a meta-annotation in conjunction with the
|
||||
xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
|
||||
* `@BootstrapWith`
|
||||
* `@ContextConfiguration`
|
||||
@@ -38,7 +37,111 @@ the xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
* `@EnabledIf` _(only supported on JUnit Jupiter)_
|
||||
* `@DisabledIf` _(only supported on JUnit Jupiter)_
|
||||
|
||||
Consider the following test classes that use the `SpringExtension` with JUnit Jupiter:
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public class UserRepositoryTests { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
----
|
||||
======
|
||||
|
||||
If we discover that we are repeating the preceding configuration across our JUnit 4-based
|
||||
test suite, we can reduce the duplication by introducing a custom composed annotation
|
||||
that centralizes the common test configuration for Spring, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public @interface TransactionalDevTestConfig { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
annotation class TransactionalDevTestConfig { }
|
||||
----
|
||||
======
|
||||
|
||||
Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the
|
||||
configuration of individual JUnit 4 based test classes, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@TransactionalDevTestConfig
|
||||
public class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@TransactionalDevTestConfig
|
||||
public class UserRepositoryTests { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@TransactionalDevTestConfig
|
||||
class OrderRepositoryTests
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@TransactionalDevTestConfig
|
||||
class UserRepositoryTests
|
||||
----
|
||||
======
|
||||
|
||||
If we write tests that use JUnit Jupiter, we can reduce code duplication even further,
|
||||
since annotations in JUnit 5 can also be used as meta-annotations. Consider the following
|
||||
example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -47,13 +150,13 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
@@ -64,22 +167,23 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
----
|
||||
======
|
||||
|
||||
If we discover that we are repeating the preceding configuration across our test suite,
|
||||
we can reduce the duplication by introducing a custom composed annotation that
|
||||
centralizes the common test configuration for Spring and JUnit Jupiter, as follows:
|
||||
If we discover that we are repeating the preceding configuration across our JUnit
|
||||
Jupiter-based test suite, we can reduce the duplication by introducing a custom composed
|
||||
annotation that centralizes the common test configuration for Spring and JUnit Jupiter,
|
||||
as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -90,7 +194,7 @@ Java::
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public @interface TransactionalDevTestConfig { }
|
||||
@@ -103,7 +207,7 @@ Kotlin::
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
annotation class TransactionalDevTestConfig { }
|
||||
|
||||
-7
@@ -72,13 +72,6 @@ bean definition profiles programmatically by implementing a custom
|
||||
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc#testcontext-ctx-management-env-profiles-ActiveProfilesResolver[`ActiveProfilesResolver`]
|
||||
and registering it by using the `resolver` attribute of `@ActiveProfiles`.
|
||||
|
||||
NOTE: When `@ActiveProfiles` is declared on a test class, the `spring.profiles.active`
|
||||
property (whether configured as a JVM system property or environment variable) is not
|
||||
taken into account by the TestContext Framework when determining active profiles. If
|
||||
you need to allow `spring.profiles.active` to override the profiles configured via
|
||||
`@ActiveProfiles`, you can implement a custom `ActiveProfilesResolver` as described in
|
||||
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles].
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles],
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration], and the
|
||||
{spring-framework-api}/test/context/ActiveProfiles.html[`@ActiveProfiles`] javadoc for
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user