Compare commits

..

1 Commits

Author SHA1 Message Date
Stéphane Nicoll 8f64480c9f Release v6.2.10 2025-08-14 09:15:43 +02:00
5561 changed files with 80388 additions and 126734 deletions
+1 -1
View File
@@ -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,20 +19,19 @@ inputs:
java-version:
description: 'Java version to use for the build'
required: false
default: '25'
default: '17'
runs:
using: composite
steps:
- name: Set Up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
java-version: |
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
25
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
@@ -17,14 +17,14 @@ runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
shell: bash
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
- name: Sync
uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0
uses: spring-io/central-publish-action@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.0
with:
token: ${{ inputs.central-token-password }}
token-name: ${{ inputs.central-token-username }}
-3
View File
@@ -1,3 +0,0 @@
require:
members: false
+2 -2
View File
@@ -18,9 +18,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
@@ -2,7 +2,7 @@ name: Build and Deploy Snapshot
on:
push:
branches:
- main
- 6.2.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
@@ -13,7 +13,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
@@ -21,13 +21,13 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
build-name: 'spring-framework-7.0.x'
build-name: 'spring-framework-6.2.x'
folder: 'deployment-repository'
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-snapshot-local'
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build
id: build
uses: ./.github/actions/build
@@ -19,7 +19,7 @@ jobs:
uses: ./.github/actions/print-jvm-thread-dumps
- name: Upload Build Reports
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
+5 -4
View File
@@ -1,7 +1,8 @@
name: CI
on:
schedule:
- cron: '30 9 * * *'
push:
branches:
- 6.2.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
@@ -20,7 +21,7 @@ jobs:
toolchain: false
- version: 21
toolchain: true
- version: 25
- version: 24
toolchain: true
exclude:
- os:
@@ -35,7 +36,7 @@ jobs:
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build
id: build
uses: ./.github/actions/build
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 1
ref: docs-build
-95
View File
@@ -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 }}
+6 -6
View File
@@ -2,7 +2,7 @@ name: Release
on:
push:
tags:
- v7.0.[0-9]+
- v6.2.[0-9]+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
@@ -20,7 +20,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
@@ -56,7 +56,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Sync to Maven Central
uses: ./.github/actions/sync-to-maven-central
with:
@@ -72,7 +72,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
@@ -85,7 +85,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
branch: [ '6.2.x', 'main' ]
branch: [ '6.1.x' ]
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
name: Update
+5 -5
View File
@@ -30,23 +30,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Release Verification Tests
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
ref: 'v0.0.2'
repository: spring-projects/spring-framework-release-verification
token: ${{ secrets.token }}
- name: Check Out Send Notification Action
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
path: send-notification
sparse-checkout: .github/actions/send-notification
- name: Set Up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with:
cache-read-only: false
- name: Configure Gradle Properties
@@ -64,7 +64,7 @@ jobs:
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
+1 -2
View File
@@ -38,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
@@ -54,4 +54,3 @@ atlassian-ide-plugin.xml
cached-antora-playbook.yml
node_modules
/.kotlin/
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
## Continuous Integration Builds
CI builds are defined with [GitHub Actions workflows](.github/workflows).
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
## Stay in Touch
+41 -16
View File
@@ -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 {
@@ -65,21 +76,35 @@ configure([rootProject] + javaProjects) { project ->
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/current/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.jboss.org/hibernate/orm/5.6/javadocs/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.13.4/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
// 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
View File
@@ -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
View File
@@ -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
+7 -6
View File
@@ -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>
-2
View File
@@ -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.3.0");
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";
}
}
}
@@ -1,139 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.multirelease;
import javax.inject.Inject;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.java.archives.Attributes;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
/**
* @author Cedric Champeau
* @author Brian Clozel
*/
public abstract class MultiReleaseExtension {
private final TaskContainer tasks;
private final SourceSetContainer sourceSets;
private final DependencyHandler dependencies;
private final ObjectFactory objects;
private final ConfigurationContainer configurations;
@Inject
public MultiReleaseExtension(SourceSetContainer sourceSets,
ConfigurationContainer configurations,
TaskContainer tasks,
DependencyHandler dependencies,
ObjectFactory objectFactory) {
this.sourceSets = sourceSets;
this.configurations = configurations;
this.tasks = tasks;
this.dependencies = dependencies;
this.objects = objectFactory;
}
public void releaseVersions(int... javaVersions) {
releaseVersions("src/main/", "src/test/", javaVersions);
}
private void releaseVersions(String mainSourceDirectory, String testSourceDirectory, int... javaVersions) {
for (int javaVersion : javaVersions) {
addLanguageVersion(javaVersion, mainSourceDirectory, testSourceDirectory);
}
}
private void addLanguageVersion(int javaVersion, String mainSourceDirectory, String testSourceDirectory) {
String javaN = "java" + javaVersion;
SourceSet langSourceSet = sourceSets.create(javaN, srcSet -> srcSet.getJava().srcDir(mainSourceDirectory + javaN));
SourceSet testSourceSet = sourceSets.create(javaN + "Test", srcSet -> srcSet.getJava().srcDir(testSourceDirectory + javaN));
SourceSet sharedSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
SourceSet sharedTestSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME);
FileCollection mainClasses = objects.fileCollection().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs());
dependencies.add(javaN + "Implementation", mainClasses);
tasks.named(langSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
task.getOptions().getRelease().set(javaVersion)
);
tasks.named(testSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
task.getOptions().getRelease().set(javaVersion)
);
TaskProvider<Test> testTask = createTestTask(javaVersion, testSourceSet, sharedTestSourceSet, langSourceSet, sharedSourceSet);
tasks.named("check", task -> task.dependsOn(testTask));
configureMultiReleaseJar(javaVersion, langSourceSet);
}
private TaskProvider<Test> createTestTask(int javaVersion, SourceSet testSourceSet, SourceSet sharedTestSourceSet, SourceSet langSourceSet, SourceSet sharedSourceSet) {
Configuration testImplementation = configurations.getByName(testSourceSet.getImplementationConfigurationName());
testImplementation.extendsFrom(configurations.getByName(sharedTestSourceSet.getImplementationConfigurationName()));
Configuration testCompileOnly = configurations.getByName(testSourceSet.getCompileOnlyConfigurationName());
testCompileOnly.extendsFrom(configurations.getByName(sharedTestSourceSet.getCompileOnlyConfigurationName()));
testCompileOnly.getDependencies().add(dependencies.create(langSourceSet.getOutput().getClassesDirs()));
testCompileOnly.getDependencies().add(dependencies.create(sharedSourceSet.getOutput().getClassesDirs()));
Configuration testRuntimeClasspath = configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName());
// so here's the deal. MRjars are JARs! Which means that to execute tests, we need
// the JAR on classpath, not just classes + resources as Gradle usually does
testRuntimeClasspath.getAttributes()
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
TaskProvider<Test> testTask = tasks.register("java" + javaVersion + "Test", Test.class, test -> {
test.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
ConfigurableFileCollection testClassesDirs = objects.fileCollection();
testClassesDirs.from(testSourceSet.getOutput());
testClassesDirs.from(sharedTestSourceSet.getOutput());
test.setTestClassesDirs(testClassesDirs);
ConfigurableFileCollection classpath = objects.fileCollection();
// must put the MRJar first on classpath
classpath.from(tasks.named("jar"));
// then we put the specific test sourceset tests, so that we can override
// the shared versions
classpath.from(testSourceSet.getOutput());
// then we add the shared tests
classpath.from(sharedTestSourceSet.getRuntimeClasspath());
test.setClasspath(classpath);
});
return testTask;
}
private void configureMultiReleaseJar(int version, SourceSet languageSourceSet) {
tasks.named("jar", Jar.class, jar -> {
jar.into("META-INF/versions/" + version, s -> s.from(languageSourceSet.getOutput()));
Attributes attributes = jar.getManifest().getAttributes();
attributes.put("Multi-Release", "true");
});
}
}
@@ -1,76 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.multirelease;
import javax.inject.Inject;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JavaToolchainService;
/**
* A plugin which adds support for building multi-release jars
* with Gradle.
* @author Cedric Champeau
* @author Brian Clozel
* @see <a href="https://github.com/melix/mrjar-gradle-plugin">original project</a>
*/
public class MultiReleaseJarPlugin implements Plugin<Project> {
public static String VALIDATE_JAR_TASK_NAME = "validateMultiReleaseJar";
@Inject
protected JavaToolchainService getToolchains() {
throw new UnsupportedOperationException();
}
public void apply(Project project) {
project.getPlugins().apply(JavaPlugin.class);
ExtensionContainer extensions = project.getExtensions();
JavaPluginExtension javaPluginExtension = extensions.getByType(JavaPluginExtension.class);
ConfigurationContainer configurations = project.getConfigurations();
TaskContainer tasks = project.getTasks();
DependencyHandler dependencies = project.getDependencies();
ObjectFactory objects = project.getObjects();
extensions.create("multiRelease", MultiReleaseExtension.class,
javaPluginExtension.getSourceSets(),
configurations,
tasks,
dependencies,
objects);
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
});
tasks.named("check", task -> task.dependsOn(validateJarTask));
}
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.multirelease;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.jvm.toolchain.JavaToolchainService;
import java.util.List;
import javax.inject.Inject;
@CacheableTask
public abstract class MultiReleaseJarValidateTask extends JavaExec {
public MultiReleaseJarValidateTask() {
getMainModule().set("jdk.jartool");
getArgumentProviders().add(() -> List.of("--validate", "--file", getJar().get().getAsFile().getAbsolutePath()));
}
@Inject
protected abstract JavaToolchainService getJavaToolchainService();
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
public abstract RegularFileProperty getJar();
}
@@ -78,7 +78,7 @@ public class ShadowSource extends DefaultTask {
}
@OutputDirectory
public DirectoryProperty getOutputDirectory() {
DirectoryProperty getOutputDirectory() {
return this.outputDirectory;
}
@@ -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();
}
}
+14 -20
View File
@@ -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"
}
}
+2 -4
View File
@@ -13,9 +13,7 @@ content:
- url: https://github.com/spring-projects/spring-framework
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
branches: ['main', '{6..9}.+({1..9}).x']
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
start_path: framework-docs
asciidoc:
@@ -38,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
+4 -23
View File
@@ -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,8 +12,8 @@ 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,
]
}
@@ -44,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"))
@@ -67,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")
@@ -80,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

+10 -16
View File
@@ -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,9 +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-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[]
@@ -228,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[]
@@ -261,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[]
@@ -294,9 +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-cors.adoc[]
*** xref:web/webflux-versioning.adoc[]
*** xref:web/webflux/ann-rest-exceptions.adoc[]
*** xref:web/webflux/security.adoc[]
*** xref:web/webflux/caching.adoc[]
@@ -313,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[]
@@ -330,10 +326,9 @@
*** xref:testing/testcontext-framework/application-events.adoc[]
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
*** xref:testing/testcontext-framework/ctx-management.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
@@ -344,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[]
@@ -356,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[]
@@ -440,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[]
@@ -450,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]
@@ -81,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].
@@ -129,10 +124,6 @@ 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
@@ -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]
@@ -104,7 +104,7 @@ Note that pointcut definitions are generally matched against any intercepted met
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
potential non-public interactions through proxies, it needs to be defined accordingly.
If your interception needs to include method calls or even constructors within the target
If your interception needs include method calls or even constructors within the target
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
with different characteristics, so be sure to make yourself familiar with weaving
@@ -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
@@ -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.
@@ -151,17 +151,17 @@ injected into a `Set<MovieCatalog>` annotated with `@Qualifier("action")`.
[TIP]
====
Letting qualifier values select against target bean names, within the type-matching
candidates, does not require a `@Qualifier` annotation at the injection point. If there
is no other resolution indicator (such as a qualifier, a primary marker, or a fallback
marker), for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and chooses the
same-named candidate, if any (either by bean name or by associated alias).
candidates, does not require a `@Qualifier` annotation at the injection point.
If there is no other resolution indicator (such as a qualifier or a primary marker),
for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and chooses
the same-named candidate, if any (either by bean name or by associated alias).
Since version 6.1, this requires the `-parameters` Java compiler flag to be present. As
of 6.2, the container applies fast shortcut resolution for bean name matches, bypassing
the full type matching algorithm when the parameter name matches the bean name and no
type, qualifier, primary, or fallback conditions override the match. It is therefore
recommendable for your parameter names to match the target bean names.
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
As of 6.2, the container applies fast shortcut resolution for bean name matches,
bypassing the full type matching algorithm when the parameter name matches the
bean name and no type, qualifier or primary conditions override the match. It is
therefore recommendable for your parameter names to match the target bean names.
====
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
@@ -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
@@ -309,13 +308,12 @@ set of multiple matches for the specific bean type (as returned by the factory m
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
through `@Order` values in combination with `@Primary` or `@Fallback` on a single bean
for each type.
through `@Order` values in combination with `@Primary` on a single bean 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 +431,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 +445,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 +477,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 +528,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.
====
@@ -27,5 +27,4 @@ with the `CustomAutowireConfigurer`
When multiple beans qualify as autowire candidates, the determination of a "`primary`" is
as follows: If exactly one bean definition among the candidates has a `primary`
attribute set to `true`, it is selected. For annotation-based configuration, see
xref:core/beans/annotation-config/autowired-primary.adoc[Fine-tuning with `@Primary` or `@Fallback`].
attribute set to `true`, it is selected.
@@ -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.
@@ -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.
@@ -50,8 +50,9 @@ XML configuration:
The preceding XML is more succinct. However, typos are discovered at runtime rather than
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
completion when you create bean definitions. Such IDE assistance is highly recommended.
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
that supports automatic property completion when you create bean definitions. Such IDE
assistance is highly recommended.
You can also configure a `java.util.Properties` instance, as follows:
@@ -84,11 +85,11 @@ element. The following example shows how to use it:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="collaborator" class="..." />
<bean id="theTargetBean" class="..."/>
<bean id="client" class="...">
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="collaborator" />
<idref bean="theTargetBean"/>
</property>
</bean>
----
@@ -98,24 +99,28 @@ following snippet:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="collaborator" class="..." />
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="collaborator" />
<bean id="theClientBean" class="...">
<property name="targetName" ref="theTargetBean"/>
</bean>
----
The first form is preferable to the second, because using the `idref` tag lets the
container validate at deployment time that the referenced, named bean actually exists. In
the second variation, no validation is performed on the value that is passed to the
`targetName` property of the `client` bean. Typos are therefore only discovered (with most
container validate at deployment time that the referenced, named bean actually
exists. In the second variation, no validation is performed on the value that is passed
to the `targetName` property of the `client` bean. Typos are only discovered (with most
likely fatal results) when the `client` bean is actually instantiated. If the `client`
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting
exception may only be discovered long after the container is deployed.
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting exception
may only be discovered long after the container is deployed.
NOTE: A common place (at least in versions earlier than Spring 2.0) where the `<idref/>`
element brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors]
in a `ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
NOTE: The `local` attribute on the `idref` element is no longer supported in the 4.0 beans
XSD, since it does not provide value over a regular `bean` reference any more. Change
your existing `idref local` references to `idref bean` when upgrading to the 4.0 schema.
A common place (at least in versions earlier than Spring 2.0) where the `<idref/>` element
brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors] in a
`ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
interceptor names prevents you from misspelling an interceptor ID.
@@ -67,13 +67,6 @@ interface, clearly indicating the post-processor nature of that bean. Otherwise,
Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the
initialization of other beans in the context, this early type detection is critical.
Furthermore, when registering a `BeanPostProcessor` via an `@Bean` factory method,
declare the method as `static` and ideally with no dependencies. Doing so avoids eager
initialization of the configuration class and other beans, which would make them
ineligible for full post-processing (such as auto-proxying). See the
"BeanPostProcessor-returning `@Bean` methods" section in the
{spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc for details.
[[beans-factory-programmatically-registering-beanpostprocessors]]
.Programmatically registering `BeanPostProcessor` instances
NOTE: While the recommended approach for `BeanPostProcessor` registration is through
@@ -87,7 +80,7 @@ of execution. Note also that `BeanPostProcessor` instances registered programmat
are always processed before those registered through auto-detection, regardless of any
explicit ordering.
.`BeanPostProcessor` instances and early initialization
.`BeanPostProcessor` instances and AOP auto-proxying
[NOTE]
====
Classes that implement the `BeanPostProcessor` interface are special and are treated
@@ -97,23 +90,17 @@ of the `ApplicationContext`. Next, all `BeanPostProcessor` instances are registe
in a sorted fashion and applied to all further beans in the container. Because AOP
auto-proxying is implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessor`
instances nor the beans they directly reference are eligible for auto-proxying and,
thus, do not have aspects woven into them. More generally, any bean that is instantiated
during this early phase is not eligible for full post-processing by all
`BeanPostProcessor` instances.
thus, do not have aspects woven into them.
For any such bean, you should see a WARN-level log message similar to the following.
For any such bean, you should see an informational log message: `Bean someBean is not
eligible for getting processed by all BeanPostProcessor interfaces (for example: not
eligible for auto-proxying)`.
[quote]
Bean 'someBean' of type [org.example.SomeType] is not eligible for getting processed by
all BeanPostProcessors (for example: not eligible for auto-proxying).
To minimize the number of beans affected, register a `BeanPostProcessor` with a
`static` `@Bean` method that has no dependencies (see the note above). If you have
beans wired into your `BeanPostProcessor` by using autowiring or `@Resource` (which
may fall back to autowiring), Spring might access unexpected beans when searching
for type-matching dependency candidates and, therefore, make them ineligible for
auto-proxying or other kinds of bean post-processing. For example, if you have a
dependency annotated with `@Resource` where the field or setter name does not
If you have beans wired into your `BeanPostProcessor` by using autowiring or
`@Resource` (which may fall back to autowiring), Spring might access unexpected beans
when searching for type-matching dependency candidates and, therefore, make them
ineligible for auto-proxying or other kinds of bean post-processing. For example, if you
have a dependency annotated with `@Resource` where the field or setter name does not
directly correspond to the declared name of a bean and no name attribute is used,
Spring accesses other beans for matching them by type.
====
@@ -148,7 +135,7 @@ Java::
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean);
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
@@ -177,48 +164,7 @@ Kotlin::
----
======
You can register the `InstantiationTracingBeanPostProcessor` with Java configuration
by using a `static` `@Bean` method (recommended to avoid eager initialization of the
configuration class and other beans):
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
@Configuration
public class AppConfig {
@Bean
public static InstantiationTracingBeanPostProcessor instantiationTracingBeanPostProcessor() {
return new InstantiationTracingBeanPostProcessor();
}
// ... other bean definitions
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
@Configuration
class AppConfig {
@Bean
companion object {
@JvmStatic
fun instantiationTracingBeanPostProcessor() = InstantiationTracingBeanPostProcessor()
}
// ... other bean definitions
}
----
======
Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via the
`bean` element with XML configuration:
The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -246,9 +192,10 @@ 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
even have a name, and, because it is a bean, it can be dependency-injected as you would 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:
@@ -354,23 +301,6 @@ implement the `BeanFactoryPostProcessor` interface. It uses these beans as bean
post-processors, at the appropriate time. You can deploy these post-processor beans as
you would any other bean.
When registering a `BeanFactoryPostProcessor` via an `@Bean` factory method in a
`@Configuration` class, declare the method as `static` to avoid lifecycle conflicts
with annotation processing (such as `@Autowired`, `@Value`, and `@PostConstruct`) in the
configuration class. See the "BeanFactoryPostProcessor-returning `@Bean` methods"
section in the {spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc
for details and an example.
For any non-static `@Bean` factory method with a `BeanFactoryPostProcessor` return type,
you should see an INFO-level log message similar to the following.
[quote]
@Bean method MyConfig.myBfpp is non-static and returns an object assignable to Spring's
BeanFactoryPostProcessor interface. This will result in a failure to process annotations
such as @Autowired, @Resource, and @PostConstruct within the method's declaring
@Configuration class. Add the 'static' modifier to this method to avoid these container
lifecycle issues; see @Bean javadoc for complete details.
NOTE: As with ``BeanPostProcessor``s , you typically do not want to configure
``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a
`Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all.
@@ -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
@@ -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]]
@@ -338,11 +339,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
modularity, but determining exactly where the autowired bean definitions are declared is
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
explicit in the code, and this may be just fine. Note that the
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
showing how everything is wired, which may be all you need. Also, your Java IDE can
easily find all declarations and uses of the `AccountRepository` type and quickly show
you the location of `@Bean` methods that return that type.
explicit in the code, and this may be just fine. Remember that the
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
can render graphs showing how everything is wired, which may be all you need. Also,
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
and quickly show you the location of `@Bean` methods that return that type.
In cases where this ambiguity is not acceptable and you wish to have direct navigation
from within your IDE from one `@Configuration` class to another, consider autowiring the
@@ -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:
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
Language] but offers additional features, most notably method invocation and basic string
templating functionality.
While there are several other Java expression languages available -- OGNL, MVEL, and
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
Spring community with a single well supported expression language that can be used across
all the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements for
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
is based on a technology-agnostic API that lets other expression language implementations
be integrated, should the need arise.
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
EL, to name a few -- the Spring Expression Language was created to provide the Spring
community with a single well supported expression language that can be used across all
the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
That said, SpEL is based on a technology-agnostic API that lets other expression language
implementations be integrated, should the need arise.
While SpEL serves as the foundation for expression evaluation within the Spring
portfolio, it is not directly tied to Spring and can be used independently. To
@@ -531,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
@@ -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.
=====
@@ -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: &#8288; is the Unicode Character 'WORD JOINER', which prevents undesired line wraps.
Similarly, if `names` is of type `Optional<List<String>>`, the expression
`names?.?&#8288;[#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
@@ -53,7 +53,7 @@ Kotlin::
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
// uses CustomValue:::compareTo
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java)
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java);
----
======
@@ -167,7 +167,7 @@ Kotlin::
[CAUTION]
====
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>`.
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
to `false`.
@@ -312,13 +312,13 @@ Kotlin::
// evaluates to 'a'
val ch = parser.parseExpression("'d' - 3")
.getValue(Char::class.java)
.getValue(Character::class.java);
// -- Repeat --
// evaluates to "abcabc"
val repeated = parser.parseExpression("'abc' * 2")
.getValue(String::class.java)
.getValue(String::class.java);
----
======
@@ -485,7 +485,7 @@ Kotlin::
// -- Operator precedence --
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
----
======
@@ -541,7 +541,32 @@ For example, if we want to overload the `ADD` operator to allow two lists to be
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
follows.
include-code::./ListConcatenation[]
[source,java,indent=0,subs="verbatim,quotes"]
----
pubic class ListConcatenation implements OperatorOverloader {
@Override
public boolean overridesOperation(Operation operation, Object left, Object right) {
return (operation == Operation.ADD &&
left instanceof List && right instanceof List);
}
@Override
@SuppressWarnings("unchecked")
public Object operate(Operation operation, Object left, Object right) {
if (operation == Operation.ADD &&
left instanceof List list1 && right instanceof List list2) {
List result = new ArrayList(list1);
result.addAll(list2);
return result;
}
throw new UnsupportedOperationException(
"No overload for operation %s and operands [%s] and [%s]"
.formatted(operation, left, right));
}
}
----
If we register `ListConcatenation` as the `OperatorOverloader` in a
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
@@ -564,8 +589,8 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val context = StandardEvaluationContext()
context.operatorOverloader = ListConcatenation()
StandardEvaluationContext context = StandardEvaluationContext()
context.setOperatorOverloader(ListConcatenation())
// evaluates to a new list: [1, 2, 3, 4, 5]
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
@@ -6,7 +6,7 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
[NOTE]
====
Variable names must begin with a letter (as defined below), an underscore, or a dollar
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
sign.
Variable names must be composed of one or more of the following supported types of
@@ -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,271 +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.
[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.
[[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.execute(
() -> 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.execute(
() -> 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.execute(
() -> 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.execute(
() -> jmsClient.destination("notifications").send(...));
----
[TIP]
====
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
with a `RetryTemplate` to react to events published during key retry phases (before a
retry attempt, after a retry attempt, etc.), and you can compose multiple listeners via a
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
====
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`.
@@ -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)
// ...
}
----
======
@@ -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%"]
|===
@@ -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
@@ -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,
@@ -676,7 +676,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
[source,java,indent=0,subs="verbatim,quotes"]
----
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
.paramSource(new Actor("Leonor", "Watling"))
.paramSource(new Actor("Leonor", "Watling")
.update();
----
@@ -15,7 +15,7 @@ configuration options. You should instantiate the `SimpleJdbcInsert` in the data
layer's initialization method. For this example, the initializing method is the
`setDataSource` method. You do not need to subclass the `SimpleJdbcInsert` class. Instead,
you can create a new instance and set the table name by using the `withTableName` method.
Configuration methods for this class follow the `fluent` style that returns the instance
Configuration methods for this class follow the `fluid` style that returns the instance
of the `SimpleJdbcInsert`, which lets you chain all configuration methods. The following
example uses only one configuration method (we show examples of multiple methods later):
@@ -349,11 +349,11 @@ parameters return the data read from the table.
You can declare `SimpleJdbcCall` in a manner similar to declaring `SimpleJdbcInsert`. You
should instantiate and configure the class in the initialization method of your data-access
layer. In contrast to the `StoredProcedure` class, you do not need to create a subclass,
and you do not need to declare parameters that can be looked up in the database metadata.
The following `SimpleJdbcCall` configuration example uses the preceding stored procedure.
The only configuration option (other than the `DataSource`) is the name of the stored
procedure.
layer. Compared to the `StoredProcedure` class, you need not create a subclass
and you need not to declare parameters that can be looked up in the database metadata.
The following example of a `SimpleJdbcCall` configuration uses the preceding stored
procedure (the only configuration option, in addition to the `DataSource`, is the name
of the stored procedure):
[tabs]
======
@@ -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>
@@ -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,
@@ -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
@@ -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,11 +1,16 @@
[[appendix]]
= Appendix
[[appendix.xsd-schemas]]
== XML Schemas
This part of the appendix lists XML schemas related to integration technologies.
[[appendix.xsd-schemas-jee]]
=== The `jee` Schema
@@ -167,7 +172,7 @@ different properties with `jee`:
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
without `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -179,7 +184,7 @@ without `jee`:
</bean>
----
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
with `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -195,7 +200,7 @@ with `jee`:
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
and a number of properties without `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -210,7 +215,7 @@ and a number of properties without `jee`:
</bean>
----
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
and a number of properties with `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -229,7 +234,7 @@ and a number of properties with `jee`:
The `<jee:remote-slsb/>` element configures a reference to a `remote` EJB Stateless Session Bean.
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
without `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -246,7 +251,7 @@ without `jee`:
</bean>
----
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
with `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -308,7 +313,7 @@ xref:integration/jmx/naming.adoc#jmx-context-mbeanexport[Configuring Annotation-
=== The `cache` Schema
You can use the `cache` elements to enable support for Spring's `@CacheEvict`, `@CachePut`,
and `@Caching` annotations. The `cache` schema also supports declarative XML-based caching. See
and `@Caching` annotations. It it also supports declarative XML-based caching. See
xref:integration/cache/annotations.adoc#cache-annotation-enable[Enabling Caching Annotations] and
xref:integration/cache/declarative-xml.adoc[Declarative XML-based Caching] for details.
@@ -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]]
@@ -189,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.
@@ -207,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
@@ -233,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(restCqlientAdapter)
.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 Springs 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,16 +3,14 @@
: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],
since the latter requires a reference to the outer class.
since the later requires a reference to the outer class.
@@ -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.

Some files were not shown because too many files have changed in this diff Show More