diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml
index 9eaafdc2da31..1e486fe672c8 100644
--- a/.github/actions/build/action.yml
+++ b/.github/actions/build/action.yml
@@ -19,7 +19,7 @@ inputs:
java-version:
description: 'Java version to compile and test with'
required: false
- default: '24'
+ default: '25'
publish:
description: 'Whether to publish artifacts ready for deployment to Artifactory'
required: false
diff --git a/.github/actions/prepare-gradle-build/action.yml b/.github/actions/prepare-gradle-build/action.yml
index c07a74c1dfef..99f5f23cff13 100644
--- a/.github/actions/prepare-gradle-build/action.yml
+++ b/.github/actions/prepare-gradle-build/action.yml
@@ -19,7 +19,7 @@ inputs:
java-version:
description: 'Java version to use for the build'
required: false
- default: '24'
+ default: '25'
runs:
using: composite
steps:
@@ -30,6 +30,7 @@ runs:
java-version: |
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
+ 25
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4bf511a2827f..f1fa7b826222 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
toolchain: false
- version: 21
toolchain: true
- - version: 24
+ - version: 25
toolchain: true
exclude:
- os:
diff --git a/.sdkmanrc b/.sdkmanrc
index b41ba343b002..2b4236b43e3c 100644
--- a/.sdkmanrc
+++ b/.sdkmanrc
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
-java=24.0.1-librca
+java=25-librca
diff --git a/README.md b/README.md
index 9dd6a0f15954..edd478f0d829 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
## Continuous Integration Builds
-Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
+CI builds are defined with [GitHub Actions workflows](.github/workflows).
## Stay in Touch
diff --git a/build.gradle b/build.gradle
index 6fafc96e97f3..a86c93fcd8a6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,9 +4,9 @@ plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
id 'org.jetbrains.dokka'
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
- id 'io.github.goooler.shadow' version '8.1.8' 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.1" apply false
+ id 'io.spring.nullability' version '0.0.8' apply false
}
ext {
@@ -69,23 +69,16 @@ configure([rootProject] + javaProjects) { project ->
ext.javadocLinks = [
"https://docs.oracle.com/en/java/javase/17/docs/api/",
- "https://jakarta.ee/specifications/platform/11/apidocs/",
- "https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
+ //"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/5.13.3/api/",
+ "https://docs.junit.org/6.0.1/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
- //"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
- // Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
- // but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
- // JakartaEE equivalents in the jakarta.annotation package.
- //"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/",
- "https://jspecify.dev/docs/api/",
- "https://www.javadoc.io/doc/tools.jackson.core/jackson-databind/3.0.0-rc4/"
-
+ "https://jspecify.dev/docs/api/"
] as String[]
}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index ed471b72eae6..58cbf888cd4b 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -20,8 +20,8 @@ ext {
dependencies {
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
- implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.0.0"
- implementation "com.tngtech.archunit:archunit:1.4.0"
+ implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.1.0"
+ implementation "com.tngtech.archunit:archunit:1.4.1"
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
diff --git a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java
index 19a06771cdf1..9f235e4991c0 100644
--- a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java
+++ b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java
@@ -50,7 +50,7 @@ public void apply(Project project) {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
- checkstyle.setToolVersion("10.26.1");
+ checkstyle.setToolVersion("12.1.1");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
diff --git a/buildSrc/src/main/java/org/springframework/build/JavaConventions.java b/buildSrc/src/main/java/org/springframework/build/JavaConventions.java
index 52add8eb7804..741999760f23 100644
--- a/buildSrc/src/main/java/org/springframework/build/JavaConventions.java
+++ b/buildSrc/src/main/java/org/springframework/build/JavaConventions.java
@@ -45,7 +45,7 @@ public class JavaConventions {
*
NOTE: If you update this value, you should also update the value used in
* the {@code javadoc} task in {@code framework-api.gradle}.
*/
- private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(24);
+ private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(25);
/**
* The Java version we should use as the baseline for the compiled bytecode
diff --git a/buildSrc/src/main/java/org/springframework/build/multirelease/MultiReleaseJarPlugin.java b/buildSrc/src/main/java/org/springframework/build/multirelease/MultiReleaseJarPlugin.java
index 9e9c871d05cf..a2aa7fba46c7 100644
--- a/buildSrc/src/main/java/org/springframework/build/multirelease/MultiReleaseJarPlugin.java
+++ b/buildSrc/src/main/java/org/springframework/build/multirelease/MultiReleaseJarPlugin.java
@@ -27,6 +27,10 @@
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.jvm.tasks.Jar;
+import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JavaToolchainService;
/**
@@ -38,6 +42,8 @@
*/
public class MultiReleaseJarPlugin implements Plugin {
+ public static String VALIDATE_JAR_TASK_NAME = "validateMultiReleaseJar";
+
@Inject
protected JavaToolchainService getToolchains() {
throw new UnsupportedOperationException();
@@ -57,5 +63,11 @@ public void apply(Project project) {
tasks,
dependencies,
objects);
+
+ TaskProvider validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
+ task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
+ task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
+ });
+ tasks.named("check", task -> task.dependsOn(validateJarTask));
}
}
diff --git a/buildSrc/src/main/java/org/springframework/build/multirelease/MultiReleaseJarValidateTask.java b/buildSrc/src/main/java/org/springframework/build/multirelease/MultiReleaseJarValidateTask.java
new file mode 100644
index 000000000000..fd1e49606499
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/build/multirelease/MultiReleaseJarValidateTask.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.build.multirelease;
+
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.CacheableTask;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.PathSensitive;
+import org.gradle.api.tasks.PathSensitivity;
+import org.gradle.jvm.toolchain.JavaToolchainService;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+@CacheableTask
+public abstract class MultiReleaseJarValidateTask extends JavaExec {
+
+
+ public MultiReleaseJarValidateTask() {
+ getMainModule().set("jdk.jartool");
+ getArgumentProviders().add(() -> List.of("--validate", "--file", getJar().get().getAsFile().getAbsolutePath()));
+ }
+
+ @Inject
+ protected abstract JavaToolchainService getJavaToolchainService();
+
+ @InputFile
+ @PathSensitive(PathSensitivity.RELATIVE)
+ public abstract RegularFileProperty getJar();
+
+}
diff --git a/buildSrc/src/test/java/org/springframework/build/multirelease/MultiReleaseJarPluginTests.java b/buildSrc/src/test/java/org/springframework/build/multirelease/MultiReleaseJarPluginTests.java
index a678d4506d45..58d47e82e124 100644
--- a/buildSrc/src/test/java/org/springframework/build/multirelease/MultiReleaseJarPluginTests.java
+++ b/buildSrc/src/test/java/org/springframework/build/multirelease/MultiReleaseJarPluginTests.java
@@ -26,11 +26,13 @@
import java.util.jar.JarFile;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
+import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link MultiReleaseJarPlugin}
@@ -116,6 +118,34 @@ public class Main {}
}
}
+ @Test
+ void validateJar() throws IOException {
+ writeBuildFile("""
+ plugins {
+ id 'java'
+ id 'org.springframework.build.multiReleaseJar'
+ }
+ version = '1.2.3'
+ tasks.withType(JavaCompile).configureEach {
+ options.release = 11
+ }
+ multiRelease { releaseVersions 17 }
+ """);
+ writeClass("src/main/java17", "Main.java", """
+ public class Main {
+
+ public void method() {}
+
+ }
+ """);
+ writeClass("src/main/java", "Main.java", """
+ public class Main {}
+ """);
+ assertThatThrownBy(() ->runGradle("validateMultiReleaseJar"))
+ .isInstanceOf(UnexpectedBuildFailure.class)
+ .hasMessageContaining("entry: META-INF/versions/17/Main.class, contains a class with different api from earlier version");
+ }
+
private void writeBuildFile(String buildContent) throws IOException {
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
out.print(buildContent);
diff --git a/framework-api/framework-api.gradle b/framework-api/framework-api.gradle
index fc3ecb8637f1..978ff802e6d6 100644
--- a/framework-api/framework-api.gradle
+++ b/framework-api/framework-api.gradle
@@ -22,7 +22,7 @@ dependencies {
javadoc {
javadocTool.set(javaToolchains.javadocToolFor({
- languageVersion = JavaLanguageVersion.of(24)
+ languageVersion = JavaLanguageVersion.of(25)
}))
title = "${rootProject.description} ${version} API"
diff --git a/framework-docs/framework-docs.gradle b/framework-docs/framework-docs.gradle
index 89250dcf590b..1ba931790dc6 100644
--- a/framework-docs/framework-docs.gradle
+++ b/framework-docs/framework-docs.gradle
@@ -1,3 +1,4 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
plugins {
@@ -43,10 +44,14 @@ repositories {
}
}
-// To avoid a redeclaration error with Kotlin compiler
-tasks.named('compileKotlin', KotlinCompilationTask.class) {
+// To avoid a redeclaration error with Kotlin compiler and set the JVM target
+tasks.withType(KotlinCompilationTask.class).configureEach {
javaSources.from = []
- compilerOptions.freeCompilerArgs = [ "-Xannotation-default-target=param-property" ] // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
+ compilerOptions.jvmTarget = JvmTarget.JVM_17
+ compilerOptions.freeCompilerArgs.addAll(
+ "-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
+ "-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
+ )
}
dependencies {
@@ -65,6 +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.projectreactor.netty:reactor-netty-http")
implementation("jakarta.jms:jakarta.jms-api")
implementation("jakarta.servlet:jakarta.servlet-api")
@@ -78,6 +84,8 @@ dependencies {
implementation("org.assertj:assertj-core")
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("org.junit.jupiter:junit-jupiter-api")
implementation("tools.jackson.core:jackson-databind")
implementation("tools.jackson.dataformat:jackson-dataformat-xml")
diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc
index 7d8e6903e15f..e51cc221fa36 100644
--- a/framework-docs/modules/ROOT/nav.adoc
+++ b/framework-docs/modules/ROOT/nav.adoc
@@ -314,7 +314,7 @@
*** xref:web/webflux-webclient/client-context.adoc[]
*** xref:web/webflux-webclient/client-synchronous.adoc[]
*** xref:web/webflux-webclient/client-testing.adoc[]
-** xref:web/webflux-http-interface-client.adoc[]
+** xref:web/webflux-http-service-client.adoc[]
** xref:web/webflux-websocket.adoc[]
** xref:web/webflux-test.adoc[]
** xref:rsocket.adoc[]
@@ -355,6 +355,7 @@
*** xref:testing/testcontext-framework/support-classes.adoc[]
*** xref:testing/testcontext-framework/aot.adoc[]
** xref:testing/webtestclient.adoc[]
+** xref:testing/resttestclient.adoc[]
** xref:testing/mockmvc.adoc[]
*** xref:testing/mockmvc/overview.adoc[]
*** xref:testing/mockmvc/setup-options.adoc[]
@@ -448,14 +449,13 @@
*** xref:languages/kotlin/null-safety.adoc[]
*** xref:languages/kotlin/classes-interfaces.adoc[]
*** xref:languages/kotlin/annotations.adoc[]
-*** xref:languages/kotlin/bean-definition-dsl.adoc[]
+*** xref:languages/kotlin/bean-registration-dsl.adoc[]
*** xref:languages/kotlin/web.adoc[]
*** xref:languages/kotlin/coroutines.adoc[]
*** xref:languages/kotlin/spring-projects-in.adoc[]
*** xref:languages/kotlin/getting-started.adoc[]
*** xref:languages/kotlin/resources.adoc[]
** xref:languages/groovy.adoc[]
-** xref:languages/dynamic.adoc[]
* xref:appendix.adoc[]
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
diff --git a/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc b/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc
index 4a01901853c1..b69cac9f570a 100644
--- a/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc
+++ b/framework-docs/modules/ROOT/pages/core/aop-api/targetsource.adoc
@@ -87,14 +87,12 @@ A crucial difference between Spring pooling and SLSB pooling is that Spring pool
be applied to any POJO. As with Spring in general, this service can be applied in a
non-invasive way.
-Spring provides support for Commons Pool 2.2, which provides a
+Spring provides support for Commons Pool 2, which provides a
fairly efficient pooling implementation. You need the `commons-pool` Jar on your
application's classpath to use this feature. You can also subclass
`org.springframework.aop.target.AbstractPoolingTargetSource` to support any other
pooling API.
-NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.
-
The following listing shows an example configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
diff --git a/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc b/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc
index 58d150a4c8d2..429e5d6e7ef1 100644
--- a/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc
+++ b/framework-docs/modules/ROOT/pages/core/aop/proxying.adoc
@@ -28,6 +28,10 @@ you can do so. However, you should consider the following issues:
deploying on the module path. Such cases require a JVM bootstrap flag
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
+
+[[aop-forcing-proxy-types]]
+== Forcing Specific AOP Proxy Types
+
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
of the `` element to true, as follows:
@@ -60,6 +64,24 @@ To be clear, using `proxy-target-class="true"` on ``,
proxies _for all three of them_.
====
+`@EnableAspectJAutoProxy`, `@EnableTransactionManagement` and related configuration
+annotations offer a corresponding `proxyTargetClass` attribute. These are collapsed
+into a single unified auto-proxy creator too, effectively applying the _strongest_
+proxy settings at runtime. As of 7.0, this applies to individual proxy processors
+as well, for example `@EnableAsync`, consistently participating in unified global
+default settings for all auto-proxying attempts in a given application.
+
+The global default proxy type may differ between setups. While the core framework
+suggests interface-based proxies by default, Spring Boot may - depending on
+configuration properties - enable class-based proxies by default.
+
+As of 7.0, forcing a specific proxy type for individual beans is possible through
+the `@Proxyable` annotation on a given `@Bean` method or `@Component` class, with
+`@Proxyable(INTERFACES)` or `@Proxyable(TARGET_CLASS)` overriding any globally
+configured default. For very specific purposes, you may even specify the proxy
+interface(s) to use through `@Proxyable(interfaces=...)`, limiting the exposure
+to selected interfaces rather than all interfaces that the target bean implements.
+
[[aop-understanding-aop-proxies]]
== Understanding AOP Proxies
diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc
index c108047ec6cf..a821a5b8a690 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc
@@ -37,18 +37,18 @@ Kotlin::
----
======
-[NOTE]
+[TIP]
====
-As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is no longer
-necessary if the target bean defines only one constructor to begin with. However, if
-several constructors are available and there is no primary/default constructor, at least
-one of the constructors must be annotated with `@Autowired` in order to instruct the
-container which one to use. See the discussion on
-xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution] for details.
+An `@Autowired` annotation on such a constructor is not necessary if the target bean
+defines only one constructor. However, if several constructors are available and there is
+no primary or default constructor, at least one of the constructors must be annotated
+with `@Autowired` in order to instruct the container which one to use. See the discussion
+on xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution]
+for details.
====
-You can also apply the `@Autowired` annotation to _traditional_ setter methods,
-as the following example shows:
+You can apply the `@Autowired` annotation to _traditional_ setter methods, as the
+following example shows:
[tabs]
======
@@ -84,8 +84,8 @@ Kotlin::
----
======
-You can also apply the annotation to methods with arbitrary names and multiple
-arguments, as the following example shows:
+You can apply `@Autowired` to methods with arbitrary names and multiple arguments, as the
+following example shows:
[tabs]
======
@@ -176,14 +176,15 @@ Kotlin::
====
Make sure that your target components (for example, `MovieCatalog` or `CustomerPreferenceDao`)
are consistently declared by the type that you use for your `@Autowired`-annotated
-injection points. Otherwise, injection may fail due to a "no type match found" error at runtime.
+injection points. Otherwise, injection may fail due to a "no type match found" error at
+runtime.
For XML-defined beans or component classes found via classpath scanning, the container
usually knows the concrete type up front. However, for `@Bean` factory methods, you need
to make sure that the declared return type is sufficiently expressive. For components
that implement several interfaces or for components potentially referred to by their
-implementation type, consider declaring the most specific return type on your factory
-method (at least as specific as required by the injection points referring to your bean).
+implementation type, declare the most specific return type on your factory method (at
+least as specific as required by the injection points referring to your bean).
====
.[[beans-autowired-annotation-self-injection]]Self Injection
@@ -312,8 +313,8 @@ through `@Order` values in combination with `@Primary` on a single bean for each
====
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
-The map values contain all beans of the expected type, and the keys contain the
-corresponding bean names, as the following example shows:
+The map values are all beans of the expected type, and the keys are the corresponding
+bean names, as the following example shows:
[tabs]
======
@@ -431,7 +432,7 @@ annotated constructor does not have to be public.
====
Alternatively, you can express the non-required nature of a particular dependency
-through Java 8's `java.util.Optional`, as the following example shows:
+through Java's `java.util.Optional`, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -445,8 +446,8 @@ through Java 8's `java.util.Optional`, as the following example shows:
----
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
-for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
-null-safety support:
+for example, `org.jspecify.annotations.Nullable` from JSpecify) or just leverage Kotlin's
+built-in null-safety support:
[tabs]
======
@@ -477,13 +478,6 @@ Kotlin::
----
======
-[NOTE]
-====
-A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
-Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
-detects type-level annotations and consistently declares JSpecify in its own codebase.
-====
-
You can also use `@Autowired` for interfaces that are well-known resolvable
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
@@ -528,5 +522,6 @@ class MovieRecommender {
The `@Autowired`, `@Inject`, `@Value`, and `@Resource` annotations are handled by Spring
`BeanPostProcessor` implementations. This means that you cannot apply these annotations
within your own `BeanPostProcessor` or `BeanFactoryPostProcessor` types (if any).
+
These types must be 'wired up' explicitly by using XML or a Spring `@Bean` method.
====
diff --git a/framework-docs/modules/ROOT/pages/core/beans/basics.adoc b/framework-docs/modules/ROOT/pages/core/beans/basics.adoc
index 25217f97f65e..ea07e2be83df 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/basics.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/basics.adoc
@@ -239,40 +239,6 @@ The namespace itself provides the import directive feature. Further
configuration features beyond plain bean definitions are available in a selection
of XML namespaces provided by Spring -- for example, the `context` and `util` namespaces.
-[[beans-factory-groovy]]
-=== The Groovy Bean Definition DSL
-
-As a further example for externalized configuration metadata, bean definitions can also
-be expressed in Spring's Groovy Bean Definition DSL, as known from the Grails framework.
-Typically, such configuration live in a ".groovy" file with the structure shown in the
-following example:
-
-[source,groovy,indent=0,subs="verbatim,quotes"]
-----
- beans {
- dataSource(BasicDataSource) {
- driverClassName = "org.hsqldb.jdbcDriver"
- url = "jdbc:hsqldb:mem:grailsDB"
- username = "sa"
- password = ""
- settings = [mynew:"setting"]
- }
- sessionFactory(SessionFactory) {
- dataSource = dataSource
- }
- myService(MyService) {
- nestedBean = { AnotherBean bean ->
- dataSource = dataSource
- }
- }
- }
-----
-
-This configuration style is largely equivalent to XML bean definitions and even
-supports Spring's XML configuration namespaces. It also allows for importing XML
-bean definition files through an `importBeans` directive.
-
-
[[beans-factory-client]]
== Using the Container
diff --git a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc
index b016731fd495..e4e546684489 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc
@@ -1,24 +1,26 @@
[[beans-classpath-scanning]]
= Classpath Scanning and Managed Components
-Most examples in this chapter use XML to specify the configuration metadata that produces
-each `BeanDefinition` within the Spring container. The previous section
-(xref:core/beans/annotation-config.adoc[Annotation-based Container Configuration]) demonstrates how to provide a lot of the configuration
-metadata through source-level annotations. Even in those examples, however, the "base"
-bean definitions are explicitly defined in the XML file, while the annotations drive only
-the dependency injection. This section describes an option for implicitly detecting the
-candidate components by scanning the classpath. Candidate components are classes that
-match against a filter criteria and have a corresponding bean definition registered with
-the container. This removes the need to use XML to perform bean registration. Instead, you
-can use annotations (for example, `@Component`), AspectJ type expressions, or your own
+Most examples in this chapter use XML to specify the configuration metadata that
+produces each `BeanDefinition` within the Spring container. The previous section
+(xref:core/beans/annotation-config.adoc[Annotation-based Container Configuration])
+demonstrates how to provide a lot of the configuration metadata through source-level
+annotations. Even in those examples, however, the "base" bean definitions are explicitly
+defined in the XML file, while the annotations drive only the dependency injection.
+
+This section describes an option for implicitly detecting the candidate components by
+scanning the classpath. Candidate components are classes that match against filter
+criteria and have a corresponding bean definition registered with the container.
+This removes the need to use XML to perform bean registration. Instead, you can use
+annotations (for example, `@Component`), AspectJ type expressions, or your own
custom filter criteria to select which classes have bean definitions registered with
the container.
[NOTE]
====
You can define beans using Java rather than using XML files. Take a look at the
-`@Configuration`, `@Bean`, `@Import`, and `@DependsOn` annotations for examples of how to
-use these features.
+`@Configuration`, `@Bean`, `@Import`, and `@DependsOn` annotations for examples
+of how to use these features.
====
@@ -68,7 +70,7 @@ Java::
// ...
}
----
-<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
+<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
Kotlin::
+
@@ -83,7 +85,7 @@ Kotlin::
// ...
}
----
-<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
+<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
======
You can also combine meta-annotations to create "`composed annotations`". For example,
@@ -95,7 +97,7 @@ meta-annotations to allow customization. This can be particularly useful when yo
want to only expose a subset of the meta-annotation's attributes. For example, Spring's
`@SessionScope` annotation hard codes the scope name to `session` but still allows
customization of the `proxyMode`. The following listing shows the definition of the
-`SessionScope` annotation:
+`@SessionScope` annotation:
[tabs]
======
@@ -209,7 +211,7 @@ Java::
@Service
public class SimpleMovieLister {
- private MovieFinder movieFinder;
+ private final MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
@@ -249,11 +251,11 @@ Kotlin::
----
======
-
To autodetect these classes and register the corresponding beans, you need to add
-`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute
-is a common parent package for the two classes. (Alternatively, you can specify a
-comma- or semicolon- or space-separated list that includes the parent package of each class.)
+`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute is
+configured with a common parent package for the two classes. Alternatively, you can
+specify a comma-, semicolon-, or space-separated list that includes the parent package
+of each class.
[tabs]
======
@@ -280,10 +282,10 @@ Kotlin::
----
======
-NOTE: For brevity, the preceding example could have used the `value` attribute of the
-annotation (that is, `@ComponentScan("org.example")`).
+TIP: For brevity, the preceding example could have used the implicit `value` attribute of
+the annotation instead: `@ComponentScan("org.example")`
-The following alternative uses XML:
+The following example uses XML configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -323,16 +325,68 @@ sure that they are 'opened' (that is, that they use an `opens` declaration inste
Furthermore, the `AutowiredAnnotationBeanPostProcessor` and
`CommonAnnotationBeanPostProcessor` are both implicitly included when you use the
-component-scan element. That means that the two components are autodetected and
-wired together -- all without any bean configuration metadata provided in XML.
+`` element. That means that the two components are autodetected
+and wired together -- all without any bean configuration metadata provided in XML.
NOTE: You can disable the registration of `AutowiredAnnotationBeanPostProcessor` and
`CommonAnnotationBeanPostProcessor` by including the `annotation-config` attribute
with a value of `false`.
+[[beans-scanning-placeholders-and-patterns]]
+=== Property Placeholders and Ant-style Patterns
+
+The `basePackages` and `value` attributes in `@ComponentScan` support `${...}` property
+placeholders which are resolved against the `Environment` as well as Ant-style package
+patterns such as `"org.example.+++**+++"`.
+
+In addition, multiple packages or patterns may be specified, either separately or within
+a single String — for example, `{"org.example.config", "org.example.service.+++**+++"}`
+or `"org.example.config, org.example.service.+++**+++"`.
+
+The following example specifies the `app.scan.packages` property placeholder for the
+implicit `value` attribute in `@ComponentScan`.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+@Configuration
+@ComponentScan("${app.scan.packages}") // <1>
+public class AppConfig {
+ // ...
+}
+----
+<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+@Configuration
+@ComponentScan(["\${app.scan.packages}"]) // <1>
+class AppConfig {
+ // ...
+}
+----
+<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
+======
+
+The following listing represents a properties file which defines the `app.scan.packages`
+property. In the preceding example, it is assumed that this properties file has been
+registered with the `Environment` – for example, via `@PropertySource` or a similar
+mechanism.
+
+[source,properties,indent=0,subs="verbatim,quotes"]
+----
+app.scan.packages=org.example.config, org.example.service.**
+----
+
+
[[beans-scanning-filters]]
-== Using Filters to Customize Scanning
+=== Using Filters to Customize Scanning
By default, classes annotated with `@Component`, `@Repository`, `@Service`, `@Controller`,
`@Configuration`, or a custom annotation that itself is annotated with `@Component` are
@@ -369,8 +423,8 @@ The following table describes the filtering options:
| A custom implementation of the `org.springframework.core.type.TypeFilter` interface.
|===
-The following example shows the configuration ignoring all `@Repository` annotations
-and using "`stub`" repositories instead:
+The following example shows `@ComponentScan` configuration that excludes all
+`@Repository` annotations and includes "`Stub`" repositories instead:
[tabs]
======
@@ -422,244 +476,8 @@ annotated or meta-annotated with `@Component`, `@Repository`, `@Service`, `@Cont
`@RestController`, or `@Configuration`.
-[[beans-factorybeans-annotations]]
-== Defining Bean Metadata within Components
-
-Spring components can also contribute bean definition metadata to the container. You can do
-this with the same `@Bean` annotation used to define bean metadata within `@Configuration`
-annotated classes. The following example shows how to do so:
-
-[tabs]
-======
-Java::
-+
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- @Component
- public class FactoryMethodComponent {
-
- @Bean
- @Qualifier("public")
- public TestBean publicInstance() {
- return new TestBean("publicInstance");
- }
-
- public void doWork() {
- // Component method implementation omitted
- }
- }
-----
-
-Kotlin::
-+
-[source,kotlin,indent=0,subs="verbatim,quotes"]
-----
- @Component
- class FactoryMethodComponent {
-
- @Bean
- @Qualifier("public")
- fun publicInstance() = TestBean("publicInstance")
-
- fun doWork() {
- // Component method implementation omitted
- }
- }
-----
-======
-
-The preceding class is a Spring component that has application-specific code in its
-`doWork()` method. However, it also contributes a bean definition that has a factory
-method referring to the method `publicInstance()`. The `@Bean` annotation identifies the
-factory method and other bean definition properties, such as a qualifier value through
-the `@Qualifier` annotation. Other method-level annotations that can be specified are
-`@Scope`, `@Lazy`, and custom qualifier annotations.
-
-[[beans-factorybeans-annotations-lazy-injection-points]]
-[TIP]
-====
-In addition to its role for component initialization, you can also place the `@Lazy`
-annotation on injection points marked with `@Autowired` or `@Inject`. In this context,
-it leads to the injection of a lazy-resolution proxy. However, such a proxy approach
-is rather limited. For sophisticated lazy interactions, in particular in combination
-with optional dependencies, we recommend `ObjectProvider` instead.
-====
-
-Autowired fields and methods are supported, as previously discussed, with additional
-support for autowiring of `@Bean` methods. The following example shows how to do so:
-
-[tabs]
-======
-Java::
-+
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- @Component
- public class FactoryMethodComponent {
-
- private static int i;
-
- @Bean
- @Qualifier("public")
- public TestBean publicInstance() {
- return new TestBean("publicInstance");
- }
-
- // use of a custom qualifier and autowiring of method parameters
- @Bean
- protected TestBean protectedInstance(
- @Qualifier("public") TestBean spouse,
- @Value("#{privateInstance.age}") String country) {
- TestBean tb = new TestBean("protectedInstance", 1);
- tb.setSpouse(spouse);
- tb.setCountry(country);
- return tb;
- }
-
- @Bean
- private TestBean privateInstance() {
- return new TestBean("privateInstance", i++);
- }
-
- @Bean
- @RequestScope
- public TestBean requestScopedInstance() {
- return new TestBean("requestScopedInstance", 3);
- }
- }
-----
-
-Kotlin::
-+
-[source,kotlin,indent=0,subs="verbatim,quotes"]
-----
- @Component
- class FactoryMethodComponent {
-
- companion object {
- private var i: Int = 0
- }
-
- @Bean
- @Qualifier("public")
- fun publicInstance() = TestBean("publicInstance")
-
- // use of a custom qualifier and autowiring of method parameters
- @Bean
- protected fun protectedInstance(
- @Qualifier("public") spouse: TestBean,
- @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
- this.spouse = spouse
- this.country = country
- }
-
- @Bean
- private fun privateInstance() = TestBean("privateInstance", i++)
-
- @Bean
- @RequestScope
- fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
- }
-----
-======
-
-The example autowires the `String` method parameter `country` to the value of the `age`
-property on another bean named `privateInstance`. A Spring Expression Language element
-defines the value of the property through the notation `#{ }`. For `@Value`
-annotations, an expression resolver is preconfigured to look for bean names when
-resolving expression text.
-
-As of Spring Framework 4.3, you may also declare a factory method parameter of type
-`InjectionPoint` (or its more specific subclass: `DependencyDescriptor`) to
-access the requesting injection point that triggers the creation of the current bean.
-Note that this applies only to the actual creation of bean instances, not to the
-injection of existing instances. As a consequence, this feature makes most sense for
-beans of prototype scope. For other scopes, the factory method only ever sees the
-injection point that triggered the creation of a new bean instance in the given scope
-(for example, the dependency that triggered the creation of a lazy singleton bean).
-You can use the provided injection point metadata with semantic care in such scenarios.
-The following example shows how to use `InjectionPoint`:
-
-[tabs]
-======
-Java::
-+
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- @Component
- public class FactoryMethodComponent {
-
- @Bean @Scope("prototype")
- public TestBean prototypeInstance(InjectionPoint injectionPoint) {
- return new TestBean("prototypeInstance for " + injectionPoint.getMember());
- }
- }
-----
-
-Kotlin::
-+
-[source,kotlin,indent=0,subs="verbatim,quotes"]
-----
- @Component
- class FactoryMethodComponent {
-
- @Bean
- @Scope("prototype")
- fun prototypeInstance(injectionPoint: InjectionPoint) =
- TestBean("prototypeInstance for ${injectionPoint.member}")
- }
-----
-======
-
-The `@Bean` methods in a regular Spring component are processed differently than their
-counterparts inside a Spring `@Configuration` class. The difference is that `@Component`
-classes are not enhanced with CGLIB to intercept the invocation of methods and fields.
-CGLIB proxying is the means by which invoking methods or fields within `@Bean` methods
-in `@Configuration` classes creates bean metadata references to collaborating objects.
-Such methods are not invoked with normal Java semantics but rather go through the
-container in order to provide the usual lifecycle management and proxying of Spring
-beans, even when referring to other beans through programmatic calls to `@Bean` methods.
-In contrast, invoking a method or field in a `@Bean` method within a plain `@Component`
-class has standard Java semantics, with no special CGLIB processing or other
-constraints applying.
-
-[NOTE]
-====
-You may declare `@Bean` methods as `static`, allowing for them to be called without
-creating their containing configuration class as an instance. This makes particular
-sense when defining post-processor beans (for example, of type `BeanFactoryPostProcessor`
-or `BeanPostProcessor`), since such beans get initialized early in the container
-lifecycle and should avoid triggering other parts of the configuration at that point.
-
-Calls to static `@Bean` methods never get intercepted by the container, not even within
-`@Configuration` classes (as described earlier in this section), due to technical
-limitations: CGLIB subclassing can override only non-static methods. As a consequence,
-a direct call to another `@Bean` method has standard Java semantics, resulting
-in an independent instance being returned straight from the factory method itself.
-
-The Java language visibility of `@Bean` methods does not have an immediate impact on
-the resulting bean definition in Spring's container. You can freely declare your
-factory methods as you see fit in non-`@Configuration` classes and also for static
-methods anywhere. However, regular `@Bean` methods in `@Configuration` classes need
-to be overridable -- that is, they must not be declared as `private` or `final`.
-
-`@Bean` methods are also discovered on base classes of a given component or
-configuration class, as well as on Java 8 default methods declared in interfaces
-implemented by the component or configuration class. This allows for a lot of
-flexibility in composing complex configuration arrangements, with even multiple
-inheritance being possible through Java 8 default methods as of Spring 4.2.
-
-Finally, a single class may hold multiple `@Bean` methods for the same
-bean, as an arrangement of multiple factory methods to use depending on available
-dependencies at runtime. This is the same algorithm as for choosing the "`greediest`"
-constructor or factory method in other configuration scenarios: The variant with
-the largest number of satisfiable dependencies is picked at construction time,
-analogous to how the container selects between multiple `@Autowired` constructors.
-====
-
-
[[beans-scanning-name-generator]]
-== Naming Autodetected Components
+=== Naming Autodetected Components
When a component is autodetected as part of the scanning process, its bean name is
generated by the `BeanNameGenerator` strategy known to that scanner.
@@ -790,7 +608,7 @@ auto-generated names are adequate whenever the container is responsible for wiri
[[beans-scanning-scope-resolver]]
-== Providing a Scope for Autodetected Components
+=== Providing a Scope for Autodetected Components
As with Spring-managed components in general, the default and most common scope for
autodetected components is `singleton`. However, sometimes you need a different scope
@@ -828,10 +646,10 @@ definitions, there is no notion of bean definition inheritance, and inheritance
hierarchies at the class level are irrelevant for metadata purposes.
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
-see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes]. As with the pre-built annotations for those scopes,
-you may also compose your own scoping annotations by using Spring's meta-annotation
-approach: for example, a custom annotation meta-annotated with `@Scope("prototype")`,
-possibly also declaring a custom scoped-proxy mode.
+see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes].
+As with the pre-built annotations for those scopes, you may also compose your own scoping
+annotations by using Spring's meta-annotation approach: for example, a custom annotation
+meta-annotated with `@Scope("prototype")`, possibly also declaring a custom scoped-proxy mode.
NOTE: To provide a custom strategy for scope resolution rather than relying on the
annotation-based approach, you can implement the
@@ -873,7 +691,8 @@ Kotlin::
----
When using certain non-singleton scopes, it may be necessary to generate proxies for the
-scoped objects. The reasoning is described in xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
+scoped objects. The reasoning is described in
+xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
For this purpose, a scoped-proxy attribute is available on the component-scan
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
the following configuration results in standard JDK dynamic proxies:
@@ -912,7 +731,7 @@ Kotlin::
[[beans-scanning-qualifiers]]
-== Providing Qualifier Metadata with Annotations
+=== Providing Qualifier Metadata with Annotations
The `@Qualifier` annotation is discussed in
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
@@ -1002,3 +821,239 @@ NOTE: As with most annotation-based alternatives, keep in mind that the annotati
bound to the class definition itself, while the use of XML allows for multiple beans
of the same type to provide variations in their qualifier metadata, because that
metadata is provided per-instance rather than per-class.
+
+
+[[beans-factorybeans-annotations]]
+== Defining Bean Metadata within Components
+
+Spring components can also contribute bean definition metadata to the container. You can do
+this with the same `@Bean` annotation used to define bean metadata within `@Configuration`
+annotated classes. The following example shows how to do so:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ @Component
+ public class FactoryMethodComponent {
+
+ @Bean
+ @Qualifier("public")
+ public TestBean publicInstance() {
+ return new TestBean("publicInstance");
+ }
+
+ public void doWork() {
+ // Component method implementation omitted
+ }
+ }
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ @Component
+ class FactoryMethodComponent {
+
+ @Bean
+ @Qualifier("public")
+ fun publicInstance() = TestBean("publicInstance")
+
+ fun doWork() {
+ // Component method implementation omitted
+ }
+ }
+----
+======
+
+The preceding class is a Spring component that has application-specific code in its
+`doWork()` method. However, it also contributes a bean definition that has a factory
+method referring to the method `publicInstance()`. The `@Bean` annotation identifies the
+factory method and other bean definition properties, such as a qualifier value through
+the `@Qualifier` annotation. Other method-level annotations that can be specified are
+`@Scope`, `@Lazy`, and custom qualifier annotations.
+
+[[beans-factorybeans-annotations-lazy-injection-points]]
+[TIP]
+====
+In addition to its role for component initialization, you can also place the `@Lazy`
+annotation on injection points marked with `@Autowired` or `@Inject`. In this context,
+it leads to the injection of a lazy-resolution proxy. However, such a proxy approach
+is rather limited. For sophisticated lazy interactions, in particular in combination
+with optional dependencies, we recommend `ObjectProvider` instead.
+====
+
+Autowired fields and methods are supported, as previously discussed, with additional
+support for autowiring of `@Bean` methods. The following example shows how to do so:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ @Component
+ public class FactoryMethodComponent {
+
+ private static int i;
+
+ @Bean
+ @Qualifier("public")
+ public TestBean publicInstance() {
+ return new TestBean("publicInstance");
+ }
+
+ // use of a custom qualifier and autowiring of method parameters
+ @Bean
+ protected TestBean protectedInstance(
+ @Qualifier("public") TestBean spouse,
+ @Value("#{privateInstance.age}") String country) {
+ TestBean tb = new TestBean("protectedInstance", 1);
+ tb.setSpouse(spouse);
+ tb.setCountry(country);
+ return tb;
+ }
+
+ @Bean
+ private TestBean privateInstance() {
+ return new TestBean("privateInstance", i++);
+ }
+
+ @Bean
+ @RequestScope
+ public TestBean requestScopedInstance() {
+ return new TestBean("requestScopedInstance", 3);
+ }
+ }
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ @Component
+ class FactoryMethodComponent {
+
+ companion object {
+ private var i: Int = 0
+ }
+
+ @Bean
+ @Qualifier("public")
+ fun publicInstance() = TestBean("publicInstance")
+
+ // use of a custom qualifier and autowiring of method parameters
+ @Bean
+ protected fun protectedInstance(
+ @Qualifier("public") spouse: TestBean,
+ @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
+ this.spouse = spouse
+ this.country = country
+ }
+
+ @Bean
+ private fun privateInstance() = TestBean("privateInstance", i++)
+
+ @Bean
+ @RequestScope
+ fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
+ }
+----
+======
+
+The example autowires the `String` method parameter `country` to the value of the `age`
+property on another bean named `privateInstance`. A Spring Expression Language element
+defines the value of the property through the notation `#{ }`. For `@Value`
+annotations, an expression resolver is preconfigured to look for bean names when
+resolving expression text.
+
+As of Spring Framework 4.3, you may also declare a factory method parameter of type
+`InjectionPoint` (or its more specific subclass: `DependencyDescriptor`) to
+access the requesting injection point that triggers the creation of the current bean.
+Note that this applies only to the actual creation of bean instances, not to the
+injection of existing instances. As a consequence, this feature makes most sense for
+beans of prototype scope. For other scopes, the factory method only ever sees the
+injection point that triggered the creation of a new bean instance in the given scope
+(for example, the dependency that triggered the creation of a lazy singleton bean).
+You can use the provided injection point metadata with semantic care in such scenarios.
+The following example shows how to use `InjectionPoint`:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ @Component
+ public class FactoryMethodComponent {
+
+ @Bean @Scope("prototype")
+ public TestBean prototypeInstance(InjectionPoint injectionPoint) {
+ return new TestBean("prototypeInstance for " + injectionPoint.getMember());
+ }
+ }
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ @Component
+ class FactoryMethodComponent {
+
+ @Bean
+ @Scope("prototype")
+ fun prototypeInstance(injectionPoint: InjectionPoint) =
+ TestBean("prototypeInstance for ${injectionPoint.member}")
+ }
+----
+======
+
+The `@Bean` methods in a regular Spring component are processed differently than their
+counterparts inside a Spring `@Configuration` class. The difference is that `@Component`
+classes are not enhanced with CGLIB to intercept the invocation of methods and fields.
+CGLIB proxying is the means by which invoking methods or fields within `@Bean` methods
+in `@Configuration` classes creates bean metadata references to collaborating objects.
+Such methods are not invoked with normal Java semantics but rather go through the
+container in order to provide the usual lifecycle management and proxying of Spring
+beans, even when referring to other beans through programmatic calls to `@Bean` methods.
+In contrast, invoking a method or field in a `@Bean` method within a plain `@Component`
+class has standard Java semantics, with no special CGLIB processing or other
+constraints applying.
+
+[NOTE]
+====
+You may declare `@Bean` methods as `static`, allowing for them to be called without
+creating their containing configuration class as an instance. This makes particular
+sense when defining post-processor beans (for example, of type `BeanFactoryPostProcessor`
+or `BeanPostProcessor`), since such beans get initialized early in the container
+lifecycle and should avoid triggering other parts of the configuration at that point.
+
+Calls to static `@Bean` methods never get intercepted by the container, not even within
+`@Configuration` classes (as described earlier in this section), due to technical
+limitations: CGLIB subclassing can override only non-static methods. As a consequence,
+a direct call to another `@Bean` method has standard Java semantics, resulting
+in an independent instance being returned straight from the factory method itself.
+
+The Java language visibility of `@Bean` methods does not have an immediate impact on
+the resulting bean definition in Spring's container. You can freely declare your
+factory methods as you see fit in non-`@Configuration` classes and also for static
+methods anywhere. However, regular `@Bean` methods in `@Configuration` classes need
+to be overridable -- that is, they must not be declared as `private` or `final`.
+
+`@Bean` methods are also discovered on base classes of a given component or
+configuration class, as well as on Java default methods declared in interfaces
+implemented by the component or configuration class. This allows for a lot of
+flexibility in composing complex configuration arrangements, with even multiple
+inheritance being possible through Java default methods.
+
+Finally, a single class may hold multiple `@Bean` methods for the same
+bean, as an arrangement of multiple factory methods to use depending on available
+dependencies at runtime. This is the same algorithm as for choosing the "`greediest`"
+constructor or factory method in other configuration scenarios: The variant with
+the largest number of satisfiable dependencies is picked at construction time,
+analogous to how the container selects between multiple `@Autowired` constructors.
+====
diff --git a/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc b/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc
index 7f6f19572af8..4bafff8d45f3 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/context-introduction.adoc
@@ -933,13 +933,12 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
// create a startup step and start recording
- StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
- // add tagging information to the current step
- scanPackages.tag("packages", () -> Arrays.toString(basePackages));
- // perform the actual phase we're instrumenting
- this.scanner.scan(basePackages);
- // end the current step
- scanPackages.end();
+ try (StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
+ // add tagging information to the current step
+ scanPackages.tag("packages", () -> Arrays.toString(basePackages));
+ // perform the actual phase we're instrumenting
+ this.scanner.scan(basePackages);
+ }
----
Kotlin::
@@ -947,13 +946,12 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// create a startup step and start recording
- val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
- // add tagging information to the current step
- scanPackages.tag("packages", () -> Arrays.toString(basePackages))
- // perform the actual phase we're instrumenting
- this.scanner.scan(basePackages)
- // end the current step
- scanPackages.end()
+ try (val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
+ // add tagging information to the current step
+ scanPackages.tag("packages", () -> Arrays.toString(basePackages));
+ // perform the actual phase we're instrumenting
+ this.scanner.scan(basePackages);
+ }
----
======
diff --git a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc
index ed7df447d09f..c010f46148c2 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc
@@ -99,7 +99,7 @@ the `@Bean` factory method in favor of any pre-declared constructor on the bean
****
NOTE: We acknowledge that overriding beans in test scenarios is convenient, and there is
-explicit support for this as of Spring Framework 6.2. Please refer to
+explicit support for this. Please refer to
xref:testing/testcontext-framework/bean-overriding.adoc[this section] for more details.
diff --git a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc
index 207be618a01a..612451fbcea1 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/dependencies/factory-properties-detailed.adoc
@@ -85,11 +85,11 @@ element. The following example shows how to use it:
[source,xml,indent=0,subs="verbatim,quotes"]
----
-
+
-
+
-
+
----
@@ -99,28 +99,24 @@ following snippet:
[source,xml,indent=0,subs="verbatim,quotes"]
----
-
+
-
-
+
+
----
The first form is preferable to the second, because using the `idref` tag lets the
-container validate at deployment time that the referenced, named bean actually
-exists. In the second variation, no validation is performed on the value that is passed
-to the `targetName` property of the `client` bean. Typos are only discovered (with most
+container validate at deployment time that the referenced, named bean actually exists. In
+the second variation, no validation is performed on the value that is passed to the
+`targetName` property of the `client` bean. Typos are therefore only discovered (with most
likely fatal results) when the `client` bean is actually instantiated. If the `client`
-bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting exception
-may only be discovered long after the container is deployed.
+bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting
+exception may only be discovered long after the container is deployed.
-NOTE: The `local` attribute on the `idref` element is no longer supported in the 4.0 beans
-XSD, since it does not provide value over a regular `bean` reference any more. Change
-your existing `idref local` references to `idref bean` when upgrading to the 4.0 schema.
-
-A common place (at least in versions earlier than Spring 2.0) where the `` element
-brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors] in a
-`ProxyFactoryBean` bean definition. Using `` elements when you specify the
+NOTE: A common place (at least in versions earlier than Spring 2.0) where the ``
+element brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors]
+in a `ProxyFactoryBean` bean definition. Using `` elements when you specify the
interceptor names prevents you from misspelling an interceptor ID.
diff --git a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc
index ea79cbfb22fa..2becb1d7eedd 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc
@@ -194,8 +194,7 @@ The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
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 you would any
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
-script. The Spring dynamic language support is detailed in the chapter entitled
-xref:languages/dynamic.adoc[Dynamic Language Support].)
+script.)
The following Java application runs the preceding code and configuration:
diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc
index 6b99094ad0ea..f00c8711a6a4 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/java/composing-configuration-classes.adoc
@@ -327,9 +327,8 @@ Kotlin::
----
======
-TIP: Constructor injection in `@Configuration` classes is only supported as of Spring
-Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
-bean defines only one constructor.
+TIP: Note that there is no need to specify `@Autowired` if the target bean defines
+only one constructor.
[discrete]
[[beans-java-injecting-imported-beans-fq]]
diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/programmatic-bean-registration.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/programmatic-bean-registration.adoc
index f50c94025568..3663fc13fe77 100644
--- a/framework-docs/modules/ROOT/pages/core/beans/java/programmatic-bean-registration.adoc
+++ b/framework-docs/modules/ROOT/pages/core/beans/java/programmatic-bean-registration.adoc
@@ -9,28 +9,7 @@ efficient way.
Those bean registrar implementations are typically imported with an `@Import` annotation
on `@Configuration` classes.
-[tabs]
-======
-Java::
-+
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- @Configuration
- @Import(MyBeanRegistrar.class)
- class MyConfiguration {
- }
-----
-
-Kotlin::
-+
-[source,kotlin,indent=0,subs="verbatim,quotes"]
-----
- @Configuration
- @Import(MyBeanRegistrar::class)
- class MyConfiguration {
- }
-----
-======
+include-code::./MyConfiguration[tag=snippet,indent=0]
NOTE: You can leverage type-level conditional annotations ({spring-framework-api}/context/annotation/Conditional.html[`@Conditional`],
but also other variants) to conditionally import the related bean registrars.
@@ -40,49 +19,7 @@ The bean registrar implementation uses {spring-framework-api}/beans/factory/Bean
and flexible way. For example, it allows custom registration through an `if` expression, a
`for` loop, etc.
-[tabs]
-======
-Java::
-+
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- class MyBeanRegistrar implements BeanRegistrar {
-
- @Override
- public void register(BeanRegistry registry, Environment env) {
- registry.registerBean("foo", Foo.class);
- registry.registerBean("bar", Bar.class, spec -> spec
- .prototype()
- .lazyInit()
- .description("Custom description")
- .supplier(context -> new Bar(context.bean(Foo.class))));
- if (env.matchesProfiles("baz")) {
- registry.registerBean(Baz.class, spec -> spec
- .supplier(context -> new Baz("Hello World!")));
- }
- }
- }
-----
-
-Kotlin::
-+
-[source,kotlin,indent=0,subs="verbatim,quotes"]
-----
- class MyBeanRegistrar : BeanRegistrarDsl({
- registerBean()
- registerBean(
- name = "bar",
- prototype = true,
- lazyInit = true,
- description = "Custom description") {
- Bar(bean())
- }
- profile("baz") {
- registerBean { Baz("Hello World!") }
- }
- })
-----
-======
+include-code::./MyBeanRegistrar[tag=snippet,indent=0]
NOTE: Bean registrars are supported with xref:core/aot.adoc[Ahead of Time Optimizations],
either on the JVM or with GraalVM native images, including when instance suppliers are used.
diff --git a/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc b/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc
index cdd8c6c15a7e..af18ce82e605 100644
--- a/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc
+++ b/framework-docs/modules/ROOT/pages/core/databuffer-codec.adoc
@@ -3,8 +3,8 @@
Java NIO provides `ByteBuffer` but many libraries build their own byte buffer API on top,
especially for network operations where reusing buffers and/or using direct buffers is
-beneficial for performance. For example Netty has the `ByteBuf` hierarchy, Undertow uses
-XNIO, Jetty uses pooled byte buffers with a callback to be released, and so on.
+beneficial for performance. For example Netty has the `ByteBuf` hierarchy,
+Jetty uses pooled byte buffers with a callback to be released, and so on.
The `spring-core` module provides a set of abstractions to work with various byte buffer
APIs as follows:
diff --git a/framework-docs/modules/ROOT/pages/core/null-safety.adoc b/framework-docs/modules/ROOT/pages/core/null-safety.adoc
index 108259e3730f..2460bbe292f4 100644
--- a/framework-docs/modules/ROOT/pages/core/null-safety.adoc
+++ b/framework-docs/modules/ROOT/pages/core/null-safety.adoc
@@ -8,9 +8,9 @@ recommended in order to get familiar with those annotations and semantics.
The primary goal of this null-safety arrangement is to prevent a `NullPointerException` from being thrown at
runtime via build time checks and to use explicit nullability as a way to express the possible absence of value.
-It is useful in both Java by leveraging some tooling (https://github.com/uber/NullAway[NullAway] or IDEs supporting
-JSpecify annotations such as IntelliJ IDEA) and Kotlin where JSpecify annotations are automatically translated to
-{kotlin-docs}/null-safety.html[Kotlin's null safety].
+It is useful in Java by leveraging nullability checkers such as https://github.com/uber/NullAway[NullAway] or IDEs
+supporting JSpecify annotations such as IntelliJ IDEA and Eclipse (the latter requiring manual configuration). In Kotlin,
+JSpecify annotations are automatically translated to {kotlin-docs}/null-safety.html[Kotlin's null safety].
The {spring-framework-api}/core/Nullness.html[`Nullness` Spring API] can be used at runtime to detect the
nullness of a type usage, a field, a method return type, or a parameter. It provides full support for
diff --git a/framework-docs/modules/ROOT/pages/core/resilience.adoc b/framework-docs/modules/ROOT/pages/core/resilience.adoc
index b2b4b3f817b5..50432b9fec77 100644
--- a/framework-docs/modules/ROOT/pages/core/resilience.adoc
+++ b/framework-docs/modules/ROOT/pages/core/resilience.adoc
@@ -1,16 +1,19 @@
[[resilience]]
= Resilience Features
-As of 7.0, the core Spring Framework includes a couple of common resilience features,
-in particular `@Retryable` and `@ConcurrencyLimit` annotations for method invocations.
+As of 7.0, the core Spring Framework includes common resilience features, in particular
+<> and <>
+annotations for method invocations as well as <>.
-[[resilience-retryable]]
-== Using `@Retryable`
+[[resilience-annotations-retryable]]
+== `@Retryable`
-`@Retryable` is a common annotation that specifies retry characteristics for an individual
-method (with the annotation declared at the method level), or for all proxy-invoked
-methods in a given class hierarchy (with the annotation declared at the type level).
+{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`] is an annotation
+that specifies retry characteristics for an individual method (with the annotation
+declared at the method level), or for all proxy-invoked methods in a given class hierarchy
+(with the annotation declared at the type level).
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -20,11 +23,24 @@ public void sendNotification() {
}
----
-By default, the method invocation will be retried for any exception thrown: with at
-most 3 retry attempts after an initial failure, and a delay of 1 second between attempts.
+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.
-This can be specifically adapted for every method if necessary – for example, by narrowing
-the exceptions to retry:
+[NOTE]
+====
+A `@Retryable` method will be invoked at least once and retried at most `maxRetries`
+times, where `maxRetries` is the maximum number of retry attempts. Specifically,
+`total attempts = 1 initial attempt + maxRetries attempts`.
+
+For example, if `maxRetries` is set to `4`, the `@Retryable` method will be invoked at
+least once and at most 5 times.
+====
+
+This can be specifically adapted for every method if necessary — for example, by narrowing
+the exceptions to retry via the `includes` and `excludes` attributes. The supplied
+exception types will be matched against an exception thrown by a failed invocation as well
+as nested causes.
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -34,42 +50,63 @@ public void sendNotification() {
}
----
-Or for 5 retry attempts and an exponential back-off strategy with a bit of jitter:
+NOTE: `@Retryable(MessageDeliveryException.class)` is a shortcut for
+`@Retryable(includes{nbsp}={nbsp}MessageDeliveryException.class)`.
+
+[TIP]
+====
+For advanced use cases, you can specify a custom `MethodRetryPredicate` via the
+`predicate` attribute in `@Retryable`, and the predicate will be used to determine whether
+to retry a failed method invocation based on a `Method` and a given `Throwable` – for
+example, by checking the message of the `Throwable`.
+
+Custom predicates can be combined with `includes` and `excludes`; however, custom
+predicates will always be applied after `includes` and `excludes` have been applied.
+====
+
+Or for 4 retry attempts and an exponential back-off strategy with a bit of jitter:
[source,java,indent=0,subs="verbatim,quotes"]
----
-@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
+@Retryable(
+ includes = MessageDeliveryException.class,
+ maxRetries = 4,
+ delay = 100,
+ jitter = 10,
+ multiplier = 2,
+ maxDelay = 1000)
public void sendNotification() {
this.jmsClient.destination("notifications").send(...);
}
----
-Last but not least, `@Retryable` also works for reactive methods with a reactive
-return type, decorating the pipeline with Reactor's retry capabilities:
+Last but not least, `@Retryable` also works for reactive methods with a reactive return
+type, decorating the pipeline with Reactor's retry capabilities:
[source,java,indent=0,subs="verbatim,quotes"]
----
-@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
+@Retryable(maxRetries = 4, delay = 100)
public Mono sendNotification() {
return Mono.from(...); // <1>
}
----
<1> This raw `Mono` will get decorated with a retry spec.
-For details on the various characteristics, see the available annotation attributes
-in {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
+For details on the various characteristics, see the available annotation attributes in
+{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
-NOTE: There a `String` variants with placeholder support available for several attributes
-as well, as an alternative to the specifically typed annotation attributes used in the
-above examples.
+TIP: Several attributes in `@Retryable` have `String` variants that provide property
+placeholder and SpEL support, as an alternative to the specifically typed annotation
+attributes used in the above examples.
-[[resilience-concurrency]]
-== Using `@ConcurrencyLimit`
+[[resilience-annotations-concurrencylimit]]
+== `@ConcurrencyLimit`
-`@ConcurrencyLimit` is an annotation that specifies a concurrency limit for an individual
-method (with the annotation declared at the method level), or for all proxy-invoked
-methods in a given class hierarchy (with the annotation declared at the type level).
+{spring-framework-api}/resilience/annotation/ConcurrencyLimit.html[`@ConcurrencyLimit`] is
+an annotation that specifies a concurrency limit for an individual method (with the
+annotation declared at the method level), or for all proxy-invoked methods in a given
+class hierarchy (with the annotation declared at the type level).
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -83,33 +120,152 @@ This is meant to protect the target resource from being accessed from too many t
the same time, similar to the effect of a pool size limit for a thread pool or a
connection pool that blocks access if its limit is reached.
-You may optionally set the limit to 1, effectively locking access to the target bean
+You may optionally set the limit to `1`, effectively locking access to the target bean
instance:
[source,java,indent=0,subs="verbatim,quotes"]
----
-@ConcurrencyLimit(1) // <1>
+@ConcurrencyLimit(1)
public void sendNotification() {
this.jmsClient.destination("notifications").send(...);
}
----
-<1> 1 is the default, but specifying it makes the intent clearer.
-Such limiting is particularly useful with Virtual Threads where there is generally
-no thread pool limit in place. For asynchronous tasks, this can be constrained on
+Such limiting is particularly useful with Virtual Threads where there is generally no
+thread pool limit in place. For asynchronous tasks, this can be constrained on
{spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`].
For synchronous invocations, this annotation provides equivalent behavior through
{spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`]
which has been available since Spring Framework 1.0 for programmatic use with the AOP
framework.
+TIP: `@ConcurrencyLimit` also has a `limitString` attribute that provides property
+placeholder and SpEL support, as an alternative to the `int` based examples above.
+
+
+[[resilience-annotations-configuration]]
+== Enabling Resilient Methods
+
+Like many of Spring's core annotation-based features, `@Retryable` and `@ConcurrencyLimit`
+are designed as metadata that you can choose to honor or ignore. The most convenient way
+to enable processing of the resilience annotations is to declare
+{spring-framework-api}/resilience/annotation/EnableResilientMethods.html[`@EnableResilientMethods`]
+on a corresponding `@Configuration` class.
+
+Alternatively, these annotations can be individually enabled by defining a
+`RetryAnnotationBeanPostProcessor` or a `ConcurrencyLimitBeanPostProcessor` bean in the
+context.
+
+
+[[resilience-programmatic-retry]]
+== Programmatic Retry Support
+
+In contrast to <> which provides a declarative approach
+for specifying retry semantics for methods within beans registered in the
+`ApplicationContext`,
+{spring-framework-api}/core/retry/RetryTemplate.html[`RetryTemplate`] provides a
+programmatic API for retrying arbitrary blocks of code.
+
+Specifically, a `RetryTemplate` executes and potentially retries a
+{spring-framework-api}/core/retry/Retryable.html[`Retryable`] operation based on a
+configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`].
+
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ var retryTemplate = new RetryTemplate(); // <1>
+
+ retryTemplate.execute(
+ () -> jmsClient.destination("notifications").send(...));
+----
+<1> Implicitly uses `RetryPolicy.withDefaults()`.
+
+By default, a retryable operation will be retried for any exception thrown: with at most
+3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
+between attempts.
+
+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`
+times, where `maxRetries` is the maximum number of retry attempts. Specifically,
+`total attempts = 1 initial attempt + maxRetries attempts`.
+
+For example, if `maxRetries` is set to `4`, the retryable operation will be invoked at
+least once and at most 5 times.
+====
+
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1>
+
+ retryTemplate.execute(
+ () -> jmsClient.destination("notifications").send(...));
+----
+<1> Explicitly uses `RetryPolicy.withMaxRetries(4)`.
+
+If you need to narrow the types of exceptions to retry, that can be achieved via the
+`includes()` and `excludes()` builder methods. The supplied exception types will be
+matched against an exception thrown by a failed operation as well as nested causes.
+
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ var retryPolicy = RetryPolicy.builder()
+ .includes(MessageDeliveryException.class) // <1>
+ .excludes(...) // <2>
+ .build();
+
+ var retryTemplate = new RetryTemplate(retryPolicy);
+
+ retryTemplate.execute(
+ () -> jmsClient.destination("notifications").send(...));
+----
+<1> Specify one or more exception types to include.
+<2> Specify one or more exception types to exclude.
+
+[TIP]
+====
+For advanced use cases, you can specify a custom `Predicate` via the
+`predicate()` method in the `RetryPolicy.Builder`, and the predicate will be used to
+determine whether to retry a failed operation based on a given `Throwable` – for example,
+by checking the message of the `Throwable`.
+
+Custom predicates can be combined with `includes` and `excludes`; however, custom
+predicates will always be applied after `includes` and `excludes` have been applied.
+====
+
+The following example demonstrates how to configure a `RetryPolicy` with 4 retry attempts
+and an exponential back-off strategy with a bit of jitter.
+
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ var retryPolicy = RetryPolicy.builder()
+ .includes(MessageDeliveryException.class)
+ .maxRetries(4)
+ .delay(Duration.ofMillis(100))
+ .jitter(Duration.ofMillis(10))
+ .multiplier(2)
+ .maxDelay(Duration.ofSeconds(1))
+ .build();
+
+ var retryTemplate = new RetryTemplate(retryPolicy);
+
+ retryTemplate.execute(
+ () -> jmsClient.destination("notifications").send(...));
+----
-[[resilience-enable]]
-== Configuring `@EnableResilientMethods`
+[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`].
+====
-Note that like many of Spring's core annotation-based features, `@Retryable` and
-`@ConcurrencyLimit` are designed as metadata that you can choose to honor or ignore.
-The most convenient way to enable actual processing of the resilience annotations
-through AOP interception is to declare `@EnableResilientMethods` on a corresponding
-configuration class. Alternatively, you may declare `RetryAnnotationBeanPostProcessor`
-and/or `ConcurrencyLimitBeanPostProcessor` individually.
+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`.
diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc
index 9fa68312f598..8dd2442558ae 100644
--- a/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc
+++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc
@@ -676,7 +676,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
[source,java,indent=0,subs="verbatim,quotes"]
----
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
- .paramSource(new Actor("Leonor", "Watling")
+ .paramSource(new Actor("Leonor", "Watling"))
.update();
----
diff --git a/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc b/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc
index 2a5ef2f0eab6..a5c38886a52b 100644
--- a/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc
+++ b/framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc
@@ -10,11 +10,11 @@ cover the other ORM technologies and show brief examples.
[NOTE]
====
-As of Spring Framework 7.0, Spring requires Hibernate ORM 7.0 for Spring's
-`HibernateJpaVendorAdapter` as well as for a native Hibernate `SessionFactory` setup.
+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.0, 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.
====
diff --git a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc
index a16f4985f2d4..42ad16cd0e9e 100644
--- a/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc
+++ b/framework-docs/modules/ROOT/pages/data-access/transaction/declarative/rolling-back.adoc
@@ -86,11 +86,13 @@ rollback rules may be configured via the `rollbackFor`/`noRollbackFor` and
`rollbackForClassName`/`noRollbackForClassName` attributes, which allow rules to be
defined based on exception types or patterns, respectively.
-When a rollback rule is defined with an exception type, that type will be used to match
-against the type of a thrown exception and its super types, providing type safety and
-avoiding any unintentional matches that may occur when using a pattern. For example, a
-value of `jakarta.servlet.ServletException.class` will only match thrown exceptions of
-type `jakarta.servlet.ServletException` and its subclasses.
+When a rollback rule is defined with an exception type – for example, via `rollbackFor` –
+that type will be used to match against the type of a thrown exception. Specifically,
+given a configured exception type `C`, a thrown exception of type `T` will be considered
+a match against `C` if `T` is equal to `C` or a subclass of `C`. This provides type
+safety and avoids any unintentional matches that may occur when using a pattern. For
+example, a value of `jakarta.servlet.ServletException.class` will only match thrown
+exceptions of type `jakarta.servlet.ServletException` and its subclasses.
When a rollback rule is defined with an exception pattern, the pattern can be a fully
qualified class name or a substring of a fully qualified class name for an exception type
diff --git a/framework-docs/modules/ROOT/pages/integration/jms/sending.adoc b/framework-docs/modules/ROOT/pages/integration/jms/sending.adoc
index 5ad13d40b189..27beb6225796 100644
--- a/framework-docs/modules/ROOT/pages/integration/jms/sending.adoc
+++ b/framework-docs/modules/ROOT/pages/integration/jms/sending.adoc
@@ -9,39 +9,7 @@ that takes no destination argument uses the default destination.
The following example uses the `MessageCreator` callback to create a text message from the
supplied `Session` object:
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- import jakarta.jms.ConnectionFactory;
- import jakarta.jms.JMSException;
- import jakarta.jms.Message;
- import jakarta.jms.Queue;
- import jakarta.jms.Session;
-
- import org.springframework.jms.core.MessageCreator;
- import org.springframework.jms.core.JmsTemplate;
-
- public class JmsQueueSender {
-
- private JmsTemplate jmsTemplate;
- private Queue queue;
-
- public void setConnectionFactory(ConnectionFactory cf) {
- this.jmsTemplate = new JmsTemplate(cf);
- }
-
- public void setQueue(Queue queue) {
- this.queue = queue;
- }
-
- public void simpleSend() {
- this.jmsTemplate.send(this.queue, new MessageCreator() {
- public Message createMessage(Session session) throws JMSException {
- return session.createTextMessage("hello queue world");
- }
- });
- }
- }
-----
+include-code::./JmsQueueSender[]
In the preceding example, the `JmsTemplate` is constructed by passing a reference to a
`ConnectionFactory`. As an alternative, a zero-argument constructor and
@@ -84,21 +52,7 @@ gives you access to the message after it has been converted but before it is sen
following example shows how to modify a message header and a property after a
`java.util.Map` is converted to a message:
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- public void sendWithConversion() {
- Map map = new HashMap<>();
- map.put("Name", "Mark");
- map.put("Age", new Integer(47));
- jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
- public Message postProcessMessage(Message message) throws JMSException {
- message.setIntProperty("AccountID", 1234);
- message.setJMSCorrelationID("123-00001");
- return message;
- }
- });
- }
-----
+include-code::./JmsSenderWithConversion[]
This results in a message of the following form:
@@ -126,32 +80,6 @@ to `jakarta.jms.TextMessage`, `jakarta.jms.BytesMessage`, etc. For a contract su
generic message payloads, use `org.springframework.messaging.converter.MessageConverter`
with `JmsMessagingTemplate` or preferably `JmsClient` as your central delegate instead.
-
-[[jms-sending-jmsclient]]
-== Sending a Message with `JmsClient`
-
-[source,java,indent=0,subs="verbatim,quotes"]
-----
-// Reusable handle, typically created through JmsClient.create(ConnectionFactory)
-// For custom conversion, use JmsClient.create(ConnectionFactory, MessageConverter)
-private JmsClient jmsClient;
-
-public void sendWithConversion() {
- this.jmsClient.destination("myQueue")
- .withTimeToLive(1000)
- .send("myPayload"); // optionally with a headers Map next to the payload
-}
-
-public void sendCustomMessage() {
- Message> message =
- MessageBuilder.withPayload("myPayload").build(); // optionally with headers
- this.jmsClient.destination("myQueue")
- .withTimeToLive(1000)
- .send(message);
-}
-----
-
-
[[jms-sending-callbacks]]
== Using `SessionCallback` and `ProducerCallback` on `JmsTemplate`
@@ -160,3 +88,21 @@ want to perform multiple operations on a JMS `Session` or `MessageProducer`. The
`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` /
`MessageProducer` pair, respectively. The `execute()` methods on `JmsTemplate` run
these callback methods.
+
+
+[[jms-sending-jmsclient]]
+== Sending a Message with `JmsClient`
+
+include-code::./JmsClientSample[]
+
+
+[[jms-sending-postprocessor]]
+== Post-processing outgoing messages
+
+Applications often need to intercept messages before they are sent out, for example to add message properties to all outgoing messages.
+The `org.springframework.messaging.core.MessagePostProcessor` based on the spring-messaging `Message` can do that,
+when configured on the `JmsClient`. It will be used for all outgoing messages sent with the `send` and `sendAndReceive` methods.
+
+Here is an example of an interceptor adding a "tenantId" property to all outgoing messages.
+
+include-code::./JmsClientWithPostProcessor[]
diff --git a/framework-docs/modules/ROOT/pages/integration/observability.adoc b/framework-docs/modules/ROOT/pages/integration/observability.adoc
index c9129ef1e544..a32d4b5a2c04 100644
--- a/framework-docs/modules/ROOT/pages/integration/observability.adoc
+++ b/framework-docs/modules/ROOT/pages/integration/observability.adoc
@@ -189,13 +189,13 @@ This observation uses the `io.micrometer.jakarta9.instrument.jms.DefaultJmsProce
[[observability.http-server]]
== HTTP Server instrumentation
-HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
+HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications,
+or `"http.server.request.duration"` if using the OpenTelemetry convention.
[[observability.http-server.servlet]]
=== Servlet applications
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
-It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the Servlet filter.
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and xref:web/webmvc/mvc-ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
@@ -207,6 +207,11 @@ NOTE: Because the instrumentation is done at the Servlet Filter level, the obser
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside the scope of this project.
+[[observability.http-server.servlet.default]]
+==== Default Semantic Convention
+
+It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
+
By default, the following `KeyValues` are created:
.Low cardinality Keys
@@ -228,6 +233,16 @@ By default, the following `KeyValues` are created:
|`http.url` _(required)_|HTTP request URI.
|===
+
+[[observability.http-server.servlet.otel]]
+==== OpenTelemetry Semantic Convention
+
+An OpenTelemetry variant is available with `org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention`, backed by the `ServerRequestObservationContext`.
+
+This variant complies with the https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/http/http-metrics.md[OpenTelemetry Semantic Conventions for HTTP Metrics (v1.36.0)]
+and the https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/http/http-spans.md[OpenTelemetry Semantic Conventions for HTTP Spans (v1.36.0)].
+
+
[[observability.http-server.reactive]]
=== Reactive applications
diff --git a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc
index 0417613f496e..018ca4e4c73c 100644
--- a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc
+++ b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc
@@ -5,8 +5,8 @@ The Spring Framework provides the following choices for making calls to REST end
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] -- synchronous client with a fluent API
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] -- non-blocking, reactive client with fluent API
-* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] -- synchronous client with template method API
-* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface Clients] -- annotated interface backed by generated proxy
+* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] -- synchronous client with template method API, now deprecated in favor of `RestClient`
+* xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service Clients] -- annotated interface backed by generated proxy
[[rest-restclient]]
@@ -479,7 +479,8 @@ See xref:web/webflux-webclient.adoc[WebClient] for more details.
The `RestTemplate` provides a high-level API over HTTP client libraries in the form of a classic Spring Template class.
It exposes the following groups of overloaded methods:
-NOTE: The xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] offers a more modern API for synchronous HTTP access.
+WARNING: As of Spring Framework 7.0, `RestTemplate` is deprecated in favor of `RestClient` and will be removed in a future version,
+please use the xref:integration/rest-clients.adoc#migrating-to-restclient["Migrating to RestClient"] guide.
For asynchronous and streaming scenarios, consider the reactive xref:web/webflux-webclient.adoc[WebClient].
[[rest-overview-of-resttemplate-methods-tbl]]
@@ -547,10 +548,26 @@ See the xref:integration/observability.adoc#http-client.resttemplate[RestTemplat
Objects passed into and returned from `RestTemplate` methods are converted to and from HTTP messages
with the help of an `HttpMessageConverter`, see <>.
-=== Migrating from `RestTemplate` to `RestClient`
+[[migrating-to-restclient]]
+=== Migrating to `RestClient`
+
+Applications can adopt `RestClient` in a gradual fashion, first focusing on API usage and then on infrastructure setup.
+You can consider the following steps:
+
+1. Create one or more `RestClient` from existing `RestTemplate` instances, like: `RestClient restClient = RestClient.create(restTemplate)`.
+ Gradually replace `RestTemplate` usage in your application, component by component, by focusing first on issuing requests.
+ See the table below for API equivalents.
+2. Once all client requests go through `RestClient` instances, you can now work on replicating your existing
+ `RestTemplate` instance creations by using `RestClient.Builder`. Because `RestTemplate` and `RestClient`
+ share the same infrastructure, you can reuse custom `ClientHttpRequestFactory` or `ClientHttpRequestInterceptor`
+ in your setup. See xref:integration/rest-clients.adoc#rest-restclient[the `RestClient` builder API].
+
+If no other library is available on the classpath, `RestClient` will choose the `JdkClientHttpRequestFactory`
+powered by the modern JDK `HttpClient`, whereas `RestTemplate` would pick the `SimpleClientHttpRequestFactory` that
+uses `HttpURLConnection`. This can explain subtle behavior difference at runtime at the HTTP level.
+
The following table shows `RestClient` equivalents for `RestTemplate` methods.
-It can be used to migrate from the latter to the former.
.RestClient equivalents for RestTemplate methods
[cols="1,1", options="header"]
@@ -854,9 +871,14 @@ It can be used to migrate from the latter to the former.
|===
+`RestClient` and `RestTemplate` instances share the same behavior when it comes to throwing exceptions
+(with the `RestClientException` type being at the top of the hierarchy).
+When `RestTemplate` consistently throws `HttpClientErrorException` for "4xx" response statues,
+`RestClient` allows for more flexibility with custom xref:integration/rest-clients.adoc#rest-http-service-client-exceptions["status handlers"].
-[[rest-http-interface]]
-== HTTP Interface Clients
+
+[[rest-http-service-client]]
+== HTTP Service Clients
You can define an HTTP Service as a Java interface with `@HttpExchange` methods, and use
`HttpServiceProxyFactory` to create a client proxy from it for remote access over HTTP via
@@ -928,7 +950,14 @@ Now, you're ready to create client proxies:
// Use service methods for remote calls...
----
-[[rest-http-interface-method-parameters]]
+HTTP service clients is a powerful and expressive choice for remote access over HTTP.
+It allows one team to own the knowledge of how a REST API works, what parts are relevant
+to a client application, what input and output types to create, what endpoint method
+signatures are needed, what Javadoc to have, and so on. The resulting Java API guides and
+is ready to use.
+
+
+[[rest-http-service-client-method-parameters]]
=== Method Parameters
`@HttpExchange` methods support flexible method signatures with the following inputs:
@@ -1000,13 +1029,13 @@ parameter annotation) is set to `false`, or the parameter is marked optional as
`StreamingHttpOutputMessage.Body` that allows sending the request body by writing to an
`OutputStream`.
-[[rest-http-interface.custom-resolver]]
+[[rest-http-service-client.custom-resolver]]
=== Custom Arguments
You can configure a custom `HttpServiceArgumentResolver`. The example interface below
uses a custom `Search` method parameter type:
-include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
+include-code::./CustomHttpServiceArgumentResolver[tag=httpserviceclient,indent=0]
A custom argument resolver could be implemented like this:
@@ -1021,7 +1050,7 @@ the use of more fine-grained method parameters for individual parts of the reque
-[[rest-http-interface-return-values]]
+[[rest-http-service-client-return-values]]
=== Return Values
The supported return values depend on the underlying client.
@@ -1097,7 +1126,7 @@ underlying HTTP client, which operates at a lower level and provides more contro
`InputStream` or `ResponseEntity` that provides access to the raw response
body content.
-[[rest-http-interface-exceptions]]
+[[rest-http-service-client-exceptions]]
=== Error Handling
To customize error handling for HTTP Service client proxies, you can configure the
@@ -1134,7 +1163,7 @@ documentation for each client, as well as the Javadoc of `defaultStatusHandler`
-[[rest-http-interface-adapter-decorator]]
+[[rest-http-service-client-adapter-decorator]]
=== Decorating the Adapter
`HttpExchangeAdapter` and `ReactorHttpExchangeAdapter` are contracts that decouple HTTP
@@ -1162,8 +1191,8 @@ built-in decorators to suppress 404 exceptions and return a `ResponseEntity` wit
-[[rest-http-interface-group-config]]
-=== HTTP Interface Groups
+[[rest-http-service-client-group-config]]
+=== HTTP Service Groups
It's trivial to create client proxies with `HttpServiceProxyFactory`, but to have them
declared as beans leads to repetitive configuration. You may also have multiple
diff --git a/framework-docs/modules/ROOT/pages/languages/dynamic.adoc b/framework-docs/modules/ROOT/pages/languages/dynamic.adoc
deleted file mode 100644
index 7afe1d3d0558..000000000000
--- a/framework-docs/modules/ROOT/pages/languages/dynamic.adoc
+++ /dev/null
@@ -1,831 +0,0 @@
-[[dynamic-language]]
-= Dynamic Language Support
-
-Spring provides comprehensive support for using classes and objects that have been
-defined by using a dynamic language (such as Groovy) with Spring. This support lets
-you write any number of classes in a supported dynamic language and have the Spring
-container transparently instantiate, configure, and dependency inject the resulting
-objects.
-
-Spring's scripting support primarily targets Groovy and BeanShell. Beyond those
-specifically supported languages, the JSR-223 scripting mechanism is supported
-for integration with any JSR-223 capable language provider (as of Spring 4.2),
-for example, JRuby.
-
-You can find fully working examples of where this dynamic language support can be
-immediately useful in xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios].
-
-
-[[dynamic-language-a-first-example]]
-== A First Example
-
-The bulk of this chapter is concerned with describing the dynamic language support in
-detail. Before diving into all of the ins and outs of the dynamic language support,
-we look at a quick example of a bean defined in a dynamic language. The dynamic
-language for this first bean is Groovy. (The basis of this example was taken from the
-Spring test suite. If you want to see equivalent examples in any of the other
-supported languages, take a look at the source code).
-
-The next example shows the `Messenger` interface, which the Groovy bean is going to
-implement. Note that this interface is defined in plain Java. Dependent objects that
-are injected with a reference to the `Messenger` do not know that the underlying
-implementation is a Groovy script. The following listing shows the `Messenger` interface:
-
-[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting;
-
- public interface Messenger {
-
- String getMessage();
- }
-----
-
-The following example defines a class that has a dependency on the `Messenger` interface:
-
-[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting;
-
- public class DefaultBookingService implements BookingService {
-
- private Messenger messenger;
-
- public void setMessenger(Messenger messenger) {
- this.messenger = messenger;
- }
-
- public void processBooking() {
- // use the injected Messenger object...
- }
- }
-----
-
-The following example implements the `Messenger` interface in Groovy:
-
-[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
-----
- package org.springframework.scripting.groovy
-
- // Import the Messenger interface (written in Java) that is to be implemented
- import org.springframework.scripting.Messenger
-
- // Define the implementation in Groovy in file 'Messenger.groovy'
- class GroovyMessenger implements Messenger {
-
- String message
- }
-----
-
-[NOTE]
-====
-To use the custom dynamic language tags to define dynamic-language-backed beans, you
-need to have the XML Schema preamble at the top of your Spring XML configuration file.
-You also need to use a Spring `ApplicationContext` implementation as your IoC
-container. Using the dynamic-language-backed beans with a plain `BeanFactory`
-implementation is supported, but you have to manage the plumbing of the Spring internals
-to do so.
-
-For more information on schema-based configuration, see xref:languages/dynamic.adoc#xsd-schemas-lang[XML Schema-based Configuration]
-.
-====
-
-Finally, the following example shows the bean definitions that effect the injection of the
-Groovy-defined `Messenger` implementation into an instance of the
-`DefaultBookingService` class:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-----
-
-The `bookingService` bean (a `DefaultBookingService`) can now use its private `messenger`
-member variable as normal, because the `Messenger` instance that was injected into it is
-a `Messenger` instance. There is nothing special going on here -- just plain Java and
-plain Groovy.
-
-Hopefully, the preceding XML snippet is self-explanatory, but do not worry unduly if it is not.
-Keep reading for the in-depth detail on the whys and wherefores of the preceding configuration.
-
-
-[[dynamic-language-beans]]
-== Defining Beans that Are Backed by Dynamic Languages
-
-This section describes exactly how you define Spring-managed beans in any of the
-supported dynamic languages.
-
-Note that this chapter does not attempt to explain the syntax and idioms of the supported
-dynamic languages. For example, if you want to use Groovy to write certain of the classes
-in your application, we assume that you already know Groovy. If you need further details
-about the dynamic languages themselves, see
-xref:languages/dynamic.adoc#dynamic-language-resources[Further Resources] at the end of
-this chapter.
-
-[[dynamic-language-beans-concepts]]
-=== Common Concepts
-
-The steps involved in using dynamic-language-backed beans are as follows:
-
-. Write the test for the dynamic language source code (naturally).
-. Then write the dynamic language source code itself.
-. Define your dynamic-language-backed beans by using the appropriate ``
- element in the XML configuration (you can define such beans programmatically by
- using the Spring API, although you will have to consult the source code for
- directions on how to do this, as this chapter does not cover this type of advanced configuration).
- Note that this is an iterative step. You need at least one bean definition for each dynamic
- language source file (although multiple bean definitions can reference the same source file).
-
-The first two steps (testing and writing your dynamic language source files) are beyond
-the scope of this chapter. See the language specification and reference manual
-for your chosen dynamic language and crack on with developing your dynamic language
-source files. You first want to read the rest of this chapter, though, as
-Spring's dynamic language support does make some (small) assumptions about the contents
-of your dynamic language source files.
-
-[[dynamic-language-beans-concepts-xml-language-element]]
-==== The element
-
-The final step in the list in the xref:languages/dynamic.adoc#dynamic-language-beans-concepts[preceding section]
-involves defining dynamic-language-backed bean definitions, one for each bean that you
-want to configure (this is no different from normal JavaBean configuration). However,
-instead of specifying the fully qualified class name of the class that is to be
-instantiated and configured by the container, you can use the ``
-element to define the dynamic language-backed bean.
-
-Each of the supported languages has a corresponding `` element:
-
-* `` (Groovy)
-* `` (BeanShell)
-* `` (JSR-223, for example, with JRuby)
-
-The exact attributes and child elements that are available for configuration depends on
-exactly which language the bean has been defined in (the language-specific sections
-later in this chapter detail this).
-
-[[dynamic-language-refreshable-beans]]
-==== Refreshable Beans
-
-One of the (and perhaps the single) most compelling value adds of the dynamic language
-support in Spring is the "`refreshable bean`" feature.
-
-A refreshable bean is a dynamic-language-backed bean. With a small amount of
-configuration, a dynamic-language-backed bean can monitor changes in its underlying
-source file resource and then reload itself when the dynamic language source file is
-changed (for example, when you edit and save changes to the file on the file system).
-
-This lets you deploy any number of dynamic language source files as part of an
-application, configure the Spring container to create beans backed by dynamic
-language source files (using the mechanisms described in this chapter), and (later,
-as requirements change or some other external factor comes into play) edit a dynamic
-language source file and have any change they make be reflected in the bean that is
-backed by the changed dynamic language source file. There is no need to shut down a
-running application (or redeploy in the case of a web application). The
-dynamic-language-backed bean so amended picks up the new state and logic from the
-changed dynamic language source file.
-
-NOTE: This feature is off by default.
-
-Now we can take a look at an example to see how easy it is to start using refreshable
-beans. To turn on the refreshable beans feature, you have to specify exactly one
-additional attribute on the `` element of your bean definition. So,
-if we stick with xref:languages/dynamic.adoc#dynamic-language-a-first-example[the example] from earlier in
-this chapter, the following example shows what we would change in the Spring XML
-configuration to effect refreshable beans:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
- script-source="classpath:Messenger.groovy">
-
-
-
-
-
-
-
-
-----
-
-That really is all you have to do. The `refresh-check-delay` attribute defined on the
-`messenger` bean definition is the number of milliseconds after which the bean is
-refreshed with any changes made to the underlying dynamic language source file.
-You can turn off the refresh behavior by assigning a negative value to the
-`refresh-check-delay` attribute. Remember that, by default, the refresh behavior is
-disabled. If you do not want the refresh behavior, do not define the attribute.
-
-If we then run the following application, we can exercise the refreshable feature.
-(Please excuse the "`jumping-through-hoops-to-pause-the-execution`" shenanigans
-in this next slice of code.) The `System.in.read()` call is only there so that the
-execution of the program pauses while you (the developer in this scenario) go off
-and edit the underlying dynamic language source file so that the refresh triggers
-on the dynamic-language-backed bean when the program resumes execution.
-
-The following listing shows this sample application:
-
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import org.springframework.scripting.Messenger;
-
- public final class Boot {
-
- public static void main(final String[] args) throws Exception {
- ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
- Messenger messenger = (Messenger) ctx.getBean("messenger");
- System.out.println(messenger.getMessage());
- // pause execution while I go off and make changes to the source file...
- System.in.read();
- System.out.println(messenger.getMessage());
- }
- }
-----
-
-Assume then, for the purposes of this example, that all calls to the `getMessage()`
-method of `Messenger` implementations have to be changed such that the message is
-surrounded by quotation marks. The following listing shows the changes that you
-(the developer) should make to the `Messenger.groovy` source file when the
-execution of the program is paused:
-
-[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting
-
- class GroovyMessenger implements Messenger {
-
- private String message = "Bingo"
-
- public String getMessage() {
- // change the implementation to surround the message in quotes
- return "'" + this.message + "'"
- }
-
- public void setMessage(String message) {
- this.message = message
- }
- }
-----
-
-When the program runs, the output before the input pause will be `I Can Do The Frug`.
-After the change to the source file is made and saved and the program resumes execution,
-the result of calling the `getMessage()` method on the dynamic-language-backed
-`Messenger` implementation is `'I Can Do The Frug'` (notice the inclusion of the
-additional quotation marks).
-
-Changes to a script do not trigger a refresh if the changes occur within the window of
-the `refresh-check-delay` value. Changes to the script are not actually picked up until
-a method is called on the dynamic-language-backed bean. It is only when a method is
-called on a dynamic-language-backed bean that it checks to see if its underlying script
-source has changed. Any exceptions that relate to refreshing the script (such as
-encountering a compilation error or finding that the script file has been deleted)
-results in a fatal exception being propagated to the calling code.
-
-The refreshable bean behavior described earlier does not apply to dynamic language
-source files defined with the `` element notation (see
-xref:languages/dynamic.adoc#dynamic-language-beans-inline[Inline Dynamic Language Source Files]).
-Additionally, it applies only to beans where changes to the underlying source file can
-actually be detected (for example, by code that checks the last modified date of a
-dynamic language source file that exists on the file system).
-
-[[dynamic-language-beans-inline]]
-==== Inline Dynamic Language Source Files
-
-The dynamic language support can also cater to dynamic language source files that are
-embedded directly in Spring bean definitions. More specifically, the
-`` element lets you define dynamic language source immediately
-inside a Spring configuration file. An example might clarify how the inline script
-feature works:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
- package org.springframework.scripting.groovy
-
- import org.springframework.scripting.Messenger
-
- class GroovyMessenger implements Messenger {
- String message
- }
-
-
-
-
-----
-
-If we put to one side the issues surrounding whether it is good practice to define
-dynamic language source inside a Spring configuration file, the ``
-element can be useful in some scenarios. For instance, we might want to quickly add a
-Spring `Validator` implementation to a Spring MVC `Controller`. This is but a moment's
-work using inline source. (See
-xref:languages/dynamic.adoc#dynamic-language-scenarios-validators[Scripted Validators]
-for such an example.)
-
-[[dynamic-language-beans-ctor-injection]]
-==== Understanding Constructor Injection in the Context of Dynamic-language-backed Beans
-
-There is one very important thing to be aware of with regard to Spring's dynamic
-language support. Namely, you can not (currently) supply constructor arguments
-to dynamic-language-backed beans (and, hence, constructor-injection is not available for
-dynamic-language-backed beans). In the interests of making this special handling of
-constructors and properties 100% clear, the following mixture of code and configuration
-does not work:
-
-.An approach that cannot work
-[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting.groovy
-
- import org.springframework.scripting.Messenger
-
- // from the file 'Messenger.groovy'
- class GroovyMessenger implements Messenger {
-
- GroovyMessenger() {}
-
- // this constructor is not available for Constructor Injection
- GroovyMessenger(String message) {
- this.message = message;
- }
-
- String message
-
- String anotherMessage
- }
-----
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-
-
-
-
-
-----
-
-In practice this limitation is not as significant as it first appears, since setter
-injection is the injection style favored by the overwhelming majority of developers
-(we leave the discussion as to whether that is a good thing to another day).
-
-[[dynamic-language-beans-groovy]]
-=== Groovy Beans
-
-This section describes how to use beans defined in Groovy in Spring.
-
-The Groovy homepage includes the following description:
-
-"`Groovy is an agile dynamic language for the Java 2 Platform that has many of the
-features that people like so much in languages like Python, Ruby and Smalltalk, making
-them available to Java developers using a Java-like syntax.`"
-
-If you have read this chapter straight from the top, you have already
-xref:languages/dynamic.adoc#dynamic-language-a-first-example[seen an example] of a Groovy-dynamic-language-backed
-bean. Now consider another example (again using an example from the Spring test suite):
-
-[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting;
-
- public interface Calculator {
-
- int add(int x, int y);
- }
-----
-
-The following example implements the `Calculator` interface in Groovy:
-
-[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting.groovy
-
- // from the file 'calculator.groovy'
- class GroovyCalculator implements Calculator {
-
- int add(int x, int y) {
- x + y
- }
- }
-----
-
-The following bean definition uses the calculator defined in Groovy:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-----
-
-Finally, the following small application exercises the preceding configuration:
-
-[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting;
-
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
-
- public class Main {
-
- public static void main(String[] args) {
- ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
- Calculator calc = ctx.getBean("calculator", Calculator.class);
- System.out.println(calc.add(2, 8));
- }
- }
-----
-
-The resulting output from running the above program is (unsurprisingly) `10`.
-(For more interesting examples, see the dynamic language showcase project for a more
-complex example or see the examples xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios] later in this chapter).
-
-You must not define more than one class per Groovy source file. While this is perfectly
-legal in Groovy, it is (arguably) a bad practice. In the interests of a consistent
-approach, you should (in the opinion of the Spring team) respect the standard Java
-conventions of one (public) class per source file.
-
-[[dynamic-language-beans-groovy-customizer]]
-==== Customizing Groovy Objects by Using a Callback
-
-The `GroovyObjectCustomizer` interface is a callback that lets you hook additional
-creation logic into the process of creating a Groovy-backed bean. For example,
-implementations of this interface could invoke any required initialization methods,
-set some default property values, or specify a custom `MetaClass`. The following listing
-shows the `GroovyObjectCustomizer` interface definition:
-
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- public interface GroovyObjectCustomizer {
-
- void customize(GroovyObject goo);
- }
-----
-
-The Spring Framework instantiates an instance of your Groovy-backed bean and then
-passes the created `GroovyObject` to the specified `GroovyObjectCustomizer` (if one
-has been defined). You can do whatever you like with the supplied `GroovyObject`
-reference. We expect that most people want to set a custom `MetaClass` with this
-callback, and the following example shows how to do so:
-
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer {
-
- public void customize(GroovyObject goo) {
- DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) {
-
- public Object invokeMethod(Object object, String methodName, Object[] arguments) {
- System.out.println("Invoking '" + methodName + "'.");
- return super.invokeMethod(object, methodName, arguments);
- }
- };
- metaClass.initialize();
- goo.setMetaClass(metaClass);
- }
-
- }
-----
-
-A full discussion of meta-programming in Groovy is beyond the scope of the Spring
-reference manual. See the relevant section of the Groovy reference manual or do a
-search online. Plenty of articles address this topic. Actually, making use of a
-`GroovyObjectCustomizer` is easy if you use the Spring namespace support, as the
-following example shows:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-
-----
-
-If you do not use the Spring namespace support, you can still use the
-`GroovyObjectCustomizer` functionality, as the following example shows:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-
-
-
-
-
-----
-
-NOTE: You may also specify a Groovy `CompilationCustomizer` (such as an `ImportCustomizer`)
-or even a full Groovy `CompilerConfiguration` object in the same place as Spring's
-`GroovyObjectCustomizer`. Furthermore, you may set a common `GroovyClassLoader` with custom
-configuration for your beans at the `ConfigurableApplicationContext.setClassLoader` level;
-this also leads to shared `GroovyClassLoader` usage and is therefore recommendable in case of
-a large number of scripted beans (avoiding an isolated `GroovyClassLoader` instance per bean).
-
-[[dynamic-language-beans-bsh]]
-=== BeanShell Beans
-
-This section describes how to use BeanShell beans in Spring.
-
-The https://beanshell.github.io/intro.html[BeanShell homepage] includes the following
-description:
-
-----
-BeanShell is a small, free, embeddable Java source interpreter with dynamic language
-features, written in Java. BeanShell dynamically runs standard Java syntax and
-extends it with common scripting conveniences such as loose types, commands, and method
-closures like those in Perl and JavaScript.
-----
-
-In contrast to Groovy, BeanShell-backed bean definitions require some (small) additional
-configuration. The implementation of the BeanShell dynamic language support in Spring is
-interesting, because Spring creates a JDK dynamic proxy that implements all of the
-interfaces that are specified in the `script-interfaces` attribute value of the
-`` element (this is why you must supply at least one interface in the value
-of the attribute, and, consequently, program to interfaces when you use BeanShell-backed
-beans). This means that every method call on a BeanShell-backed object goes through the
-JDK dynamic proxy invocation mechanism.
-
-Now we can show a fully working example of using a BeanShell-based bean that implements
-the `Messenger` interface that was defined earlier in this chapter. We again show the
-definition of the `Messenger` interface:
-
-[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.scripting;
-
- public interface Messenger {
-
- String getMessage();
- }
-----
-
-The following example shows the BeanShell "`implementation`" (we use the term loosely here)
-of the `Messenger` interface:
-
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- String message;
-
- String getMessage() {
- return message;
- }
-
- void setMessage(String aMessage) {
- message = aMessage;
- }
-----
-
-The following example shows the Spring XML that defines an "`instance`" of the above
-"`class`" (again, we use these terms very loosely here):
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-----
-
-See xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios] for some scenarios
-where you might want to use BeanShell-based beans.
-
-
-[[dynamic-language-scenarios]]
-== Scenarios
-
-The possible scenarios where defining Spring managed beans in a scripting language would
-be beneficial are many and varied. This section describes two possible use cases for the
-dynamic language support in Spring.
-
-[[dynamic-language-scenarios-controllers]]
-=== Scripted Spring MVC Controllers
-
-One group of classes that can benefit from using dynamic-language-backed beans is that
-of Spring MVC controllers. In pure Spring MVC applications, the navigational flow
-through a web application is, to a large extent, determined by code encapsulated within
-your Spring MVC controllers. As the navigational flow and other presentation layer logic
-of a web application needs to be updated to respond to support issues or changing
-business requirements, it may well be easier to effect any such required changes by
-editing one or more dynamic language source files and seeing those changes being
-immediately reflected in the state of a running application.
-
-Remember that, in the lightweight architectural model espoused by projects such as
-Spring, you typically aim to have a really thin presentation layer, with all
-the meaty business logic of an application being contained in the domain and service
-layer classes. Developing Spring MVC controllers as dynamic-language-backed beans lets
-you change presentation layer logic by editing and saving text files. Any
-changes to such dynamic language source files is (depending on the configuration)
-automatically reflected in the beans that are backed by dynamic language source files.
-
-NOTE: To effect this automatic "`pickup`" of any changes to dynamic-language-backed
-beans, you have to enable the "`refreshable beans`" functionality. See
-xref:languages/dynamic.adoc#dynamic-language-refreshable-beans[Refreshable Beans] for a full treatment of this feature.
-
-The following example shows an `org.springframework.web.servlet.mvc.Controller` implemented
-by using the Groovy dynamic language:
-
-[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
-----
- package org.springframework.showcase.fortune.web
-
- import org.springframework.showcase.fortune.service.FortuneService
- import org.springframework.showcase.fortune.domain.Fortune
- import org.springframework.web.servlet.ModelAndView
- import org.springframework.web.servlet.mvc.Controller
-
- import jakarta.servlet.http.HttpServletRequest
- import jakarta.servlet.http.HttpServletResponse
-
- // from the file '/WEB-INF/groovy/FortuneController.groovy'
- class FortuneController implements Controller {
-
- @Property FortuneService fortuneService
-
- ModelAndView handleRequest(HttpServletRequest request,
- HttpServletResponse httpServletResponse) {
- return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
- }
- }
-----
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-----
-
-[[dynamic-language-scenarios-validators]]
-=== Scripted Validators
-
-Another area of application development with Spring that may benefit from the
-flexibility afforded by dynamic-language-backed beans is that of validation. It can
-be easier to express complex validation logic by using a loosely typed dynamic language
-(that may also have support for inline regular expressions) as opposed to regular Java.
-
-Again, developing validators as dynamic-language-backed beans lets you change
-validation logic by editing and saving a simple text file. Any such changes is
-(depending on the configuration) automatically reflected in the execution of a
-running application and would not require the restart of an application.
-
-NOTE: To effect the automatic "`pickup`" of any changes to dynamic-language-backed
-beans, you have to enable the 'refreshable beans' feature. See
-xref:languages/dynamic.adoc#dynamic-language-refreshable-beans[Refreshable Beans]
-for a full and detailed treatment of this feature.
-
-The following example shows a Spring `org.springframework.validation.Validator`
-implemented by using the Groovy dynamic language (see
-xref:core/validation/validator.adoc[Validation using Spring’s Validator interface]
-for a discussion of the `Validator` interface):
-
-[source,groovy,indent=0,subs="verbatim,quotes"]
-----
- import org.springframework.validation.Validator
- import org.springframework.validation.Errors
- import org.springframework.beans.TestBean
-
- class TestBeanValidator implements Validator {
-
- boolean supports(Class clazz) {
- return TestBean.class.isAssignableFrom(clazz)
- }
-
- void validate(Object bean, Errors errors) {
- if(bean.name?.trim()?.size() > 0) {
- return
- }
- errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
- }
- }
-----
-
-
-[[dynamic-language-final-notes]]
-== Additional Details
-
-This last section contains some additional details related to the dynamic language support.
-
-[[dynamic-language-final-notes-aop]]
-=== AOP -- Advising Scripted Beans
-
-You can use the Spring AOP framework to advise scripted beans. The Spring AOP
-framework actually is unaware that a bean that is being advised might be a scripted
-bean, so all of the AOP use cases and functionality that you use (or aim to use)
-work with scripted beans. When you advise scripted beans, you cannot use class-based
-proxies. You must use xref:core/aop/proxying.adoc[interface-based proxies].
-
-You are not limited to advising scripted beans. You can also write aspects themselves
-in a supported dynamic language and use such beans to advise other Spring beans.
-This really would be an advanced use of the dynamic language support though.
-
-[[dynamic-language-final-notes-scopes]]
-=== Scoping
-
-In case it is not immediately obvious, scripted beans can be scoped in the same way as
-any other bean. The `scope` attribute on the various `` elements lets
-you control the scope of the underlying scripted bean, as it does with a regular
-bean. (The default scope is xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton],
-as it is with "`regular`" beans.)
-
-The following example uses the `scope` attribute to define a Groovy bean scoped as
-a xref:core/beans/factory-scopes.adoc#beans-factory-scopes-prototype[prototype]:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-
-
-
-
-
-
-
-
-----
-
-See xref:core/beans/factory-scopes.adoc[Bean Scopes] in
-xref:web/webmvc-view/mvc-xslt.adoc#mvc-view-xslt-beandefs[The IoC Container]
-for a full discussion of the scoping support in the Spring Framework.
-
-[[xsd-schemas-lang]]
-=== The `lang` XML schema
-
-The `lang` elements in Spring XML configuration deal with exposing objects that have been
-written in a dynamic language (such as Groovy or BeanShell) as beans in the Spring container.
-
-These elements (and the dynamic language support) are comprehensively covered in
-xref:languages/dynamic.adoc[Dynamic Language Support]. See that section
-for full details on this support and the `lang` elements.
-
-To use the elements in the `lang` schema, you need to have the following preamble at the
-top of your Spring XML configuration file. The text in the following snippet references
-the correct schema so that the tags in the `lang` namespace are available to you:
-
-[source,xml,indent=0,subs="verbatim,quotes"]
-----
-
-
-
-
-
-
-----
-
-
-[[dynamic-language-resources]]
-== Further Resources
-
-The following links go to further resources about the various dynamic languages referenced
-in this chapter:
-
-* The https://www.groovy-lang.org/[Groovy] homepage
-* The https://beanshell.github.io/intro.html[BeanShell] homepage
-* The https://www.jruby.org[JRuby] homepage
diff --git a/framework-docs/modules/ROOT/pages/languages/groovy.adoc b/framework-docs/modules/ROOT/pages/languages/groovy.adoc
index e50f136b23bf..a552fdf61eab 100644
--- a/framework-docs/modules/ROOT/pages/languages/groovy.adoc
+++ b/framework-docs/modules/ROOT/pages/languages/groovy.adoc
@@ -6,9 +6,37 @@ Groovy is a powerful, optionally typed, and dynamic language, with static-typing
compilation capabilities. It offers a concise syntax and integrates smoothly with any
existing Java application.
+[[beans-factory-groovy]]
+== The Groovy Bean Definition DSL
+
The Spring Framework provides a dedicated `ApplicationContext` that supports a Groovy-based
-Bean Definition DSL. For more details, see
-xref:core/beans/basics.adoc#beans-factory-groovy[The Groovy Bean Definition DSL].
+Bean Definition DSL, as known from the Grails framework.
+
+Typically, such configuration live in a ".groovy" file with the structure shown in the
+following example:
+
+[source,groovy,indent=0,subs="verbatim,quotes"]
+----
+ beans {
+ dataSource(BasicDataSource) {
+ driverClassName = "org.hsqldb.jdbcDriver"
+ url = "jdbc:hsqldb:mem:grailsDB"
+ username = "sa"
+ password = ""
+ settings = [mynew:"setting"]
+ }
+ sessionFactory(SessionFactory) {
+ dataSource = dataSource
+ }
+ myService(MyService) {
+ nestedBean = { AnotherBean bean ->
+ dataSource = dataSource
+ }
+ }
+ }
+----
+
+This configuration style is largely equivalent to XML bean definitions and even
+supports Spring's XML configuration namespaces. It also allows for importing XML
+bean definition files through an `importBeans` directive.
-Further support for Groovy, including beans written in Groovy, refreshable script beans,
-and more is available in xref:languages/dynamic.adoc[Dynamic Language Support].
diff --git a/framework-docs/modules/ROOT/pages/languages/kotlin/bean-definition-dsl.adoc b/framework-docs/modules/ROOT/pages/languages/kotlin/bean-registration-dsl.adoc
similarity index 62%
rename from framework-docs/modules/ROOT/pages/languages/kotlin/bean-definition-dsl.adoc
rename to framework-docs/modules/ROOT/pages/languages/kotlin/bean-registration-dsl.adoc
index db7e1809ae95..da759af2bf0c 100644
--- a/framework-docs/modules/ROOT/pages/languages/kotlin/bean-definition-dsl.adoc
+++ b/framework-docs/modules/ROOT/pages/languages/kotlin/bean-registration-dsl.adoc
@@ -1,4 +1,4 @@
-[[kotlin-bean-definition-dsl]]
-= Bean Definition DSL
+[[kotlin-bean-registration-dsl]]
+= Bean Registration DSL
See xref:core/beans/java/programmatic-bean-registration.adoc[Programmatic Bean Registration].
diff --git a/framework-docs/modules/ROOT/pages/languages/kotlin/coroutines.adoc b/framework-docs/modules/ROOT/pages/languages/kotlin/coroutines.adoc
index 7d8dd95ae73f..17729c6fda29 100644
--- a/framework-docs/modules/ROOT/pages/languages/kotlin/coroutines.adoc
+++ b/framework-docs/modules/ROOT/pages/languages/kotlin/coroutines.adoc
@@ -250,3 +250,29 @@ For Kotlin `Flow`, a `Flow.transactional` extension is provided.
}
}
----
+
+[[coroutines.propagation]]
+== Context Propagation
+
+Spring applications are xref:integration/observability.adoc[instrumented with Micrometer for Observability support].
+For tracing support, the current observation is propagated through a `ThreadLocal` for blocking code,
+or the Reactor `Context` for reactive pipelines. But the current observation also needs to be made available
+in the execution context of a suspended function. Without that, the current "traceId" will not be automatically
+prepended to logged statements from coroutines.
+
+The {spring-framework-api-kdoc}/spring-core/org.springframework.core/-propagation-context-element/index.html[`PropagationContextElement`] operator generally ensures that the
+{micrometer-context-propagation-docs}/[Micrometer Context Propagation library] works with Kotlin Coroutines.
+
+It requires the `io.micrometer:context-propagation` dependency and optionally the
+`org.jetbrains.kotlinx:kotlinx-coroutines-reactor` one. Automatic context propagation via
+`CoroutinesUtils#invokeSuspendingFunction` (used by Spring to adapt Coroutines to Reactor `Flux` or `Mono`) can be
+enabled by invoking `Hooks.enableAutomaticContextPropagation()`.
+
+Applications can also use `PropagationContextElement` explicitly to augment the `CoroutineContext`
+with the context propagation mechanism:
+
+include-code::./ContextPropagationSample[tag=context,indent=0]
+
+Here, assuming that Micrometer Tracing is configured, the resulting logging statement will show the current "traceId"
+and unlock better observability for your application.
+
diff --git a/framework-docs/modules/ROOT/pages/overview.adoc b/framework-docs/modules/ROOT/pages/overview.adoc
index e7ff8af9a18b..8ac7c152c6b8 100644
--- a/framework-docs/modules/ROOT/pages/overview.adoc
+++ b/framework-docs/modules/ROOT/pages/overview.adoc
@@ -73,7 +73,7 @@ As of Spring Framework 6.0, Spring has been upgraded to the Jakarta EE 9 level
traditional `javax` packages. With EE 9 as the minimum and EE 10 supported already,
Spring is prepared to provide out-of-the-box support for the further evolution of
the Jakarta EE APIs. Spring Framework 6.0 is fully compatible with Tomcat 10.1,
-Jetty 11 and Undertow 2.3 as web servers, and also with Hibernate ORM 6.1.
+Jetty 11 as web servers, and also with Hibernate ORM 6.1.
Over time, the role of Java/Jakarta EE in application development has evolved. In the
early days of J2EE and Spring, applications were created to be deployed to an application
diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc
index 4cc25b1c1765..2b072e8b0e5a 100644
--- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc
@@ -77,14 +77,35 @@ exactly one candidate bean exists.
[TIP]
====
-Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
-will result in an exception.
+As stated in the documentation for Mockito, there are times when using `Mockito.when()` is
+inappropriate for stubbing a spy – for example, if calling a real method on a spy results
+in undesired side effects.
+
+To avoid such undesired side effects, consider using
+`Mockito.doReturn(...).when(spy)...`, `Mockito.doThrow(...).when(spy)...`,
+`Mockito.doNothing().when(spy)...`, and similar methods.
+====
+
+[NOTE]
+====
+When using `@MockitoBean` to mock a non-singleton bean, the non-singleton bean will be
+replaced with a singleton mock, and the corresponding bean definition will be converted
+to a `singleton`. Consequently, if you mock a `prototype` or scoped bean, the mock will
+be treated as a `singleton`.
+
+Similarly, when using `@MockitoSpyBean` to create a spy for a non-singleton bean, the
+corresponding bean definition will be converted to a `singleton`. Consequently, if you
+create a spy for a `prototype` or scoped bean, the spy will be treated as a `singleton`.
When using `@MockitoBean` to mock a bean created by a `FactoryBean`, the `FactoryBean`
will be replaced with a singleton mock of the type of object created by the `FactoryBean`.
-When using `@MockitoSpyBean` to create a spy for a `FactoryBean`, a spy will be created
-for the object created by the `FactoryBean`, not for the `FactoryBean` itself.
+Similarly, when using `@MockitoSpyBean` to create a spy for a `FactoryBean`, a spy will
+be created for the object created by the `FactoryBean`, not for the `FactoryBean` itself.
+
+Furthermore, `@MockitoSpyBean` cannot be used to spy on a scoped proxy — for example, a
+bean annotated with `@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)`. Any attempt to do
+so will fail with an exception.
====
[NOTE]
diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc
index 4ec33c0c154f..b752becaaa96 100644
--- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc
@@ -116,12 +116,15 @@ fully-qualified method name following the syntax `#<
– for example, `methodName = "org.example.TestUtils#createCustomService"`.
====
-[TIP]
+[NOTE]
====
-Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
-will result in an exception.
-
-When overriding a bean created by a `FactoryBean`, the `FactoryBean` will be replaced
-with a singleton bean corresponding to the value returned from the `@TestBean` factory
-method.
+When overriding a non-singleton bean, the non-singleton bean will be replaced with a
+singleton bean corresponding to the value returned from the `@TestBean` factory method,
+and the corresponding bean definition will be converted to a `singleton`. Consequently,
+if `@TestBean` is used to override a `prototype` or scoped bean, the overridden bean will
+be treated as a `singleton`.
+
+Similarly, when overriding a bean created by a `FactoryBean`, the `FactoryBean` will be
+replaced with a singleton bean corresponding to the value returned from the `@TestBean`
+factory method.
====
diff --git a/framework-docs/modules/ROOT/pages/testing/mockmvc/htmlunit/webdriver.adoc b/framework-docs/modules/ROOT/pages/testing/mockmvc/htmlunit/webdriver.adoc
index d3a8df109d38..a9e3533cac50 100644
--- a/framework-docs/modules/ROOT/pages/testing/mockmvc/htmlunit/webdriver.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/mockmvc/htmlunit/webdriver.adoc
@@ -261,7 +261,7 @@ Kotlin::
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
by leveraging the Page Object Pattern. As we mentioned in
-xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
+xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
with HtmlUnit, but it is much easier with WebDriver. Consider the following
`CreateMessagePage` implementation:
diff --git a/framework-docs/modules/ROOT/pages/testing/resttestclient.adoc b/framework-docs/modules/ROOT/pages/testing/resttestclient.adoc
new file mode 100644
index 000000000000..5fd807111b23
--- /dev/null
+++ b/framework-docs/modules/ROOT/pages/testing/resttestclient.adoc
@@ -0,0 +1,506 @@
+[[resttestclient]]
+= RestTestClient
+
+`RestTestClient` is an HTTP client designed for testing server applications. It wraps
+Spring's xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] and uses it to perform requests,
+but exposes a testing facade for verifying responses. `RestTestClient` can be used to
+perform end-to-end HTTP tests. It can also be used to test Spring MVC
+applications without a running server via MockMvc.
+
+
+
+
+[[resttestclient-setup]]
+== Setup
+
+To set up a `RestTestClient` you need to choose a server setup to bind to. This can be one
+of several MockMvc setup choices, or a connection to a live server.
+
+
+
+[[resttestclient-controller-config]]
+=== Bind to Controller
+
+This setup allows you to test specific controller(s) via mock request and response objects,
+without a running server.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ RestTestClient client =
+ RestTestClient.bindToController(new TestController()).build();
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ val client = RestTestClient.bindToController(TestController()).build()
+----
+======
+
+[[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.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ @SpringJUnitConfig(WebConfig.class) // <1>
+ class MyTests {
+
+ RestTestClient client;
+
+ @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
+mock request and response objects, without a running server.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ RouterFunction> route = ...
+ client = RestTestClient.bindToRouterFunction(route).build();
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ val route: RouterFunction<*> = ...
+ val client = RestTestClient.bindToRouterFunction(route).build()
+----
+======
+
+[[resttestclient-server-config]]
+=== Bind to Server
+
+This setup connects to a running server to perform full, end-to-end HTTP tests:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ client = RestTestClient.bindToServer().baseUrl("http://localhost:8080").build();
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ client = RestTestClient.bindToServer().baseUrl("http://localhost:8080").build()
+----
+======
+
+
+
+[[resttestclient-client-config]]
+=== Client Config
+
+In addition to the server setup options described earlier, you can also configure client
+options, including base URL, default headers, client filters, and others. These options
+are readily available following the initial `bindTo` call, as follows:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ client = RestTestClient.bindToController(new TestController())
+ .baseUrl("/test")
+ .build();
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ client = RestTestClient.bindToController(TestController())
+ .baseUrl("/test")
+ .build()
+----
+======
+
+
+
+
+[[resttestclient-tests]]
+== Writing Tests
+
+xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] and `RestTestClient` have
+the same API up to the point of the call to `exchange()`. After that, `RestTestClient`
+provides two alternative ways to verify the response:
+
+1. xref:resttestclient-workflow[Built-in Assertions] extend the request workflow with a chain of expectations
+2. xref:resttestclient-assertj[AssertJ Integration] to verify the response via `assertThat()` statements
+
+
+
+[[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:
+
+[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.
+
+[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:
+
+* `expectBody(Class)`: Decode to single object.
+* `expectBody()`: Decode to `byte[]` for xref:testing/resttestclient.adoc#resttestclient-json[JSON Content] or an empty body.
+
+
+If the built-in assertions are insufficient, you can consume the object instead and
+perform any other assertions:
+
+[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()
+ .consumeWith {
+ // custom assertions (for example, AssertJ)...
+ }
+----
+======
+
+Or you can exit the workflow and obtain a `EntityExchangeResult`:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ EntityExchangeResult 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()
+ .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`.
+
+
+[[resttestclient-no-content]]
+==== No Content
+
+If the response is not expected to have content, you can assert that as follows:
+
+[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:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ client.get().uri("/persons/123")
+ .exchange()
+ .expectStatus().isNotFound()
+ .expectBody(Void.class);
+----
+
+Kotlin::
++
+[source,kotlin,indent=0,subs="verbatim,quotes"]
+----
+ client.get().uri("/persons/123")
+ .exchange()
+ .expectStatus().isNotFound
+ .expectBody()
+----
+======
+
+
+[[resttestclient-json]]
+==== JSON Content
+
+You can use `expectBody()` without a target type to perform assertions on the raw
+content rather than through higher level Object(s).
+
+To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAssert]:
+
+[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]:
+
+[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]]
+=== 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:
+
+[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:
+
+[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)
+ // ...
+----
+======
diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc
index 5348b383c4cf..df049c3eb6e2 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/aot.adoc
@@ -43,6 +43,16 @@ alternative, you can set the same property via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
====
+[TIP]
+====
+JPA's `@PersistenceContext` and `@PersistenceUnit` annotations cannot be used to perform
+dependency injection within test classes in AOT mode.
+
+However, as of Spring Framework 7.0, you can inject an `EntityManager` or
+`EntityManagerFactory` into tests using `@Autowired` instead of `@PersistenceContext` and
+`@PersistenceUnit`, respectively.
+====
+
[NOTE]
====
The `@ContextHierarchy` annotation is not supported in AOT mode.
diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc
index 659cf33a4cf4..54dc7c1262df 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/application-events.adoc
@@ -2,10 +2,10 @@
= Application Events
The TestContext framework provides support for recording
-xref:core/beans/context-introduction.adoc#context-functionality-events[application events] published in the
-`ApplicationContext` so that assertions can be performed against those events within
-tests. All events published during the execution of a single test are made available via
-the `ApplicationEvents` API which allows you to process the events as a
+xref:core/beans/context-introduction.adoc#context-functionality-events[application events]
+published in the `ApplicationContext` so that assertions can be performed against those
+events within tests. All events published during the execution of a single test are made
+available via the `ApplicationEvents` API which allows you to process the events as a
`java.util.Stream`.
To use `ApplicationEvents` in your tests, do the following.
@@ -16,16 +16,23 @@ To use `ApplicationEvents` in your tests, do the following.
that `ApplicationEventsTestExecutionListener` is registered by default and only needs
to be manually registered if you have custom configuration via
`@TestExecutionListeners` that does not include the default listeners.
-* Annotate a field of type `ApplicationEvents` with `@Autowired` and use that instance of
- `ApplicationEvents` in your test and lifecycle methods (such as `@BeforeEach` and
- `@AfterEach` methods in JUnit Jupiter).
-** When using the xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[SpringExtension for JUnit Jupiter], you may declare a method
- parameter of type `ApplicationEvents` in a test or lifecycle method as an alternative
- to an `@Autowired` field in the test class.
+* When using the
+ xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[SpringExtension for JUnit Jupiter],
+ declare a method parameter of type `ApplicationEvents` in a `@Test`, `@BeforeEach`, or
+ `@AfterEach` method.
+** Since `ApplicationEvents` is scoped to the lifecycle of the current test method, this
+ is the recommended approach.
+* Alternatively, you can annotate a field of type `ApplicationEvents` with `@Autowired`
+ and use that instance of `ApplicationEvents` in your test and lifecycle methods.
+
+NOTE: `ApplicationEvents` is registered with the `ApplicationContext` as a _resolvable
+dependency_ which is scoped to the lifecycle of the current test method. Consequently,
+`ApplicationEvents` cannot be accessed outside the lifecycle of a test method and cannot be
+`@Autowired` into the constructor of a test class.
The following test class uses the `SpringExtension` for JUnit Jupiter and
-{assertj-docs}[AssertJ] to assert the types of application events
-published while invoking a method in a Spring-managed component:
+{assertj-docs}[AssertJ] to assert the types of application events published while
+invoking a method in a Spring-managed component:
// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */
[tabs]
@@ -38,16 +45,10 @@ Java::
@RecordApplicationEvents // <1>
class OrderServiceTests {
- @Autowired
- OrderService orderService;
-
- @Autowired
- ApplicationEvents events; // <2>
-
@Test
- void submitOrder() {
+ void submitOrder(@Autowired OrderService service, ApplicationEvents events) { // <2>
// Invoke method in OrderService that publishes an event
- orderService.submitOrder(new Order(/* ... */));
+ service.submitOrder(new Order(/* ... */));
// Verify that an OrderSubmitted event was published
long numEvents = events.stream(OrderSubmitted.class).count(); // <3>
assertThat(numEvents).isEqualTo(1);
@@ -66,16 +67,10 @@ Kotlin::
@RecordApplicationEvents // <1>
class OrderServiceTests {
- @Autowired
- lateinit var orderService: OrderService
-
- @Autowired
- lateinit var events: ApplicationEvents // <2>
-
@Test
- fun submitOrder() {
+ fun submitOrder(@Autowired service: OrderService, events: ApplicationEvents) { // <2>
// Invoke method in OrderService that publishes an event
- orderService.submitOrder(Order(/* ... */))
+ service.submitOrder(Order(/* ... */))
// Verify that an OrderSubmitted event was published
val numEvents = events.stream(OrderSubmitted::class).count() // <3>
assertThat(numEvents).isEqualTo(1)
diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc
index a709dd96e432..055b718feaae 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc
@@ -62,8 +62,11 @@ defined by the corresponding `BeanOverrideStrategy`:
[TIP]
====
-Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
-will result in an exception.
+When replacing a non-singleton bean, the non-singleton bean will be replaced with a
+singleton bean corresponding to bean override instance created by the applicable
+`BeanOverrideHandler`, and the corresponding bean definition will be converted to a
+`singleton`. Consequently, if a handler overrides a `prototype` or scoped bean, the
+overridden bean will be treated as a `singleton`.
When replacing a bean created by a `FactoryBean`, the `FactoryBean` itself will be
replaced with a singleton bean corresponding to bean override instance created by the
diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc
index b4beba546d8f..be0eda85172b 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/caching.adoc
@@ -63,13 +63,15 @@ 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
-stopped when it is no longer actively in use and automatically restarted the next time
+_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.
+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
diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc
index e1e03b504644..935da475f255 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc
@@ -309,7 +309,7 @@ before-transaction method or after-transaction method runs at the appropriate ti
Generally speaking, `@BeforeTransaction` and `@AfterTransaction` methods must not accept
any arguments.
-However, as of Spring Framework 6.1, for tests using the
+However, for tests using the
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
with JUnit Jupiter, `@BeforeTransaction` and `@AfterTransaction` methods may optionally
accept arguments which will be resolved by any registered JUnit `ParameterResolver`
diff --git a/framework-docs/modules/ROOT/pages/testing/unit.adoc b/framework-docs/modules/ROOT/pages/testing/unit.adoc
index 2b360936eafa..5645587d64ef 100644
--- a/framework-docs/modules/ROOT/pages/testing/unit.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/unit.adoc
@@ -46,8 +46,8 @@ mock objects that are useful for testing web contexts, controllers, and filters.
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock]).
-TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are
-based on the Servlet 6.0 API.
+TIP: Since Spring Framework 7.0, the mock objects in `org.springframework.mock.web` are
+based on the Servlet 6.1 API.
MockMvc builds on the mock Servlet API objects to provide an integration testing
framework for Spring MVC. See xref:testing/mockmvc.adoc[MockMvc].
diff --git a/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc b/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc
index 4bd9e9ac06cb..7e2c642124ff 100644
--- a/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/webtestclient.adoc
@@ -270,16 +270,26 @@ Kotlin::
======
+
+
[[webtestclient-tests]]
== Writing Tests
-`WebTestClient` provides an API identical to xref:web/webflux-webclient.adoc[WebClient]
-up to the point of performing a request by using `exchange()`. See the
-xref:web/webflux-webclient/client-body.adoc[WebClient] documentation for examples on how to
-prepare a request with any content including form data, multipart data, and more.
+xref:web/webflux-webclient.adoc[WebClient] and `WebTestClient` have
+the same API up to the point of the call to `exchange()`. After that, `WebTestClient`
+provides two alternative ways to verify the response:
+
+1. xref:webtestclient-workflow[Built-in Assertions] extend the request workflow with a chain of expectations
+2. xref:webtestclient-assertj[AssertJ Integration] to verify the response via `assertThat()` statements
+
+TIP: See the xref:web/webflux-webclient/client-body.adoc[WebClient] documentation for
+examples on how to prepare a request with any content including form data,
+multipart data, and more.
-After the call to `exchange()`, `WebTestClient` diverges from the `WebClient` and
-instead continues with a workflow to verify responses.
+
+
+[[webtestclient-workflow]]
+=== Built-in Assertions
To assert the response status and headers, use the following:
@@ -443,8 +453,10 @@ that accept
{spring-framework-api}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`]
instead of `Class`.
+
+
[[webtestclient-no-content]]
-=== No Content
+==== No Content
If the response is not expected to have content, you can assert that as follows:
@@ -499,8 +511,10 @@ Kotlin::
----
======
+
+
[[webtestclient-json]]
-=== JSON Content
+==== JSON Content
You can use `expectBody()` without a target type to perform assertions on the raw
content rather than through higher level Object(s).
@@ -561,8 +575,10 @@ Kotlin::
----
======
+
+
[[webtestclient-stream]]
-=== Streaming Responses
+==== Streaming Responses
To test potentially infinite streams such as `"text/event-stream"` or
`"application/x-ndjson"`, start by verifying the response status and headers, and then
@@ -629,6 +645,78 @@ Kotlin::
----
======
+
+
+[[webtestclient-assertj]]
+=== AssertJ Integration
+
+`WebTestClientResponse` 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:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ ResponseSpec spec = client.get().uri("/persons").exchange();
+
+ WebTestClientResponse response = WebTestClientResponse.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 = WebTestClientResponse.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:
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ ExchangeResult result = client.get().uri("/persons").exchange()
+ . // ...
+ .returnResult();
+
+ WebTestClientResponse response = WebTestClientResponse.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 = WebTestClientResponse.from(spec)
+ assertThat(response).hasStatusOk()
+ assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN)
+ // ...
+----
+======
+
+
+
[[webtestclient-mockmvc]]
=== MockMvc Assertions
diff --git a/framework-docs/modules/ROOT/pages/web-reactive.adoc b/framework-docs/modules/ROOT/pages/web-reactive.adoc
index ff774a425144..eea40b37309f 100644
--- a/framework-docs/modules/ROOT/pages/web-reactive.adoc
+++ b/framework-docs/modules/ROOT/pages/web-reactive.adoc
@@ -3,7 +3,7 @@
This part of the documentation covers support for reactive-stack web applications built
on a {reactive-streams-site}/[Reactive Streams] API to run on non-blocking servers,
-such as Netty, Undertow, and Servlet containers. Individual chapters cover
+such as Netty and Servlet containers. Individual chapters cover
the xref:web/webflux.adoc#webflux[Spring WebFlux] framework,
the reactive xref:web/webflux-webclient.adoc[`WebClient`],
support for xref:web/webflux-test.adoc[testing],
diff --git a/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc b/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc
index 0d2dfb0cac8a..7cca9351d908 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux-functional.adoc
@@ -294,7 +294,28 @@ allPartsEvents.windowUntil(PartEvent::isLast)
----
======
-Note that the body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
+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, 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")})
+----
+======
+
+
[[webflux-fn-response]]
=== ServerResponse
diff --git a/framework-docs/modules/ROOT/pages/web/webflux-http-interface-client.adoc b/framework-docs/modules/ROOT/pages/web/webflux-http-service-client.adoc
similarity index 53%
rename from framework-docs/modules/ROOT/pages/web/webflux-http-interface-client.adoc
rename to framework-docs/modules/ROOT/pages/web/webflux-http-service-client.adoc
index 890478d79bb0..f083a87545ab 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux-http-interface-client.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux-http-service-client.adoc
@@ -1,9 +1,9 @@
-[[webflux-http-interface-client]]
-= HTTP Interface Client
+[[webflux-http-service-client]]
+= HTTP Service Client
The Spring Frameworks lets you define an HTTP service as a Java interface with HTTP
exchange methods. You can then generate a proxy that implements this interface and
performs the exchanges. This helps to simplify HTTP remote access and provides additional
-flexibility for to choose an API style such as synchronous or reactive.
+flexibility in choosing an API style such as synchronous or reactive.
-See xref:integration/rest-clients.adoc#rest-http-interface[REST Endpoints] for details.
+See xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service Clients] for details.
diff --git a/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc b/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc
index 6c446bcb142e..070bc7798c74 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc
@@ -17,7 +17,7 @@ to annotated controller methods with an API version
to functional endpoints with an API version
Client support for API versioning is available also in `RestClient`, `WebClient`, and
-xref:integration/rest-clients.adoc#rest-http-interface[HTTP Service] clients, as well as
+xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service] clients, as well as
for testing in `WebTestClient`.
@@ -46,8 +46,13 @@ directly with it.
[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-resolver[See equivalent in the Servlet stack]#
This strategy resolves the API version from a request. The WebFlux config provides built-in
-options to resolve from a header, a request parameter, or from the URL path.
-You can also use a custom `ApiVersionResolver`.
+options to resolve from a header, query parameter, media type parameter,
+or from the URL path. You can also use a custom `ApiVersionResolver`.
+
+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.
+
diff --git a/framework-docs/modules/ROOT/pages/web/webflux-webclient.adoc b/framework-docs/modules/ROOT/pages/web/webflux-webclient.adoc
index 01e2cb144a50..a8b3b595ed3e 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux-webclient.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux-webclient.adoc
@@ -2,18 +2,18 @@
= WebClient
:page-section-summary-toc: 1
-Spring WebFlux includes a client to perform HTTP requests with. `WebClient` has a
-functional, fluent API based on Reactor, see xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries],
+Spring WebFlux includes a client to perform HTTP requests. `WebClient` has a
+functional, fluent API based on Reactor (see xref:web/webflux-reactive-libraries.adoc[Reactive Libraries])
which enables declarative composition of asynchronous logic without the need to deal with
-threads or concurrency. It is fully non-blocking, it supports streaming, and relies on
+threads or concurrency. It is fully non-blocking, supports streaming, and relies on
the same xref:web/webflux/reactive-spring.adoc#webflux-codecs[codecs] that are also used to encode and
decode request and response content on the server side.
-`WebClient` needs an HTTP client library to perform requests with. There is built-in
+`WebClient` needs an HTTP client library to perform requests. There is built-in
support for the following:
* {reactor-github-org}/reactor-netty[Reactor Netty]
* {java-api}/java.net.http/java/net/http/HttpClient.html[JDK HttpClient]
* https://github.com/jetty-project/jetty-reactive-httpclient[Jetty Reactive HttpClient]
* https://hc.apache.org/index.html[Apache HttpComponents]
-* Others can be plugged via `ClientHttpConnector`.
+* Others can be plugged in via `ClientHttpConnector`.
diff --git a/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc b/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc
index 5c5dce2a3f9c..e08eed918259 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux-websocket.adoc
@@ -310,7 +310,7 @@ Java::
Flux source = ... ;
Mono output = session.send(source.map(session::textMessage)); <2>
- return Mono.zip(input, output).then(); <3>
+ return input.and(output); <3>
}
}
----
@@ -338,7 +338,7 @@ Kotlin::
val source: Flux = ...
val output = session.send(source.map(session::textMessage)) // <2>
- return Mono.zip(input, output).then() // <3>
+ return input.and(output) // <3>
}
}
----
@@ -367,7 +367,7 @@ subsequently use `DataBufferUtils.release(dataBuffer)` when the buffers are cons
`WebSocketHandlerAdapter` delegates to a `WebSocketService`. By default, that is an instance
of `HandshakeWebSocketService`, which performs basic checks on the WebSocket request and
then uses `RequestUpgradeStrategy` for the server in use. Currently, there is built-in
-support for Reactor Netty, Tomcat, Jetty, and Undertow.
+support for Reactor Netty, Tomcat, and Jetty.
`HandshakeWebSocketService` exposes a `sessionAttributePredicate` property that allows
setting a `Predicate` to extract attributes from the `WebSession` and insert them
@@ -446,7 +446,7 @@ specify CORS settings by URL pattern. If both are specified, they are combined b
=== Client
Spring WebFlux provides a `WebSocketClient` abstraction with implementations for
-Reactor Netty, Tomcat, Jetty, Undertow, and standard Java (that is, JSR-356).
+Reactor Netty, Tomcat, Jetty, and standard Java (that is, JSR-356).
NOTE: The Tomcat client is effectively an extension of the standard Java one with some extra
functionality in the `WebSocketSession` handling to take advantage of the Tomcat-specific
diff --git a/framework-docs/modules/ROOT/pages/web/webflux.adoc b/framework-docs/modules/ROOT/pages/web/webflux.adoc
index ffbe046f5bd4..ffc5729b79b7 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux.adoc
@@ -8,7 +8,7 @@ The original web framework included in the Spring Framework, Spring Web MVC, was
purpose-built for the Servlet API and Servlet containers. The reactive-stack web framework,
Spring WebFlux, was added later in version 5.0. It is fully non-blocking, supports
{reactive-streams-site}/[Reactive Streams] back pressure, and runs on such servers as
-Netty, Undertow, and Servlet containers.
+Netty, and Servlet containers.
Both web frameworks mirror the names of their source modules
({spring-framework-code}/spring-webmvc[spring-webmvc] and
diff --git a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc
index 2fdd068f99e1..9f3e371686f7 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc
@@ -691,7 +691,7 @@ Java::
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
- configurer.useRequestHeader("X-API-Version");
+ configurer.useRequestHeader("API-Version");
}
}
----
@@ -704,7 +704,7 @@ Kotlin::
class WebConfiguration : WebMvcConfigurer {
override fun configureApiVersioning(configurer: ApiVersionConfigurer) {
- configurer.useRequestHeader("X-API-Version")
+ configurer.useRequestHeader("API-Version")
}
}
----
@@ -718,8 +718,12 @@ alternatively use a custom `ApiVersionResolver`:
- Path segment
- Media type parameter
-TIP: When using a path segment, consider configuring a shared path prefix externally
-in xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching] options.
+To resolve from a path segment, you need to specify the index of the path segment expected
+to contain the version. The path segment must be declared as a URI variable, e.g.
+"/\{version}", "/api/\{version}", etc. where the actual name is not important.
+As the version is typically at the start of the path, consider configuring it externally
+as a common path prefix for all handlers through the
+xref:web/webflux/config.adoc#webflux-config-path-matching[Path Matching] options.
By default, the version is parsed with `SemanticVersionParser`, but you can also configure
a custom xref:web/webflux-versioning.adoc#webflux-versioning-parser[ApiVersionParser].
diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc
index 5b4e756899ad..965c51d581e6 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc
@@ -89,39 +89,7 @@ Kotlin::
You can map requests by using glob patterns and wildcards:
-[cols="2,3,5"]
-|===
-|Pattern |Description |Example
-
-| `+?+`
-| Matches one character
-| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
-
-| `+*+`
-| Matches zero or more characters within a path segment
-| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
-
-`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`
-
-| `+**+`
-| Matches zero or more path segments until the end of the path
-| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
-
-`+"/resources/**/file.png"+` is invalid as `+**+` is only allowed at the end of the path.
-
-| `+{name}+`
-| Matches a path segment and captures it as a variable named "name"
-| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
-
-| `+{name:[a-z]+}+`
-| Matches the regexp `+"[a-z]+"+` as a path variable named "name"
-| `+"/projects/{project:[a-z]+}/versions"+` matches `+"/projects/spring/versions"+` but not `+"/projects/spring1/versions"+`
-
-| `+{*path}+`
-| Matches zero or more path segments until the end of the path and captures it as a variable named "path"
-| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
-
-|===
+include::partial$web/uri-patterns.adoc[leveloffset=+1]
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
@@ -234,10 +202,13 @@ Kotlin::
======
--
-URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
-by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
-other property sources. You can use this, for example, to parameterize a base URL based on
-some external configuration.
+URI path patterns can also have:
+
+- Embedded `${...}` placeholders that are resolved on startup via
+`PropertySourcesPlaceholderConfigurer` against local, system, environment, and
+other property sources. This is useful, for example, to parameterize a base URL based on
+external configuration.
+- SpEL expressions `#{...}`.
NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support.
Both classes are located in `spring-web` and are expressly designed for use with HTTP URL
@@ -604,17 +575,16 @@ Kotlin::
== `@HttpExchange`
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Servlet stack]#
-While the main purpose of `@HttpExchange` is to abstract HTTP client code with a
-generated proxy, the
-xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] on which
-such annotations are placed is a contract neutral to client vs server use.
-In addition to simplifying client code, there are also cases where an HTTP Interface
-may be a convenient way for servers to expose their API for client access. This leads
-to increased coupling between client and server and is often not a good choice,
-especially for public API's, but may be exactly the goal for an internal API.
-It is an approach commonly used in Spring Cloud, and it is why `@HttpExchange` is
-supported as an alternative to `@RequestMapping` for server side handling in
-controller classes.
+While the main purpose of `@HttpExchange` is for an HTTP Service
+xref:integration/rest-clients.adoc#rest-http-service-client[client with a generated proxy],
+the HTTP Service interface on which such annotations are placed is a contract neutral
+to client vs server use. In addition to simplifying client code, there are also cases
+where an HTTP Service interface may be a convenient way for servers to expose their
+API for client access. This leads to increased coupling between client and server and
+is often not a good choice, especially for public API's, but may be exactly the goal
+for an internal API. It is an approach commonly used in Spring Cloud, and it is why
+`@HttpExchange` is supported as an alternative to `@RequestMapping` for server side
+handling in controller classes.
For example:
@@ -685,5 +655,5 @@ path, and content types.
For method parameters and returns values, generally, `@HttpExchange` supports a
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
server-side specific parameter types. For details, see the list for
-xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
+xref:integration/rest-clients.adoc#rest-http-service-client-method-parameters[@HttpExchange] and
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc
index 553b9d9dac21..67fb1256a889 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc
@@ -7,19 +7,23 @@ Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
-1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
+1. Java Bean Validation is applied individually to an
+xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
-xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
-resolvers validate a method argument individually if the method parameter is annotated
-with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
-`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
-discussed next). The exception raised in this case is `WebExchangeBindException`.
-
-2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
-directly on method parameters, or on the method (for the return value), then method
-validation must be applied, and that supersedes validation at the method argument level
-because method validation covers both method parameter constraints and nested constraints
-via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
+xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
+annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
+it is a command object rather than a container such as `Map` or `Collection`, it does not
+have `Errors` or `BindingResult` immediately after in the method signature, and does not
+otherwise require method validation (see next). `WebExchangeBindException` is the
+exception raised when validating a method parameter individually.
+
+2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
+`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
+method for the return value, and it supersedes any validation that would be applied
+otherwise to a method parameter individually because method validation covers both
+method parameter constraints and nested constraints via `@Valid`.
+`HandlerMethodValidationException` is the exception raised validation is applied
+to the method.
Applications must handle both `WebExchangeBindException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
diff --git a/framework-docs/modules/ROOT/pages/web/webflux/http2.adoc b/framework-docs/modules/ROOT/pages/web/webflux/http2.adoc
index 1b5a9e643a7e..c9a5f19080b1 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/http2.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/http2.adoc
@@ -4,6 +4,6 @@
[.small]#xref:web/webmvc/mvc-http2.adoc[See equivalent in the Servlet stack]#
-HTTP/2 is supported with Reactor Netty, Tomcat, Jetty, and Undertow. However, there are
+HTTP/2 is supported with Reactor Netty, Tomcat, and Jetty. However, there are
considerations related to server configuration. For more details, see the
{spring-framework-wiki}/HTTP-2-support[HTTP/2 wiki page].
diff --git a/framework-docs/modules/ROOT/pages/web/webflux/new-framework.adoc b/framework-docs/modules/ROOT/pages/web/webflux/new-framework.adoc
index a6dfda382304..f8f0062ceb18 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/new-framework.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/new-framework.adoc
@@ -127,7 +127,7 @@ You have maximum choice of libraries, since, historically, most are blocking.
* If you are already shopping for a non-blocking web stack, Spring WebFlux offers the same
execution model benefits as others in this space and also provides a choice of servers
-(Netty, Tomcat, Jetty, Undertow, and Servlet containers), a choice of programming models
+(Netty, Tomcat, Jetty, and Servlet containers), a choice of programming models
(annotated controllers and functional web endpoints), and a choice of reactive libraries
(Reactor, RxJava, or other).
@@ -165,7 +165,7 @@ unsure what benefits to look for, start by learning about how non-blocking I/O w
== Servers
Spring WebFlux is supported on Tomcat, Jetty, Servlet containers, as well as on
-non-Servlet runtimes such as Netty and Undertow. All servers are adapted to a low-level,
+non-Servlet runtimes such as Netty. All servers are adapted to a low-level,
xref:web/webflux/reactive-spring.adoc#webflux-httphandler[common API] so that higher-level
xref:web/webflux/new-framework.adoc#webflux-programming-models[programming models] can be supported across servers.
@@ -175,7 +175,7 @@ xref:web/webflux/dispatcher-handler.adoc#webflux-framework-config[WebFlux infras
lines of code.
Spring Boot has a WebFlux starter that automates these steps. By default, the starter uses
-Netty, but it is easy to switch to Tomcat, Jetty, or Undertow by changing your
+Netty, but it is easy to switch to Tomcat, or Jetty by changing your
Maven or Gradle dependencies. Spring Boot defaults to Netty, because it is more widely
used in the asynchronous, non-blocking space and lets a client and a server share resources.
@@ -188,8 +188,6 @@ adapter. It is not exposed for direct use.
NOTE: It is strongly advised not to map Servlet filters or directly manipulate the Servlet API in the context of a WebFlux application.
For the reasons listed above, mixing blocking I/O and non-blocking I/O in the same context will cause runtime issues.
-For Undertow, Spring WebFlux uses Undertow APIs directly without the Servlet API.
-
[[webflux-performance]]
== Performance
diff --git a/framework-docs/modules/ROOT/pages/web/webflux/range.adoc b/framework-docs/modules/ROOT/pages/web/webflux/range.adoc
index 7e2d5417ffe5..edcd170bd574 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/range.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/range.adoc
@@ -15,6 +15,9 @@ xref:web/webflux-functional.adoc#webflux-fn-resources[serves a `Resource`]. `Ran
support is also transparently handled when serving
xref:web/webflux/config.adoc#webflux-config-static-resources[static resources].
+TIP: The `Resource` must not be an `InputStreamResource` and with `ResponseEntity`,
+the status of the response must be 200.
+
The underlying support is in the `HttpRange` class, which exposes methods to parse
`Range` headers and split a `Resource` into a `List` that in turn can be
then written to the response via `ResourceRegionEncoder` and `ResourceHttpMessageWriter`.
\ No newline at end of file
diff --git a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc
index 6301994d0bac..084ebba4aa2f 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc
@@ -7,7 +7,7 @@ applications:
* For server request processing there are two levels of support.
** xref:web/webflux/reactive-spring.adoc#webflux-httphandler[HttpHandler]: Basic contract for HTTP request handling with
non-blocking I/O and Reactive Streams back pressure, along with adapters for Reactor Netty,
-Undertow, Tomcat, Jetty, and any Servlet container.
+Tomcat, Jetty, and any Servlet container.
** xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[`WebHandler` API]: Slightly higher level, general-purpose web API for
request handling, on top of which concrete programming models such as annotated
controllers and functional endpoints are built.
@@ -40,10 +40,6 @@ The following table describes the supported server APIs:
| Netty API
| {reactor-github-org}/reactor-netty[Reactor Netty]
-| Undertow
-| Undertow API
-| spring-web: Undertow to Reactive Streams bridge
-
| Tomcat
| Servlet non-blocking I/O; Tomcat API to read and write ByteBuffers vs byte[]
| spring-web: Servlet non-blocking I/O to Reactive Streams bridge
@@ -67,10 +63,6 @@ The following table describes server dependencies (also see
|io.projectreactor.netty
|reactor-netty
-|Undertow
-|io.undertow
-|undertow-core
-
|Tomcat
|org.apache.tomcat.embed
|tomcat-embed-core
@@ -104,30 +96,6 @@ Kotlin::
----
======
-*Undertow*
-[tabs]
-======
-Java::
-+
-[source,java,indent=0,subs="verbatim,quotes"]
-----
- HttpHandler handler = ...
- UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
- Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
- server.start();
-----
-
-Kotlin::
-+
-[source,kotlin,indent=0,subs="verbatim,quotes"]
-----
- val handler: HttpHandler = ...
- val adapter = UndertowHttpHandlerAdapter(handler)
- val server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build()
- server.start()
-----
-======
-
*Tomcat*
[tabs]
======
@@ -375,10 +343,6 @@ the request, based on forwarded headers, and then removes those headers. If you
it as a bean with the name `forwardedHeaderTransformer`, it will be
xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api-special-beans[detected] and used.
-NOTE: In 5.1 `ForwardedHeaderFilter` was deprecated and superseded by
-`ForwardedHeaderTransformer` so forwarded headers can be processed earlier, before the
-exchange is created. If the filter is configured anyway, it is taken out of the list of
-filters, and `ForwardedHeaderTransformer` is used instead.
[[webflux-forwarded-headers-security]]
=== Security Considerations
@@ -581,6 +545,20 @@ The `ProtobufJsonDecoder` and `ProtobufJsonEncoder` variants support reading and
They require the "com.google.protobuf:protobuf-java-util" dependency. Note, the JSON variants do not support reading stream of messages,
see the {spring-framework-api}/http/codec/protobuf/ProtobufJsonDecoder.html[javadoc of `ProtobufJsonDecoder`] for more details.
+[[webflux-codecs-gson]]
+=== Google Gson
+
+Applications can use the `GsonEncoder` and `GsonDecoder` to serialize and deserialize JSON documents thanks to the https://google.github.io/gson/[Google Gson] library .
+This codec supports both JSON media types and the NDJSON format for streaming.
+
+[NOTE]
+====
+`Gson` does not support non-blocking parsing, so the `GsonDecoder` does not support deserializing
+to `Flux<*>` types. For example, if this decoder is used for deserializing a JSON stream or even a list of elements
+as a `Flux<*>`, an `UnsupportedOperationException` will be thrown at runtime.
+Applications should instead focus on deserializing bounded collections and use `Mono>` as target types.
+====
+
[[webflux-codecs-limits]]
=== Limits
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-client.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-client.adoc
index 17bd4bcd567b..03b8950d7e40 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc-client.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc-client.adoc
@@ -15,27 +15,27 @@ See xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] for more de
[[webmvc-webclient]]
== `WebClient`
-`WebClient` is a reactive client to perform HTTP requests with a fluent API.
+`WebClient` is a reactive client for making HTTP requests with a fluent API.
-See xref:web/webflux-webclient.adoc[WebClient] for more details.
+See xref:web/webflux-webclient.adoc[`WebClient`] for more details.
[[webmvc-resttemplate]]
== `RestTemplate`
-`RestTemplate` is a synchronous client to perform HTTP requests. It is the original
+`RestTemplate` is a synchronous client for making HTTP requests. It is the original
Spring REST client and exposes a simple, template-method API over underlying HTTP client
libraries.
-See xref:integration/rest-clients.adoc[REST Endpoints] for details.
+See xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] for details.
-[[webmvc-http-interface]]
-== HTTP Interface
+[[webmvc-http-service-client]]
+== HTTP Service Client
-The Spring Frameworks lets you define an HTTP service as a Java interface with HTTP
+The Spring Framework lets you define an HTTP service as a Java interface with HTTP
exchange methods. You can then generate a proxy that implements this interface and
performs the exchanges. This helps to simplify HTTP remote access and provides additional
-flexibility for to choose an API style such as synchronous or reactive.
+flexibility for choosing an API style such as synchronous or reactive.
-See xref:integration/rest-clients.adoc#rest-http-interface[REST Endpoints] for details.
+See xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service Client] for details.
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc
index 141a5c7d00a8..5901b99fb945 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc-functional.adoc
@@ -184,6 +184,26 @@ val map = request.params()
----
======
+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")})
+----
+======
+
+
[[webmvc-fn-response]]
=== ServerResponse
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc
index 2be54cd83351..6d548ad356b9 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc
@@ -16,7 +16,7 @@ to annotated controller methods with an API version
to functional endpoints with an API version
Client support for API versioning is available also in `RestClient`, `WebClient`, and
-xref:integration/rest-clients.adoc#rest-http-interface[HTTP Service] clients, as well as
+xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service] clients, as well as
for testing in MockMvc and `WebTestClient`.
@@ -46,8 +46,13 @@ directly with it.
[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-resolver[See equivalent in the Reactive stack]#
This strategy resolves the API version from a request. The MVC config provides built-in
-options to resolve from a header, from a request parameter, or from the URL path.
-You can also use a custom `ApiVersionResolver`.
+options to resolve from a header, query parameter, media type parameter,
+or from the URL path. You can also use a custom `ApiVersionResolver`.
+
+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.
+
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc
index 73e1e7c27e52..d1e50c6e53a7 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-document.adoc
@@ -4,10 +4,14 @@
Spring offers ways to return output other than HTML, including PDF and Excel spreadsheets.
This section describes how to use those features.
+WARNING: As of Spring Framework 7.0, view classes in the `org.springframework.web.servlet.view.document`
+package are deprecated. Instead, libraries can adapt this existing code to provide support with their own `*View` types.
+As an alternative, applications can perform direct rendering in web handlers.
[[mvc-view-document-intro]]
== Introduction to Document Views
+
An HTML page is not always the best way for the user to view the model output,
and Spring makes it simple to generate a PDF document or an Excel spreadsheet
dynamically from the model data. The document is the view and is streamed from the
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc
index 825dfd46607c..78069fc588c2 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-feeds.adoc
@@ -1,6 +1,10 @@
[[mvc-view-feeds]]
= RSS and Atom
+WARNING: As of Spring Framework 7.0, view classes in the `org.springframework.web.servlet.view.feed`
+package are deprecated. Instead, libraries can adapt this existing code to provide support with their own `*View` types.
+As an alternative, applications can perform direct rendering in web handlers.
+
Both `AbstractAtomFeedView` and `AbstractRssFeedView` inherit from the
`AbstractFeedView` base class and are used to provide Atom and RSS Feed views, respectively. They
are based on https://rometools.github.io/rome/[ROME] project and are located in the
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc
index 6ca828bef563..7eb33c7180ac 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-jsp.adoc
@@ -11,16 +11,42 @@ When developing with JSPs, you typically declare an `InternalResourceViewResolve
`InternalResourceViewResolver` can be used for dispatching to any Servlet resource but in
particular for JSPs. As a best practice, we strongly encourage placing your JSP files in
-a directory under the `'WEB-INF'` directory so there can be no direct access by clients.
+a directory under the `WEB-INF` directory so there can be no direct access by clients.
+This is what is done by the configuration below which registers a JSP view resolver using
+a default view name prefix of `"/WEB-INF/"` and a default suffix of `".jsp"`.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+@EnableWebMvc
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+ @Override
+ public void configureViewResolvers(ViewResolverRegistry registry) {
+ registry.jsp();
+ }
+}
+----
+
+XML::
++
[source,xml,indent=0,subs="verbatim,quotes"]
----
-
-
-
-
-
+
+
+
+
+
----
+======
+
+[NOTE]
+You can specify custom prefix and suffix.
[[mvc-view-jsp-jstl]]
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc
index d2813753a95e..490f5d9e623b 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc
@@ -3,7 +3,10 @@
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-filters[See equivalent in the Reactive stack]#
-The `spring-web` module provides some useful filters:
+In the Servlet API, you can add a `jakarta.servlet.Filter` to apply interception-style logic
+before and after the rest of the processing chain of filters and the target `Servlet`.
+
+The `spring-web` module has a number of built-in `Filter` implementations:
* xref:web/webmvc/filters.adoc#filters-http-put[Form Data]
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
@@ -11,9 +14,19 @@ The `spring-web` module provides some useful filters:
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
-Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations.
-If you are using Spring Boot, you can
-{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare them as beans and configure them as part of your application].
+There are also base class implementations for use in Spring applications:
+
+* `GenericFilterBean` -- base class for a `Filter` configured as a Spring bean;
+integrates with the Spring `ApplicationContext` lifecycle.
+* `OncePerRequestFilter` -- extension of `GenericFilterBean` that supports a single
+invocation at the start of a request, i.e. during the `REQUEST` dispatch phase, and
+ignoring further handling via `FORWARD` dispatches. The filter also provides control
+over whether the `Filter` gets involved in `ASYNC` and `ERROR` dispatches.
+
+Servlet filters can be configured in `web.xml` or via Servlet annotations.
+In a Spring Boot application , you can
+{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare Filter's as beans]
+and Boot will have them configured.
[[filters-http-put]]
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc
index ea0a9ce87f61..b44f0828ade8 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-async.adoc
@@ -422,7 +422,7 @@ reactive types from the controller method.
Reactive return values are handled as follows:
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
-include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
+include `CompletionStage` (JDK), `Mono` (Reactor), and `Single` (RxJava).
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc
index c989de0dcdd5..0f221f33a8ee 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc
@@ -15,8 +15,12 @@ alternatively use a custom `ApiVersionResolver`:
- Path segment
- Media type parameter
-TIP: When using a path segment, consider configuring a shared path prefix externally
-in xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching] options.
+To resolve from a path segment, you need to specify the index of the path segment expected
+to contain the version. The path segment must be declared as a URI variable, e.g.
+"/\{version}", "/api/\{version}", etc. where the actual name is not important.
+As the version is typically at the start of the path, consider configuring it externally
+as a common path prefix for all handlers through the
+xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching] options.
By default, the version is parsed with `SemanticVersionParser`, but you can also configure
a custom xref:web/webmvc-versioning.adoc#mvc-versioning-parser[ApiVersionParser].
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc
index 94ff1b4f420b..9659c62f1cf8 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc
@@ -88,37 +88,17 @@ Kotlin::
== URI patterns
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-uri-templates[See equivalent in the Reactive stack]#
-`@RequestMapping` methods can be mapped using URL patterns. There are two alternatives:
-
-* `PathPattern` -- a pre-parsed pattern matched against the URL path also pre-parsed as
-`PathContainer`. Designed for web use, this solution deals effectively with encoding and
-path parameters, and matches efficiently.
-* `AntPathMatcher` -- match String patterns against a String path. This is the original
-solution also used in Spring configuration to select resources on the classpath, on the
-filesystem, and other locations. It is less efficient and the String path input is a
-challenge for dealing effectively with encoding and other issues with URLs.
-
-`PathPattern` is the recommended solution for web applications and it is the only choice in
-Spring WebFlux. It was enabled for use in Spring MVC from version 5.3 and is enabled by
-default from version 6.0. See xref:web/webmvc/mvc-config/path-matching.adoc[MVC config] for
-customizations of path matching options.
+`@RequestMapping` methods can be mapped using URL patterns.
+Spring MVC is using `PathPattern` -- a pre-parsed pattern matched against the URL path also pre-parsed as `PathContainer`.
+Designed for web use, this solution deals effectively with encoding and path parameters, and matches efficiently.
+See xref:web/webmvc/mvc-config/path-matching.adoc[MVC config] for customizations of path matching options.
-`PathPattern` supports the same pattern syntax as `AntPathMatcher`. In addition, it also
-supports the capturing pattern, for example, `+{*spring}+`, for matching 0 or more path segments
-at the end of a path. `PathPattern` also restricts the use of `+**+` for matching multiple
-path segments such that it's only allowed at the end of a pattern. This eliminates many
-cases of ambiguity when choosing the best matching pattern for a given request.
-For full pattern syntax please refer to
-{spring-framework-api}/web/util/pattern/PathPattern.html[PathPattern] and
-{spring-framework-api}/util/AntPathMatcher.html[AntPathMatcher].
+NOTE: the `AntPathMatcher` variant is now deprecated because it is less efficient and the String path input is a
+challenge for dealing effectively with encoding and other issues with URLs.
-Some example patterns:
+You can map requests by using glob patterns and wildcards:
-* `+"/resources/ima?e.png"+` - match one character in a path segment
-* `+"/resources/*.png"+` - match zero or more characters in a path segment
-* `+"/resources/**"+` - match multiple path segments
-* `+"/projects/{project}/versions"+` - match a path segment and capture it as a variable
-* `++"/projects/{project:[a-z]+}/versions"++` - match and capture a variable with a regex
+include::partial$web/uri-patterns.adoc[leveloffset=+1]
Captured URI variables can be accessed with `@PathVariable`. For example:
@@ -217,10 +197,13 @@ Kotlin::
----
======
-URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
-by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
-other property sources. You can use this, for example, to parameterize a base URL based on
-some external configuration.
+URI path patterns can also have:
+
+- Embedded `${...}` placeholders that are resolved on startup via
+`PropertySourcesPlaceholderConfigurer` against local, system, environment, and
+other property sources. This is useful, for example, to parameterize a base URL based on
+external configuration.
+- SpEL expression `#{...}`.
[[mvc-ann-requestmapping-pattern-comparison]]
@@ -622,10 +605,9 @@ Kotlin::
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-httpexchange-annotation[See equivalent in the Reactive stack]#
While the main purpose of `@HttpExchange` is to abstract HTTP client code with a
-generated proxy, the
-xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] on which
-such annotations are placed is a contract neutral to client vs server use.
-In addition to simplifying client code, there are also cases where an HTTP Interface
+generated proxy, the interface on which such annotations are placed is a contract neutral
+to client vs server use. In addition to simplifying client code, there are also cases
+where an xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service Client]
may be a convenient way for servers to expose their API for client access. This leads
to increased coupling between client and server and is often not a good choice,
especially for public API's, but may be exactly the goal for an internal API.
@@ -702,7 +684,7 @@ path, and content types.
For method parameters and returns values, generally, `@HttpExchange` supports a
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
server-side specific parameter types. For details, see the list for
-xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
+xref:integration/rest-clients.adoc#rest-http-service-client-method-parameters[@HttpExchange] and
xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[@RequestMapping].
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc
index 99ddf8635eb1..45e5c632f4b7 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc
@@ -7,22 +7,26 @@ Spring MVC has built-in xref:core/validation/validator.adoc[validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
-1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
+1. Java Bean Validation is applied individually to an
+xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
-xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
-resolvers validate a method argument individually if the method parameter is annotated
-with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
-`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
-discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
-
-2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
-directly on method parameters, or on the method (for the return value), then method
-validation must be applied, and that supersedes validation at the method argument level
-because method validation covers both method parameter constraints and nested constraints
-via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
-
-Applications must handle both `MethodArgumentNotValidException` and
-`HandlerMethodValidationException` as either may be raised depending on the controller
+xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
+annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
+it is a command object rather than a container such as `Map` or `Collection`, it does not
+have `Errors` or `BindingResult` immediately after in the method signature, and does not
+otherwise require method validation (see next). `MethodArgumentNotValidException` is the
+exception raised when validating a method parameter individually.
+
+2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
+`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
+method for the return value, and it supersedes any validation that would be applied
+otherwise to a method parameter individually because method validation covers both
+method parameter constraints and nested constraints via `@Valid`.
+`HandlerMethodValidationException` is the exception raised validation is applied
+to the method.
+
+Applications should handle both `MethodArgumentNotValidException` and
+`HandlerMethodValidationException` since either may be raised depending on the controller
method signature. The two exceptions, however are designed to be very similar, and can be
handled with almost identical code. The main difference is that the former is for a single
object while the latter is for a list of method parameters.
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-range.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-range.adoc
index f47c85cb79e6..7ba60ead5379 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-range.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-range.adoc
@@ -15,6 +15,9 @@ xref:web/webmvc-functional.adoc#webmvc-fn-resources[serves a `Resource`]. `Range
support is also transparently handled when serving
xref:web/webmvc/mvc-config/static-resources.adoc[static resources].
+TIP: The `Resource` must not be an `InputStreamResource` and with `ResponseEntity`,
+the status of the response must be 200.
+
The underlying support is in the `HttpRange` class, which exposes methods to parse
`Range` headers and split a `Resource` into a `List` that in turn can be
then written to the response via `ResourceRegionHttpMessageConverter`.
\ No newline at end of file
diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc
index 75dbbb1919b3..cdf564f281d8 100644
--- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/multipart.adoc
@@ -6,8 +6,6 @@
`MultipartResolver` from the `org.springframework.web.multipart` package is a strategy
for parsing multipart requests including file uploads. There is a container-based
`StandardServletMultipartResolver` implementation for Servlet multipart request parsing.
-Note that the outdated `CommonsMultipartResolver` based on Apache Commons FileUpload is
-not available anymore, as of Spring Framework 6.0 with its new Servlet 5.0+ baseline.
To enable multipart handling, you need to declare a `MultipartResolver` bean in your
`DispatcherServlet` Spring configuration with a name of `multipartResolver`.
diff --git a/framework-docs/modules/ROOT/pages/web/websocket/stomp/configuration-performance.adoc b/framework-docs/modules/ROOT/pages/web/websocket/stomp/configuration-performance.adoc
index 1456a7e07703..c1706a321d38 100644
--- a/framework-docs/modules/ROOT/pages/web/websocket/stomp/configuration-performance.adoc
+++ b/framework-docs/modules/ROOT/pages/web/websocket/stomp/configuration-performance.adoc
@@ -72,7 +72,7 @@ such as https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] and others split
STOMP messages at 16K boundaries and send them as multiple WebSocket messages,
which requires the server to buffer and re-assemble.
-Spring's STOMP-over-WebSocket support does this ,so applications can configure the
+Spring's STOMP-over-WebSocket support does this, so applications can configure the
maximum size for STOMP messages irrespective of WebSocket server-specific message
sizes. Keep in mind that the WebSocket message size is automatically
adjusted, if necessary, to ensure they can carry 16K WebSocket messages at a
diff --git a/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc b/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc
index b066e00e56a5..0be9eecb94a4 100644
--- a/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc
+++ b/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc
@@ -1,8 +1,8 @@
[[websocket-stomp-websocket-scope]]
= WebSocket Scope
-Each WebSocket session has a map of attributes. The map is attached as a header to
-inbound client messages and may be accessed from a controller method, as the following example shows:
+Each WebSocket session has a map of attributes. The map is attached as a header to inbound
+client messages and may be accessed from a controller method, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -20,13 +20,13 @@ public class MyController {
You can declare a Spring-managed bean in the `websocket` scope.
You can inject WebSocket-scoped beans into controllers and any channel interceptors
registered on the `clientInboundChannel`. Those are typically singletons and live
-longer than any individual WebSocket session. Therefore, you need to use a
-scope proxy mode for WebSocket-scoped beans, as the following example shows:
+longer than any individual WebSocket session. Therefore, you need to use
+WebSocket-scoped beans in proxy mode, conveniently defined with `@WebSocketScope`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
- @Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
+ @WebSocketScope
public class MyBean {
@PostConstruct
diff --git a/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc b/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc
index 45e48114a618..c1f63fad0de0 100644
--- a/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc
+++ b/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc
@@ -9,7 +9,7 @@ that proxies can use to provide information about the original request.
=== Non-standard Headers
There are other non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`,
-`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`.
+`X-Forwarded-Proto`, `X-Forwarded-Ssl`, `X-Forwarded-Prefix`, and `X-Forwarded-For`.
[[x-forwarded-host]]
==== X-Forwarded-Host
@@ -113,3 +113,12 @@ https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}
In this case, the proxy has a prefix of `/api/app1` and the server has a prefix of
`/app1`. The proxy can send `X-Forwarded-Prefix: /api/app1` to have the original prefix
`/api/app1` override the server prefix `/app1`.
+
+[[x-forwarded-for]]
+==== X-Forwarded-For
+
+https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For[`X-Forwarded-For: `]
+is a de-facto standard header that is used to communicate the original `InetSocketAddress` of the client to a
+downstream server. For example, if a request is sent by a client at `[fd00:fefe:1::4]` to a proxy at
+`192.168.0.1`, the "remote address" information contained in the HTTP request will reflect the actual address of the
+client, not the proxy.
diff --git a/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc b/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc
new file mode 100644
index 000000000000..494c50a6c0a1
--- /dev/null
+++ b/framework-docs/modules/ROOT/partials/web/uri-patterns.adoc
@@ -0,0 +1,51 @@
+[cols="2,3,5"]
+|===
+|Pattern |Description |Example
+
+| `spring`
+| Literal pattern
+| `+"/spring"+` matches `+"/spring"+`
+
+| `+?+`
+| Matches one character
+| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
+
+| `+*+`
+| Matches zero or more characters within a path segment
+| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
+
+`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`.
+
+`+"/projects/*"+` matches `+"/projects/spring"+` but does not match `+"/projects"+` as the path segment is not present.
+
+| `+**+`
+| Matches zero or more path segments
+| `+"/resources/**"+` matches `+"/resources"+`, `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
+
+`+"/**/info"+` matches `+"/info"+`, `+"/spring/info"+` and `+"/spring/framework/info"+`
+
+`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path.
+
+`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
+
+| `+{name}+`
+| Similar to `+*+`, but also captures the path segment as a variable named "name"
+| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
+
+`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment.
+
+| `{name:[a-z]+}`
+| Matches the regexp `"[a-z]+"` as a path variable named "name"
+| `"/projects/{project:[a-z]+}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"`
+
+| `+{*path}+`
+| Similar to `+**+`, but also captures the path segments as a variable named "path"
+| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
+
+`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+`
+
+`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path.
+
+`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
+
+|===
\ No newline at end of file
diff --git a/framework-docs/src/main/java/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyBeanRegistrar.java b/framework-docs/src/main/java/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyBeanRegistrar.java
new file mode 100644
index 000000000000..22fb1c08c4b3
--- /dev/null
+++ b/framework-docs/src/main/java/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyBeanRegistrar.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration;
+
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.BeanRegistry;
+import org.springframework.core.env.Environment;
+import org.springframework.web.servlet.function.RouterFunction;
+import org.springframework.web.servlet.function.RouterFunctions;
+import org.springframework.web.servlet.function.ServerResponse;
+
+// tag::snippet[]
+class MyBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class);
+ registry.registerBean("bar", Bar.class, spec -> spec
+ .prototype()
+ .lazyInit()
+ .description("Custom description")
+ .supplier(context -> new Bar(context.bean(Foo.class))));
+ if (env.matchesProfiles("baz")) {
+ registry.registerBean(Baz.class, spec -> spec
+ .supplier(context -> new Baz("Hello World!")));
+ }
+ registry.registerBean(MyRepository.class);
+ registry.registerBean(RouterFunction.class, spec ->
+ spec.supplier(context -> router(context.bean(MyRepository.class))));
+ }
+
+ RouterFunction router(MyRepository myRepository) {
+ return RouterFunctions.route()
+ // ...
+ .build();
+ }
+
+}
+// end::snippet[]
diff --git a/framework-docs/src/main/java/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyConfiguration.java
new file mode 100644
index 000000000000..8593224eacee
--- /dev/null
+++ b/framework-docs/src/main/java/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyConfiguration.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+// tag::snippet[]
+@Configuration
+@Import(MyBeanRegistrar.class)
+class MyConfiguration {
+}
+// end::snippet[]
diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssending/JmsQueueSender.java b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssending/JmsQueueSender.java
new file mode 100644
index 000000000000..9b8aede1b8b9
--- /dev/null
+++ b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssending/JmsQueueSender.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.integration.jms.jmssending;
+
+import jakarta.jms.ConnectionFactory;
+import jakarta.jms.JMSException;
+import jakarta.jms.Message;
+import jakarta.jms.Queue;
+import jakarta.jms.Session;
+
+import org.springframework.jms.core.MessageCreator;
+import org.springframework.jms.core.JmsTemplate;
+
+public class JmsQueueSender {
+
+ private JmsTemplate jmsTemplate;
+ private Queue queue;
+
+ public void setConnectionFactory(ConnectionFactory cf) {
+ this.jmsTemplate = new JmsTemplate(cf);
+ }
+
+ public void setQueue(Queue queue) {
+ this.queue = queue;
+ }
+
+ public void simpleSend() {
+ this.jmsTemplate.send(this.queue, new MessageCreator() {
+ public Message createMessage(Session session) throws JMSException {
+ return session.createTextMessage("hello queue world");
+ }
+ });
+ }
+}
diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingconversion/JmsSenderWithConversion.java b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingconversion/JmsSenderWithConversion.java
new file mode 100644
index 000000000000..a9f43e2c5f08
--- /dev/null
+++ b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingconversion/JmsSenderWithConversion.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.integration.jms.jmssendingconversion;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import jakarta.jms.JMSException;
+import jakarta.jms.Message;
+
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.jms.core.MessagePostProcessor;
+
+public class JmsSenderWithConversion {
+
+ private JmsTemplate jmsTemplate;
+
+ public void sendWithConversion() {
+ Map map = new HashMap<>();
+ map.put("Name", "Mark");
+ map.put("Age", 47);
+ jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
+ public Message postProcessMessage(Message message) throws JMSException {
+ message.setIntProperty("AccountID", 1234);
+ message.setJMSCorrelationID("123-00001");
+ return message;
+ }
+ });
+ }
+
+}
diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingjmsclient/JmsClientSample.java b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingjmsclient/JmsClientSample.java
new file mode 100644
index 000000000000..3f7468c839c3
--- /dev/null
+++ b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingjmsclient/JmsClientSample.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.integration.jms.jmssendingjmsclient;
+
+import jakarta.jms.ConnectionFactory;
+
+import org.springframework.jms.core.JmsClient;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+
+public class JmsClientSample {
+
+ private final JmsClient jmsClient;
+
+ public JmsClientSample(ConnectionFactory connectionFactory) {
+ // For custom options, use JmsClient.builder(ConnectionFactory)
+ this.jmsClient = JmsClient.create(connectionFactory);
+ }
+
+ public void sendWithConversion() {
+ this.jmsClient.destination("myQueue")
+ .withTimeToLive(1000)
+ .send("myPayload"); // optionally with a headers Map next to the payload
+ }
+
+ public void sendCustomMessage() {
+ Message> message = MessageBuilder.withPayload("myPayload").build(); // optionally with headers
+ this.jmsClient.destination("myQueue")
+ .withTimeToLive(1000)
+ .send(message);
+ }
+}
diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingpostprocessor/JmsClientWithPostProcessor.java b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingpostprocessor/JmsClientWithPostProcessor.java
new file mode 100644
index 000000000000..2e5a4a4a634a
--- /dev/null
+++ b/framework-docs/src/main/java/org/springframework/docs/integration/jms/jmssendingpostprocessor/JmsClientWithPostProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.integration.jms.jmssendingpostprocessor;
+
+
+import jakarta.jms.ConnectionFactory;
+
+import org.springframework.jms.core.JmsClient;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.core.MessagePostProcessor;
+import org.springframework.messaging.support.MessageBuilder;
+
+public class JmsClientWithPostProcessor {
+
+ private final JmsClient jmsClient;
+
+ public JmsClientWithPostProcessor(ConnectionFactory connectionFactory) {
+ this.jmsClient = JmsClient.builder(connectionFactory)
+ .messagePostProcessor(new TenantIdMessageInterceptor("42"))
+ .build();
+ }
+
+ public void sendWithPostProcessor() {
+ this.jmsClient.destination("myQueue")
+ .withTimeToLive(1000)
+ .send("myPayload");
+ }
+
+ static class TenantIdMessageInterceptor implements MessagePostProcessor {
+
+ private final String tenantId;
+
+ public TenantIdMessageInterceptor(String tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public Message> postProcessMessage(Message> message) {
+ return MessageBuilder.fromMessage(message)
+ .setHeader("tenantId", this.tenantId)
+ .build();
+ }
+ }
+}
diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/resthttpinterface/customresolver/CustomHttpServiceArgumentResolver.java b/framework-docs/src/main/java/org/springframework/docs/integration/resthttpserviceclient/customresolver/CustomHttpServiceArgumentResolver.java
similarity index 95%
rename from framework-docs/src/main/java/org/springframework/docs/integration/resthttpinterface/customresolver/CustomHttpServiceArgumentResolver.java
rename to framework-docs/src/main/java/org/springframework/docs/integration/resthttpserviceclient/customresolver/CustomHttpServiceArgumentResolver.java
index 3702dc51048b..6982f682d0c4 100644
--- a/framework-docs/src/main/java/org/springframework/docs/integration/resthttpinterface/customresolver/CustomHttpServiceArgumentResolver.java
+++ b/framework-docs/src/main/java/org/springframework/docs/integration/resthttpserviceclient/customresolver/CustomHttpServiceArgumentResolver.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.docs.integration.resthttpinterface.customresolver;
+package org.springframework.docs.integration.resthttpserviceclient.customresolver;
import java.util.List;
@@ -28,14 +28,14 @@
public class CustomHttpServiceArgumentResolver {
- // tag::httpinterface[]
+ // tag::httpserviceclient[]
public interface RepositoryService {
@GetExchange("/repos/search")
List searchRepository(Search search);
}
- // end::httpinterface[]
+ // end::httpserviceclient[]
class Sample {
diff --git a/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java
index a58293146da2..d1baeae21328 100644
--- a/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java
+++ b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java
@@ -26,7 +26,7 @@ public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
- configurer.useRequestHeader("X-API-Version");
+ configurer.useRequestHeader("API-Version");
}
}
// end::snippet[]
diff --git a/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.java
index cfdba475159e..f5290333bd3a 100644
--- a/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.java
+++ b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.java
@@ -44,8 +44,8 @@ public void configureMessageConverters(HttpMessageConverters.ServerBuilder build
.findAndAddModules()
.defaultUseWrapper(false)
.build();
- builder.jsonMessageConverter(new JacksonJsonHttpMessageConverter(jsonMapper))
- .xmlMessageConverter(new JacksonXmlHttpMessageConverter(xmlMapper));
+ builder.withJsonConverter(new JacksonJsonHttpMessageConverter(jsonMapper))
+ .withXmlConverter(new JacksonXmlHttpMessageConverter(xmlMapper));
}
}
// end::snippet[]
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Bar.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Bar.kt
new file mode 100644
index 000000000000..eeeb6a546288
--- /dev/null
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Bar.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration
+
+data class Bar(val foo: Foo)
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Baz.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Baz.kt
new file mode 100644
index 000000000000..0dab54a5c545
--- /dev/null
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Baz.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration
+
+data class Baz(val value: String)
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Foo.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Foo.kt
new file mode 100644
index 000000000000..0942b2193a5b
--- /dev/null
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/Foo.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration
+
+class Foo
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyBeanRegistrar.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyBeanRegistrar.kt
new file mode 100644
index 000000000000..51a898fb7f46
--- /dev/null
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyBeanRegistrar.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration
+
+import org.springframework.beans.factory.BeanRegistrarDsl
+import org.springframework.web.servlet.function.router
+
+// tag::snippet[]
+class MyBeanRegistrar : BeanRegistrarDsl({
+ registerBean()
+ registerBean(
+ name = "bar",
+ prototype = true,
+ lazyInit = true,
+ description = "Custom description") {
+ Bar(bean()) // Also possible with Bar(bean())
+ }
+ profile("baz") {
+ registerBean { Baz("Hello World!") }
+ }
+ registerBean()
+ registerBean {
+ myRouter(bean()) // Also possible with myRouter(bean())
+ }
+})
+
+fun myRouter(myRepository: MyRepository) = router {
+ // ...
+}
+// end::snippet[]
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyConfiguration.kt
new file mode 100644
index 000000000000..81f7c29fc84e
--- /dev/null
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyConfiguration.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
+
+// tag::snippet[]
+@Configuration
+@Import(MyBeanRegistrar::class)
+class MyConfiguration {
+}
+// end::snippet[]
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyRepository.kt b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyRepository.kt
new file mode 100644
index 000000000000..e40ad268baf9
--- /dev/null
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/core/beans/java/beansjavaprogrammaticregistration/MyRepository.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.core.beans.java.beansjavaprogrammaticregistration
+
+interface MyRepository {
+}
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/integration/resthttpinterface/customresolver/CustomHttpServiceArgumentResolver.kt b/framework-docs/src/main/kotlin/org/springframework/docs/integration/resthttpserviceclient/customresolver/CustomHttpServiceArgumentResolver.kt
similarity index 94%
rename from framework-docs/src/main/kotlin/org/springframework/docs/integration/resthttpinterface/customresolver/CustomHttpServiceArgumentResolver.kt
rename to framework-docs/src/main/kotlin/org/springframework/docs/integration/resthttpserviceclient/customresolver/CustomHttpServiceArgumentResolver.kt
index b1412985d7e7..8f608e2d182c 100644
--- a/framework-docs/src/main/kotlin/org/springframework/docs/integration/resthttpinterface/customresolver/CustomHttpServiceArgumentResolver.kt
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/integration/resthttpserviceclient/customresolver/CustomHttpServiceArgumentResolver.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.docs.integration.resthttpinterface.customresolver
+package org.springframework.docs.integration.resthttpserviceclient.customresolver
import org.springframework.core.MethodParameter
import org.springframework.web.client.RestClient
@@ -26,14 +26,14 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory
class CustomHttpServiceArgumentResolver {
- // tag::httpinterface[]
+ // tag::httpserviceclient[]
interface RepositoryService {
@GetExchange("/repos/search")
fun searchRepository(search: Search): List
}
- // end::httpinterface[]
+ // end::httpserviceclient[]
class Sample {
fun sample() {
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/languages/kotlin/coroutines/propagation/ContextPropagationSample.kt b/framework-docs/src/main/kotlin/org/springframework/docs/languages/kotlin/coroutines/propagation/ContextPropagationSample.kt
new file mode 100644
index 000000000000..0de429ba57f6
--- /dev/null
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/languages/kotlin/coroutines/propagation/ContextPropagationSample.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.docs.languages.kotlin.coroutines.propagation
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.apache.commons.logging.Log
+import org.apache.commons.logging.LogFactory
+import org.springframework.core.PropagationContextElement
+
+class ContextPropagationSample {
+
+ companion object {
+ private val logger: Log = LogFactory.getLog(
+ ContextPropagationSample::class.java
+ )
+ }
+
+ // tag::context[]
+ fun main() {
+ runBlocking(Dispatchers.IO + PropagationContextElement()) {
+ waitAndLog()
+ }
+ }
+
+ suspend fun waitAndLog() {
+ delay(10)
+ logger.info("Suspending function with traceId")
+ }
+ // end::context[]
+}
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt
index dec34ad91964..4a315aef00be 100644
--- a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt
@@ -25,7 +25,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
class WebConfiguration : WebMvcConfigurer {
override fun configureApiVersioning(configurer: ApiVersionConfigurer) {
- configurer.useRequestHeader("X-API-Version")
+ configurer.useRequestHeader("API-Version")
}
}
// end::snippet[]
\ No newline at end of file
diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.kt
index 476f717b4624..854f43ff7677 100644
--- a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.kt
+++ b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.kt
@@ -26,8 +26,8 @@ class WebConfiguration : WebMvcConfigurer {
.findAndAddModules()
.defaultUseWrapper(false)
.build()
- builder.jsonMessageConverter(JacksonJsonHttpMessageConverter(jsonMapper))
- .xmlMessageConverter(JacksonXmlHttpMessageConverter(xmlMapper))
+ builder.withJsonConverter(JacksonJsonHttpMessageConverter(jsonMapper))
+ .withXmlConverter(JacksonXmlHttpMessageConverter(xmlMapper))
}
}
// end::snippet[]
\ No newline at end of file
diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle
index 83a2031f5c64..6d2c0cbc5d96 100644
--- a/framework-platform/framework-platform.gradle
+++ b/framework-platform/framework-platform.gradle
@@ -7,37 +7,37 @@ javaPlatform {
}
dependencies {
- api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
- api(platform("io.micrometer:micrometer-bom:1.16.0-M1"))
- api(platform("io.netty:netty-bom:4.2.3.Final"))
- api(platform("io.projectreactor:reactor-bom:2025.0.0-M5"))
+ api(platform("com.fasterxml.jackson:jackson-bom:2.20.0"))
+ api(platform("io.micrometer:micrometer-bom:1.16.0-RC1"))
+ api(platform("io.netty:netty-bom:4.2.7.Final"))
+ api(platform("io.projectreactor:reactor-bom:2025.0.0"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
- api(platform("org.apache.groovy:groovy-bom:4.0.27"))
- api(platform("org.apache.logging.log4j:log4j-bom:3.0.0-beta3"))
- api(platform("org.assertj:assertj-bom:3.27.3"))
- api(platform("org.eclipse.jetty:jetty-bom:12.1.0.beta1"))
- api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.0.beta1"))
+ api(platform("org.apache.groovy:groovy-bom:5.0.2"))
+ api(platform("org.apache.logging.log4j:log4j-bom:2.25.1"))
+ api(platform("org.assertj:assertj-bom:3.27.6"))
+ api(platform("org.eclipse.jetty:jetty-bom:12.1.3"))
+ api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0"))
- api(platform("org.junit:junit-bom:5.13.3"))
- api(platform("org.mockito:mockito-bom:5.18.0"))
- api(platform("tools.jackson:jackson-bom:3.0.0-rc5"))
+ api(platform("org.junit:junit-bom:6.0.1"))
+ api(platform("org.mockito:mockito-bom:5.20.0"))
+ api(platform("tools.jackson:jackson-bom:3.0.1"))
constraints {
- api("com.fasterxml:aalto-xml:1.3.2")
+ api("com.fasterxml:aalto-xml:1.3.4")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
- api("com.github.ben-manes.caffeine:caffeine:3.2.1")
+ api("com.github.ben-manes.caffeine:caffeine:3.2.3")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
- api("com.google.code.gson:gson:2.13.1")
- api("com.google.protobuf:protobuf-java-util:4.30.2")
+ api("com.google.code.gson:gson:2.13.2")
+ api("com.google.protobuf:protobuf-java-util:4.32.1")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.networknt:json-schema-validator:1.5.3")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
api("com.rometools:rome:1.19.0")
- api("com.squareup.okhttp3:mockwebserver3:5.1.0")
+ api("com.squareup.okhttp3:mockwebserver3:5.3.0")
api("com.sun.activation:jakarta.activation:2.0.1")
api("com.sun.xml.bind:jaxb-core:3.0.2")
api("com.sun.xml.bind:jaxb-impl:3.0.2")
@@ -47,16 +47,13 @@ dependencies {
api("commons-io:commons-io:2.15.0")
api("commons-logging:commons-logging:1.3.5")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
- api("io.mockk:mockk:1.14.4")
+ api("io.mockk:mockk:1.14.5")
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
- api("io.reactivex.rxjava3:rxjava:3.1.10")
+ api("io.reactivex.rxjava3:rxjava:3.1.12")
api("io.smallrye.reactive:mutiny:1.10.0")
- api("io.undertow:undertow-core:2.3.18.Final")
- api("io.undertow:undertow-servlet:2.3.18.Final")
- api("io.undertow:undertow-websockets-jsr:2.3.18.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.1.3")
api("jakarta.annotation:jakarta.annotation-api:3.0.0")
@@ -88,22 +85,22 @@ dependencies {
api("junit:junit:4.13.2")
api("net.sf.jopt-simple:jopt-simple:5.0.4")
api("org.apache-extras.beanshell:bsh:2.0b6")
- api("org.apache.activemq:activemq-broker:5.17.6")
- api("org.apache.activemq:activemq-kahadb-store:5.17.6")
- api("org.apache.activemq:activemq-stomp:5.17.6")
- api("org.apache.activemq:artemis-jakarta-client:2.31.2")
- api("org.apache.activemq:artemis-junit-5:2.31.2")
- api("org.apache.commons:commons-pool2:2.9.0")
+ api("org.apache.activemq:activemq-broker:5.17.7")
+ api("org.apache.activemq:activemq-kahadb-store:5.17.7")
+ api("org.apache.activemq:activemq-stomp:5.17.7")
+ api("org.apache.activemq:artemis-jakarta-client:2.42.0")
+ api("org.apache.activemq:artemis-junit-5:2.42.0")
+ api("org.apache.commons:commons-pool2:2.12.1")
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.5")
- api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
+ api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.5")
api("org.apache.poi:poi-ooxml:5.2.5")
- api("org.apache.tomcat.embed:tomcat-embed-core:11.0.7")
- api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.7")
- api("org.apache.tomcat:tomcat-util:11.0.7")
- api("org.apache.tomcat:tomcat-websocket:11.0.7")
+ api("org.apache.tomcat.embed:tomcat-embed-core:11.0.13")
+ api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.13")
+ api("org.apache.tomcat:tomcat-util:11.0.13")
+ api("org.apache.tomcat:tomcat-websocket:11.0.13")
api("org.aspectj:aspectjrt:1.9.24")
api("org.aspectj:aspectjtools:1.9.24")
api("org.aspectj:aspectjweaver:1.9.24")
@@ -111,11 +108,11 @@ dependencies {
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
- api("org.dom4j:dom4j:2.1.4")
- api("org.easymock:easymock:5.5.0")
+ api("org.dom4j:dom4j:2.2.0")
+ api("org.easymock:easymock:5.6.0")
api("org.eclipse.angus:angus-mail:2.0.3")
- api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
- api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-B08")
+ api("org.eclipse.jetty:jetty-reactive-httpclient:4.1.0")
+ api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-B11")
api("org.eclipse:yasson:3.0.4")
api("org.ehcache:ehcache:3.10.8")
api("org.ehcache:jcache:1.0.1")
@@ -124,13 +121,13 @@ dependencies {
api("org.glassfish:jakarta.el:4.0.2")
api("org.graalvm.sdk:graal-sdk:22.3.1")
api("org.hamcrest:hamcrest:3.0")
- api("org.hibernate.orm:hibernate-core:7.0.5.Final")
- api("org.hibernate.validator:hibernate-validator:9.0.1.Final")
+ api("org.hibernate.orm:hibernate-core:7.2.0.CR1")
+ api("org.hibernate.validator:hibernate-validator:9.1.0.Final")
api("org.hsqldb:hsqldb:2.7.4")
- api("org.htmlunit:htmlunit:4.13.0")
+ api("org.htmlunit:htmlunit:4.18.0")
api("org.javamoney:moneta:1.4.4")
api("org.jboss.logging:jboss-logging:3.6.1.Final")
- api("org.jruby:jruby:9.4.12.0")
+ api("org.jruby:jruby:10.0.2.0")
api("org.jspecify:jspecify:1.0.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
@@ -138,14 +135,14 @@ dependencies {
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.reactivestreams:reactive-streams:1.0.4")
- api("org.seleniumhq.selenium:htmlunit3-driver:4.33.0")
- api("org.seleniumhq.selenium:selenium-java:4.34.0")
- api("org.skyscreamer:jsonassert:2.0-rc1")
+ api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
+ api("org.seleniumhq.selenium:selenium-java:4.38.0")
+ api("org.skyscreamer:jsonassert:1.5.3")
api("org.testng:testng:7.11.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-lite:1.1.0")
- api("org.xmlunit:xmlunit-assertj:2.10.3")
- api("org.xmlunit:xmlunit-matchers:2.10.3")
- api("org.yaml:snakeyaml:2.4")
+ api("org.xmlunit:xmlunit-assertj:2.10.4")
+ api("org.xmlunit:xmlunit-matchers:2.10.4")
+ api("org.yaml:snakeyaml:2.5")
}
}
diff --git a/gradle.properties b/gradle.properties
index 8d3de9c13196..e5e2aa8ff3f3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,11 +4,8 @@ org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
org.gradle.parallel=true
-kotlinVersion=2.2.0
+kotlinVersion=2.2.21
byteBuddyVersion=1.17.6
kotlin.jvm.target.validation.mode=ignore
kotlin.stdlib.default.dependency=false
-
-org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
-org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
\ No newline at end of file
diff --git a/gradle/spring-module.gradle b/gradle/spring-module.gradle
index a899900abf56..09755cdac4a0 100644
--- a/gradle/spring-module.gradle
+++ b/gradle/spring-module.gradle
@@ -78,7 +78,7 @@ javadoc {
// Change modularity mismatch from warn to info.
// See https://github.com/spring-projects/spring-framework/issues/27497
addStringOption("-link-modularity-mismatch", "info")
- // With the javadoc tool on Java 24, it appears that the 'reference'
+ // With the javadoc tool on Java 25, it appears that the 'reference'
// group is always active and the '-reference' flag is not honored.
// Thus, we do NOT fail the build on Javadoc warnings due to
// cross-module @see and @link references which are only reachable
@@ -118,4 +118,3 @@ publishing {
// Disable publication of test fixture artifacts.
components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { skip() }
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() }
-
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 1b33c55baabb..f8e1ee3125fe 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d4081da476bb..bad7c2462f5a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index 23d15a936707..adff685a0348 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
#!/bin/sh
#
-# Copyright © 2015-2021 the original authors.
+# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
- CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
- -classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
diff --git a/gradlew.bat b/gradlew.bat
index 5eed7ee84528..e509b2dd8fe5 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
-set CLASSPATH=
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java
index 0df3dc5c0f72..bf6c07c8173f 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java
@@ -39,13 +39,13 @@ class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotP
private static final String AJC_MAGIC = "ajc$";
- private static final boolean aspectjPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
+ private static final boolean ASPECTJ_PRESENT = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
AspectJAdvisorBeanRegistrationAotProcessor.class.getClassLoader());
@Override
public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
- if (aspectjPresent) {
+ if (ASPECTJ_PRESENT) {
Class> beanClass = registeredBean.getBeanClass();
if (compiledByAjc(beanClass)) {
return new AspectJAdvisorContribution(beanClass);
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJBeanFactoryInitializationAotProcessor.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJBeanFactoryInitializationAotProcessor.java
index 28c2408757a7..32ecda58883a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJBeanFactoryInitializationAotProcessor.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJBeanFactoryInitializationAotProcessor.java
@@ -41,13 +41,13 @@
*/
class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
- private static final boolean aspectJPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
+ private static final boolean ASPECTJ_PRESENT = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
@Override
public @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
- if (aspectJPresent) {
+ if (ASPECTJ_PRESENT) {
return AspectDelegate.processAheadOfTime(beanFactory);
}
return null;
diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java
index a7a9e033d1f7..3247fa213d39 100644
--- a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java
@@ -23,6 +23,8 @@
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator;
+import org.springframework.aop.framework.ProxyConfig;
+import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -96,17 +98,22 @@ public abstract class AopConfigUtils {
}
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
- if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
- BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
- definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
- }
+ defaultProxyConfig(registry).getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
- if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
- BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
- definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
+ defaultProxyConfig(registry).getPropertyValues().add("exposeProxy", Boolean.TRUE);
+ }
+
+ private static BeanDefinition defaultProxyConfig(BeanDefinitionRegistry registry) {
+ if (registry.containsBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME)) {
+ return registry.getBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME);
}
+ RootBeanDefinition beanDefinition = new RootBeanDefinition(ProxyConfig.class);
+ beanDefinition.setSource(AopConfigUtils.class);
+ beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ registry.registerBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME, beanDefinition);
+ return beanDefinition;
}
private static @Nullable BeanDefinition registerOrEscalateApcAsRequired(
@@ -115,12 +122,12 @@ public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry reg
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
- BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
- if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
- int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
+ BeanDefinition beanDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
+ if (!cls.getName().equals(beanDefinition.getBeanClassName())) {
+ int currentPriority = findPriorityForClass(beanDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
- apcDefinition.setBeanClassName(cls.getName());
+ beanDefinition.setBeanClassName(cls.getName());
}
}
return null;
@@ -128,8 +135,8 @@ public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry reg
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
- beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java
index 3b7b17052437..70f0c63122e6 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java
@@ -112,11 +112,13 @@ else if (advised.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE &&
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
- if (!proxyFactory.isProxyTargetClass()) {
+ if (!proxyFactory.isProxyTargetClass() && !proxyFactory.hasUserSuppliedInterfaces()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
+ proxyFactory.setFrozen(isFrozen());
+ proxyFactory.setPreFiltered(true);
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
@@ -187,6 +189,7 @@ protected boolean isEligible(Class> targetClass) {
protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
+ proxyFactory.setFrozen(false);
proxyFactory.setTarget(bean);
return proxyFactory;
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
index ba670ff33bd0..f91792694f06 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
@@ -70,6 +70,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
/** use serialVersionUID from Spring 2.0 for interoperability. */
private static final long serialVersionUID = 2651364800145442165L;
+ private static final Advisor[] EMPTY_ADVISOR_ARRAY = new Advisor[0];
+
/**
* Canonical TargetSource when there's no target, and behavior is
@@ -288,7 +290,7 @@ private boolean isAdvisorIntroducedInterface(Class> ifc) {
@Override
public final Advisor[] getAdvisors() {
- return this.advisors.toArray(new Advisor[0]);
+ return this.advisors.toArray(EMPTY_ADVISOR_ARRAY);
}
@Override
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
index be021f99f58b..1efed81dec82 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
@@ -98,7 +98,7 @@ class CglibAopProxy implements AopProxy, Serializable {
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
- private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
+ private static final boolean COROUTINES_REACTOR_PRESENT = ClassUtils.isPresent(
"kotlinx.coroutines.reactor.MonoKt", CglibAopProxy.class.getClassLoader());
private static final GeneratorStrategy undeclaredThrowableStrategy =
@@ -435,7 +435,7 @@ private static boolean implementsInterface(Method method, Set> ifcs) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
- if (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method)) {
+ if (COROUTINES_REACTOR_PRESENT && KotlinDetector.isSuspendingFunction(method)) {
return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
CoroutinesUtils.asFlow(returnValue) :
CoroutinesUtils.awaitSingleOrNull(returnValue, arguments[arguments.length - 1]);
@@ -694,7 +694,7 @@ public DynamicAdvisedInterceptor(AdvisedSupport advised) {
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
- if (this.advised.exposeProxy) {
+ if (this.advised.isExposeProxy()) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CoroutinesUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/CoroutinesUtils.java
index 3cb74cbdf782..f0e56cab0fa1 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/CoroutinesUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/CoroutinesUtils.java
@@ -40,7 +40,7 @@ static Object asFlow(@Nullable Object publisher) {
}
}
- @SuppressWarnings({"unchecked", "rawtypes"})
+ @SuppressWarnings({"rawtypes", "unchecked"})
static @Nullable Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
(Continuation