Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel b038beb854 Release v7.0.1 2025-11-20 09:57:36 +01:00
2150 changed files with 20216 additions and 41419 deletions
@@ -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"
@@ -24,7 +24,7 @@ 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: |
@@ -32,7 +32,7 @@ runs:
${{ 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 }}
+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:
- 7.0.x
- main
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,7 +21,7 @@ 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
+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/'
+1 -1
View File
@@ -35,7 +35,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
+5 -5
View File
@@ -13,7 +13,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
@@ -21,7 +21,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@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
@@ -57,7 +57,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:
@@ -73,7 +73,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
@@ -86,7 +86,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:
+5 -5
View File
@@ -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', '7.0.x', 'main' ]
branch: [ '6.2.x', 'main' ]
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
@@ -9,7 +9,6 @@ ivy-cache
argfile*
activemq-data/
classes/
.cursor/
# Log files
jxl.log
@@ -39,7 +38,7 @@ bin
.springBeans
spring-*/src/main/java/META-INF/MANIFEST.MF
# IntelliJ IDEA artifacts and output dirs
# IDEA artifacts and output dirs
*.iml
*.ipr
*.iws
+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
+13 -16
View File
@@ -6,7 +6,7 @@ plugins {
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
id 'com.gradleup.shadow' version "9.2.2" apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'io.spring.nullability' version '0.0.11' apply false
id 'io.spring.nullability' version '0.0.8' apply false
}
ext {
@@ -61,27 +61,24 @@ configure([rootProject] + javaProjects) { project ->
testImplementation("org.junit.platform:junit-platform-suite")
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("io.mockk:mockk") {
exclude group: 'junit', module: 'junit'
}
testImplementation("io.mockk:mockk")
testImplementation("org.assertj:assertj-core")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
}
ext.javadocLinks = [
"https://docs.oracle.com/en/java/javase/17/docs/api/",
//"https://jakarta.ee/specifications/platform/11/apidocs/",
"https://docs.hibernate.org/orm/7.2/javadocs/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://hc.apache.org/httpcomponents-client-5.6.x/5.6/httpclient5/apidocs/",
"https://projectreactor.io/docs/core/release/api/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
"https://docs.junit.org/6.0.3/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
"https://jspecify.dev/docs/api/"
"https://docs.oracle.com/en/java/javase/17/docs/api/",
//"https://jakarta.ee/specifications/platform/11/apidocs/",
"https://docs.hibernate.org/orm/5.6/javadocs/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
"https://docs.junit.org/6.0.1/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
"https://jspecify.dev/docs/api/"
] as String[]
}
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("13.4.2");
checkstyle.setToolVersion("12.1.2");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
@@ -49,6 +49,7 @@ import static org.springframework.build.architecture.ArchitectureRules.classesSh
import static org.springframework.build.architecture.ArchitectureRules.javaClassesShouldNotImportKotlinAnnotations;
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToLowerCaseWithoutLocale;
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToUpperCaseWithoutLocale;
import static org.springframework.build.architecture.ArchitectureRules.packageInfoShouldBeNullMarked;
/**
* {@link Task} that checks for architecture problems.
@@ -63,7 +64,8 @@ public abstract class ArchitectureCheck extends DefaultTask {
public ArchitectureCheck() {
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
getProhibitObjectsRequireNonNull().convention(true);
getRules().addAll(classesShouldNotImportForbiddenTypes(),
getRules().addAll(packageInfoShouldBeNullMarked(),
classesShouldNotImportForbiddenTypes(),
javaClassesShouldNotImportKotlinAnnotations(),
allPackagesShouldBeFreeOfTangles(),
noClassesShouldCallStringToLowerCaseWithoutLocale(),
@@ -46,6 +46,13 @@ abstract class ArchitectureRules {
.because("String.toUpperCase(Locale.ROOT) should be used instead");
}
static ArchRule packageInfoShouldBeNullMarked() {
return ArchRuleDefinition.classes()
.that().haveSimpleName("package-info")
.should().beAnnotatedWith("org.jspecify.annotations.NullMarked")
.allowEmptyShould(true);
}
static ArchRule classesShouldNotImportForbiddenTypes() {
return ArchRuleDefinition.noClasses()
.should().dependOnClassesThat()
@@ -78,7 +78,7 @@ public class ShadowSource extends DefaultTask {
}
@OutputDirectory
public DirectoryProperty getOutputDirectory() {
DirectoryProperty getOutputDirectory() {
return this.outputDirectory;
}
+2 -3
View File
@@ -20,7 +20,6 @@ dependencies {
}
}
def springAspectsOutput = project(":spring-aspects").sourceSets.main.output
javadoc {
javadocTool.set(javaToolchains.javadocToolFor({
languageVersion = JavaLanguageVersion.of(25)
@@ -49,8 +48,8 @@ javadoc {
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 })
}
+3 -5
View File
@@ -13,10 +13,8 @@ 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: include all releases from 6.2.0 to 9.*.*.
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
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:
extensions:
@@ -38,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.20/ui-bundle.zip
+1 -1
View File
@@ -31,7 +31,7 @@ asciidoc:
spring-org: 'spring-projects'
spring-github-org: "https://github.com/{spring-org}"
spring-framework-github: "https://github.com/{spring-org}/spring-framework"
spring-framework-code: '{spring-framework-github}/tree/7.0.x'
spring-framework-code: '{spring-framework-github}/tree/main'
spring-framework-issues: '{spring-framework-github}/issues'
spring-framework-wiki: '{spring-framework-github}/wiki'
# Docs
+7 -13
View File
@@ -15,15 +15,11 @@ apply from: "${rootDir}/gradle/publications.gradle"
antora {
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
environment = [
'BUILD_REFNAME': 'HEAD',
'BUILD_VERSION': project.version,
'BUILD_REFNAME': 'HEAD',
'BUILD_VERSION': project.version,
]
}
node {
version = '24.15.0'
}
tasks.named("generateAntoraYml") {
asciidocAttributes = project.provider( {
return ["spring-version": project.version ]
@@ -53,8 +49,8 @@ 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
"-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
)
}
@@ -74,7 +70,7 @@ dependencies {
implementation("com.github.ben-manes.caffeine:caffeine")
implementation("com.mchange:c3p0:0.9.5.5")
implementation("com.oracle.database.jdbc:ojdbc11")
implementation("io.micrometer:context-propagation")
implementation("io.micrometer:context-propagation")
implementation("io.projectreactor.netty:reactor-netty-http")
implementation("jakarta.jms:jakarta.jms-api")
implementation("jakarta.servlet:jakarta.servlet-api")
@@ -84,14 +80,12 @@ 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.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

+2 -5
View File
@@ -198,7 +198,6 @@
*** xref:web/webmvc/mvc-uri-building.adoc[]
*** xref:web/webmvc/mvc-ann-async.adoc[]
*** xref:web/webmvc/mvc-range.adoc[]
*** xref:web/webmvc/mvc-data-binding.adoc[]
*** xref:web/webmvc-cors.adoc[]
*** xref:web/webmvc-versioning.adoc[]
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
@@ -262,6 +261,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[]
@@ -296,7 +296,6 @@
*** xref:web/webflux-functional.adoc[]
*** xref:web/webflux/uri-building.adoc[]
*** xref:web/webflux/range.adoc[]
*** xref:web/webflux/data-binding.adoc[]
*** xref:web/webflux-cors.adoc[]
*** xref:web/webflux-versioning.adoc[]
*** xref:web/webflux/ann-rest-exceptions.adoc[]
@@ -332,10 +331,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[]
@@ -346,7 +344,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[]
@@ -74,11 +74,6 @@ expressions used in XML bean definitions, `@Value`, etc.
| The mode to use when compiling expressions for the
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
| `spring.expression.maxOperations`
| The default maximum number of operations permitted during
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
expression evaluation.
| `spring.getenv.ignore`
| Instructs Spring to ignore operating system environment variables if a Spring
`Environment` property -- for example, a placeholder in a configuration String -- isn't
@@ -86,11 +81,6 @@ resolvable otherwise. See
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
for details.
| `spring.http.response.flush.enabled`
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
| `spring.jdbc.getParameterType.ignore`
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
@@ -134,20 +124,11 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int
| The maximum size of the context cache in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
| `spring.test.context.cache.pause`
| The pause mode for the context cache in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context Pausing].
| `spring.test.context.failure.threshold`
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
| `spring.test.extension.context.scope`
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
class hierarchies. See
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
| `spring.test.enclosing.configuration`
| The default _enclosing configuration inheritance mode_ to use if
`@NestedTestConfiguration` is not present on a test class. See
@@ -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
@@ -378,7 +378,7 @@ The container also supports creating a bean with {spring-framework-api}++/beans/
. The custom arguments require dynamic introspection of a matching constructor or factory method.
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
. Bypassing the instance supplier means that all other optimizations after creation are skipped as well.
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
@@ -19,6 +19,10 @@ its behavior changes.
| Initialization of `SmartInitializingSingleton` beans.
| `beanName` the name of the bean.
| `spring.context.annotated-bean-reader.create`
| Creation of the `AnnotatedBeanDefinitionReader`.
|
| `spring.context.base-packages.scan`
| Scanning of base packages.
| `packages` array of base packages for scanning.
@@ -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
@@ -274,7 +274,7 @@ Kotlin::
Next, you can provide the information for the candidate bean definitions. You can add
`<qualifier/>` tags as sub-elements of the `<bean/>` tag and then specify the `type` and
`value` to match your custom qualifier annotations. The type is matched against the
fully-qualified class name of the annotation. Alternatively, as a convenience if no risk of
fully-qualified class name of the annotation. Alternately, as a convenience if no risk of
conflicting names exists, you can use the short class name. The following example
demonstrates both approaches:
@@ -309,8 +309,7 @@ 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`.
@@ -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.
@@ -556,11 +556,18 @@ Kotlin::
======
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`]
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 examples show.
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]
======
@@ -595,27 +602,6 @@ Kotlin::
</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.
@@ -575,7 +575,7 @@ Kotlin::
----
======
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honor that and will not register the method to listen to events.
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
The method signature once again declares the event type to which it listens,
but, this time, with a flexible name and without implementing a specific listener interface.
@@ -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:
@@ -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,7 +192,7 @@ 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.)
@@ -354,23 +300,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
@@ -338,11 +338,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
@@ -610,19 +610,6 @@ Kotlin::
See the {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`]
javadoc for more detail.
[NOTE]
====
A `@Conditional` annotation declared on an enclosing `@Configuration` class is only
applied to the registration of a nested `@Configuration` class if the nested class is
reached through the parser's recursion from its enclosing class, or via `@Import`. If a
nested class is discovered independently of its enclosing class — for example, via
`@ComponentScan` or by directly registering it against the application context — it is
processed using only its own `@Conditional` annotations. Thus, if you wish to ensure that
the same `@Conditional` annotations apply in such scenarios, you must redeclare the
relevant annotations on the nested class, or extract them into a composed annotation
which you apply to both the enclosing class and the nested class.
====
[[beans-java-combining]]
== Combining Java and XML Configuration
@@ -1,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.
@@ -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
@@ -395,16 +395,6 @@ set a JVM system property or Spring property named `spring.context.expression.ma
to the maximum expression length needed by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
Similarly, the number of operations performed during the evaluation of a SpEL expression
cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If
you create a `SpelExpressionParser` programmatically (the recommend approach), you can
specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that
you provide to the `SpelExpressionParser`. If you are not able to configure an explicit
value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system
property or Spring property named `spring.expression.maxOperations` to the maximum number
of operations required by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
[[expressions-spel-compilation]]
== SpEL Compilation
@@ -541,7 +531,6 @@ following kinds of expressions cannot be compiled.
* Expressions relying on the conversion service
* Expressions using custom resolvers
* Expressions using overloaded operators
* Expressions using `Optional` with the null-safe or Elvis operator
* Expressions using array construction syntax
* Expressions using selection or projection
* Expressions using bean references
@@ -2,12 +2,4 @@
= Language Reference
:page-section-summary-toc: 1
Spring Expression Language (SpEL) expressions are composed of a sequence of tokens such
as literals, operators, method invocations, and so forth.
Whitespace can be used freely between tokens to format and improve the readability of
expressions. Specifically, the `\s` (space), `\t` (tab), `\r` (carriage return), and `\n`
(newline) characters are all valid separators between tokens. However, whitespace is
ignored by the expression parser unless it is part of a string literal.
The following sections describe the features and syntax of SpEL.
This section describes how the Spring Expression Language works.
@@ -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)
@@ -270,7 +270,7 @@ is applicable for typical implementations of indexed structures.
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
to bytecode for read access. Note, however, that the configured read-method must be
invocable via a `public` class or `public` interface for compilation to succeed.
invokable via a `public` class or `public` interface for compilation to succeed.
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
@@ -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
@@ -25,8 +25,7 @@ public void sendNotification() {
By default, the method invocation will be retried for any exception thrown: with at most
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
between attempts. If all attempts have failed and the retry policy has been exhausted,
the last original exception from the target method will be propagated to the caller.
between attempts.
[NOTE]
====
@@ -70,12 +69,12 @@ Or for 4 retry attempts and an exponential back-off strategy with a bit of jitte
[source,java,indent=0,subs="verbatim,quotes"]
----
@Retryable(
includes = MessageDeliveryException.class,
maxRetries = 4,
delay = 100,
jitter = 10,
multiplier = 2,
maxDelay = 1000)
includes = MessageDeliveryException.class,
maxRetries = 4,
delay = 100,
jitter = 10,
multiplier = 2,
maxDelay = 1000)
public void sendNotification() {
this.jmsClient.destination("notifications").send(...);
}
@@ -100,13 +99,6 @@ TIP: Several attributes in `@Retryable` have `String` variants that provide prop
placeholder and SpEL support, as an alternative to the specifically typed annotation
attributes used in the above examples.
[TIP]
====
During `@Retryable` processing, Spring publishes a `MethodRetryEvent` for every exception
coming out of the target method. This can be used to track/log all original exceptions
whereas the caller of the `@Retryable` method will only ever see the last exception.
====
[[resilience-annotations-concurrencylimit]]
== `@ConcurrencyLimit`
@@ -182,7 +174,7 @@ configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`].
----
var retryTemplate = new RetryTemplate(); // <1>
retryTemplate.invoke(
retryTemplate.execute(
() -> jmsClient.destination("notifications").send(...));
----
<1> Implicitly uses `RetryPolicy.withDefaults()`.
@@ -191,6 +183,9 @@ By default, a retryable operation will be retried for any exception thrown: with
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
between attempts.
If you only need to customize the number of retry attempts, you can use the
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
[NOTE]
====
A retryable operation will be executed at least once and retried at most `maxRetries`
@@ -201,14 +196,11 @@ For example, if `maxRetries` is set to `4`, the retryable operation will be invo
least once and at most 5 times.
====
If you only need to customize the number of retry attempts, you can use the
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
[source,java,indent=0,subs="verbatim,quotes"]
----
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1>
retryTemplate.invoke(
retryTemplate.execute(
() -> jmsClient.destination("notifications").send(...));
----
<1> Explicitly uses `RetryPolicy.withMaxRetries(4)`.
@@ -226,7 +218,7 @@ matched against an exception thrown by a failed operation as well as nested caus
var retryTemplate = new RetryTemplate(retryPolicy);
retryTemplate.invoke(
retryTemplate.execute(
() -> jmsClient.destination("notifications").send(...));
----
<1> Specify one or more exception types to include.
@@ -259,101 +251,21 @@ and an exponential back-off strategy with a bit of jitter.
var retryTemplate = new RetryTemplate(retryPolicy);
retryTemplate.invoke(
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`.
====
Note that the examples above apply a pattern similar to `@Retryable` method invocations
where the last original exception will be propagated to the caller, using the `invoke`
variants on `RetryTemplate` which are available with and without a return value.
The callback may throw unchecked exceptions, the last one of which is exposed for
direct handling on the caller side:
[source,java,indent=0,subs="verbatim,quotes"]
----
try {
retryTemplate.invoke(
() -> jmsClient.destination("notifications").send(...));
}
catch (MessageDeliveryException ex) {
// coming out of the original JmsClient send method
}
----
[source,java,indent=0,subs="verbatim,quotes"]
----
try {
var result = retryTemplate.invoke(() -> {
jmsClient.destination("notifications").send(...);
return "result";
});
}
catch (MessageDeliveryException ex) {
// coming out of the original JmsClient send method
}
----
`RetryTemplate` instances are very light and can be created on the fly,
potentially with a specific retry policy to use for a given invocation:
[source,java,indent=0,subs="verbatim,quotes"]
----
try {
new RetryTemplate(RetryPolicy.withMaxRetries(4)).invoke(
() -> jmsClient.destination("notifications").send(...));
}
catch (MessageDeliveryException ex) {
// coming out of the original JmsClient send method
}
----
For deeper interaction, you may use RetryTemplate's `execute` method. The caller will
have to handle the checked `RetryException` thrown by `RetryTemplate`, exposing the
outcome of all attempts:
[source,java,indent=0,subs="verbatim,quotes"]
----
try {
var result = retryTemplate.execute(() -> {
jmsClient.destination("notifications").send(...);
return "result";
});
}
catch (RetryException ex) {
// ex.getExceptions() / ex.getLastException() ...
}
----
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
with a `RetryTemplate` to react to key retry steps (before or after a retry attempt etc.)
or simply to every invocation attempt, being able to track all exceptions coming out of
the callback and all retry outcomes (exhaustion, interruption, timeout). This is
particularly useful when using `invoke` where no retry state other than the last
original exception is exposed otherwise:
[source,java,indent=0,subs="verbatim,quotes"]
----
var retryTemplate = new RetryTemplate();
retryTemplate.setRetryListener(new RetryListener() {
@Override
public void onRetryableExecution(RetryPolicy retryPolicy, Retryable<?> retryable, RetryState retryState) {
...
}
});
retryTemplate.invoke(
() -> jmsClient.destination("notifications").send(...));
----
You can also compose multiple listeners via a
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
{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`.
@@ -347,10 +347,10 @@ recognized and used as the `PropertyEditor` for `Something`-typed properties.
[literal,subs="verbatim,quotes"]
----
com
└── example
└── things
├── *Something*
└── *SomethingEditor* // the PropertyEditor for the Something class
chank
pop
Something
SomethingEditor // the PropertyEditor for the Something class
----
Note that you can also use the standard `BeanInfo` JavaBeans mechanism here as well
@@ -362,10 +362,10 @@ following example uses the `BeanInfo` mechanism to explicitly register one or mo
[literal,subs="verbatim,quotes"]
----
com
└── example
└── things
├── *Something*
└── *SomethingBeanInfo* // the BeanInfo for the Something class
chank
pop
Something
SomethingBeanInfo // the BeanInfo for the Something class
----
The following Java source code for the referenced `SomethingBeanInfo` class
@@ -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]
======
@@ -14,7 +14,7 @@ As of Spring Framework 7.0, Spring requires Hibernate ORM 7.x for Spring's
`HibernateJpaVendorAdapter`.
The `org.springframework.orm.jpa.hibernate` package supersedes the former `orm.hibernate5`:
now for use with Hibernate ORM 7.x, tightly integrated with `HibernateJpaVendorAdapter`
now for use with Hibernate ORM 7.1+, tightly integrated with `HibernateJpaVendorAdapter`
as well as supporting Hibernate's native `SessionFactory.getCurrentSession()` style.
====
@@ -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,
@@ -22,7 +22,7 @@ where all the underlying resources have to participate in the service-level tran
NOTE: By default, a participating transaction joins the characteristics of the outer scope,
silently ignoring the local isolation level, timeout value, or read-only flag (if any).
Consider switching the `validateExistingTransaction` flag to `true` on your transaction
Consider switching the `validateExistingTransactions` flag to `true` on your transaction
manager if you want isolation level declarations to be rejected when participating in
an existing transaction with a different isolation level. This non-lenient mode also
rejects read-only mismatches (that is, an inner read-write transaction that tries to participate
@@ -3,22 +3,21 @@
: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
The ahead-of-time cache is a JVM feature introduced in Java 24 via the
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)].
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+).
later if available in the JVM version your 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
training run performed for example when packaging the application thanks to an 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.
which is designed to fulfill the class loading requirements of both AOT cache and CDS.
== Creating the cache
@@ -30,22 +29,14 @@ invoked; but the lifecycle has not started, and the `ContextRefreshedEvent` has
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
JVM flag to start 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)::
AOT cache::
+
[source,bash,subs="verbatim,quotes"]
----
@@ -64,12 +55,6 @@ 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:
@@ -97,7 +82,7 @@ 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
an extra attribute: `-Xlog:class+load:file=aot-cache.log`. This creates a `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:
@@ -110,11 +95,11 @@ a "shared objects file" source, as shown in the following example:
[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
If the AOT cache 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 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).
- When using the cache, the classpath must be the same than the one used to create it, in the same order.
Additional JARs or directories can be specified *at the end* (but won't 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.
@@ -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.
@@ -1,15 +1,10 @@
[[observability]]
= Observability Support
With https://docs.micrometer.io/micrometer/reference/concepts.html[Micrometer], developers can instrument libraries and applications for metrics (timers, gauges, counters)
that collect statistics about their runtime behavior. Metrics can help you to track error rates, usage patterns, performance, and more.
https://docs.micrometer.io/tracing/reference/[Micrometer can also produce traces], giving you a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
Micrometer defines an {micrometer-docs}/observation.html[Observation concept that enables both Metrics and Traces] in applications.
Each observation will produce:
* https://docs.micrometer.io/micrometer/reference/observation/components.html#micrometer-observation-default-meter-handler[several metrics - a timer, a long task timer and many counters]
* a https://docs.micrometer.io/tracing/reference/glossary.html[span for the current trace]
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
Metrics can help you to track error rates, usage patterns, performance, and more.
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
You can learn more about {spring-boot-docs-ref}/actuator/observability.html[configuring the observability infrastructure in Spring Boot].
@@ -466,7 +466,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
@@ -1179,7 +1179,7 @@ built-in decorators to suppress 404 exceptions and return a `ResponseEntity` wit
[source,java,indent=0,subs="verbatim,quotes"]
----
// For RestClient
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restClientAdapter)
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restCqlientAdapter)
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
.build();
@@ -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`.
@@ -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 { }`]
@@ -319,17 +319,9 @@ progresses.
== Testing
This section addresses testing with the combination of Kotlin and Spring Framework.
The recommended testing framework is https://junit.org/[JUnit] along with
The recommended testing framework is https://junit.org/junit5/[JUnit] along with
https://mockk.io/[Mockk] for mocking.
[TIP]
====
Kotlin lets you specify meaningful test function names between backticks (```).
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
in this section.
====
NOTE: If you are using Spring Boot, see
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
@@ -360,6 +352,7 @@ file with a `spring.test.constructor.autowire.mode = all` property.
[[per_class-lifecycle]]
=== `PER_CLASS` Lifecycle
Kotlin lets you specify meaningful test function names between backticks (```).
With JUnit Jupiter, Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
@@ -237,8 +237,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
RSocketStrategies strategies = RSocketStrategies.builder()
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
.build();
RSocketRequester requester = RSocketRequester.builder()
@@ -251,8 +251,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val strategies = RSocketStrategies.builder()
.encoders { it.add(JacksonCborEncoder()) }
.decoders { it.add(JacksonCborDecoder()) }
.encoders { it.add(Jackson2CborEncoder()) }
.decoders { it.add(Jackson2CborDecoder()) }
.build()
val requester = RSocketRequester.builder()
@@ -681,8 +681,8 @@ Java::
@Bean
public RSocketStrategies rsocketStrategies() {
return RSocketStrategies.builder()
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
.routeMatcher(new PathPatternRouteMatcher())
.build();
}
@@ -703,8 +703,8 @@ Kotlin::
@Bean
fun rsocketStrategies() = RSocketStrategies.builder()
.encoders { it.add(JacksonCborEncoder()) }
.decoders { it.add(JacksonCborDecoder()) }
.encoders { it.add(Jackson2CborEncoder()) }
.decoders { it.add(Jackson2CborDecoder()) }
.routeMatcher(PathPatternRouteMatcher())
.build()
}
@@ -3,9 +3,8 @@
The following annotations are supported when used in conjunction with the
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
and the JUnit Jupiter testing framework:
and JUnit Jupiter (that is, the programming model in JUnit):
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitwebconfig[`@SpringJUnitWebConfig`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
@@ -15,56 +14,6 @@ and the JUnit Jupiter testing framework:
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
[[integration-testing-annotations-springextensionconfig]]
== `@SpringExtensionConfig`
`@SpringExtensionConfig` is a type-level annotation that can be used to configure the
behavior of the `SpringExtension`.
As of Spring Framework 7.0, the `SpringExtension` is configured to use a test-method
scoped `ExtensionContext`, which enables consistent dependency injection into fields and
constructors from the `ApplicationContext` for the current test method in a `@Nested`
test class hierarchy. However, if a third-party `TestExecutionListener` is not compatible
with the semantics associated with a test-method scoped extension context — or if a
developer wishes to switch to test-class scoped semantics — the `SpringExtension` can be
configured to use a test-class scoped `ExtensionContext` by annotating a top-level test
class with `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
Alternatively, you can change the global default by setting the
`spring.test.extension.context.scope` property to `test_class`. The property is resolved
first via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism
which also supports JVM system properties — for example,
`-Dspring.test.extension.context.scope=test_class`. If the Spring property has not been
set, the `SpringExtension` will attempt to resolve the property as a
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter]
as a fallback mechanism. If the property has not been set via either of those mechanisms,
the `SpringExtension` will use a test-method scoped extension context by default. Note,
however, that a `@SpringExtensionConfig` declaration always takes precedence over this
property.
[TIP]
====
If a test class uses JUnit Jupiter's `@TestInstance(Lifecycle.PER_CLASS)` semantics, the
`SpringExtension` will always use a test-class scoped `ExtensionContext`, and
configuration via `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)` or
the `spring.test.extension.context.scope` property will have no effect for that test
class.
====
[NOTE]
====
This annotation is currently only applicable to `@Nested` test class hierarchies and
should be applied to the top-level enclosing class of a `@Nested` test class hierarchy.
Consequently, there is no need to declare this annotation on a test class that does not
contain `@Nested` test classes.
In addition,
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
does not apply to this annotation. `@SpringExtensionConfig` will always be detected
within a `@Nested` test class hierarchy, effectively disregarding any
`@NestedTestConfiguration(OVERRIDE)` declarations.
====
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
== `@SpringJUnitConfig`
@@ -238,7 +187,7 @@ default mode may be set via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
The default mode may also be configured as a
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter].
https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
If the `spring.test.constructor.autowire.mode` property is not set, test class
constructors will not be automatically autowired.
@@ -2,12 +2,11 @@
= Meta-Annotation Support for Testing
You can use most test-related annotations as
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to
create custom composed annotations and reduce configuration duplication across a test
suite.
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to create custom composed
annotations and reduce configuration duplication across a test suite.
For example, you can use each of the following as a meta-annotation in conjunction with
the xref:testing/testcontext-framework.adoc[TestContext framework].
You can use each of the following as a meta-annotation in conjunction with the
xref:testing/testcontext-framework.adoc[TestContext framework].
* `@BootstrapWith`
* `@ContextConfiguration`
@@ -38,7 +37,111 @@ the xref:testing/testcontext-framework.adoc[TestContext framework].
* `@EnabledIf` _(only supported on JUnit Jupiter)_
* `@DisabledIf` _(only supported on JUnit Jupiter)_
Consider the following test classes that use the `SpringExtension` with JUnit Jupiter:
Consider the following example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
----
======
If we discover that we are repeating the preceding configuration across our JUnit 4-based
test suite, we can reduce the duplication by introducing a custom composed annotation
that centralizes the common test configuration for Spring, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
----
======
Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the
configuration of individual JUnit 4 based test classes, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class OrderRepositoryTests
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class UserRepositoryTests
----
======
If we write tests that use JUnit Jupiter, we can reduce code duplication even further,
since annotations in JUnit Jupiter can also be used as meta-annotations. Consider the
following example:
[tabs]
======
@@ -47,13 +150,13 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
@@ -64,22 +167,23 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
----
======
If we discover that we are repeating the preceding configuration across our test suite,
we can reduce the duplication by introducing a custom composed annotation that
centralizes the common test configuration for Spring and JUnit Jupiter, as follows:
If we discover that we are repeating the preceding configuration across our JUnit
Jupiter-based test suite, we can reduce the duplication by introducing a custom composed
annotation that centralizes the common test configuration for Spring and JUnit Jupiter,
as follows:
[tabs]
======
@@ -90,7 +194,7 @@ Java::
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
@@ -103,7 +207,7 @@ Kotlin::
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
@@ -72,13 +72,6 @@ bean definition profiles programmatically by implementing a custom
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc#testcontext-ctx-management-env-profiles-ActiveProfilesResolver[`ActiveProfilesResolver`]
and registering it by using the `resolver` attribute of `@ActiveProfiles`.
NOTE: When `@ActiveProfiles` is declared on a test class, the `spring.profiles.active`
property (whether configured as a JVM system property or environment variable) is not
taken into account by the TestContext Framework when determining active profiles. If
you need to allow `spring.profiles.active` to override the profiles configured via
`@ActiveProfiles`, you can implement a custom `ActiveProfilesResolver` as described in
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles].
See xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles],
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration], and the
{spring-framework-api}/test/context/ActiveProfiles.html[`@ActiveProfiles`] javadoc for
@@ -4,7 +4,8 @@
`@AfterTransaction` indicates that the annotated `void` method should be run after a
transaction is ended, for test methods that have been configured to run within a
transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` methods
are not required to be `public` and may be declared on interface default methods.
are not required to be `public` and may be declared on Java 8-based interface default
methods.
[tabs]
======
@@ -4,7 +4,8 @@
`@BeforeTransaction` indicates that the annotated `void` method should be run before a
transaction is started, for test methods that have been configured to run within a
transaction by using Spring's `@Transactional` annotation. `@BeforeTransaction` methods
are not required to be `public` and may be declared on interface default methods.
are not required to be `public` and may be declared on Java 8-based interface default
methods.
The following example shows how to use the `@BeforeTransaction` annotation:
@@ -140,21 +140,6 @@ Java::
}
----
<1> Replace the bean with type `CustomService` with a Mockito mock.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests {
@MockitoBean // <1>
lateinit var customService: CustomService
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito mock.
======
In the example above, we are creating a mock for `CustomService`. If more than one bean
@@ -183,22 +168,6 @@ Java::
}
----
<1> Replace the bean named `service` with a Mockito mock.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests {
@MockitoBean("service") // <1>
lateinit var customService: CustomService
// tests...
}
----
<1> Replace the bean named `service` with a Mockito mock.
======
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
@@ -218,19 +187,6 @@ Java::
----
<1> Register `OrderService` and `UserService` mocks by-type.
<2> Register `PrintingService` mock by-name.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MockitoBean(types = [OrderService::class, UserService::class]) // <1>
@MockitoBean(name = "ps1", types = [PrintingService::class]) // <2>
annotation class SharedMocks
----
<1> Register `OrderService` and `UserService` mocks by-type.
<2> Register `PrintingService` mock by-name.
======
The following demonstrates how `@SharedMocks` can be used on a test class.
@@ -261,34 +217,6 @@ Java::
----
<1> Register common mocks via the custom `@SharedMocks` annotation.
<2> Optionally inject mocks to _stub_ or _verify_ them.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
@SharedMocks // <1>
class BeanOverrideTests {
@Autowired
lateinit var orderService: OrderService // <2>
@Autowired
lateinit var userService: UserService // <2>
@Autowired
lateinit var ps1: PrintingService // <2>
// Inject other components that rely on the mocks.
@Test
fun testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common mocks via the custom `@SharedMocks` annotation.
<2> Optionally inject mocks to _stub_ or _verify_ them.
======
TIP: The mocks can also be injected into `@Configuration` classes or other test-related
@@ -318,21 +246,6 @@ Java::
}
----
<1> Wrap the bean with type `CustomService` with a Mockito spy.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests {
@MockitoSpyBean // <1>
lateinit var customService: CustomService
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito spy.
======
In the example above, we are wrapping the bean with type `CustomService`. If more than
@@ -358,21 +271,6 @@ Java::
}
----
<1> Wrap the bean named `service` with a Mockito spy.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests {
@MockitoSpyBean("service") // <1>
lateinit var customService: CustomService
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito spy.
======
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
@@ -392,19 +290,6 @@ Java::
----
<1> Register `OrderService` and `UserService` spies by-type.
<2> Register `PrintingService` spy by-name.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MockitoSpyBean(types = [OrderService::class, UserService::class]) // <1>
@MockitoSpyBean(name = "ps1", types = [PrintingService::class]) // <2>
annotation class SharedSpies
----
<1> Register `OrderService` and `UserService` spies by-type.
<2> Register `PrintingService` spy by-name.
======
The following demonstrates how `@SharedSpies` can be used on a test class.
@@ -435,34 +320,6 @@ Java::
----
<1> Register common spies via the custom `@SharedSpies` annotation.
<2> Optionally inject spies to _stub_ or _verify_ them.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
@SharedSpies // <1>
class BeanOverrideTests {
@Autowired
lateinit var orderService: OrderService // <2>
@Autowired
lateinit var userService: UserService // <2>
@Autowired
lateinit var ps1: PrintingService // <2>
// Inject other components that rely on the spies.
@Test
fun testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common spies via the custom `@SharedSpies` annotation.
<2> Optionally inject spies to _stub_ or _verify_ them.
======
TIP: The spies can also be injected into `@Configuration` classes or other test-related
@@ -3,7 +3,7 @@
`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. You can
use `@SqlGroup` natively to declare several nested `@Sql` annotations, or you can use it
in conjunction with Java's support for repeatable annotations, where `@Sql` can be
in conjunction with Java 8's support for repeatable annotations, where `@Sql` can be
declared several times on the same class or method, implicitly generating this container
annotation. The following example shows how to declare an SQL group:
@@ -73,27 +73,6 @@ Java::
----
<1> Mark a field for overriding the bean with type `CustomService`.
<2> The result of this static method will be used as the instance and injected into the field.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@TestBean // <1>
lateinit var customService: CustomService
// test case body...
companion object {
@JvmStatic
fun customService(): CustomService { // <2>
return MyFakeCustomService()
}
}
}
----
<1> Mark a field for overriding the bean with type `CustomService`.
<2> The result of this static method will be used as the instance and injected into the field.
======
In the example above, we are overriding the bean with type `CustomService`. If more than
@@ -123,28 +102,6 @@ Java::
<1> Mark a field for overriding the bean with name `service`, and specify that the
factory method is named `createCustomService`.
<2> The result of this static method will be used as the instance and injected into the field.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@TestBean(name = "service", methodName = "createCustomService") // <1>
lateinit var customService: CustomService
// test case body...
companion object {
@JvmStatic
fun createCustomService(): CustomService { // <2>
return MyFakeCustomService()
}
}
}
----
<1> Mark a field for overriding the bean with name `service`, and specify that the
factory method is named `createCustomService`.
<2> The result of this static method will be used as the instance and injected into the field.
======
[TIP]
@@ -9,6 +9,7 @@ and can be used anywhere in the Spring Framework.
* `@Qualifier`
* `@Value`
* `@Resource` (jakarta.annotation) if JSR-250 is present
* `@ManagedBean` (jakarta.annotation) if JSR-250 is present
* `@Inject` (jakarta.inject) if JSR-330 is present
* `@Named` (jakarta.inject) if JSR-330 is present
* `@PersistenceContext` (jakarta.persistence) if JPA is present
@@ -5,25 +5,24 @@ It is important to be able to perform some integration testing without requiring
deployment to your application server or connecting to other enterprise infrastructure.
Doing so lets you test things such as:
* The correct wiring of your Spring components.
* Data access using JDBC or an ORM tool.
** This can include such things as the correctness of SQL statements, Hibernate queries,
JPA entity mappings, and so forth.
* The correct wiring of your Spring IoC container contexts.
* Data access using JDBC or an ORM tool. This can include such things as the correctness
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
The Spring Framework provides first-class support for integration testing in the
`spring-test` module. The name of the actual JAR file might include the release version,
depending on where you get it from (see the
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
for details). This library includes the `org.springframework.test` package, which
`spring-test` module. The name of the actual JAR file might include the release version
and might also be in the long `org.springframework.test` form, depending on where you get
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
for an explanation). This library includes the `org.springframework.test` package, which
contains valuable classes for integration testing with a Spring container. This testing
does not rely on an application server or other deployment environment. Such tests are
slower to run than unit tests but much faster than the equivalent Selenium tests or
remote tests that rely on deployment to an application server.
Unit and integration testing support is provided in the form of the annotation-driven
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
framework is agnostic of the actual testing framework in use, which allows
instrumentation of tests in various environments, including JUnit, TestNG, and others.
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
agnostic of the actual testing framework in use, which allows instrumentation of tests
in various environments, including JUnit, TestNG, and others.
The following section provides an overview of the high-level goals of Spring's
integration support, and the rest of this chapter then focuses on dedicated topics:
@@ -3,7 +3,7 @@
In the previous sections, we have seen how to use MockMvc in conjunction with the raw
HtmlUnit APIs. In this section, we use additional abstractions within the Selenium
https://www.selenium.dev/documentation/webdriver/[WebDriver] to make things even easier.
https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
[[mockmvc-server-htmlunit-webdriver-why]]
== Why WebDriver and MockMvc?
@@ -12,8 +12,8 @@ We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver?
Selenium WebDriver provides a very elegant API that lets us easily organize our code. To
better show how it works, we explore an example in this section.
NOTE: Despite being a part of https://www.selenium.dev/documentation/[Selenium],
WebDriver does not require a Selenium Server to run your tests.
NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not
require a Selenium Server to run your tests.
Suppose we need to ensure that a message is created properly. The tests involve finding
the HTML form input elements, filling them out, and making various assertions.
@@ -308,7 +308,7 @@ interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
@@ -352,7 +352,7 @@ interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
@@ -11,7 +11,7 @@ https://testng.org/[TestNG] ::
testing, distributed testing, and other features. Supported in the
xref:testing/testcontext-framework.adoc[Spring TestContext Framework].
{assertj-docs}[AssertJ] ::
"Fluent assertions for Java", including support for lambda expressions, streams, and
"Fluent assertions for Java", including support for Java 8 lambdas, streams, and
numerous other features. Supported in Spring's
xref:testing/mockmvc/assertj.adoc[MockMvc testing support].
https://en.wikipedia.org/wiki/Mock_Object[Mock Objects] ::
@@ -10,7 +10,7 @@ applications without a running server via MockMvc.
[[resttestclient.setup]]
[[resttestclient-setup]]
== Setup
To set up a `RestTestClient` you need to choose a server setup to bind to. This can be one
@@ -18,7 +18,7 @@ of several MockMvc setup choices, or a connection to a live server.
[[resttestclient.controller-config]]
[[resttestclient-controller-config]]
=== Bind to Controller
This setup allows you to test specific controller(s) via mock request and response objects,
@@ -42,17 +42,55 @@ Kotlin::
----
======
[[resttestclient.context-config]]
[[resttestclient-context-config]]
=== Bind to `ApplicationContext`
This setup allows you to load Spring configuration with Spring MVC
infrastructure and controller declarations and use it to handle requests via mock request
and response objects, without a running server.
include-code::./RestClientContextTests[indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(WebConfig.class) // <1>
class MyTests {
RestTestClient client;
[[resttestclient.fn-config]]
@BeforeEach
void setUp(ApplicationContext context) { // <2>
client = RestTestClient.bindToApplicationContext(context).build(); // <3>
}
}
----
<1> Specify the configuration to load
<2> Inject the configuration
<3> Create the `RestTestClient`
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(WebConfig::class) // <1>
class MyTests {
lateinit var client: RestTestClient
@BeforeEach
fun setUp(context: ApplicationContext) { // <2>
client = RestTestClient.bindToApplicationContext(context).build() // <3>
}
}
----
<1> Specify the configuration to load
<2> Inject the configuration
<3> Create the `RestTestClient`
======
[[resttestclient-fn-config]]
=== Bind to Router Function
This setup allows you to test xref:web/webmvc-functional.adoc[functional endpoints] via
@@ -77,7 +115,7 @@ Kotlin::
----
======
[[resttestclient.server-config]]
[[resttestclient-server-config]]
=== Bind to Server
This setup connects to a running server to perform full, end-to-end HTTP tests:
@@ -101,7 +139,7 @@ Kotlin::
[[resttestclient.client-config]]
[[resttestclient-client-config]]
=== Client Config
In addition to the server setup options described earlier, you can also configure client
@@ -132,7 +170,7 @@ Kotlin::
[[resttestclient.tests]]
[[resttestclient-tests]]
== Writing Tests
xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] and `RestTestClient` have
@@ -144,22 +182,70 @@ provides two alternative ways to verify the response:
[[resttestclient.workflow]]
[[resttestclient-workflow]]
=== Built-in Assertions
To use the built-in assertions, remain in the workflow after the call to `exchange()`, and
use one of the expectation methods. For example:
include-code::./RestClientWorkflowTests[tag=test,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
----
======
If you would like for all expectations to be asserted even if one of them fails, you can
use `expectAll(..)` instead of multiple chained `expect*(..)` calls. This feature is
similar to the _soft assertions_ support in AssertJ and the `assertAll()` support in
JUnit Jupiter.
include-code::./RestClientWorkflowTests[tag=soft-assertions,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectAll(
spec -> spec.expectStatus().isOk(),
spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectAll(
{ spec -> spec.expectStatus().isOk() },
{ spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) }
)
----
======
You can then choose to decode the response body through one of the following:
@@ -170,34 +256,124 @@ You can then choose to decode the response body through one of the following:
If the built-in assertions are insufficient, you can consume the object instead and
perform any other assertions:
include-code::./RestClientWorkflowTests[tag=consume,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.consumeWith(result -> {
// custom assertions (for example, AssertJ)...
});
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody<Person>()
.consumeWith {
// custom assertions (for example, AssertJ)...
}
----
======
Or you can exit the workflow and obtain a `EntityExchangeResult`:
include-code::./RestClientWorkflowTests[tag=result,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk
.expectBody<Person>()
.returnResult()
----
======
TIP: When you need to decode to a target type with generics, look for the overloaded methods
that accept {spring-framework-api}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`]
instead of `Class<T>`.
[[resttestclient.no-content]]
[[resttestclient-no-content]]
==== No Content
If the response is not expected to have content, you can assert that as follows:
include-code::./NoContentTests[tag=emptyBody,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
client.post().uri("/persons")
.body(person)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.post().uri("/persons")
.body(person)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty()
----
======
If you want to ignore the response content, the following releases the content without any assertions:
include-code::./NoContentTests[tag=ignoreBody,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound()
.expectBody(Void.class);
----
NOTE: Consuming the response body (for example, with `expectBody`) is required if your tests are running with
leak detection for pooled buffers. Without that, the tool will report buffers being leaked.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound
.expectBody<Unit>()
----
======
[[resttestclient.json]]
[[resttestclient-json]]
==== JSON Content
You can use `expectBody()` without a target type to perform assertions on the raw
@@ -205,25 +381,126 @@ content rather than through higher level Object(s).
To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAssert]:
include-code::./JsonTests[tag=jsonBody,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
----
======
To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
include-code::./JsonTests[tag=jsonPath,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason")
----
======
[[resttestclient.assertj]]
[[resttestclient-assertj]]
=== AssertJ Integration
`RestTestClientResponse` is the main entry point for the AssertJ integration.
It is an `AssertProvider` that wraps the `ResponseSpec` of an exchange in order to enable
use of `assertThat()` statements. For example:
include-code::./AssertJTests[tag=withSpec,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
ResponseSpec spec = client.get().uri("/persons").exchange();
RestTestClientResponse response = RestTestClientResponse.from(spec);
assertThat(response).hasStatusOk();
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN);
// ...
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val spec = client.get().uri("/persons").exchange()
val response = RestTestClientResponse.from(spec)
assertThat(response).hasStatusOk()
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN)
// ...
----
======
You can also use the built-in workflow first, and then obtain an `ExchangeResult` to wrap
and continue with AssertJ. For example:
include-code::./AssertJTests[tag=withResult,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
ExchangeResult result = client.get().uri("/persons").exchange()
. // ...
.returnResult();
RestTestClientResponse response = RestTestClientResponse.from(result);
assertThat(response).hasStatusOk();
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN);
// ...
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = client.get().uri("/persons").exchange()
. // ...
.returnResult()
val response = RestTestClientResponse.from(spec)
assertThat(response).hasStatusOk()
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN)
// ...
----
======
@@ -19,6 +19,6 @@ meta-annotation. If a bootstrapper is not explicitly configured by using
`WebTestContextBootstrapper` is used, depending on the presence of `@WebAppConfiguration`.
Since the `TestContextBootstrapper` SPI is likely to change in the future (to accommodate
new requirements), we strongly encourage implementors not to implement this interface
new requirements), we strongly encourage implementers not to implement this interface
directly but rather to extend `AbstractTestContextBootstrapper` or one of its concrete
subclasses instead.
@@ -49,6 +49,7 @@ Kotlin::
<1> Injecting the `ApplicationContext`.
======
Similarly, if your test is configured to load a `WebApplicationContext`, you can inject
the web application context into your test, as follows:
@@ -86,6 +87,7 @@ Kotlin::
<2> Injecting the `WebApplicationContext`.
======
Dependency injection by using `@Autowired` is provided by the
`DependencyInjectionTestExecutionListener`, which is configured by default
(see xref:testing/testcontext-framework/fixture-di.adoc[Dependency Injection of Test Fixtures]).
@@ -94,24 +96,22 @@ Dependency injection by using `@Autowired` is provided by the
Test classes that use the TestContext framework do not need to extend any particular
class or implement a specific interface to configure their application context. Instead,
configuration is achieved by declaring the `@ContextConfiguration` annotation at the
class level. If your test class does not explicitly declare component classes or resource
locations, the configured `ContextLoader` determines how to load a context from _default_
configuration classes or a _default_ location. In addition to component classes and
context resource locations, an application context can also be configured through
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[context customizers]
or xref:testing/testcontext-framework/ctx-management/initializers.adoc[context initializers].
class level. If your test class does not explicitly declare application context resource
locations or component classes, the configured `ContextLoader` determines how to load a
context from a default location or default configuration classes. In addition to context
resource locations and component classes, an application context can also be configured
through application context initializers.
The following sections explain how to use `@ContextConfiguration` and related annotations
to configure a test `ApplicationContext` by using component classes (typically
`@Configuration` classes), XML configuration files, Groovy scripts, context customizers,
or context initializers. Alternatively, you can implement and configure your own custom
`SmartContextLoader` for advanced use cases.
The following sections explain how to use Spring's `@ContextConfiguration` annotation to
configure a test `ApplicationContext` by using XML configuration files, Groovy scripts,
component classes (typically `@Configuration` classes), or context initializers.
Alternatively, you can implement and configure your own custom `SmartContextLoader` for
advanced use cases.
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML Resources]
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML resources]
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
* xref:testing/testcontext-framework/ctx-management/default-config.adoc[Default Context Configuration]
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing Component Classes, XML, and Groovy Scripts]
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing XML, Groovy Scripts, and Component Classes]
* xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[Context Configuration with Context Customizers]
* xref:testing/testcontext-framework/ctx-management/initializers.adoc[Context Configuration with Context Initializers]
* xref:testing/testcontext-framework/ctx-management/inheritance.adoc[Context Configuration Inheritance]
@@ -62,6 +62,17 @@ script by setting a JVM system property named `spring.test.context.cache.maxSize
alternative, you can set the same property via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
As of Spring Framework 7.0, an application context stored in the context cache will be
_paused_ when it is no longer actively in use and automatically _restarted_ the next time
the context is retrieved from the cache. Specifically, the latter will restart all
auto-startup beans in the application context, effectively restoring the lifecycle state.
This ensures that background processes within the context are not actively running while
the context is not used by tests. For example, JMS listener containers, scheduled tasks,
and any other components in the context that implement `Lifecycle` or `SmartLifecycle`
will be in a "stopped" state until the context is used again by a test. Note, however,
that `SmartLifecycle` components can opt out of pausing by returning `false` from
`SmartLifecycle#isPauseable()`.
Since having a large number of application contexts loaded within a given test suite can
cause the suite to take an unnecessarily long time to run, it is often beneficial to
know exactly how many contexts have been loaded and cached. To view the statistics for
@@ -1,45 +0,0 @@
[[testcontext-ctx-management-pausing]]
= Context Pausing
As of Spring Framework 7.0, an `ApplicationContext` stored in the context cache (see
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching]) may be
_paused_ when it is no longer actively in use and automatically _restarted_ the next time
the context is retrieved from the cache. Specifically, the latter will restart all
auto-startup beans in the application context, effectively restoring the lifecycle state.
This ensures that background processes within the context are not actively running while
the context is not used by tests. For example, JMS listener containers, scheduled tasks,
and any other components in the context that implement `Lifecycle` or `SmartLifecycle`
will be in a "stopped" state until the context is used again by a test. Note, however,
that `SmartLifecycle` components can opt out of pausing by returning `false` from
`SmartLifecycle#isPauseable()`.
You can control whether inactive application contexts should be paused by setting the
`PauseMode` to one of the following supported values.
`ALWAYS` :: Always pause inactive application contexts.
`ON_CONTEXT_SWITCH` :: Only pause inactive application contexts if the next context
retrieved from the context cache is a different context.
`NEVER` :: Never pause inactive application contexts, effectively disabling the pausing
feature of the context cache.
The `PauseMode` defaults to `ON_CONTEXT_SWITCH`, but it can be changed from the command
line or a build script by setting a JVM system property named
`spring.test.context.cache.pause` to one of the supported values (case insensitive). As
an alternative, you can set the property via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
For example, if you want inactive application contexts to always be paused, you can
switch from the default `ON_CONTEXT_SWITCH` mode to `ALWAYS` by setting the
`spring.test.context.cache.pause` system property to `always`.
```shell
-Dspring.test.context.cache.pause=always
```
Similarly, if you encounter issues with `Lifecycle` components that cannot or should not
opt out of pausing, or if you discover that your test suite runs more slowly due to the
pausing and restarting of application contexts, you can disable the pausing feature by
setting the `spring.test.context.cache.pause` system property to `never`.
```shell
-Dspring.test.context.cache.pause=never
```
@@ -1,53 +0,0 @@
[[testcontext-ctx-management-default-config]]
= Default Context Configuration
As explained in the sections on
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[component classes],
xref:testing/testcontext-framework/ctx-management/xml.adoc[XML resources], and
xref:testing/testcontext-framework/ctx-management/groovy.adoc[Groovy scripts], the
TestContext framework will attempt to locate _default_ context configuration if you do
not explicitly specify `@Configuration` classes, XML configuration files, or Groovy
scripts from which the test's `ApplicationContext` should be loaded.
However, due to a bug in the detection algorithm, default context configuration for a
superclass or enclosing class is currently ignored if the type hierarchy or enclosing
class hierarchy (for `@Nested` test classes) does not declare `@ContextConfiguration`.
Beginning with Spring Framework 7.1, the TestContext framework will reliably detect
**all** _default_ context configuration within a type hierarchy or enclosing class
hierarchy above a given test class in such scenarios. Consequently, test suites may
encounter issues after the upgrade to 7.1. For example, if a static nested
`@Configuration` class in a superclass or enclosing class is ignored due to the
aforementioned bug, after the bug has been fixed in 7.1 that `@Configuration` class will
no longer be ignored, which may lead to unexpected beans in the resulting
`ApplicationContext` our outright failures in tests.
In the interim, the TestContext framework logs a warning whenever it encounters _default_
context configuration that is currently ignored — for example, a `@Configuration` class
or XML configuration file. The remainder of this section provides guidance on how to
address such issues if you encounter warnings in your test suite.
[TIP]
====
Annotating the affected subclass or `@Nested` class with `@ContextConfiguration` allows
you to take matters into your own hands and specify which classes in the hierarchy are
actually intended to contribute context configuration.
====
If you do not want static nested `@Configuration` classes to be processed, you can:
- Remove the `@Configuration` declaration.
- Apply `@ContextConfiguration` only where you actually want such classes to be processed.
- Move the static nested `@Configuration` classes to standalone top-level classes so that
they cannot be accidentally interpreted as _default_ configuration classes.
Similarly, if you encounter issues with _default_ XML configuration files or Groovy
scripts being detected and you do not want them to be processed, you can:
- Apply `@ContextConfiguration` only where you actually want such resources to be
processed.
- Rename the resource files to something that does not match the default naming
convention (such as `*-context.xml` for XML configuration) so that they cannot be
accidentally interpreted as _default_ configuration files.
- Move the affected resource files to a different package or filesystem location within
your project.
@@ -543,85 +543,3 @@ Kotlin::
----
======
The following example demonstrates how to implement and register a custom
`SystemPropertyOverrideActiveProfilesResolver` that allows the `spring.profiles.active`
property (when configured as a JVM system property) to override profiles configured via
`@ActiveProfiles`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// profiles resolved programmatically via a custom resolver that
// allows "spring.profiles.active" to override @ActiveProfiles
@ActiveProfiles(
resolver = SystemPropertyOverrideActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// test body
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// profiles resolved programmatically via a custom resolver that
// allows "spring.profiles.active" to override @ActiveProfiles
@ActiveProfiles(
resolver = SystemPropertyOverrideActiveProfilesResolver::class,
inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
// test body
}
----
======
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary",fold="-imports"]
----
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.test.context.support.DefaultActiveProfilesResolver;
import org.springframework.util.StringUtils;
public class SystemPropertyOverrideActiveProfilesResolver extends DefaultActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
String profiles = System.getProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
return StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles));
}
return super.resolve(testClass);
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",fold="-imports"]
----
import org.springframework.core.env.AbstractEnvironment
import org.springframework.test.context.support.DefaultActiveProfilesResolver
import org.springframework.util.StringUtils
class SystemPropertyOverrideActiveProfilesResolver : DefaultActiveProfilesResolver() {
override fun resolve(testClass: Class<*>): Array<String> {
val profiles = System.getProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME)
if (StringUtils.hasText(profiles)) {
return StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)
)
}
return super.resolve(testClass)
}
}
----
======
@@ -1,32 +1,33 @@
[[testcontext-ctx-management-mixed-config]]
= Mixing Component Classes, XML, and Groovy Scripts
= Mixing XML, Groovy Scripts, and Component Classes
It may sometimes be desirable to mix component classes (typically `@Configuration`
classes), XML configuration files, or Groovy scripts to configure an `ApplicationContext`
for your tests. For example, if you use XML configuration in production for legacy
reasons, you may decide that you want to use `@Configuration` classes to configure
It may sometimes be desirable to mix XML configuration files, Groovy scripts, and
component classes (typically `@Configuration` classes) to configure an
`ApplicationContext` for your tests. For example, if you use XML configuration in
production, you may decide that you want to use `@Configuration` classes to configure
specific Spring-managed components for your tests, or vice versa.
Furthermore, some third-party frameworks (such as Spring Boot) provide first-class
support for loading an `ApplicationContext` from different types of resources
simultaneously (for example, `@Configuration` classes, XML configuration files, and
Groovy scripts). The Spring Framework, historically, has not supported this for standard
deployments. Consequently, most of the `SmartContextLoader` implementations that the
Spring Framework delivers in the `spring-test` module support only one resource type for
each test context. However, this does not mean that you cannot use a mixture of resource
types. One exception to the general rule is that the `GenericGroovyXmlContextLoader` and
simultaneously (for example, XML configuration files, Groovy scripts, and
`@Configuration` classes). The Spring Framework, historically, has not supported this for
standard deployments. Consequently, most of the `SmartContextLoader` implementations that
the Spring Framework delivers in the `spring-test` module support only one resource type
for each test context. However, this does not mean that you cannot use both. One
exception to the general rule is that the `GenericGroovyXmlContextLoader` and
`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy
scripts simultaneously. Furthermore, third-party frameworks may choose to support the
declaration of both `classes` and `locations` through `@ContextConfiguration`, and, with
declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with
the standard testing support in the TestContext framework, you have the following options.
If you want to use `@Configuration` classes and resource locations (for example, XML or
Groovy) to configure your tests, you must pick one as the entry point, and that one must
import or include the other. For example, in a `@Configuration` class, you can use
`@ImportResource` to import XML configuration files or Groovy scripts; whereas, in XML or
Groovy scripts, you can include `@Configuration` classes by using component scanning or
defining them as normal Spring beans. Note that this behavior is semantically equivalent
If you want to use resource locations (for example, XML or Groovy) and `@Configuration`
classes to configure your tests, you must pick one as the entry point, and that one must
include or import the other. For example, in XML or Groovy scripts, you can include
`@Configuration` classes by using component scanning or defining them as normal Spring
beans, whereas, in a `@Configuration` class, you can use `@ImportResource` to import XML
configuration files or Groovy scripts. Note that this behavior is semantically equivalent
to how you configure your application in production: In production configuration, you
define either a set of `@Configuration` classes or a set of XML or Groovy resource
locations from which your production `ApplicationContext` is loaded, but you still have
the freedom to import or include the other type of configuration.
define either a set of XML or Groovy resource locations or a set of `@Configuration`
classes from which your production `ApplicationContext` is loaded, but you still have the
freedom to include or import the other type of configuration.
@@ -1,5 +1,5 @@
[[testcontext-ctx-management-xml]]
= Context Configuration with XML Resources
= Context Configuration with XML resources
To load an `ApplicationContext` for your tests by using XML configuration files, annotate
your test class with `@ContextConfiguration` and configure the `locations` attribute with
@@ -8,15 +8,12 @@ in JUnit and TestNG.
[[testcontext-junit-jupiter-extension]]
== SpringExtension for JUnit Jupiter
The `SpringExtension` integrates the Spring TestContext Framework into the JUnit Jupiter
testing framework.
NOTE: As of Spring Framework 7.0, the `SpringExtension` requires JUnit Jupiter 6.0 or higher.
By annotating test classes with `@ExtendWith(SpringExtension.class)`, you can implement
standard JUnit Jupiter-based unit and integration tests and simultaneously reap the
benefits of the TestContext framework, such as support for loading application contexts,
dependency injection of test instances, transactional test method execution, and so on.
The Spring TestContext Framework offers full integration with the JUnit Jupiter testing
framework, originally introduced in JUnit 5. By annotating test classes with
`@ExtendWith(SpringExtension.class)`, you can implement standard JUnit Jupiter-based unit
and integration tests and simultaneously reap the benefits of the TestContext framework,
such as support for loading application contexts, dependency injection of test instances,
transactional test method execution, and so on.
Furthermore, thanks to the rich extension API in JUnit Jupiter, Spring provides the
following features above and beyond the feature set that Spring supports for JUnit 4 and
@@ -25,7 +22,7 @@ TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://docs.junit.org/current/extensions/conditional-test-execution.html[conditional
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
@@ -163,7 +160,7 @@ for further details.
=== Dependency Injection with the `SpringExtension`
The `SpringExtension` implements the
link:https://docs.junit.org/current/extensions/parameter-resolution.html[`ParameterResolver`]
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
constructors, test methods, and test lifecycle callback methods.
@@ -392,16 +389,6 @@ any of its subclasses and nested classes. Thus, you may annotate a top-level tes
with `@NestedTestConfiguration`, and that will apply to all of its nested test classes
recursively.
[NOTE]
====
As of Spring Framework 7.0, the `SpringExtension` uses a test-method scoped
`ExtensionContext` within `@Nested` test class hierarchies by default. However, the
`SpringExtension` can be configured to use a test-class scoped `ExtensionContext` — for
example via `@SpringExtensionConfig` or the `spring.test.extension.context.scope` Spring
property (see
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]).
====
[TIP]
====
If you are developing a component that integrates with the Spring TestContext Framework
@@ -362,7 +362,7 @@ of `PlatformTransactionManager` within the test's `ApplicationContext`, you can
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
`@Configuration` class. Consult the
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager(org.springframework.test.context.TestContext,java.lang.String)[javadoc
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
@@ -75,7 +75,7 @@ infrastructure and controller declarations and use it to handle requests via moc
and response objects, without a running server.
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext(org.springframework.context.ApplicationContext)[WebHttpHandlerBuilder]
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
requests:
@@ -0,0 +1,186 @@
[[web-integration]]
= Other Web Frameworks
This chapter details Spring's integration with third-party web frameworks.
One of the core value propositions of the Spring Framework is that of enabling
_choice_. In a general sense, Spring does not force you to use or buy into any
particular architecture, technology, or methodology (although it certainly recommends
some over others). This freedom to pick and choose the architecture, technology, or
methodology that is most relevant to a developer and their development team is
arguably most evident in the web area, where Spring provides its own web frameworks
(xref:web/webmvc.adoc#mvc[Spring MVC] and xref:web/webflux.adoc#webflux[Spring WebFlux])
while, at the same time, supporting integration with a number of popular third-party
web frameworks.
[[web-integration-common]]
== Common Configuration
Before diving into the integration specifics of each supported web framework, let us
first take a look at common Spring configuration that is not specific to any one web
framework. (This section is equally applicable to Spring's own web framework variants.)
One of the concepts (for want of a better word) espoused by Spring's lightweight
application model is that of a layered architecture. Remember that in a "classic"
layered architecture, the web layer is but one of many layers. It serves as one of the
entry points into a server-side application, and it delegates to service objects
(facades) that are defined in a service layer to satisfy business-specific (and
presentation-technology agnostic) use cases. In Spring, these service objects, any other
business-specific objects, data-access objects, and others exist in a distinct "business
context", which contains no web or presentation layer objects (presentation objects,
such as Spring MVC controllers, are typically configured in a distinct "presentation
context"). This section details how you can configure a Spring container (a
`WebApplicationContext`) that contains all of the 'business beans' in your application.
Moving on to specifics, all you need to do is declare a
{spring-framework-api}/web/context/ContextLoaderListener.html[`ContextLoaderListener`]
in the standard Jakarta EE servlet `web.xml` file of your web application and add a
`contextConfigLocation` `<context-param/>` section (in the same file) that defines which
set of Spring XML configuration files to load.
Consider the following `<listener/>` configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
----
Further consider the following `<context-param/>` configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
----
If you do not specify the `contextConfigLocation` context parameter, the
`ContextLoaderListener` looks for a file called `/WEB-INF/applicationContext.xml` to
load. Once the context files are loaded, Spring creates a
{spring-framework-api}/web/context/WebApplicationContext.html[`WebApplicationContext`]
object based on the bean definitions and stores it in the `ServletContext` of the web
application.
All Java web frameworks are built on top of the Servlet API, so you can use the
following code snippet to get access to this "business context" `ApplicationContext`
created by the `ContextLoaderListener`.
The following example shows how to get the `WebApplicationContext`:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
----
The
{spring-framework-api}/web/context/support/WebApplicationContextUtils.html[`WebApplicationContextUtils`]
class is for convenience, so you need not remember the name of the `ServletContext`
attribute. Its `getWebApplicationContext()` method returns `null` if an object
does not exist under the `WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`
key. Rather than risk getting `NullPointerExceptions` in your application, it is better
to use the `getRequiredWebApplicationContext()` method. This method throws an exception
when the `ApplicationContext` is missing.
Once you have a reference to the `WebApplicationContext`, you can retrieve beans by their
name or type. Most developers retrieve beans by name and then cast them to one of their
implemented interfaces.
Fortunately, most of the frameworks in this section have simpler ways of looking up beans.
Not only do they make it easy to get beans from a Spring container, but they also let you
use dependency injection on their controllers. Each web framework section has more detail
on its specific integration strategies.
[[jsf]]
== JSF
JavaServer Faces (JSF) is the JCP's standard component-based, event-driven web
user interface framework. It is an official part of the Jakarta EE umbrella but also
individually usable, for example, through embedding Mojarra or MyFaces within Tomcat.
Please note that recent versions of JSF became closely tied to CDI infrastructure
in application servers, with some new JSF functionality only working in such an
environment. Spring's JSF support is not actively evolved anymore and primarily
exists for migration purposes when modernizing older JSF-based applications.
The key element in Spring's JSF integration is the JSF `ELResolver` mechanism.
[[jsf-springbeanfaceselresolver]]
=== Spring Bean Resolver
`SpringBeanFacesELResolver` is a JSF compliant `ELResolver` implementation,
integrating with the standard Unified EL as used by JSF and JSP. It delegates to
Spring's "business context" `WebApplicationContext` first and then to the
default resolver of the underlying JSF implementation.
Configuration-wise, you can define `SpringBeanFacesELResolver` in your JSF
`faces-context.xml` file, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<faces-config>
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
...
</application>
</faces-config>
----
[[jsf-facescontextutils]]
=== Using `FacesContextUtils`
A custom `ELResolver` works well when mapping your properties to beans in
`faces-config.xml`, but, at times, you may need to explicitly grab a bean.
The {spring-framework-api}/web/jsf/FacesContextUtils.html[`FacesContextUtils`]
class makes this easy. It is similar to `WebApplicationContextUtils`, except that
it takes a `FacesContext` parameter rather than a `ServletContext` parameter.
The following example shows how to use `FacesContextUtils`:
[source,java,indent=0,subs="verbatim,quotes"]
----
ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
----
[[struts]]
== Apache Struts
Invented by Craig McClanahan, https://struts.apache.org[Struts] is an open-source project
hosted by the Apache Software Foundation. Struts 1.x greatly simplified the
JSP/Servlet programming paradigm and won over many developers who were using proprietary
frameworks. It simplified the programming model; it was open source; and it had a large
community, which let the project grow and become popular among Java web developers.
As a successor to the original Struts 1.x, check out Struts 2.x or more recent versions
as well as the Struts-provided
https://struts.apache.org/plugins/spring/[Spring Plugin] for built-in Spring integration.
[[tapestry]]
== Apache Tapestry
https://tapestry.apache.org/[Tapestry] is a "Component oriented framework for creating
dynamic, robust, highly scalable web applications in Java."
While Spring has its own xref:web/webmvc.adoc#mvc[powerful web layer], there are a number of unique
advantages to building an enterprise Java application by using a combination of Tapestry
for the web user interface and the Spring container for the lower layers.
For more information, see Tapestry's dedicated
https://tapestry.apache.org/integrating-with-spring-framework.html[integration module for Spring].
[[web-integration-resources]]
== Further Resources
The following links go to further resources about the various web frameworks described in
this chapter.
* The https://www.oracle.com/java/technologies/javaserverfaces.html[JSF] homepage
* The https://struts.apache.org/[Struts] homepage
* The https://tapestry.apache.org/[Tapestry] homepage
@@ -39,12 +39,12 @@ required CORS response headers set.
In order to enable cross-origin requests (that is, the `Origin` header is present and
differs from the host of the request), you need to have some explicitly declared CORS
configuration. If no matching CORS configuration is found, no CORS headers are added to
the responses to preflight, simple and actual CORS requests and, consequently, browsers
reject them.
configuration. If no matching CORS configuration is found, preflight requests are
rejected. No CORS headers are added to the responses of simple and actual CORS requests
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the WebFlux Java configuration to declare such mappings, which results in a single,
global map passed to all `HandlerMapping` implementations.
@@ -57,7 +57,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
@@ -305,6 +305,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
@@ -327,6 +328,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
@@ -13,9 +13,9 @@ the same xref:web/webflux/reactive-spring.adoc[Reactive Core] foundation.
== Overview
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-overview[See equivalent in the Servlet stack]#
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes a
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
`ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono<ServerResponse>`).
Both the request and the response object have immutable contracts that offer convenient
Both the request and the response object have immutable contracts that offer JDK 8-friendly
access to the HTTP request and response.
`HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
annotation-based programming model.
@@ -117,13 +117,13 @@ Most applications can run through the WebFlux Java configuration, see xref:web/w
== HandlerFunction
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-handler-functions[See equivalent in the Servlet stack]#
`ServerRequest` and `ServerResponse` are immutable interfaces that offer convenient
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
access to the HTTP request and response.
Both request and response provide {reactive-streams-site}[Reactive Streams] back pressure
against the body streams.
The request body is represented with a Reactor `Flux` or `Mono`.
The response body is represented with any Reactive Streams `Publisher`, including `Flux` and `Mono`.
For more on that, see xref:web/webflux-reactive-libraries.adoc[Reactive Libraries].
For more on that, see xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries].
[[webflux-fn-request]]
=== ServerRequest
@@ -235,14 +235,87 @@ val map = request.awaitMultipartData()
The following example shows how to access multipart data, one at a time, in streaming fashion:
include-code::./PartEventHandler[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
allPartsEvents.windowUntil(PartEvent::isLast)
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
if (signal.hasValue()) {
PartEvent event = signal.get();
if (event instanceof FormPartEvent formEvent) {
String value = formEvent.value();
// handle form field
}
else if (event instanceof FilePartEvent fileEvent) {
String filename = fileEvent.filename();
Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
// handle file upload
}
else {
return Mono.error(new RuntimeException("Unexpected event: " + event));
}
}
else {
return partEvents; // either complete or error signal
}
}));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val parts = request.bodyToFlux<PartEvent>()
allPartsEvents.windowUntil(PartEvent::isLast)
.concatMap {
it.switchOnFirst { signal, partEvents ->
if (signal.hasValue()) {
val event = signal.get()
if (event is FormPartEvent) {
val value: String = event.value();
// handle form field
} else if (event is FilePartEvent) {
val filename: String = event.filename();
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content);
// handle file upload
} else {
return Mono.error(RuntimeException("Unexpected event: " + event));
}
} else {
return partEvents; // either complete or error signal
}
}
}
}
----
======
NOTE: The body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
The following shows how to bind request parameters, URI variables, or headers via `DataBinder`,
and also shows how to customize the `DataBinder`:
The following shows how to bind request parameters, including an optional `DataBinder` customization:
[tabs]
======
Java::
+
[source,java]
----
Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
----
Kotlin::
+
[source,kotlin]
----
val pet = request.bind(Pet::class.java, {dataBinder -> dataBinder.setAllowedFields("name")})
----
======
include-code::./RequestHandler[tag=snippet,indent=0]
[[webflux-fn-response]]
=== ServerResponse
@@ -252,7 +325,24 @@ a `build` method to create it. You can use the builder to set the response statu
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
content:
include-code::./ResponseHandler[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java]
----
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
----
Kotlin::
+
[source,kotlin]
----
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
----
======
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
@@ -263,7 +353,7 @@ Java::
[source,java]
----
URI location = ...
return ServerResponse.created(location).build();
ServerResponse.created(location).build();
----
Kotlin::
@@ -271,7 +361,7 @@ Kotlin::
[source,kotlin]
----
val location: URI = ...
return ServerResponse.created(location).build()
ServerResponse.created(location).build()
----
======
@@ -284,14 +374,14 @@ Java::
+
[source,java]
----
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
----
Kotlin::
+
[source,kotlin]
----
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
----
======
@@ -326,7 +416,87 @@ Therefore, it is useful to group related handler functions together into a handl
has a similar role as `@Controller` in an annotation-based application.
For example, the following class exposes a reactive `Person` repository:
include-code::./PersonHandler[tag=snippet,indent=0]
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> listPeople(ServerRequest request) { // <1>
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) { // <2>
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> getPerson(ServerRequest request) { // <3>
int personId = Integer.valueOf(request.pathVariable("id"));
return repository.getPerson(personId)
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
----
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
JSON.
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
Note that `PersonRepository.savePerson(Person)` returns `Mono<Void>`: an empty `Mono` that emits
a completion signal when the person has been read from the request and stored. So we use the
`build(Publisher<Void>)` method to send a response when that completion signal is received (that is,
when the `Person` has been saved).
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
found. If it is not found, we use `switchIfEmpty(Mono<T>)` to return a 404 Not Found response.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class PersonHandler(private val repository: PersonRepository) {
suspend fun listPeople(request: ServerRequest): ServerResponse { // <1>
val people: Flow<Person> = repository.allPeople()
return ok().contentType(APPLICATION_JSON).bodyAndAwait(people);
}
suspend fun createPerson(request: ServerRequest): ServerResponse { // <2>
val person = request.awaitBody<Person>()
repository.savePerson(person)
return ok().buildAndAwait()
}
suspend fun getPerson(request: ServerRequest): ServerResponse { // <3>
val personId = request.pathVariable("id").toInt()
return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
?: ServerResponse.notFound().buildAndAwait()
}
}
----
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
JSON.
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
Note that `PersonRepository.savePerson(Person)` is a suspending function with no return type.
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
found. If it is not found, we return a 404 Not Found response.
======
--
[[webflux-fn-handler-validation]]
=== Validation
@@ -335,7 +505,66 @@ A functional endpoint can use Spring's xref:web/webmvc/mvc-config/validation.ado
apply validation to the request body. For example, given a custom Spring
xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Person`:
include-code::./PersonHandler[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class PersonHandler {
private final Validator validator = new PersonValidator(); // <1>
// ...
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); // <2>
return ok().build(repository.savePerson(person));
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString()); // <3>
}
}
}
----
<1> Create `Validator` instance.
<2> Apply validation.
<3> Raise exception for a 400 response.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class PersonHandler(private val repository: PersonRepository) {
private val validator = PersonValidator() // <1>
// ...
suspend fun createPerson(request: ServerRequest): ServerResponse {
val person = request.awaitBody<Person>()
validate(person) // <2>
repository.savePerson(person)
return ok().buildAndAwait()
}
private fun validate(person: Person) {
val errors: Errors = BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw ServerWebInputException(errors.toString()) // <3>
}
}
}
----
<1> Create `Validator` instance.
<2> Apply validation.
<3> Raise exception for a 400 response.
======
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
a global `Validator` instance based on `LocalValidatorFactoryBean`.
@@ -373,7 +602,28 @@ path, headers, xref:#api-version[API version], and more.
The following example uses an `Accept` header, request predicate:
include-code::./RouterConfiguration[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val route = coRouter {
GET("/hello-world", accept(TEXT_PLAIN)) {
ServerResponse.ok().bodyValueAndAwait("Hello World")
}
}
----
======
You can compose multiple request predicates together by using:
@@ -408,7 +658,61 @@ There are also other ways to compose multiple router functions together:
The following example shows the composition of four routes:
include-code::./RouterConfiguration[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
.POST("/person", handler::createPerson) // <3>
.add(otherRoute) // <4>
.build();
----
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
`PersonHandler.getPerson`
<2> `GET /person` with an `Accept` header that matches JSON is routed to
`PersonHandler.listPeople`
<3> `POST /person` with no additional predicates is mapped to
`PersonHandler.createPerson`, and
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import org.springframework.http.MediaType.APPLICATION_JSON
val repository: PersonRepository = ...
val handler = PersonHandler(repository);
val otherRoute: RouterFunction<ServerResponse> = coRouter { }
val route = coRouter {
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
POST("/person", handler::createPerson) // <3>
}.and(otherRoute) // <4>
----
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
`PersonHandler.getPerson`
<2> `GET /person` with an `Accept` header that matches JSON is routed to
`PersonHandler.listPeople`
<3> `POST /person` with no additional predicates is mapped to
`PersonHandler.createPerson`, and
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
======
[[nested-routes]]
=== Nested Routes
@@ -506,7 +810,7 @@ Java::
----
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", version("1.2"),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
request -> ServerResponse.ok().body("Hello World")).build();
----
Kotlin::
@@ -639,6 +943,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
@@ -675,6 +980,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Bean
@@ -742,9 +1048,9 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val route = router {
("/person" and accept(APPLICATION_JSON)).nest {
"/person".nest {
GET("/{id}", handler::getPerson)
GET(handler::listPeople)
GET("", handler::listPeople)
before { // <1>
ServerRequest.from(it)
.header("X-RequestHeader", "Value").build()
@@ -771,7 +1077,54 @@ Now we can add a simple security filter to our route, assuming that we have a `S
can determine whether a particular path is allowed.
The following example shows how to do so:
include-code::./RouterConfiguration[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val securityManager: SecurityManager = ...
val route = router {
("/person" and accept(APPLICATION_JSON)).nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
POST(handler::createPerson)
filter { request, next ->
if (securityManager.allowAccessTo(request.path())) {
next(request)
}
else {
status(UNAUTHORIZED).build();
}
}
}
}
----
======
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
We only let the handler function be run when access is allowed.
@@ -49,9 +49,10 @@ This strategy resolves the API version from a request. The WebFlux config provid
options to resolve from a header, query parameter, media type parameter,
or from the URL path. You can also use a custom `ApiVersionResolver`.
The path resolver selects the version from a path segment specified by index, or
raises `InvalidApiVersionException`, and therefore never results in `null` (no version)
unless it is configured with a `Predicate<RequestPath>` to determine if a path is versioned.
NOTE: The path resolver always resolves the version from the specified path segment, or
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
resolvers.
@@ -56,7 +56,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class WebConfiguration implements WebFluxConfigurer {
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
@@ -79,7 +80,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration
class WebConfiguration : WebFluxConfigurer {
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
@@ -117,7 +119,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class WebConfiguration implements WebFluxConfigurer {
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
// ...
@@ -139,7 +142,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration
class WebConfiguration : WebFluxConfigurer {
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
// ...
@@ -206,8 +210,13 @@ The following table shows the templating libraries that we have tested on differ
[%header]
|===
|Scripting Library |Scripting Engine
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|===
TIP: The basic rule for integrating any other script engine is that it must implement the
@@ -219,8 +228,17 @@ TIP: The basic rule for integrating any other script engine is that it must impl
You need to have the script engine on your classpath, the details of which vary by script engine:
* The https://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with
Java 8+. Using the latest update release available is highly recommended.
* https://www.jruby.org[JRuby] should be added as a dependency for Ruby support.
* https://www.jython.org[Jython] should be added as a dependency for Python support.
* `org.jetbrains.kotlin:kotlin-script-util` dependency and a `META-INF/services/javax.script.ScriptEngineFactory`
file containing a `org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory`
line should be added for Kotlin script support. See
https://github.com/sdeleuze/kotlin-script-templating[this example] for more detail.
You need to have the script templating library. One way to do that for JavaScript is
through https://www.webjars.org/[WebJars].
[[webflux-view-script-integrate]]
=== Script Templates
@@ -228,7 +246,7 @@ You need to have the script engine on your classpath, the details of which vary
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
the script files to load, what function to call to render templates, and so on.
The following example uses the Jython Python engine:
The following example uses Mustache templates and the Nashorn JavaScript engine:
[tabs]
======
@@ -237,7 +255,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class WebConfiguration implements WebFluxConfigurer {
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
@@ -247,8 +266,9 @@ Java::
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("jython");
configurer.setScripts("render.py");
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
@@ -260,7 +280,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration
class WebConfiguration : WebFluxConfigurer {
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
@@ -268,8 +289,9 @@ Kotlin::
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "jython"
setScripts("render.py")
engineName = "nashorn"
setScripts("mustache.js")
renderObject = "Mustache"
renderFunction = "render"
}
}
@@ -283,7 +305,94 @@ The `render` function is called with the following parameters:
* `RenderingContext renderingContext`: The
{spring-framework-api}/web/servlet/view/script/RenderingContext.html[`RenderingContext`]
that gives access to the application context, the locale, the template loader, and the
URL
URL (since 5.0)
`Mustache.render()` is natively compatible with this signature, so you can call it directly.
If your templating technology requires some customization, you can provide a script that
implements a custom render function. For example, https://handlebarsjs.com[Handlerbars]
needs to compile templates before using them and requires a
https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
browser facilities not available in the server-side script engine.
The following example shows how to set a custom render function:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("polyfill.js", "handlebars.js", "render.js")
renderFunction = "render"
isSharedEngine = false
}
}
----
======
NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe
script engines with templating libraries not designed for concurrency, such as Handlebars or
React running on Nashorn. In that case, Java SE 8 update 60 is required, due to
https://bugs.openjdk.java.net/browse/JDK-8076099[this bug], but it is generally
recommended to use a recent Java SE patch release in any case.
`polyfill.js` defines only the `window` object needed by Handlebars to run properly,
as the following snippet shows:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
var window = {};
----
This basic `render.js` implementation compiles the template before using it. A production
ready implementation should also store and reused cached templates or pre-compiled templates.
This can be done on the script side, as well as any customization you need (managing
template engine configuration for example).
The following example shows how compile a template:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
----
Check out the Spring Framework unit tests,
{spring-framework-code}/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[Java], and
@@ -373,7 +482,7 @@ purposes, it is useful to be able to alternate between rendering a model with an
or as other formats (such as JSON or XML), depending on the content type requested by the client.
To support doing so, Spring WebFlux provides the `HttpMessageWriterView`, which you can use to
plug in any of the available xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from
`spring-web`, such as `JacksonJsonEncoder`, `JacksonSmileEncoder`, or `Jaxb2XmlEncoder`.
`spring-web`, such as `Jackson2JsonEncoder`, `Jackson2SmileEncoder`, or `Jaxb2XmlEncoder`.
Unlike other view technologies, `HttpMessageWriterView` does not require a `ViewResolver` but is
instead xref:web/webflux/config.adoc#webflux-config-view-resolvers[configured] as a default view.
@@ -18,8 +18,8 @@ You can also use `WebClient.builder()` with further options:
* `exchangeStrategies`: HTTP message reader/writer customizations.
* `clientConnector`: HTTP client library settings.
* `apiVersionInserter`: to insert API version values in the request
* `observationRegistry`: the registry to use for enabling xref:integration/observability.adoc#observability.http-client.webclient[Observability support].
* `observationConvention`: xref:integration/observability.adoc#observability.config[an optional, custom convention to extract metadata] for recorded observations.
* `observationRegistry`: the registry to use for enabling xref:integration/observability.adoc#http-client.webclient[Observability support].
* `observationConvention`: xref:integration/observability.adoc#config[an optional, custom convention to extract metadata] for recorded observations.
For example:
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
- The `status` property of `ProblemDetail` determines the HTTP status.
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
already set.
- The Jackson JSON and XML message converters use "application/problem+json" or
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
to ensure they are favored for content negotiation.
- For content negotiation, the Jackson `HttpMessageConverter` prefers
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
@@ -66,13 +66,6 @@ from an existing `ProblemDetail`. This could be done centrally, for example, fro
`@ControllerAdvice` such as `ResponseEntityExceptionHandler` that re-creates the
`ProblemDetail` of an exception into a subclass with the additional non-standard fields.
TIP: In Spring Boot, the `spring.webflux.problemdetails.enabled` property autoconfigures
a `ResponseEntityExceptionHandler` that handles built-in exceptions with problem details.
In that case, you may prefer to create another `@ControllerAdvice` instead of extending
`ResponseEntityExceptionHandler` if you want to take over the handling of a specific
built-in exception. You'll need to ensure your handler is ordered ahead of the one
configured by Spring Boot whose order is 0.
[[webflux-ann-rest-exceptions-i18n]]
== Customization and i18n
@@ -142,10 +135,6 @@ Message codes and arguments for each error are also resolved via `MessageSource`
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
Message codes and arguments for each error are also resolved via `MessageSource`.
| `NoResourceFoundException`
| (default)
| `+{0}+` the request path (or portion of) used to find a resource
|===
NOTE: Unlike other exceptions, the message arguments for
@@ -251,8 +251,7 @@ Kotlin::
======
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
mark it with `@Primary`, or mark the one declared in the MVC config with `@Fallback`, in
order to avoid conflict.
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC config.
[[webflux-config-content-negotiation]]
@@ -335,8 +334,19 @@ Kotlin::
`ServerCodecConfigurer` provides a set of default readers and writers. You can use it to add
more readers and writers, customize the default ones, or replace the default ones completely.
For Jackson, consider using a Jackson format-specific builder like `JsonMapper.Builder` to configure Jackson's default
properties.
For Jackson JSON and XML, consider using
{spring-framework-api}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`],
which customizes Jackson's default properties with the following ones:
* {jackson-docs}/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES[`DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`] is disabled.
* {jackson-docs}/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html#DEFAULT_VIEW_INCLUSION[`MapperFeature.DEFAULT_VIEW_INCLUSION`] is disabled.
It also automatically registers the following well-known modules if they are detected on the classpath:
* {jackson-github-org}/jackson-datatype-jsr310[`jackson-datatype-jsr310`]: Support for Java 8 Date and Time API types.
* {jackson-github-org}/jackson-datatype-jdk8[`jackson-datatype-jdk8`]: Support for other Java 8 types, such as `Optional`.
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
[[webflux-config-view-resolvers]]
== View Resolvers
@@ -479,7 +489,7 @@ Java::
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
JacksonJsonEncoder encoder = new JacksonJsonEncoder();
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
registry.defaultViews(new HttpMessageWriterView(encoder));
}
@@ -498,7 +508,7 @@ Kotlin::
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
val encoder = JacksonJsonEncoder()
val encoder = Jackson2JsonEncoder()
registry.defaultViews(HttpMessageWriterView(encoder))
}
@@ -691,7 +701,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim"]
----
@Configuration
class WebConfiguration : WebFluxConfigurer {
class WebConfiguration : WebMvcConfigurer {
override fun configureApiVersioning(configurer: ApiVersionConfigurer) {
configurer.useRequestHeader("API-Version")
@@ -107,4 +107,7 @@ Kotlin::
[[webflux-ann-initbinder-model-design]]
NOTE: For more guidance on model design, please see xref:web/webflux/data-binding.adoc[Data Binding].
== Model Design
[.small]#xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[See equivalent in the Servlet stack]#
include::partial$web/web-data-binding-model-design.adoc[]
@@ -5,7 +5,7 @@
The following table shows the supported controller method arguments.
Reactive types (Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other]) are
Reactive types (Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other]) are
supported on arguments that require blocking I/O (for example, reading the request body) to
be resolved. This is marked in the Description column. Reactive types are not expected
on arguments that do not require blocking.
@@ -114,6 +114,6 @@ and others) and is equivalent to `required=false`.
| Any other argument
| If a method argument is not matched to any of the above, it is, by default, resolved as
a `@RequestParam` if it is a simple type, as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
or as a `@ModelAttribute`, otherwise.
|===
@@ -205,7 +205,7 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
@@ -41,8 +41,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyForm(
private val name: String,
private val file: FilePart)
val name: String,
val file: FilePart)
@Controller
class FileUploadController {
@@ -103,8 +103,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: Part, // <1>
@RequestPart("file-data") file: FilePart): String { // <2>
fun handle(@RequestPart("meta-data") Part metadata, // <1>
@RequestPart("file-data") FilePart file): String { // <2>
// ...
}
----
@@ -169,8 +169,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: Mono<MetaData>): String {
// use one of the onError* operators...
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
// ...
}
----
======
@@ -202,7 +202,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@RequestBody parts: Mono<MultiValueMap<String, Part>>): String { // <1>
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { // <1>
// ...
}
----
@@ -227,7 +227,87 @@ when uploading. If the file is large enough to be split across multiple buffers,
For example:
include-code::./PartEventController[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { <1>
allPartsEvents.windowUntil(PartEvent::isLast) <2>
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> { <3>
if (signal.hasValue()) {
PartEvent event = signal.get();
if (event instanceof FormPartEvent formEvent) { <4>
String value = formEvent.value();
// handle form field
}
else if (event instanceof FilePartEvent fileEvent) { <5>
String filename = fileEvent.filename();
Flux<DataBuffer> contents = partEvents.map(PartEvent::content); <6>
// handle file upload
}
else {
return Mono.error(new RuntimeException("Unexpected event: " + event));
}
}
else {
return partEvents; // either complete or error signal
}
}));
}
----
<1> Using `@RequestBody`.
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
followed by additional events belonging to subsequent parts.
This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
split events from all parts into windows that each belong to a single part.
<3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
file upload.
<4> Handling the form field.
<5> Handling the file upload.
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@PostMapping("/")
fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = { // <1>
allPartsEvents.windowUntil(PartEvent::isLast) <2>
.concatMap {
it.switchOnFirst { signal, partEvents -> <3>
if (signal.hasValue()) {
val event = signal.get()
if (event is FormPartEvent) { <4>
val value: String = event.value();
// handle form field
} else if (event is FilePartEvent) { <5>
val filename: String = event.filename();
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content); <6>
// handle file upload
} else {
return Mono.error(RuntimeException("Unexpected event: " + event));
}
} else {
return partEvents; // either complete or error signal
}
}
}
}
----
<1> Using `@RequestBody`.
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
followed by additional events belonging to subsequent parts.
This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
split events from all parts into windows that each belong to a single part.
<3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
file upload.
<4> Handling the form field.
<5> Handling the file upload.
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
======
Received part events can also be relayed to another service by using the `WebClient`.
See xref:web/webflux-webclient/client-body.adoc#webflux-client-body-multipart[Multipart Data].

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