Compare commits

..

1 Commits

Author SHA1 Message Date
Stéphane Nicoll debd312d50 Release v6.2.3 2025-02-13 13:39:03 +01:00
802 changed files with 6146 additions and 14437 deletions
@@ -15,7 +15,7 @@ runs:
using: composite
steps:
- name: Generate Changelog
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
with:
config-file: .github/actions/create-github-release/changelog-generator.yml
milestone: ${{ inputs.milestone }}
+1 -2
View File
@@ -42,8 +42,7 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
*.iml
*.ipr
*.iws
.idea/*
!.idea/icon.svg
.idea
out
test-output
atlassian-ide-plugin.xml
-52
View File
@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 510 510" style="enable-background:new 0 0 510 510;" xml:space="preserve">
<style type="text/css">
.st0{fill:#6DB33F;}
</style>
<title>icon-framework</title>
<g id="Layer_2_1_">
<g id="Layer_1-2">
<path class="st0" d="M479.2,162.5c-5.6-34.3-20.5-64.4-43.9-87.8S382,36.5,347.6,30.9C320.1,11.2,288.5,0,255,0
s-65.1,11.2-92.6,30.9C128,36.5,98,51.3,74.7,74.7C50.9,98.4,36.5,128.7,31,162.2C11.2,189.8,0,221.4,0,255s11.2,65.2,31,92.8
c5.5,33.5,19.9,63.8,43.7,87.5c23.3,23.3,53.3,38.2,87.7,43.8C190,498.8,221.5,510,255,510s65-11.2,92.6-30.9
c34.4-5.6,64.4-20.5,87.7-43.8c23.8-23.8,38.1-54.1,43.7-87.5c19.8-27.6,31-59.2,31-92.8S498.8,190,479.2,162.5z M473,224.2
c-0.8,6-1.9,11.9-3.2,17.9c-7.2-22.4-17.1-43.8-29.4-63.8c-5.5-22.8-13.6-44.9-24.4-65.8c22.9,14.8,42.2,32.6,56.7,52.6
C475.7,184.6,475.8,204.6,473,224.2z M444.7,199.6c9.3,17.7,16.6,36.3,21.9,55.6c-5.3,19.3-12.7,37.9-22,55.6
C450.9,274,451,236.4,444.7,199.6L444.7,199.6z M175.4,341.1c6.5-4.5,13.7-7.8,21.3-9.8c32.2-7.5,46.7-9.9,66.3-18.4
c36.8-15.9,74.8-53.3,84.6-94c-16.1,38.8-59.5,70.2-97.9,81.6c-26.2,7.8-79.8,13.6-80.1,13.6l-1.4-1.2
c-30.6-17.4-27-85.8,31.8-104.1c25.7-8,55.9-0.7,83.5-5.4c29.4-5,64.4-24.5,79.9-52c12.3,46.4,26.1,118.5-9.7,160.2
C309.4,363,211.8,318,175.4,341.1z M310.8,444.6c-17.7,9.4-36.5,16.8-55.8,22.1c-19.4-5.3-38.1-12.7-55.8-22.1
C236.1,450.9,273.9,450.9,310.8,444.6z M117.6,392.4c13.1,13.1,27.3,25.1,42.3,35.8c-19-5.9-37.2-13.8-54.5-23.7
c-9.9-17.3-17.8-35.7-23.7-54.7C92.4,365.1,104.5,379.3,117.6,392.4z M81.7,160.1c5.9-19,13.9-37.4,23.7-54.7
c17.3-9.8,35.5-17.8,54.5-23.7c-15.1,10.8-29.2,22.7-42.3,35.8C104.5,130.7,92.4,144.9,81.7,160.1z M149.1,346.4
c-8.5,0.2-15.5-6.5-15.7-15c-0.2-8.5,6.5-15.5,15-15.7c8.5-0.2,15.5,6.5,15.7,15c0,0,0,0.1,0,0.1
C164.2,339.3,157.5,346.2,149.1,346.4L149.1,346.4z M199.2,65.4c17.7-9.4,36.5-16.8,55.8-22.1c19.4,5.3,38.1,12.7,55.8,22.1
C273.9,59.1,236.1,59.1,199.2,65.4L199.2,65.4z M392.4,392.4c13.1-13.1,25.1-27.3,35.9-42.4c-5.9,19-13.9,37.3-23.7,54.5
c-17.3,9.8-35.5,17.7-54.5,23.7C365.2,417.5,379.3,405.5,392.4,392.4L392.4,392.4z M392.4,117.6c-13.1-13.1-27.2-25.1-42.3-35.8
c19,5.9,37.2,13.9,54.5,23.7c9.8,17.3,17.8,35.6,23.7,54.7C417.6,144.9,405.5,130.7,392.4,117.6L392.4,117.6z M430.5,79.5
c19.4,19.3,32.6,43.6,39.3,71.1c-16.2-19.2-36.6-36.2-60.4-50c-13.8-23.8-30.8-44.2-50-60.4C387.1,46.9,411.2,60.1,430.5,79.5
L430.5,79.5z M397.6,94c-20.8-10.7-42.8-18.8-65.6-24.2c-20-12.4-41.5-22.4-63.9-29.6c15.5-3.4,31.3-5.1,47.2-5.2
c10,0,19.9,0.8,29.7,2.3C365.1,51.8,382.8,71.1,397.6,94z M255,6.8c27.6,0,53.8,8,77.4,22.2c-5.6-0.5-11.3-0.7-17.1-0.7
c-20.4,0.1-40.6,2.8-60.3,8.1c-19.7-5.3-39.9-8-60.3-8.1c-5.8,0-11.5,0.2-17.1,0.7C201.2,14.7,227.4,6.8,255,6.8z M165,37.3
c9.8-1.5,19.8-2.3,29.7-2.3c15.9,0.1,31.7,1.8,47.2,5.2C219.5,47.4,198,57.3,178,69.8c-22.7,5.4-44.8,13.6-65.6,24.2
C127.2,71.1,144.9,51.8,165,37.3z M79.5,79.5c19.3-19.3,43.5-32.6,71.1-39.3c-19.2,16.2-36.2,36.6-50,60.4
c-23.7,13.8-44,30.6-60.2,49.8C47,123.4,59.8,99.1,79.5,79.5z M37.4,164.9c14.6-20,33.8-37.7,56.6-52.4
c-10.7,20.9-18.8,43-24.3,65.8c-12.3,20-22.2,41.4-29.4,63.8C34.6,215.4,33.5,189.3,37.4,164.9z M65.4,310.4
c-9.3-17.6-16.6-36.2-21.9-55.4c5.3-19.2,12.6-37.8,21.9-55.4C59.1,236.3,59.1,273.7,65.4,310.4L65.4,310.4z M29.2,332.8
c-14.4-23.7-22.4-50-22.4-77.8s8.1-54.1,22.4-77.8c-2.1,25,0.4,51.3,7.4,77.8C29.5,281.5,27,307.8,29.2,332.8z M40.3,268
c7.2,22.3,17.1,43.7,29.4,63.7c5.5,22.8,13.6,44.9,24.3,65.8c-22.8-14.7-42-32.5-56.6-52.4C33.5,320.7,34.6,294.6,40.3,268z
M79.5,430.5c-19.6-19.6-32.5-43.9-39.1-70.9c16.1,19.1,36.5,36,60.2,49.8c13.8,23.7,30.8,44.2,50,60.4
C122.9,463.1,98.8,449.9,79.5,430.5z M112.5,416c20.8,10.7,42.8,18.8,65.5,24.2c20,12.4,41.5,22.4,63.9,29.6
c-15.5,3.4-31.3,5.1-47.2,5.1c-10,0-19.9-0.8-29.7-2.3C144.9,458.1,127.2,438.9,112.5,416L112.5,416z M255,503.2
c-27.6,0-53.8-8-77.4-22.2c5.6,0.5,11.3,0.7,17.1,0.7c20.4-0.1,40.6-2.8,60.3-8.1c19.7,5.3,39.9,8,60.3,8.1
c5.8,0,11.5-0.2,17.1-0.7C308.8,495.3,282.6,503.2,255,503.2z M345,472.7c-9.8,1.5-19.8,2.3-29.7,2.3c-15.9-0.1-31.7-1.8-47.2-5.1
c22.4-7.2,43.9-17.2,63.9-29.6c22.7-5.4,44.8-13.6,65.6-24.2C382.8,438.9,365.1,458.1,345,472.7z M430.5,430.5
c-19.3,19.3-43.5,32.6-71.1,39.3c19.2-16.2,36.2-36.6,50-60.4c23.7-13.8,44-30.6,60.2-49.8C463.1,386.6,450.1,410.9,430.5,430.5
L430.5,430.5z M472.6,345.1c-14.6,20-33.8,37.7-56.6,52.4c10.7-20.8,18.8-42.8,24.3-65.6c12.3-20,22.2-41.3,29.5-63.7
C475.4,294.7,476.5,320.8,472.6,345.1L472.6,345.1z M473.5,255.3c2.6-9.9,4.7-20,6.1-30.1c2.2-15.7,2.7-31.7,1.4-47.5
c14.2,23.6,22.2,49.8,22.2,77.4s-8.1,54.1-22.4,77.8C482.9,307.9,480.5,281.6,473.5,255.3L473.5,255.3z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

+4 -3
View File
@@ -9,7 +9,7 @@ plugins {
id 'io.github.goooler.shadow' version '8.1.8' apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'me.champeau.mrjar' version '0.1.1'
id "net.ltgt.errorprone" version "4.1.0" apply false
id "net.ltgt.errorprone" version "3.1.0" apply false
}
ext {
@@ -91,17 +91,18 @@ configure([rootProject] + javaProjects) { project ->
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.13.1/api/",
// "https://junit.org/junit5/docs/5.11.4/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
+1 -1
View File
@@ -1,2 +1,2 @@
org.gradle.caching=true
javaFormatVersion=0.0.43
javaFormatVersion=0.0.42
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.25.0");
checkstyle.setToolVersion("10.21.2");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
List<String> buildFolders = List.of("bin", "build", "out");
project.allprojects(subproject -> {
Path rootPath = project.getRootDir().toPath();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -21,8 +21,6 @@ import java.util.Map;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.testing.Test;
import org.gradle.api.tasks.testing.TestFrameworkOptions;
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
import org.gradle.testretry.TestRetryPlugin;
import org.gradle.testretry.TestRetryTaskExtension;
@@ -36,7 +34,6 @@ import org.gradle.testretry.TestRetryTaskExtension;
*
* @author Brian Clozel
* @author Andy Wilkinson
* @author Sam Brannen
*/
class TestConventions {
@@ -53,12 +50,7 @@ class TestConventions {
}
private void configureTests(Project project, Test test) {
TestFrameworkOptions existingOptions = test.getOptions();
test.useJUnitPlatform(options -> {
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
options.copyFrom(junitPlatformOptions);
}
});
test.useJUnitPlatform();
test.include("**/*Tests.class", "**/*Test.class");
test.setSystemProperties(Map.of(
"java.awt.headless", "true",
+3 -5
View File
@@ -1,6 +1,6 @@
plugins {
id 'java-platform'
id 'io.freefair.aggregate-javadoc' version '8.13.1'
id 'io.freefair.aggregate-javadoc' version '8.3'
}
description = "Spring Framework API Docs"
@@ -21,7 +21,6 @@ dependencies {
javadoc {
title = "${rootProject.description} ${version} API"
failOnError = true
options {
encoding = "UTF-8"
memberLevel = JavadocMemberLevel.PROTECTED
@@ -32,9 +31,8 @@ javadoc {
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
splitIndex = true
links(rootProject.ext.javadocLinks)
// Check for 'syntax' and 'reference' during linting.
addBooleanOption('Xdoclint:syntax,reference', true)
addBooleanOption('Werror', true) // fail build on Javadoc warnings
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
addBooleanOption('Werror', true) // fail build on Javadoc warnings
}
maxMemory = "1024m"
doFirst {
+27 -28
View File
@@ -42,34 +42,33 @@ repositories {
}
dependencies {
implementation(project(":spring-aspects"))
implementation(project(":spring-context"))
implementation(project(":spring-context-support"))
implementation(project(":spring-core-test"))
implementation(project(":spring-jdbc"))
implementation(project(":spring-jms"))
implementation(project(":spring-test"))
implementation(project(":spring-web"))
implementation(project(":spring-webflux"))
implementation(project(":spring-webmvc"))
implementation(project(":spring-websocket"))
api(project(":spring-aspects"))
api(project(":spring-context"))
api(project(":spring-context-support"))
api(project(":spring-jdbc"))
api(project(":spring-jms"))
api(project(":spring-test"))
api(project(":spring-web"))
api(project(":spring-webflux"))
api(project(":spring-webmvc"))
api(project(":spring-websocket"))
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
implementation("com.mchange:c3p0:0.9.5.5")
implementation("com.oracle.database.jdbc:ojdbc11")
implementation("io.projectreactor.netty:reactor-netty-http")
implementation("jakarta.jms:jakarta.jms-api")
implementation("jakarta.servlet:jakarta.servlet-api")
implementation("jakarta.resource:jakarta.resource-api")
implementation("jakarta.validation:jakarta.validation-api")
implementation("jakarta.websocket:jakarta.websocket-client-api")
implementation("javax.cache:cache-api")
implementation("org.apache.activemq:activemq-ra:6.1.2")
implementation("org.apache.commons:commons-dbcp2:2.11.0")
implementation("org.aspectj:aspectjweaver")
api("com.fasterxml.jackson.core:jackson-databind")
api("com.fasterxml.jackson.module:jackson-module-parameter-names")
api("com.mchange:c3p0:0.9.5.5")
api("com.oracle.database.jdbc:ojdbc11")
api("io.projectreactor.netty:reactor-netty-http")
api("jakarta.jms:jakarta.jms-api")
api("jakarta.servlet:jakarta.servlet-api")
api("jakarta.resource:jakarta.resource-api")
api("jakarta.validation:jakarta.validation-api")
api("javax.cache:cache-api")
api("org.apache.activemq:activemq-ra:6.1.2")
api("org.apache.commons:commons-dbcp2:2.11.0")
api("org.aspectj:aspectjweaver")
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
api("org.jetbrains.kotlin:kotlin-stdlib")
implementation(project(":spring-core-test"))
implementation("org.assertj:assertj-core")
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.junit.jupiter:junit-jupiter-api")
}
@@ -92,25 +92,11 @@ the repeated JNDI lookup overhead. See
{spring-framework-api}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
for details.
| `spring.locking.strict`
| Instructs Spring to enforce strict locking during bean creation, rather than the mix of
strict and lenient locking that 6.2 applies by default. See
{spring-framework-api}++/beans/factory/support/DefaultListableBeanFactory.html#STRICT_LOCKING_PROPERTY_NAME++[`DefaultListableBeanFactory`]
for details.
| `spring.objenesis.ignore`
| Instructs Spring to ignore Objenesis, not even attempting to use it. See
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
for details.
| `spring.placeholder.escapeCharacter.default`
| The default escape character for property placeholder support. If not set, `'\'` will
be used. Can be set to a custom escape character or an empty string to disable support
for an escape character. The default escape character be explicitly overridden in
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
for details.
| `spring.test.aot.processing.failOnError`
| A boolean flag that controls whether errors encountered during AOT processing in the
_Spring TestContext Framework_ should result in an exception that fails the overall process.
@@ -469,20 +469,20 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
// ...
registry.registerBeanDefinition("myClient", beanDefinition)
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
// ...
registry.registerBeanDefinition("myClient", beanDefinition)
----
======
@@ -444,9 +444,9 @@ 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:
You can also use a `@Nullable` annotation (of any kind in any package -- for example,
`javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety
support:
[tabs]
======
@@ -477,13 +477,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
@@ -9,15 +9,15 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final String catalog;
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
----
Kotlin::
@@ -37,9 +37,9 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
----
Kotlin::
@@ -56,7 +56,7 @@ And the following `application.properties` file:
[source,java,indent=0,subs="verbatim,quotes"]
----
catalog.name=MovieCatalog
catalog.name=MovieCatalog
----
In that case, the `catalog` parameter and field will be equal to the `MovieCatalog` value.
@@ -101,11 +101,8 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
Using the above configuration ensures Spring initialization failure if any `${}`
placeholder could not be resolved. It is also possible to use methods like
`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or
`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default
escape character can be changed or disabled globally by setting the
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
`setEscapeCharacter` to customize placeholders.
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
will get properties from `application.properties` and `application.yml` files.
@@ -122,15 +119,15 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final String catalog;
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
----
Kotlin::
@@ -153,16 +150,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class AppConfig {
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
----
Kotlin::
@@ -191,15 +188,15 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final String catalog;
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
----
Kotlin::
@@ -220,16 +217,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
----
Kotlin::
@@ -119,7 +119,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
----
======
@@ -310,16 +310,16 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import org.springframework.beans.factory.getBean
import org.springframework.beans.factory.getBean
// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// use configured instance
var userList = service.getUsernameList()
// use configured instance
var userList = service.getUsernameList()
----
======
@@ -513,7 +513,7 @@ the classes above:
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
<!-- optional: a custom ApplicationEventMulticaster definition -->
<!-- optional: a custom ApplicationEventMulticaster definition -->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<property name="taskExecutor" ref="..."/>
<property name="errorHandler" ref="..."/>
@@ -226,7 +226,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import org.springframework.beans.factory.getBean
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
@@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the
[[beans-factory-placeholderconfigurer]]
=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer`
=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
from a bean definition in a separate file by using the standard Java `Properties` format.
@@ -341,8 +341,8 @@ with placeholder values is defined:
The example shows properties configured from an external `Properties` file. At runtime,
a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some
properties of the `DataSource`. The values to replace are specified as placeholders of the
form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style.
properties of the DataSource. The values to replace are specified as placeholders of the
form pass:q[`${property-name}`], which follows the Ant and log4j and JSP EL style.
The actual values come from another file in the standard Java `Properties` format:
@@ -355,15 +355,11 @@ jdbc.password=root
----
Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and
the same applies for other placeholder values that match keys in the properties file. The
`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
attributes of a bean definition. Furthermore, you can customize the placeholder prefix,
suffix, default value separator, and escape character. In addition, the default escape
character can be changed or disabled globally by setting the
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
the same applies for other placeholder values that match keys in the properties file.
The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
attributes of a bean definition. Furthermore, you can customize the placeholder prefix and suffix.
With the `context` namespace, you can configure property placeholders
With the `context` namespace introduced in Spring 2.5, you can configure property placeholders
with a dedicated configuration element. You can provide one or more locations as a
comma-separated list in the `location` attribute, as the following example shows:
@@ -31,7 +31,7 @@ Java::
----
public class MyBean {
private final Log log = LogFactory.getLog(getClass());
// ...
// ...
}
----
@@ -40,8 +40,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyBean {
private val log = LogFactory.getLog(javaClass)
// ...
private val log = LogFactory.getLog(javaClass)
// ...
}
----
======
@@ -399,7 +399,7 @@ A `ConstraintViolation` on the `degrees` method parameter is adapted to a
`MessageSourceResolvable` with the following:
- Error codes `"Max.myService#addStudent.degrees"`, `"Max.degrees"`, `"Max.int"`, `"Max"`
- Message arguments "degrees" and 2 (the field name and the constraint attribute)
- Message arguments "degrees2 and 2 (the field name and the constraint attribute)
- Default message "must be less than or equal to 2"
To customize the above default message, you can add a property such as:
@@ -78,27 +78,27 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class DataSourceConfig {
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
.customizeConfigurer(H2, this::customize))
.addScript("schema.sql")
.build();
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
.customizeConfigurer(H2, this::customize))
.addScript("schema.sql")
.build();
}
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
@Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
super.configureConnectionProperties(properties, databaseName);
properties.setDriverClass(CustomDriver.class);
}
};
}
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
@Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
super.configureConnectionProperties(properties, databaseName);
properties.setDriverClass(CustomDriver.class);
}
};
}
}
----
@@ -136,7 +136,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.then();
.then();
----
Kotlin::
@@ -144,7 +144,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.await()
.await()
----
======
@@ -173,7 +173,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
.fetch().first();
.fetch().first();
----
Kotlin::
@@ -181,7 +181,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val first = client.sql("SELECT id, name FROM person")
.fetch().awaitSingle()
.fetch().awaitSingle()
----
======
@@ -194,8 +194,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().first();
.bind("fn", "Joe")
.fetch().first();
----
Kotlin::
@@ -203,8 +203,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitSingle()
.bind("fn", "Joe")
.fetch().awaitSingle()
----
======
@@ -240,8 +240,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Flux<String> names = client.sql("SELECT name FROM person")
.map(row -> row.get("name", String.class))
.all();
.map(row -> row.get("name", String.class))
.all();
----
Kotlin::
@@ -249,8 +249,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val names = client.sql("SELECT name FROM person")
.map{ row: Row -> row.get("name", String.class) }
.flow()
.map{ row: Row -> row.get("name", String.class) }
.flow()
----
======
@@ -301,8 +301,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().rowsUpdated();
.bind("fn", "Joe")
.fetch().rowsUpdated();
----
Kotlin::
@@ -310,8 +310,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
----
======
@@ -337,9 +337,9 @@ The following example shows parameter binding for a query:
[source,java]
----
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
----
@@ -369,9 +369,9 @@ Indices are zero based.
[source,java]
----
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind(0, "joe")
.bind(1, "Joe")
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind(0, "joe")
.bind(1, "Joe")
.bind(2, 34);
----
@@ -379,9 +379,9 @@ In case your application is binding to many parameters, the same can be achieved
[source,java]
----
List<?> values = List.of("joe", "Joe", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(values);
List<?> values = List.of("joe", "Joe", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(values);
----
@@ -428,7 +428,7 @@ Java::
tuples.add(new Object[] {"Ann", 50});
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
.bind("tuples", tuples);
----
Kotlin::
@@ -440,7 +440,7 @@ Kotlin::
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples)
.bind("tuples", tuples)
----
======
@@ -455,7 +455,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
.bind("ages", Arrays.asList(35, 50));
----
Kotlin::
@@ -463,7 +463,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", arrayOf(35, 50))
.bind("ages", arrayOf(35, 50))
----
======
@@ -490,9 +490,9 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
----
Kotlin::
@@ -516,10 +516,10 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"));
.filter(statement -> s.returnGeneratedValues("id"));
client.sql("SELECT id, name, state FROM table")
.filter(statement -> s.fetchSize(25));
.filter(statement -> s.fetchSize(25));
----
Kotlin::
@@ -527,10 +527,10 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.filter { statement -> s.returnGeneratedValues("id") }
client.sql("SELECT id, name, state FROM table")
.filter { statement -> s.fetchSize(25) }
.filter { statement -> s.fetchSize(25) }
----
======
@@ -55,11 +55,11 @@ a "shared objects file" source, as shown in the following example:
[source,shell,indent=0,subs="verbatim"]
----
[0.064s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file (top)
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
[0.064s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file (top)
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
----
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
@@ -30,36 +30,36 @@ Java::
+
[source,java,indent=0,subs="verbatim"]
----
RestClient defaultClient = RestClient.create();
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
RestClient defaultClient = RestClient.create();
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
----
val defaultClient = RestClient.create()
val customClient = RestClient.builder()
.requestFactory(HttpComponentsClientHttpRequestFactory())
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
val defaultClient = RestClient.create()
val customClient = RestClient.builder()
.requestFactory(HttpComponentsClientHttpRequestFactory())
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
----
======
@@ -81,20 +81,20 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
....
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
...
----
======
@@ -133,12 +133,12 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
String result = restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body(String.class); <4>
System.out.println(result); <5>
String result = restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body(String.class); <4>
System.out.println(result); <5>
----
<1> Set up a GET request
<2> Specify the URL to connect to
@@ -150,12 +150,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result= restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body<String>() <4>
println(result) <5>
val result= restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body<String>() <4>
println(result) <5>
----
<1> Set up a GET request
<2> Specify the URL to connect to
@@ -172,14 +172,14 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
ResponseEntity<String> result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity(String.class); <2>
System.out.println("Response status: " + result.getStatusCode()); <3>
System.out.println("Response headers: " + result.getHeaders()); <3>
System.out.println("Contents: " + result.getBody()); <3>
ResponseEntity<String> result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity(String.class); <2>
System.out.println("Response status: " + result.getStatusCode()); <3>
System.out.println("Response headers: " + result.getHeaders()); <3>
System.out.println("Contents: " + result.getBody()); <3>
----
<1> Set up a GET request for the specified URL
<2> Convert the response into a `ResponseEntity`
@@ -189,14 +189,14 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity<String>() <2>
println("Response status: " + result.statusCode) <3>
println("Response headers: " + result.headers) <3>
println("Contents: " + result.body) <3>
val result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity<String>() <2>
println("Response status: " + result.statusCode) <3>
println("Response headers: " + result.headers) <3>
println("Contents: " + result.body) <3>
----
<1> Set up a GET request for the specified URL
<2> Convert the response into a `ResponseEntity`
@@ -212,12 +212,12 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
int id = ...;
Pet pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body(Pet.class); <3>
int id = ...;
Pet pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body(Pet.class); <3>
----
<1> Using URI variables
<2> Set the `Accept` header to `application/json`
@@ -227,12 +227,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val id = ...
val pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body<Pet>() <3>
val id = ...
val pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body<Pet>() <3>
----
<1> Using URI variables
<2> Set the `Accept` header to `application/json`
@@ -247,13 +247,13 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Pet pet = ... <1>
ResponseEntity<Void> response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity(); <5>
Pet pet = ... <1>
ResponseEntity<Void> response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity(); <5>
----
<1> Create a `Pet` domain object
<2> Set up a POST request, and the URL to connect to
@@ -265,13 +265,13 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val pet: Pet = ... <1>
val response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity() <5>
val pet: Pet = ... <1>
val response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity() <5>
----
<1> Create a `Pet` domain object
<2> Set up a POST request, and the URL to connect to
@@ -291,13 +291,13 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
String result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
})
.body(String.class);
String result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
})
.body(String.class);
----
<1> Create a GET request for a URL that returns a 404 status code
<2> Set up a status handler for all 4xx status codes
@@ -307,12 +307,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } <3>
.body<String>()
val result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } <3>
.body<String>()
----
<1> Create a GET request for a URL that returns a 404 status code
<2> Set up a status handler for all 4xx status codes
@@ -330,18 +330,18 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Pet result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(APPLICATION_JSON)
.exchange((request, response) -> { <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <2>
}
else {
Pet pet = convertResponse(response); <3>
return pet;
}
});
Pet result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(APPLICATION_JSON)
.exchange((request, response) -> { <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <2>
}
else {
Pet pet = convertResponse(response); <3>
return pet;
}
});
----
<1> `exchange` provides the request and response
<2> Throw an exception when the response has a 4xx status code
@@ -351,17 +351,17 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange { request, response -> <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <2>
} else {
val pet: Pet = convertResponse(response) <3>
pet
}
}
val result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange { request, response -> <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <2>
} else {
val pet: Pet = convertResponse(response) <3>
pet
}
}
----
<1> `exchange` provides the request and response
<2> Throw an exception when the response has a 4xx status code
@@ -380,14 +380,15 @@ To serialize only a subset of the object properties, you can specify a {baeldung
[source,java,indent=0,subs="verbatim"]
----
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
----
==== Multipart
@@ -397,24 +398,24 @@ For example:
[source,java,indent=0,subs="verbatim"]
----
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
// send using RestClient.post or RestTemplate.postForEntity
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
// send using RestClient.post or RestTemplate.postForEntity
----
In most cases, you do not have to specify the `Content-Type` for each part.
The content type is determined automatically based on the `HttpMessageConverter` chosen to serialize it or, in the case of a `Resource`, based on the file extension.
If necessary, you can explicitly provide the `MediaType` with an `HttpEntity` wrapper.
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set to `multipart/form-data` by the `FormHttpMessageConverter`.
If the `MultiValueMap` has `String` values, the `Content-Type` defaults to `application/x-www-form-urlencoded`.
@@ -856,7 +857,7 @@ Start by creating the interface with `@HttpExchange` methods:
[source,java,indent=0,subs="verbatim,quotes"]
----
public interface RepositoryService {
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@@ -907,7 +908,7 @@ For `RestTemplate`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
public interface RepositoryService {
interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@@ -1136,11 +1137,11 @@ performed through the client:
[source,java,indent=0,subs="verbatim,quotes"]
----
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
----
For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and
@@ -215,43 +215,45 @@ For suspending functions, a `TransactionalOperator.executeAndAwait` extension is
[source,kotlin,indent=0]
----
import org.springframework.transaction.reactive.executeAndAwait
import org.springframework.transaction.reactive.executeAndAwait
class PersonRepository(private val operator: TransactionalOperator) {
class PersonRepository(private val operator: TransactionalOperator) {
suspend fun initDatabase() = operator.executeAndAwait {
insertPerson1()
insertPerson2()
}
suspend fun initDatabase() = operator.executeAndAwait {
insertPerson1()
insertPerson2()
}
private suspend fun insertPerson1() {
// INSERT SQL statement
}
private suspend fun insertPerson1() {
// INSERT SQL statement
}
private suspend fun insertPerson2() {
// INSERT SQL statement
}
}
private suspend fun insertPerson2() {
// INSERT SQL statement
}
}
----
For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
[source,kotlin,indent=0]
----
import org.springframework.transaction.reactive.transactional
import org.springframework.transaction.reactive.transactional
class PersonRepository(private val operator: TransactionalOperator) {
class PersonRepository(private val operator: TransactionalOperator) {
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
private fun findPeople(): Flow<Person> {
// SELECT SQL statement
}
private fun findPeople(): Flow<Person> {
// SELECT SQL statement
}
private suspend fun updatePerson(person: Person): Person {
// UPDATE SQL statement
}
}
private suspend fun updatePerson(person: Person): Person {
// UPDATE SQL statement
}
}
----
@@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use
instead of `@Value` annotations.
As an alternative, you can customize the property placeholder prefix by declaring the
following `PropertySourcesPlaceholderConfigurer` bean:
following configuration beans:
[source,kotlin,indent=0]
----
@@ -200,10 +200,8 @@ following `PropertySourcesPlaceholderConfigurer` bean:
}
----
You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use
the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by
declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example
shows:
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
that uses the `${...}` syntax, with configuration beans, as the following example shows:
[source,kotlin,indent=0]
----
@@ -217,9 +215,6 @@ shows:
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
----
In addition, the default escape character can be changed or disabled globally by setting
the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or
via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
[[checked-exceptions]]
@@ -301,17 +296,17 @@ for example when writing a `org.springframework.core.convert.converter.Converter
[source,kotlin,indent=0]
----
class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
// ...
}
class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
// ...
}
----
When converting any kind of objects, star projection with `*` can be used instead of `out Any`.
[source,kotlin,indent=0]
----
class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
// ...
}
class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
// ...
}
----
NOTE: Spring Framework does not leverage yet declaration-site variance type information for injecting beans,
@@ -345,14 +340,13 @@ file with a `spring.test.constructor.autowire.mode = all` property.
[source,kotlin,indent=0]
----
@SpringJUnitConfig(TestConfig::class)
@TestConstructor(autowireMode = AutowireMode.ALL)
class OrderServiceIntegrationTests(
val orderService: OrderService,
val customerService: CustomerService) {
// tests that use the injected OrderService and CustomerService
}
@SpringJUnitConfig(TestConfig::class)
@TestConstructor(autowireMode = AutowireMode.ALL)
class OrderServiceIntegrationTests(val orderService: OrderService,
val customerService: CustomerService) {
// tests that use the injected OrderService and CustomerService
}
----
@@ -374,29 +368,29 @@ The following example demonstrates `@BeforeAll` and `@AfterAll` annotations on n
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTests {
val application = Application(8181)
val client = WebClient.create("http://localhost:8181")
val application = Application(8181)
val client = WebClient.create("http://localhost:8181")
@BeforeAll
fun beforeAll() {
application.start()
}
@BeforeAll
fun beforeAll() {
application.start()
}
@Test
fun `Find all users on HTML page`() {
client.get().uri("/users")
.accept(TEXT_HTML)
.retrieve()
.bodyToMono<String>()
.test()
.expectNextMatches { it.contains("Foo") }
.verifyComplete()
}
@Test
fun `Find all users on HTML page`() {
client.get().uri("/users")
.accept(TEXT_HTML)
.retrieve()
.bodyToMono<String>()
.test()
.expectNextMatches { it.contains("Foo") }
.verifyComplete()
}
@AfterAll
fun afterAll() {
application.stop()
}
@AfterAll
fun afterAll() {
application.stop()
}
}
----
@@ -409,27 +403,26 @@ The following example shows how to do so:
[source,kotlin,indent=0]
----
class SpecificationLikeTests {
@Nested
@DisplayName("a calculator")
inner class Calculator {
class SpecificationLikeTests {
val calculator = SampleCalculator()
@Test
fun `should return the result of adding the first number to the second number`() {
val sum = calculator.sum(2, 4)
assertEquals(6, sum)
}
@Test
fun `should return the result of subtracting the second number from the first number`() {
val subtract = calculator.subtract(4, 2)
assertEquals(2, subtract)
}
}
}
@Nested
@DisplayName("a calculator")
inner class Calculator {
val calculator = SampleCalculator()
@Test
fun `should return the result of adding the first number to the second number`() {
val sum = calculator.sum(2, 4)
assertEquals(6, sum)
}
@Test
fun `should return the result of subtracting the second number from the first number`() {
val subtract = calculator.subtract(4, 2)
assertEquals(2, subtract)
}
}
}
----
@@ -1,6 +1,8 @@
[[kotlin-web]]
= Web
[[router-dsl]]
== Router DSL
@@ -14,27 +16,27 @@ These DSL let you write clean and idiomatic Kotlin code to build a `RouterFuncti
[source,kotlin,indent=0]
----
@Configuration
class RouterRouterConfiguration {
@Bean
fun mainRouter(userHandler: UserHandler) = router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
@Configuration
class RouterRouterConfiguration {
@Bean
fun mainRouter(userHandler: UserHandler) = router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
}
}
----
NOTE: This DSL is programmatic, meaning that it allows custom registration logic of beans
@@ -53,22 +55,22 @@ idiomatic Kotlin API and to allow better discoverability (no usage of static met
[source,kotlin,indent=0]
----
val mockMvc: MockMvc = ...
mockMvc.get("/person/{name}", "Lee") {
secure = true
accept = APPLICATION_JSON
headers {
contentLanguage = Locale.FRANCE
}
principal = Principal { "foo" }
}.andExpect {
status { isOk }
content { contentType(APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
content { json("""{"someBoolean": false}""", false) }
}.andDo {
print()
val mockMvc: MockMvc = ...
mockMvc.get("/person/{name}", "Lee") {
secure = true
accept = APPLICATION_JSON
headers {
contentLanguage = Locale.FRANCE
}
principal = Principal { "foo" }
}.andExpect {
status { isOk }
content { contentType(APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
content { json("""{"someBoolean": false}""", false) }
}.andDo {
print()
}
----
@@ -87,9 +89,9 @@ is possible to use such feature to render Kotlin-based templates with
`build.gradle.kts`
[source,kotlin,indent=0]
----
dependencies {
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
}
dependencies {
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
}
----
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans.
@@ -97,23 +99,23 @@ Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplat
`KotlinScriptConfiguration.kt`
[source,kotlin,indent=0]
----
@Configuration
class KotlinScriptConfiguration {
@Bean
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
engineName = "kotlin"
setScripts("scripts/render.kts")
renderFunction = "render"
isSharedEngine = false
}
@Bean
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
setPrefix("templates/")
setSuffix(".kts")
}
@Configuration
class KotlinScriptConfiguration {
@Bean
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
engineName = "kotlin"
setScripts("scripts/render.kts")
renderFunction = "render"
isSharedEngine = false
}
@Bean
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
setPrefix("templates/")
setSuffix(".kts")
}
}
----
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
@@ -125,7 +127,7 @@ project for more details.
== Kotlin multiplatform serialization
{kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The built-in support currently targets CBOR, JSON, and ProtoBuf formats.
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The builtin support currently targets CBOR, JSON, and ProtoBuf formats.
To enable it, follow {kotlin-github-org}/kotlinx.serialization#setup[those instructions] to add the related dependency and plugin.
With Spring MVC and WebFlux, both Kotlin serialization and Jackson will be configured by default if they are in the classpath since
@@ -133,3 +135,6 @@ Kotlin serialization is designed to serialize only Kotlin classes annotated with
With Spring Messaging (RSocket), make sure that neither Jackson, GSON or JSONB are in the classpath if you want automatic configuration,
if Jackson is needed configure `KotlinSerializationJsonMessageConverter` manually.
@@ -2,9 +2,8 @@
= Spring JUnit 4 Testing Annotations
The following annotations are supported only when used in conjunction with the
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner],
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules], or
xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner], xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules]
, or xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-ifprofilevalue[`@IfProfileValue`]
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-profilevaluesourceconfiguration[`@ProfileValueSourceConfiguration`]
@@ -47,21 +47,6 @@ the same bean in several test classes, make sure to name the fields consistently
creating unnecessary contexts.
====
[WARNING]
====
Using `@MockitoBean` or `@MockitoSpyBean` in conjunction with `@ContextHierarchy` can
lead to undesirable results since each `@MockitoBean` or `@MockitoSpyBean` will be
applied to all context hierarchy levels by default. To ensure that a particular
`@MockitoBean` or `@MockitoSpyBean` is applied to a single context hierarchy level, set
the `contextName` attribute to match a configured `@ContextConfiguration` name for
example, `@MockitoBean(contextName = "app-config")` or
`@MockitoSpyBean(contextName = "app-config")`.
See
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
hierarchies with bean overrides] for further details and examples.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
@@ -31,19 +31,6 @@ same bean in several tests, make sure to name the field consistently to avoid cr
unnecessary contexts.
====
[WARNING]
====
Using `@TestBean` in conjunction with `@ContextHierarchy` can lead to undesirable results
since each `@TestBean` will be applied to all context hierarchy levels by default. To
ensure that a particular `@TestBean` is applied to a single context hierarchy level, set
the `contextName` attribute to match a configured `@ContextConfiguration` name for
example, `@TestBean(contextName = "app-config")`.
See
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
hierarchies with bean overrides] for further details and examples.
====
[NOTE]
====
There are no restrictions on the visibility of `@TestBean` fields or factory methods.
@@ -26,16 +26,16 @@ Java::
@Test
void test() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk()) <1>
.andExpect(request().asyncStarted()) <2>
.andExpect(request().asyncResult("body")) <3>
.andReturn();
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk()) <1>
.andExpect(request().asyncStarted()) <2>
.andExpect(request().asyncResult("body")) <3>
.andReturn();
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
.andExpect(status().isOk()) <5>
.andExpect(content().string("body"));
}
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
.andExpect(status().isOk()) <5>
.andExpect(content().string("body"));
}
----
<1> Check response status is still unchanged
<2> Async processing must have started
@@ -166,7 +166,7 @@ following sections to make this pattern much easier to implement.
== MockMvc and WebDriver Setup
To use Selenium WebDriver with `MockMvc`, make sure that your project includes a test
dependency on `org.seleniumhq.selenium:htmlunit3-driver`.
dependency on `org.seleniumhq.selenium:selenium-htmlunit3-driver`.
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
@@ -1,26 +1,10 @@
[[spring-mvc-test-client]]
= Testing Client Applications
To test code that uses the `RestClient` or `RestTemplate`, you can use a mock web server, such as
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
server, and that means you can test with the same HTTP client that is also configured in
the same way as in production, which is important because there are often subtle
differences in the way different clients handle network I/O. Another advantage of mock
web servers is the ability to simulate specific network issues and conditions at the
transport level, in combination with the client used in production.
In addition to dedicated mock web servers, historically the Spring Framework has provided
a built-in option to test `RestClient` or `RestTemplate` through `MockRestServiceServer`.
This relies on configuring the client under test with a custom `ClientHttpRequestFactory`
backed by the mock server that is in turn set up to expect requests and send "`stub`"
responses so that you can focus on testing the code in isolation, without running a server.
TIP: `MockRestServiceServer` predates the existence of mock web servers. At present, we
recommend using mock web servers for more complete testing of the transport layer and
network conditions.
The following example shows an example of using `MockRestServiceServer`:
You can use client-side tests to test code that internally uses the `RestTemplate`. The
idea is to declare expected requests and to provide "`stub`" responses so that you can
focus on testing the code in isolation (that is, without running a server). The following
example shows how to do so:
[tabs]
======
@@ -4,7 +4,7 @@
Once the TestContext framework loads an `ApplicationContext` (or `WebApplicationContext`)
for a test, that context is cached and reused for all subsequent tests that declare the
same unique context configuration within the same test suite. To understand how caching
works, it is important to understand what is meant by "unique" and "test suite."
works, it is important to understand what is meant by "`unique`" and "`test suite.`"
An `ApplicationContext` can be uniquely identified by the combination of configuration
parameters that is used to load it. Consequently, the unique combination of configuration
@@ -15,8 +15,8 @@ framework uses the following configuration parameters to build the context cache
* `classes` (from `@ContextConfiguration`)
* `contextInitializerClasses` (from `@ContextConfiguration`)
* `contextCustomizers` (from `ContextCustomizerFactory`) this includes
`@DynamicPropertySource` methods, bean overrides (such as `@TestBean`, `@MockitoBean`,
`@MockitoSpyBean` etc.), as well as various features from Spring Boot's testing support.
`@DynamicPropertySource` methods as well as various features from Spring Boot's
testing support such as `@MockBean` and `@SpyBean`.
* `contextLoader` (from `@ContextConfiguration`)
* `parent` (from `@ContextHierarchy`)
* `activeProfiles` (from `@ActiveProfiles`)
@@ -1,5 +1,5 @@
[[testcontext-context-customizers]]
= Context Configuration with Context Customizers
= Configuration Configuration with Context Customizers
A `ContextCustomizer` is responsible for customizing the supplied
`ConfigurableApplicationContext` after bean definitions have been loaded into the context
@@ -22,19 +22,8 @@ given level in the hierarchy, the configuration resource type (that is, XML conf
files or component classes) must be consistent. Otherwise, it is perfectly acceptable to
have different levels in a context hierarchy configured using different resource types.
[NOTE]
====
If you use `@DirtiesContext` in a test whose context is configured as part of a context
hierarchy, you can use the `hierarchyMode` flag to control how the context cache is
cleared.
For further details, see the discussion of `@DirtiesContext` in
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations]
and the {spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
====
The JUnit Jupiter based examples in this section show common configuration scenarios for
integration tests that require the use of context hierarchies.
The remaining JUnit Jupiter based examples in this section show common configuration
scenarios for integration tests that require the use of context hierarchies.
**Single test class with context hierarchy**
--
@@ -240,118 +229,12 @@ Kotlin::
class ExtendedTests : BaseTests() {}
----
======
.Dirtying a context within a context hierarchy
NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a
context hierarchy, you can use the `hierarchyMode` flag to control how the context cache
is cleared. For further details, see the discussion of `@DirtiesContext` in
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations] and the
{spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
--
[[testcontext-ctx-management-ctx-hierarchies-with-bean-overrides]]
**Context hierarchies with bean overrides**
--
When `@ContextHierarchy` is used in conjunction with
xref:testing/testcontext-framework/bean-overriding.adoc[bean overrides] such as
`@TestBean`, `@MockitoBean`, or `@MockitoSpyBean`, it may be desirable or necessary to
have the override applied to a single level in the context hierarchy. To achieve that,
the bean override must specify a context name that matches a name configured via the
`name` attribute in `@ContextConfiguration`.
The following test class configures the name of the second hierarchy level to be
`"user-config"` and simultaneously specifies that the `UserService` should be wrapped in
a Mockito spy in the context named `"user-config"`. Consequently, Spring will only
attempt to create the spy in the `"user-config"` context and will not attempt to create
the spy in the parent context.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = UserConfig.class, name = "user-config")
})
class IntegrationTests {
@MockitoSpyBean(contextName = "user-config")
UserService userService;
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(classes = [AppConfig::class]),
ContextConfiguration(classes = [UserConfig::class], name = "user-config"))
class IntegrationTests {
@MockitoSpyBean(contextName = "user-config")
lateinit var userService: UserService
// ...
}
----
======
When applying bean overrides in different levels of the context hierarchy, you may need
to have all of the bean override instances injected into the test class in order to
interact with them — for example, to configure stubbing for mocks. However, `@Autowired`
will always inject a matching bean found in the lowest level of the context hierarchy.
Thus, to inject bean override instances from specific levels in the context hierarchy,
you need to annotate fields with appropriate bean override annotations and configure the
name of the context level.
The following test class configures the names of the hierarchy levels to be `"parent"`
and `"child"`. It also declares two `PropertyService` fields that are configured to
create or replace `PropertyService` beans with Mockito mocks in the respective contexts,
named `"parent"` and `"child"`. Consequently, the mock from the `"parent"` context will
be injected into the `propertyServiceInParent` field, and the mock from the `"child"`
context will be injected into the `propertyServiceInChild` field.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(classes = ParentConfig.class, name = "parent"),
@ContextConfiguration(classes = ChildConfig.class, name = "child")
})
class IntegrationTests {
@MockitoBean(contextName = "parent")
PropertyService propertyServiceInParent;
@MockitoBean(contextName = "child")
PropertyService propertyServiceInChild;
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(classes = [ParentConfig::class], name = "parent"),
ContextConfiguration(classes = [ChildConfig::class], name = "child"))
class IntegrationTests {
@MockitoBean(contextName = "parent")
lateinit var propertyServiceInParent: PropertyService
@MockitoBean(contextName = "child")
lateinit var propertyServiceInChild: PropertyService
// ...
}
----
======
--
@@ -250,7 +250,7 @@ Java::
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
})
)}
void userTest() {
// run code that uses the test schema and test data
}
@@ -18,9 +18,9 @@ Do not run tests in parallel if the tests:
* Use Spring Framework's `@DirtiesContext` support.
* Use Spring Framework's `@MockitoBean` or `@MockitoSpyBean` support.
* Use Spring Boot's `@MockBean` or `@SpyBean` support.
* Use JUnit Jupiter's `@TestMethodOrder` support or any testing framework feature that is
designed to ensure that test methods run in a particular order. Note, however, that
this does not apply if entire test classes are run in parallel.
* Use JUnit 4's `@FixMethodOrder` support or any testing framework feature
that is designed to ensure that test methods run in a particular order. Note,
however, that this does not apply if entire test classes are run in parallel.
* Change the state of shared services or systems such as a database, message broker,
filesystem, and others. This applies to both embedded and external systems.
@@ -1,9 +1,166 @@
[[testcontext-support-classes]]
= TestContext Framework Support Classes
This section describes the various classes that support the Spring TestContext Framework
in JUnit and TestNG.
This section describes the various classes that support the Spring TestContext Framework.
[[testcontext-junit4-runner]]
== Spring JUnit 4 Runner
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
runner (supported on JUnit 4.12 or higher). By annotating test classes with
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
variant, developers can implement standard JUnit 4-based unit and integration tests and
simultaneously reap the benefits of the TestContext framework, such as support for
loading application contexts, dependency injection of test instances, transactional test
method execution, and so on. If you want to use the Spring TestContext Framework with an
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
(such as the `MockitoJUnitRunner`), you can, optionally, use
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules] instead.
The following code listing shows the minimal requirements for configuring a test class to
run with the custom Spring `Runner`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
@Test
fun testMethod() {
// test logic...
}
}
----
======
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
disable the default listeners, which otherwise would require an `ApplicationContext` to
be configured through `@ContextConfiguration`.
[[testcontext-junit4-rules]]
== Spring JUnit 4 Rules
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
4 rules (supported on JUnit 4.12 or higher):
* `SpringClassRule`
* `SpringMethodRule`
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
instance-level and method-level features of the Spring TestContext Framework.
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
third-party runners (such as the `MockitoJUnitRunner`).
To support the full functionality of the TestContext framework, you must combine a
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
to declare these rules in an integration test:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {
@Rule
val springMethodRule = SpringMethodRule()
@Test
fun testMethod() {
// test logic...
}
companion object {
@ClassRule
val springClassRule = SpringClassRule()
}
}
----
======
[[testcontext-support-classes-junit4]]
== JUnit 4 Support Classes
The `org.springframework.test.context.junit4` package provides the following support
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
* `AbstractJUnit4SpringContextTests`
* `AbstractTransactionalJUnit4SpringContextTests`
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
`protected` `applicationContext` instance variable that you can use to perform explicit
bean lookups or to test the state of the context as a whole.
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
access. This class expects a `javax.sql.DataSource` bean and a
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
database. You can use such queries to confirm database state both before and after
running database-related application code, and Spring ensures that such queries run in
the scope of the same transaction as the application code. When used in conjunction with
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
TIP: These classes are a convenience for extension. If you do not want your test classes
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
classes by using `@RunWith(SpringRunner.class)` or xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules]
.
[[testcontext-junit-jupiter-extension]]
== SpringExtension for JUnit Jupiter
@@ -20,17 +177,14 @@ following features above and beyond the feature set that Spring supports for JUn
TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
for further details and examples.
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details and examples.
* Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See
the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for
further details.
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for further details.
The following code listing shows how to configure a test class to use the
`SpringExtension` in conjunction with `@ContextConfiguration`:
@@ -153,8 +307,7 @@ Kotlin::
======
See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
for further details.
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details.
[[testcontext-junit-jupiter-di]]
=== Dependency Injection with the `SpringExtension`
@@ -165,9 +318,10 @@ extension API from JUnit Jupiter, which lets Spring provide dependency injection
constructors, test methods, and test lifecycle callback methods.
Specifically, the `SpringExtension` can inject dependencies from the test's
`ApplicationContext` into test constructors and methods that are annotated with Spring's
`@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`, `@AfterAll`,
`@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and others.
`ApplicationContext` into test constructors and methods that are annotated with
Spring's `@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`,
`@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`,
and others.
[[testcontext-junit-jupiter-di-constructor]]
@@ -187,9 +341,8 @@ autowirable if one of the following conditions is met (in order of precedence).
attribute set to `ALL`.
* The default _test constructor autowire mode_ has been changed to `ALL`.
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
for details on the use of `@TestConstructor` and how to change the global _test
constructor autowire mode_.
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`] for details on the use of
`@TestConstructor` and how to change the global _test constructor autowire mode_.
WARNING: If the constructor for a test class is considered to be _autowirable_, Spring
assumes the responsibility for resolving arguments for all parameters in the constructor.
@@ -254,9 +407,8 @@ Kotlin::
Note that this feature lets test dependencies be `final` and therefore immutable.
If the `spring.test.constructor.autowire.mode` property is to `all` (see
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]),
we can omit the declaration of `@Autowired` on the constructor in the previous example,
resulting in the following.
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]), we can omit the declaration of
`@Autowired` on the constructor in the previous example, resulting in the following.
[tabs]
======
@@ -401,19 +553,17 @@ honor `@NestedTestConfiguration` semantics.
In order to allow development teams to change the default to `OVERRIDE` for example,
for compatibility with Spring Framework 5.0 through 5.2 the default mode can be changed
globally via a JVM system property or a `spring.properties` file in the root of the
classpath. See the
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
note for details.
classpath. See the xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
note for details.
Although the following "Hello World" example is very simplistic, it shows how to declare
common configuration on a top-level class that is inherited by its `@Nested` test
classes. In this particular example, only the `TestConfig` configuration class is
inherited. Each nested test class provides its own set of active profiles, resulting in a
distinct `ApplicationContext` for each nested test class (see
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details).
Consult the list of
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations]
to see which annotations can be inherited in `@Nested` test classes.
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details). Consult the list of
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations] to see
which annotations can be inherited in `@Nested` test classes.
[tabs]
======
@@ -476,174 +626,8 @@ Kotlin::
----
======
[[testcontext-junit4-support]]
== JUnit 4 Support
[[testcontext-junit4-runner]]
=== Spring JUnit 4 Runner
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
runner (supported on JUnit 4.12 or higher). By annotating test classes with
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
variant, developers can implement standard JUnit 4-based unit and integration tests and
simultaneously reap the benefits of the TestContext framework, such as support for
loading application contexts, dependency injection of test instances, transactional test
method execution, and so on. If you want to use the Spring TestContext Framework with an
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
(such as the `MockitoJUnitRunner`), you can, optionally, use
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules]
instead.
The following code listing shows the minimal requirements for configuring a test class to
run with the custom Spring `Runner`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
@Test
fun testMethod() {
// test logic...
}
}
----
======
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
disable the default listeners, which otherwise would require an `ApplicationContext` to
be configured through `@ContextConfiguration`.
[[testcontext-junit4-rules]]
=== Spring JUnit 4 Rules
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
4 rules (supported on JUnit 4.12 or higher):
* `SpringClassRule`
* `SpringMethodRule`
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
instance-level and method-level features of the Spring TestContext Framework.
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
third-party runners (such as the `MockitoJUnitRunner`).
To support the full functionality of the TestContext framework, you must combine a
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
to declare these rules in an integration test:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {
@Rule
val springMethodRule = SpringMethodRule()
@Test
fun testMethod() {
// test logic...
}
companion object {
@ClassRule
val springClassRule = SpringClassRule()
}
}
----
======
[[testcontext-support-classes-junit4]]
=== JUnit 4 Base Classes
The `org.springframework.test.context.junit4` package provides the following support
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
* `AbstractJUnit4SpringContextTests`
* `AbstractTransactionalJUnit4SpringContextTests`
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
`protected` `applicationContext` instance variable that you can use to perform explicit
bean lookups or to test the state of the context as a whole.
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
access. This class expects a `javax.sql.DataSource` bean and a
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
database. You can use such queries to confirm database state both before and after
running database-related application code, and Spring ensures that such queries run in
the scope of the same transaction as the application code. When used in conjunction with
an ORM tool, be sure to avoid
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
TIP: These classes are a convenience for extension. If you do not want your test classes
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
classes by using `@RunWith(SpringRunner.class)` or
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules].
[[testcontext-support-classes-testng]]
== TestNG Support
== TestNG Support Classes
The `org.springframework.test.context.testng` package provides the following support
classes for TestNG based test cases:
@@ -666,8 +650,7 @@ extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protec
database. You can use such queries to confirm database state both before and after
running database-related application code, and Spring ensures that such queries run in
the scope of the same transaction as the application code. When used in conjunction with
an ORM tool, be sure to avoid
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
`AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
@@ -7,17 +7,16 @@ by default, exactly in the following order:
* `ServletTestExecutionListener`: Configures Servlet API mocks for a
`WebApplicationContext`.
* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext`
annotation for "before" modes.
annotation for "`before`" modes.
* `ApplicationEventsTestExecutionListener`: Provides support for
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
* `BeanOverrideTestExecutionListener`: Provides support for
xref:testing/testcontext-framework/bean-overriding.adoc[].
* `BeanOverrideTestExecutionListener`: Provides support for xref:testing/testcontext-framework/bean-overriding.adoc[] .
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
instance.
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
Micrometer's `ObservationRegistry`.
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
"after" modes.
"`after`" modes.
* `CommonCachesTestExecutionListener`: Clears resource caches in the test's
`ApplicationContext` if necessary.
* `TransactionalTestExecutionListener`: Provides transactional test execution with
@@ -162,16 +161,15 @@ change from release to release -- for example, `SqlScriptsTestExecutionListener`
introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener`
was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
Boot and Spring Security register their own default `TestExecutionListener`
implementations by using the aforementioned
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism].
implementations by using the aforementioned xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism]
.
To avoid having to be aware of and re-declare all default listeners, you can set the
`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`.
`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the
default listeners. The merging algorithm ensures that duplicates are removed from the
list and that the resulting set of merged listeners is sorted according to the semantics
of `AnnotationAwareOrderComparator`, as described in
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
of `AnnotationAwareOrderComparator`, as described in xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
If a listener implements `Ordered` or is annotated with `@Order`, it can influence the
position in which it is merged with the defaults. Otherwise, locally declared listeners
are appended to the list of default listeners when merged.
@@ -47,7 +47,8 @@ out-of-container tests for code that depends on environment-specific properties.
The `org.springframework.mock.web` package contains a comprehensive set of Servlet API
mock objects that are useful for testing web contexts, controllers, and filters. These
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]).
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock])
or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]).
TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are
based on the Servlet 6.0 API.
@@ -396,7 +396,7 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.test.web.reactive.server.expectBody
client.get().uri("/persons/1")
.exchange()
@@ -1,6 +1,5 @@
[[webflux-view]]
= View Technologies
[.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]#
The rendering of views in Spring WebFlux is pluggable. Whether you decide to
@@ -225,9 +224,9 @@ The following table shows the templating libraries that we have tested on differ
|Scripting Library |Scripting Engine
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|===
@@ -306,34 +306,34 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Resource resource = ...
Mono<String> result = webClient
.post()
.uri("https://example.com")
.body(Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
), PartEvent.class)
.retrieve()
.bodyToMono(String.class);
Resource resource = ...
Mono<String> result = webClient
.post()
.uri("https://example.com")
.body(Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
), PartEvent.class)
.retrieve()
.bodyToMono(String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
var resource: Resource = ...
var result: Mono<String> = webClient
.post()
.uri("https://example.com")
.body(
Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
)
var resource: Resource = ...
var result: Mono<String> = webClient
.post()
.uri("https://example.com")
.body(
Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
)
.retrieve()
.bodyToMono()
)
.retrieve()
.bodyToMono()
----
======
@@ -390,29 +390,29 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
ClientHttpConnector connector =
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());
ClientHttpConnector connector =
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());
WebClient webClient = WebClient.builder().clientConnector(connector).build();
WebClient webClient = WebClient.builder().clientConnector(connector).build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build()
val httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build()
val connector = JdkClientHttpConnector(httpClient, DefaultDataBufferFactory())
val connector = JdkClientHttpConnector(httpClient, DefaultDataBufferFactory())
val webClient = WebClient.builder().clientConnector(connector).build()
val webClient = WebClient.builder().clientConnector(connector).build()
----
======
@@ -158,65 +158,65 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MultipartExchangeFilterFunction implements ExchangeFilterFunction {
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
return next.exchange(ClientRequest.from(request).body((outputMessage, context) ->
request.body().insert(new BufferingDecorator(outputMessage), context)).build()
);
} else {
return next.exchange(request);
}
}
private static final class BufferingDecorator extends ClientHttpRequestDecorator {
private BufferingDecorator(ClientHttpRequest delegate) {
super(delegate);
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return DataBufferUtils.join(body).flatMap(buffer -> {
getHeaders().setContentLength(buffer.readableByteCount());
return super.writeWith(Mono.just(buffer));
});
}
}
}
public class MultipartExchangeFilterFunction implements ExchangeFilterFunction {
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
return next.exchange(ClientRequest.from(request).body((outputMessage, context) ->
request.body().insert(new BufferingDecorator(outputMessage), context)).build()
);
} else {
return next.exchange(request);
}
}
private static final class BufferingDecorator extends ClientHttpRequestDecorator {
private BufferingDecorator(ClientHttpRequest delegate) {
super(delegate);
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return DataBufferUtils.join(body).flatMap(buffer -> {
getHeaders().setContentLength(buffer.readableByteCount());
return super.writeWith(Mono.just(buffer));
});
}
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MultipartExchangeFilterFunction : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
next.exchange(ClientRequest.from(request)
.body { message, context -> request.body().insert(BufferingDecorator(message), context) }
.build())
}
else {
next.exchange(request)
}
}
private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {
override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
return DataBufferUtils.join(body)
.flatMap {
headers.contentLength = it.readableByteCount().toLong()
super.writeWith(Mono.just(it))
}
}
}
}
class MultipartExchangeFilterFunction : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
next.exchange(ClientRequest.from(request)
.body { message, context -> request.body().insert(BufferingDecorator(message), context) }
.build())
}
else {
next.exchange(request)
}
}
private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {
override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
return DataBufferUtils.join(body)
.flatMap {
headers.contentLength = it.readableByteCount().toLong()
super.writeWith(Mono.just(it))
}
}
}
}
----
======
======
@@ -2,16 +2,9 @@
= Testing
:page-section-summary-toc: 1
To test code that uses the `WebClient`, you can use a mock web server, such as
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
server, and that means you can test with the same HTTP client that is also configured in
the same way as in production, which is important because there are often subtle
differences in the way different clients handle network I/O. Another advantage of mock
web servers is the ability to simulate specific network issues and conditions at the
transport level, in combination with the client used in production.
For example use of MockWebServer, see
To test code that uses the `WebClient`, you can use a mock web server, such as the
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see an example
of its use, check out
{spring-framework-code}/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[`WebClientIntegrationTests`]
in the Spring Framework test suite or the
https://github.com/square/okhttp/tree/master/samples/static-server[`static-server`]
@@ -207,14 +207,14 @@ Kotlin::
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
return session.receive() // <1>
return session.receive() // <1>
.doOnNext {
// ... // <2>
}
.concatMap {
// ... // <3>
}
.then() // <4>
.then() // <4>
}
}
----
@@ -268,16 +268,16 @@ Kotlin::
override fun handle(session: WebSocketSession): Mono<Void> {
val output = session.receive() // <1>
val output = session.receive() // <1>
.doOnNext {
// ...
}
.concatMap {
// ...
}
.map { session.textMessage("Echo $it") } // <2>
.map { session.textMessage("Echo $it") } // <2>
return session.send(output) // <3>
return session.send(output) // <3>
}
}
----
@@ -149,7 +149,7 @@ Java::
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
}
}
----
@@ -62,7 +62,7 @@ Java::
----
class Account {
private final String firstName;
private final String firstName;
public Account(@BindParam("first-name") String firstName) {
this.firstName = firstName;
@@ -19,7 +19,7 @@ Java::
private String name;
private FilePart file;
private MultipartFile file;
// ...
@@ -42,7 +42,7 @@ Kotlin::
----
class MyForm(
val name: String,
val file: FilePart)
val file: MultipartFile)
@Controller
class FileUploadController {
@@ -34,7 +34,11 @@ Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated oper
| `HttpHeaders`
| For returning a response with headers and no body.
| `ErrorResponse`, `ProblemDetail`
| `ErrorResponse`
| To render an RFC 9457 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
@@ -93,8 +93,8 @@ Java::
----
@ModelAttribute
public void addAccount(@RequestParam String number) {
Mono<Account> accountMono = accountRepository.findAccount(number);
model.addAttribute("account", accountMono);
Mono<Account> accountMono = accountRepository.findAccount(number);
model.addAttribute("account", accountMono);
}
@PostMapping("/accounts")
@@ -234,8 +234,8 @@ 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
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
other property sources. You can use this to, for example, parameterize a base URL based on
some external configuration.
NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support.
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
For further custom handling of method validation errors, you can extend
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
The exception contains a list of ``ParameterValidationResult``s that group validation errors
The exception contains a list of``ParameterValidationResult``s that group validation errors
by method parameter. You can either iterate over those, or provide a visitor with callback
methods by controller method parameter type:
@@ -104,21 +104,21 @@ Kotlin::
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
// ...
}
}
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
// ...
}
}
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
// ...
}
}
// ...
override fun other(result: ParameterValidationResult) {
// ...
}
}
})
----
======
@@ -782,8 +782,8 @@ Java::
----
WebClient webClient = WebClient.builder()
.codecs(configurer -> {
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();
----
@@ -794,8 +794,8 @@ Kotlin::
----
val webClient = WebClient.builder()
.codecs({ configurer ->
val decoder = CustomDecoder()
configurer.customCodecs().registerWithDefaultConfig(decoder)
val decoder = CustomDecoder()
configurer.customCodecs().registerWithDefaultConfig(decoder)
})
.build()
----
@@ -276,7 +276,7 @@ ServerResponse.async(asyncResponse);
----
======
https://html.spec.whatwg.org/multipage/server-sent-events.html[Server-Sent Events] can be provided via the
https://www.w3.org/TR/eventsource/[Server-Sent Events] can be provided via the
static `sse` method on `ServerResponse`. The builder provided by that method
allows you to send Strings, or other objects as JSON. For example:
@@ -781,7 +781,7 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
ClassPathResource index = new ClassPathResource("static/index.html");
ClassPathResource index = new ClassPathResource("static/index.html");
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
RouterFunction<ServerResponse> redirectToIndex = route()
@@ -793,7 +793,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val redirectToIndex = router {
val redirectToIndex = router {
val index = ClassPathResource("static/index.html")
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
val spaPredicate = !(path("/api/**") or path("/error") or
@@ -814,16 +814,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Resource location = new FileUrlResource("public-resources/");
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
Resource location = new FileUrlResource("public-resources/");
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val location = FileUrlResource("public-resources/")
val resources = router { resources("/resources/**", location) }
val location = FileUrlResource("public-resources/")
val resources = router { resources("/resources/**", location) }
----
======
@@ -1,7 +1,6 @@
[[mvc-view]]
= View Technologies
:page-section-summary-toc: 1
[.small]#xref:web/webflux-view.adoc[See equivalent in the Reactive stack]#
The rendering of views in Spring MVC is pluggable. Whether you decide to use
@@ -130,7 +130,6 @@ the `Configuration` object.
[[mvc-view-freemarker-forms]]
== Form Handling
[.small]#xref:web/webflux-view.adoc#webflux-view-freemarker-forms[See equivalent in the Reactive stack]#
Spring provides a tag library for use in JSPs that contains, among others, a
`<spring:bind/>` element. This element primarily lets forms display values from
@@ -13,9 +13,9 @@ templating libraries on different script engines:
|Scripting Library |Scripting Engine
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|===
@@ -47,7 +47,7 @@ through https://www.webjars.org/[WebJars].
[[mvc-view-script-integrate]]
== Script Templates
[.small]#xref:web/webflux-view.adoc#webflux-view-script-integrate[See equivalent in the Reactive stack]#
[.small]#xref:web/webflux-view.adoc#webflux-view-script[See equivalent in the Reactive stack]#
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
the script files to load, what function to call to render templates, and so on.
@@ -4,13 +4,11 @@
Spring MVC has an extensive integration with Servlet asynchronous request
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-processing[processing]:
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`],
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`], and
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-webasynctask[`WebAsyncTask`] return values
in controller methods provide support for a single asynchronous return value.
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`]
return values in controller methods provide basic support for a single asynchronous
return value.
* Controllers can xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[stream] multiple values, including
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
* Controllers can use reactive clients and return
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types] for response handling.
@@ -98,47 +96,6 @@ xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[config
[[mvc-ann-async-webasynctask]]
== `WebAsyncTask`
`WebAsyncTask` is comparable to using xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[Callable]
but allows customizing additional settings such a request timeout value, and the
`AsyncTaskExecutor` to execute the `java.util.concurrent.Callable` with instead
of the defaults set up globally for Spring MVC. Below is an example of using `WebAsyncTask`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@GetMapping("/callable")
WebAsyncTask<String> handle() {
return new WebAsyncTask<String>(20000L,()->{
Thread.sleep(10000); //simulate long-running task
return "asynchronous request completed";
});
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@GetMapping("/callable")
fun handle(): WebAsyncTask<String> {
return WebAsyncTask(20000L) {
Thread.sleep(10000) // simulate long-running task
"asynchronous request completed"
}
}
----
======
[[mvc-ann-async-processing]]
== Processing
@@ -324,7 +281,7 @@ invokes the configured exception resolvers and completes the request.
=== SSE
`SseEmitter` (a subclass of `ResponseBodyEmitter`) provides support for
https://html.spec.whatwg.org/multipage/server-sent-events.html[Server-Sent Events], where events sent from the server
https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from the server
are formatted according to the W3C SSE specification. To produce an SSE
stream from a controller, return `SseEmitter`, as the following example shows:
@@ -433,7 +390,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 `Mono` (Reactor) or `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).
@@ -37,7 +37,7 @@ already set.
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring MVC exceptions and for any
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
@@ -10,20 +10,18 @@ to any controller. Moreover, as of 5.3, `@ExceptionHandler` methods in `@Control
can be used to handle exceptions from any `@Controller` or any other handler.
`@ControllerAdvice` is meta-annotated with `@Component` and therefore can be registered as
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning].
`@RestControllerAdvice` is a shortcut annotation that combines `@ControllerAdvice`
with `@ResponseBody`, in effect simply an `@ControllerAdvice` whose exception handler
methods render to the response body.
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning]
. `@RestControllerAdvice` is meta-annotated with `@ControllerAdvice`
and `@ResponseBody`, and that means `@ExceptionHandler` methods will have their return
value rendered via response body message conversion, rather than via HTML views.
On startup, `RequestMappingHandlerMapping` and `ExceptionHandlerExceptionResolver` detect
controller advice beans and apply them at runtime. Global `@ExceptionHandler` methods,
from an `@ControllerAdvice`, are applied _after_ local ones, from the `@Controller`.
By contrast, global `@ModelAttribute` and `@InitBinder` methods are applied _before_ local ones.
By default, both `@ControllerAdvice` and `@RestControllerAdvice` apply to any controller,
including `@Controller` and `@RestController`. Use attributes of the annotation to narrow
the set of controllers and handlers that they apply to. For example:
The `@ControllerAdvice` annotation has attributes that let you narrow the set of controllers
and handlers that they apply to. For example:
[tabs]
======
@@ -177,9 +177,13 @@ the content negotiation during the error handling phase will decide which conten
be converted through `HttpMessageConverter` instances and written to the response.
See xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[ResponseEntity].
| `ErrorResponse`, `ProblemDetail`
| `ErrorResponse`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `String`
| A view name to be resolved with `ViewResolver` implementations and used together with the
@@ -97,7 +97,7 @@ Java::
----
class Account {
private final String firstName;
private final String firstName;
public Account(@BindParam("first-name") String firstName) {
this.firstName = firstName;
@@ -243,7 +243,7 @@ Kotlin::
======
If there is no `BindingResult` parameter after the `@ModelAttribute`, then
a `MethodArgumentNotValidException` is raised with the validation errors. However, if method
`MethodArgumentNotValueException` is raised with the validation errors. However, if method
validation applies because other parameters have `@jakarta.validation.Constraint` annotations,
then `HandlerMethodValidationException` is raised instead. For more details, see the section
xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
@@ -22,7 +22,11 @@ supported for all return values.
| `HttpHeaders`
| For returning a response with headers and no body.
| `ErrorResponse`, `ProblemDetail`
| `ErrorResponse`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
For further custom handling of method validation errors, you can extend
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
The exception contains a list of ``ParameterValidationResult``s that group validation errors
The exception contains a list of``ParameterValidationResult``s that group validation errors
by method parameter. You can either iterate over those, or provide a visitor with callback
methods by controller method parameter type:
@@ -73,12 +73,12 @@ Java::
@Override
public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) {
// ...
// ...
}
@Override
public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) {
// ...
// ...
}
@Override
@@ -88,7 +88,7 @@ Java::
@Override
public void other(ParameterValidationResult result) {
// ...
// ...
}
});
----
@@ -103,22 +103,22 @@ Kotlin::
ex.visitResults(object : HandlerMethodValidationException.Visitor {
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
// ...
}
// ...
}
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
// ...
}
// ...
}
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
// ...
}
// ...
}
// ...
override fun other(result: ParameterValidationResult) {
// ...
}
// ...
}
})
----
======
@@ -13,7 +13,7 @@ reference documentation, including:
* {docs-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection]
* {docs-spring-security}/features/exploits/headers.html[Security Response Headers]
https://github.com/hdiv/hdiv[HDIV] is another web security framework that integrates with Spring MVC.
https://hdiv.org/[HDIV] is another web security framework that integrates with Spring MVC.
@@ -22,11 +22,11 @@ The following example code is based on it:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
const stompClient = new StompJs.Client({
brokerURL: 'ws://domain.com/portfolio',
onConnect: () => {
// ...
}
});
brokerURL: 'ws://domain.com/portfolio',
onConnect: () => {
// ...
}
});
----
Alternatively, if you connect through SockJS, you can enable the
@@ -47,3 +47,5 @@ interactive web application] -- a getting started guide.
* https://github.com/rstoyanchev/spring-websocket-portfolio[Stock Portfolio] -- a sample
application.
@@ -38,7 +38,7 @@ to inform the server that the original port was `443`.
==== X-Forwarded-Proto
While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto[`X-Forwarded-Proto: (https|http)`]
is a de-facto standard header that is used to communicate the original protocol (for example, https / http)
is a de-facto standard header that is used to communicate the original protocol (for example, https / https)
to a downstream server. For example, if a request of `https://example.com/resource` is sent to
a proxy which forwards the request to `http://localhost:8080/resource`, then a header of
`X-Forwarded-Proto: https` can be sent to inform the server that the original protocol was `https`.
@@ -119,4 +119,4 @@ 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`.
`/api/app1` override the server prefix `/app1`.
-1
View File
@@ -5,7 +5,6 @@
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.2",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
@@ -29,7 +29,7 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory;
public class CustomHttpServiceArgumentResolver {
// tag::httpinterface[]
public interface RepositoryService {
interface RepositoryService {
@GetExchange("/repos/search")
List<Repository> searchRepository(Search search);
+29 -28
View File
@@ -7,31 +7,31 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
api(platform("io.micrometer:micrometer-bom:1.14.8"))
api(platform("io.netty:netty-bom:4.1.121.Final"))
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
api(platform("io.micrometer:micrometer-bom:1.14.4"))
api(platform("io.netty:netty-bom:4.1.118.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.7"))
api(platform("io.projectreactor:reactor-bom:2024.0.3"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.27"))
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.27.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.21"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.21"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.16"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.16"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.13.1"))
api(platform("org.mockito:mockito-bom:5.18.0"))
api(platform("org.junit:junit-bom:5.11.4"))
api(platform("org.mockito:mockito-bom:5.15.2"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.2.0")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
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.12.1")
api("com.google.protobuf:protobuf-java-util:4.30.2")
api("com.google.code.gson:gson:2.11.0")
api("com.google.protobuf:protobuf-java-util:4.29.3")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
@@ -43,10 +43,11 @@ dependencies {
api("com.sun.xml.bind:jaxb-core:3.0.2")
api("com.sun.xml.bind:jaxb-impl:3.0.2")
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.2.0")
api("com.thoughtworks.qdox:qdox:2.1.0")
api("com.thoughtworks.xstream:xstream:1.4.21")
api("commons-io:commons-io:2.15.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("io.micrometer:context-propagation:1.1.1")
api("io.mockk:mockk:1.13.4")
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
@@ -99,8 +100,8 @@ dependencies {
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.client5:httpclient5:5.4.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
@@ -109,18 +110,18 @@ dependencies {
api("org.aspectj:aspectjrt:1.9.22.1")
api("org.aspectj:aspectjtools:1.9.22.1")
api("org.aspectj:aspectjweaver:1.9.22.1")
api("org.awaitility:awaitility:4.3.0")
api("org.awaitility:awaitility:4.2.2")
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.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
api("org.easymock:easymock:5.4.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
api("org.ehcache:jcache:1.0.1")
api("org.freemarker:freemarker:2.3.34")
api("org.freemarker:freemarker:2.3.33")
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
api("org.glassfish:jakarta.el:4.0.2")
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
@@ -129,24 +130,24 @@ dependencies {
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.10.0")
api("org.htmlunit:htmlunit:4.6.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.12.0")
api("org.jruby:jruby:9.4.9.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.4")
api("org.python:jython-standalone:2.7.3")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.29.0")
api("org.seleniumhq.selenium:selenium-java:4.29.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.26.0")
api("org.seleniumhq.selenium:selenium-java:4.26.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.17")
api("org.slf4j:slf4j-api:2.0.16")
api("org.testng:testng:7.11.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.59")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.webjars:webjars-locator-core:0.55")
api("org.webjars:webjars-locator-lite:1.0.0")
api("org.xmlunit:xmlunit-assertj:2.10.0")
api("org.xmlunit:xmlunit-matchers:2.10.0")
api("org.yaml:snakeyaml:2.4")
api("org.yaml:snakeyaml:2.3")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.8
version=6.2.3
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+2 -2
View File
@@ -98,7 +98,7 @@ if (project.name == "spring-oxm") {
}
// Include project specific settings
tasks.register('eclipseSettings', Copy) {
task eclipseSettings(type: Copy) {
from rootProject.files(
'src/eclipse/org.eclipse.core.resources.prefs',
'src/eclipse/org.eclipse.jdt.core.prefs',
@@ -107,7 +107,7 @@ tasks.register('eclipseSettings', Copy) {
outputs.upToDateWhen { false }
}
tasks.register('cleanEclipseSettings', Delete) {
task cleanEclipseSettings(type: Delete) {
delete project.file('.settings/org.eclipse.core.resources.prefs')
delete project.file('.settings/org.eclipse.jdt.core.prefs')
delete project.file('.settings/org.eclipse.jdt.ui.prefs')
+27 -26
View File
@@ -69,31 +69,32 @@ normalization {
javadoc {
description = "Generates project-level javadoc for use in -javadoc jar"
failOnError = true
options {
encoding = "UTF-8"
memberLevel = JavadocMemberLevel.PROTECTED
author = true
header = project.name
use = true
links(project.ext.javadocLinks)
// Check for 'syntax' during linting. Note that the global
// 'framework-api:javadoc' task checks for 'reference' in addition
// to 'syntax'.
addBooleanOption("Xdoclint:syntax,-reference", true)
addBooleanOption('Werror', true) // fail build on Javadoc warnings
}
options.encoding = "UTF-8"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.author = true
options.header = project.name
options.use = true
options.links(project.ext.javadocLinks)
// Check for syntax during linting. 'none' doesn't seem to work in suppressing
// all linting warnings all the time (see/link references most notably).
options.addStringOption("Xdoclint:syntax", "-quiet")
// Suppress warnings due to cross-module @see and @link references.
// Note that global 'api' task does display all warnings, and
// checks for 'reference' on top of 'syntax'.
logging.captureStandardError LogLevel.INFO
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
}
tasks.register('sourcesJar', Jar) {
dependsOn classes
task sourcesJar(type: Jar, dependsOn: classes) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set("sources")
from sourceSets.main.allSource
// Don't include or exclude anything explicitly by default. See SPR-12085.
}
tasks.register('javadocJar', Jar) {
task javadocJar(type: Jar) {
archiveClassifier.set("javadoc")
from javadoc
}
@@ -113,16 +114,16 @@ components.java.withVariantsFromConfiguration(configurations.testFixturesApiElem
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() }
tasks.withType(JavaCompile).configureEach {
options.errorprone {
disableAllChecks = true
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
option("NullAway:AnnotatedPackages", "org.springframework")
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
options.errorprone {
disableAllChecks = true
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
option("NullAway:AnnotatedPackages", "org.springframework")
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
"org.springframework.asm,org.springframework.cglib,org.springframework.objenesis," +
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature")
}
}
}
tasks.compileJava {
// The check defaults to a warning, bump it up to an error for the main sources
options.errorprone.error("NullAway")
}
// The check defaults to a warning, bump it up to an error for the main sources
options.errorprone.error("NullAway")
}
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+3 -3
View File
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -205,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
Vendored
+2 -2
View File
@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@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%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
+1
View File
@@ -1,6 +1,7 @@
plugins {
id "com.gradle.develocity" version "3.19"
id "io.spring.ge.conventions" version "0.0.17"
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
}
include "spring-aop"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -241,7 +241,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
try {
int algorithmicStep = STEP_JOIN_POINT_BINDING;
while (this.numberOfRemainingUnboundArguments > 0 && algorithmicStep < STEP_FINISHED) {
while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
switch (algorithmicStep++) {
case STEP_JOIN_POINT_BINDING -> {
if (!maybeBindThisJoinPoint()) {
@@ -373,8 +373,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
if (this.returningName != null) {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates. " +
"Consider compiling with -parameters in order to make declared parameter names available.");
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
}
// We're all set... find the unbound parameter, and bind it.
@@ -486,8 +485,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
*/
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
" unbound args at this()/target()/args() binding stage, with no way to determine between them");
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
}
List<String> varNames = new ArrayList<>();
@@ -536,8 +535,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
private void maybeBindReferencePointcutParameter() {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
" unbound args at reference pointcut binding stage, with no way to determine between them");
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
+ " unbound args at reference pointcut binding stage, with no way to determine between them");
}
List<String> varNames = new ArrayList<>();
@@ -742,9 +741,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
* Simple record to hold the extracted text from a pointcut body, together
* with the number of tokens consumed in extracting it.
*/
private record PointcutBody(int numTokensConsumed, @Nullable String text) {
}
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
/**
* Thrown in response to an ambiguous binding being detected when
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -112,12 +112,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
private BeanFactory beanFactory;
@Nullable
private transient volatile ClassLoader pointcutClassLoader;
private transient ClassLoader pointcutClassLoader;
@Nullable
private transient volatile PointcutExpression pointcutExpression;
private transient PointcutExpression pointcutExpression;
private transient volatile boolean pointcutParsingFailed;
private transient boolean pointcutParsingFailed = false;
/**
@@ -197,14 +197,11 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
* Lazily build the underlying AspectJ pointcut expression.
*/
private PointcutExpression obtainPointcutExpression() {
PointcutExpression pointcutExpression = this.pointcutExpression;
if (pointcutExpression == null) {
ClassLoader pointcutClassLoader = determinePointcutClassLoader();
pointcutExpression = buildPointcutExpression(pointcutClassLoader);
this.pointcutClassLoader = pointcutClassLoader;
this.pointcutExpression = pointcutExpression;
if (this.pointcutExpression == null) {
this.pointcutClassLoader = determinePointcutClassLoader();
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
}
return pointcutExpression;
return this.pointcutExpression;
}
/**
@@ -470,24 +467,40 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
ShadowMatchKey key = new ShadowMatchKey(this, targetMethod);
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(key);
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(this, targetMethod);
if (shadowMatch == null) {
PointcutExpression pointcutExpression = obtainPointcutExpression();
synchronized (pointcutExpression) {
shadowMatch = ShadowMatchUtils.getShadowMatch(key);
if (shadowMatch != null) {
return shadowMatch;
}
PointcutExpression fallbackExpression = null;
Method methodToMatch = targetMethod;
PointcutExpression fallbackExpression = null;
Method methodToMatch = targetMethod;
try {
try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
}
catch (ReflectionWorldException ex) {
// Failed to introspect target method, probably because it has been loaded
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
try {
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
if (fallbackExpression != null) {
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
}
}
catch (ReflectionWorldException ex2) {
fallbackExpression = null;
}
}
if (targetMethod != originalMethod && (shadowMatch == null ||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
// Fall back to the plain original method in case of no resolvable match or a
// negative match on a proxy class (which doesn't carry any annotations on its
// redeclared methods), as well as for annotation pointcuts.
methodToMatch = originalMethod;
try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
}
catch (ReflectionWorldException ex) {
// Failed to introspect target method, probably because it has been loaded
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
// Could neither introspect the target class nor the proxy class ->
// let's try the original method's declaring class before we give up...
try {
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
if (fallbackExpression != null) {
@@ -498,45 +511,21 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
fallbackExpression = null;
}
}
if (targetMethod != originalMethod && (shadowMatch == null ||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
// Fall back to the plain original method in case of no resolvable match or a
// negative match on a proxy class (which doesn't carry any annotations on its
// redeclared methods), as well as for annotation pointcuts.
methodToMatch = originalMethod;
try {
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
}
catch (ReflectionWorldException ex) {
// Could neither introspect the target class nor the proxy class ->
// let's try the original method's declaring class before we give up...
try {
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
if (fallbackExpression != null) {
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
}
}
catch (ReflectionWorldException ex2) {
fallbackExpression = null;
}
}
}
}
catch (Throwable ex) {
// Possibly AspectJ 1.8.10 encountering an invalid signature
logger.debug("PointcutExpression matching rejected target method", ex);
fallbackExpression = null;
}
if (shadowMatch == null) {
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
}
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
shadowMatch = new DefensiveShadowMatch(shadowMatch,
fallbackExpression.matchesMethodExecution(methodToMatch));
}
shadowMatch = ShadowMatchUtils.setShadowMatch(key, shadowMatch);
}
catch (Throwable ex) {
// Possibly AspectJ 1.8.10 encountering an invalid signature
logger.debug("PointcutExpression matching rejected target method", ex);
fallbackExpression = null;
}
if (shadowMatch == null) {
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
}
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
shadowMatch = new DefensiveShadowMatch(shadowMatch,
fallbackExpression.matchesMethodExecution(methodToMatch));
}
shadowMatch = ShadowMatchUtils.setShadowMatch(this, targetMethod, shadowMatch);
}
return shadowMatch;
}
@@ -731,8 +720,4 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
private record ShadowMatchKey(AspectJExpressionPointcut expression, Method method) {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -16,48 +16,24 @@
package org.springframework.aop.aspectj;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.aspectj.weaver.tools.ShadowMatch;
import org.springframework.aop.support.ExpressionPointcut;
import org.springframework.lang.Nullable;
/**
* Internal {@link ShadowMatch} utilities.
*
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 6.2
*/
public abstract class ShadowMatchUtils {
private static final Map<Object, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
/**
* Find a {@link ShadowMatch} for the specified key.
* @param key the key to use
* @return the {@code ShadowMatch} to use for the specified key,
* or {@code null} if none found
*/
@Nullable
static ShadowMatch getShadowMatch(Object key) {
return shadowMatchCache.get(key);
}
/**
* Associate the {@link ShadowMatch} with the specified key.
* If an entry already exists, the given {@code shadowMatch} is ignored.
* @param key the key to use
* @param shadowMatch the shadow match to use for this key
* if none already exists
* @return the shadow match to use for the specified key
*/
static ShadowMatch setShadowMatch(Object key, ShadowMatch shadowMatch) {
ShadowMatch existing = shadowMatchCache.putIfAbsent(key, shadowMatch);
return (existing != null ? existing : shadowMatch);
}
private static final Map<Key, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
/**
* Clear the cache of computed {@link ShadowMatch} instances.
@@ -66,4 +42,34 @@ public abstract class ShadowMatchUtils {
shadowMatchCache.clear();
}
/**
* Return the {@link ShadowMatch} for the specified {@link ExpressionPointcut}
* and {@link Method} or {@code null} if none is found.
* @param expression the expression
* @param method the method
* @return the {@code ShadowMatch} to use for the specified expression and method
*/
@Nullable
static ShadowMatch getShadowMatch(ExpressionPointcut expression, Method method) {
return shadowMatchCache.get(new Key(expression, method));
}
/**
* Associate the {@link ShadowMatch} to the specified {@link ExpressionPointcut}
* and method. If an entry already exists, the given {@code shadowMatch} is
* ignored.
* @param expression the expression
* @param method the method
* @param shadowMatch the shadow match to use for this expression and method
* if none already exists
* @return the shadow match to use for the specified expression and method
*/
static ShadowMatch setShadowMatch(ExpressionPointcut expression, Method method, ShadowMatch shadowMatch) {
ShadowMatch existing = shadowMatchCache.putIfAbsent(new Key(expression, method), shadowMatch);
return (existing != null ? existing : shadowMatch);
}
private record Key(ExpressionPointcut expression, Method method) {}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -111,7 +111,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
* Create a new {@code ReflectiveAspectJAdvisorFactory}, propagating the given
* {@link BeanFactory} to the created {@link AspectJExpressionPointcut} instances,
* for bean pointcut handling as well as consistent {@link ClassLoader} resolution.
* @param beanFactory the BeanFactory to propagate (may be {@code null})
* @param beanFactory the BeanFactory to propagate (may be {@code null}}
* @since 4.3.6
* @see AspectJExpressionPointcut#setBeanFactory
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -46,8 +46,8 @@ public class AspectEntry implements ParseState.Entry {
@Override
public String toString() {
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'" :
"ref='" + this.ref + "'");
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'"
: "ref='" + this.ref + "'");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -35,7 +35,6 @@ import org.springframework.aop.AopInvocationException;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
import org.springframework.aot.AotDetector;
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
import org.springframework.cglib.core.CodeGenerationException;
import org.springframework.cglib.core.GeneratorStrategy;
@@ -206,7 +205,7 @@ class CglibAopProxy implements AopProxy, Serializable {
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
enhancer.setAttemptLoad(true);
enhancer.setStrategy(KotlinDetector.isKotlinType(proxySuperClass) ?
new ClassLoaderAwareGeneratorStrategy(classLoader) :
new ClassLoaderAwareGeneratorStrategy(classLoader, undeclaredThrowableStrategy)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2020 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.
@@ -114,10 +114,10 @@ public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator {
boolean isFactoryBean = FactoryBean.class.isAssignableFrom(beanClass);
for (String mappedName : this.beanNames) {
if (isFactoryBean) {
if (mappedName.isEmpty() || mappedName.charAt(0) != BeanFactory.FACTORY_BEAN_PREFIX_CHAR) {
if (!mappedName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
continue;
}
mappedName = mappedName.substring(1); // length of '&'
mappedName = mappedName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
if (isMatch(beanName, mappedName)) {
return true;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -198,8 +198,8 @@ public abstract class ClassFilters {
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof NegateClassFilter that &&
this.original.equals(that.original)));
return (this == other || (other instanceof NegateClassFilter that
&& this.original.equals(that.original)));
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -378,8 +378,8 @@ public abstract class MethodMatchers {
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof NegateMethodMatcher that &&
this.original.equals(that.original)));
return (this == other || (other instanceof NegateMethodMatcher that
&& this.original.equals(that.original)));
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -203,6 +203,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
itb.getSpouse();
assertThat(maaif.isMaterialized()).isTrue();
assertThat(imapa.getDeclaredPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getAge"), null)).isTrue();
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(0);
@@ -300,7 +301,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void bindingWithMultipleArgsDifferentlyOrdered() {
ManyValuedArgs target = new ManyValuedArgs();
ManyValuedArgs mva = createProxy(target, ManyValuedArgs.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
String a = "a";
int b = 12;
@@ -319,7 +320,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
NotLockable notLockableTarget = new NotLockable();
assertThat(notLockableTarget).isNotInstanceOf(Lockable.class);
NotLockable notLockable1 = createProxy(notLockableTarget, NotLockable.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
assertThat(notLockable1).isInstanceOf(Lockable.class);
Lockable lockable = (Lockable) notLockable1;
assertThat(lockable.locked()).isFalse();
@@ -328,7 +329,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
NotLockable notLockable2Target = new NotLockable();
NotLockable notLockable2 = createProxy(notLockable2Target, NotLockable.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
assertThat(notLockable2).isInstanceOf(Lockable.class);
Lockable lockable2 = (Lockable) notLockable2;
assertThat(lockable2.locked()).isFalse();
@@ -342,19 +343,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void introductionAdvisorExcludedFromTargetImplementingInterface() {
assertThat(AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(), "someBean")),
aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class)).isEmpty();
assertThat(AopUtils.findAdvisorsThatCanApply(getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
}
@Test
void introductionOnTargetImplementingInterface() {
CannotBeUnlocked target = new CannotBeUnlocked();
Lockable proxy = createProxy(target, CannotBeUnlocked.class,
// Ensure that we exclude
AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class));
assertThat(proxy).isInstanceOf(Lockable.class);
Lockable lockable = proxy;
assertThat(lockable.locked()).as("Already locked").isTrue();
@@ -368,8 +370,8 @@ abstract class AbstractAspectJAdvisorFactoryTests {
ArrayList<Object> target = new ArrayList<>();
List<?> proxy = createProxy(target, List.class,
AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
List.class));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
List.class));
assertThat(proxy).as("Type pattern must have excluded mixin").isNotInstanceOf(Lockable.class);
}
@@ -377,7 +379,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void introductionBasedOnAnnotationMatch() { // gh-9980
AnnotatedTarget target = new AnnotatedTargetImpl();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
Object proxy = createProxy(target, AnnotatedTarget.class, advisors);
assertThat(proxy).isInstanceOf(Lockable.class);
Lockable lockable = (Lockable) proxy;
@@ -391,9 +393,9 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
advisors.addAll(getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(), "someBean")));
aspectInstanceFactory(new MakeLockable(), "someBean")));
Modifiable modifiable = (Modifiable) createProxy(target, ITestBean.class, advisors);
assertThat(modifiable).isInstanceOf(Modifiable.class);
@@ -424,7 +426,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
UnsupportedOperationException expectedException = new UnsupportedOperationException();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
assertThat(advisors).as("One advice method was found").hasSize(1);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(itb::getAge);
@@ -437,12 +439,12 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
RemoteException expectedException = new RemoteException();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
assertThat(advisors).as("One advice method was found").hasSize(1);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
assertThatExceptionOfType(UndeclaredThrowableException.class)
.isThrownBy(itb::getAge)
.withCause(expectedException);
.isThrownBy(itb::getAge)
.withCause(expectedException);
}
@Test
@@ -450,7 +452,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
TwoAdviceAspect twoAdviceAspect = new TwoAdviceAspect();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(twoAdviceAspect, "someBean"));
aspectInstanceFactory(twoAdviceAspect, "someBean"));
assertThat(advisors).as("Two advice methods found").hasSize(2);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
itb.setName("");
@@ -464,7 +466,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void afterAdviceTypes() throws Exception {
InvocationTrackingAspect aspect = new InvocationTrackingAspect();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
Echo echo = createProxy(new Echo(), Echo.class, advisors);
assertThat(aspect.invocations).isEmpty();
@@ -473,7 +475,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
aspect.invocations.clear();
assertThatExceptionOfType(FileNotFoundException.class)
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
}
@@ -485,6 +487,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
assertThat(Modifier.isAbstract(aspect.getClass().getSuperclass().getModifiers())).isFalse();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspect, "incrementingAspect"));
ITestBean proxy = createProxy(new TestBean("Jane", 42), ITestBean.class, advisors);
assertThat(proxy.getAge()).isEqualTo(86); // (42 + 1) * 2
}
@@ -809,20 +812,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
invocations.add("before");
}
@After("echo()")
void after() {
invocations.add("after");
}
@AfterReturning(pointcut = "this(target) && execution(* echo(*))", returning = "returnValue")
void afterReturning(JoinPoint joinPoint, Echo target, Object returnValue) {
@AfterReturning("echo()")
void afterReturning() {
invocations.add("after returning");
}
@AfterThrowing(pointcut = "this(target) && execution(* echo(*))", throwing = "exception")
void afterThrowing(JoinPoint joinPoint, Echo target, Throwable exception) {
@AfterThrowing("echo()")
void afterThrowing() {
invocations.add("after throwing");
}
@After("echo()")
void after() {
invocations.add("after");
}
}
@@ -964,7 +967,7 @@ abstract class AbstractMakeModifiable {
class MakeITestBeanModifiable extends AbstractMakeModifiable {
@DeclareParents(value = "org.springframework.beans.testfixture.beans.ITestBean+",
defaultImpl = ModifiableImpl.class)
defaultImpl=ModifiableImpl.class)
static MutableModifiable mixin;
}
@@ -27,12 +27,13 @@ import org.mockito.ArgumentCaptor;
import org.springframework.core.task.AsyncTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AsyncExecutionInterceptor}.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -24,7 +24,7 @@ import java.util.Map;
import org.springframework.lang.Nullable;
/**
* Abstract superclass for counting advice, etc.
* Abstract superclass for counting advices etc.
*
* @author Rod Johnson
* @author Chris Beams
@@ -62,7 +62,7 @@ public class MethodCounter implements Serializable {
*/
@Override
public boolean equals(@Nullable Object other) {
return (other != null && getClass() == other.getClass());
return (other != null && other.getClass() == this.getClass());
}
@Override
@@ -291,7 +291,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
String lastKey = tokens.keys[tokens.keys.length - 1];
if (propValue.getClass().isArray()) {
Class<?> componentType = propValue.getClass().componentType();
Class<?> requiredType = propValue.getClass().componentType();
int arrayIndex = Integer.parseInt(lastKey);
Object oldValue = null;
try {
@@ -299,9 +299,10 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
oldValue = Array.get(propValue, arrayIndex);
}
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
componentType, ph.nested(tokens.keys.length));
requiredType, ph.nested(tokens.keys.length));
int length = Array.getLength(propValue);
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
Class<?> componentType = propValue.getClass().componentType();
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
System.arraycopy(propValue, 0, newArray, 0, length);
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -33,7 +33,6 @@ import java.util.Map;
import java.util.Set;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.reflect.KClass;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
@@ -670,9 +669,7 @@ public abstract class BeanUtils {
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
int parameterCount = (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasDefaultConstructorMarker(ctor) ?
ctor.getParameterCount() - 1 : ctor.getParameterCount());
Assert.state(paramNames.length == parameterCount,
Assert.state(paramNames.length == ctor.getParameterCount(),
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
return paramNames;
}
@@ -942,11 +939,6 @@ public abstract class BeanUtils {
}
return kotlinConstructor.callBy(argParameters);
}
public static boolean hasDefaultConstructorMarker(Constructor<?> ctor) {
int parameterCount = ctor.getParameterCount();
return parameterCount > 0 && ctor.getParameters()[parameterCount -1].getType() == DefaultConstructorMarker.class;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2018 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.
@@ -25,7 +25,6 @@ import org.springframework.lang.Nullable;
* analogous to an InvocationTargetException.
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
@SuppressWarnings("serial")
public class MethodInvocationException extends PropertyAccessException {
@@ -42,9 +41,7 @@ public class MethodInvocationException extends PropertyAccessException {
* @param cause the Throwable raised by the invoked method
*/
public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, @Nullable Throwable cause) {
super(propertyChangeEvent,
"Property '" + propertyChangeEvent.getPropertyName() + "' threw exception: " + cause,
cause);
super(propertyChangeEvent, "Property '" + propertyChangeEvent.getPropertyName() + "' threw exception", cause);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2023 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.
@@ -124,16 +124,9 @@ public interface BeanFactory {
* beans <i>created</i> by the FactoryBean. For example, if the bean named
* {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
* will return the factory, not the instance returned by the factory.
* @see #FACTORY_BEAN_PREFIX_CHAR
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* Character variant of {@link #FACTORY_BEAN_PREFIX}.
* @since 6.2.6
*/
char FACTORY_BEAN_PREFIX_CHAR = '&';
/**
* Return an instance, which may be shared or independent, of the specified bean.
@@ -72,7 +72,7 @@ public abstract class BeanFactoryUtils {
* @see BeanFactory#FACTORY_BEAN_PREFIX
*/
public static boolean isFactoryDereference(@Nullable String name) {
return (name != null && !name.isEmpty() && name.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR);
return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
}
/**
@@ -84,14 +84,14 @@ public abstract class BeanFactoryUtils {
*/
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
if (name.isEmpty() || name.charAt(0) != BeanFactory.FACTORY_BEAN_PREFIX_CHAR) {
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
return name;
}
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
beanName = beanName.substring(1); // length of '&'
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
while (beanName.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR);
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanName;
});
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -145,6 +145,8 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -174,6 +176,8 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>Bean names returned by this method should always return bean names <i>in the
* order of definition</i> in the backend configuration, as far as possible.
* @param type the generically typed class or interface to match
@@ -206,6 +210,8 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -233,6 +239,8 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>Bean names returned by this method should always return bean names <i>in the
* order of definition</i> in the backend configuration, as far as possible.
* @param type the class or interface to match, or {@code null} for all bean names
@@ -257,24 +265,21 @@ public interface ListableBeanFactory extends BeanFactory {
* subclasses), judging from either bean definitions or the value of
* {@code getObjectType} in the case of FactoryBeans.
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
* check nested beans which might match the specified type as well. Also, it
* <b>suppresses exceptions for beans that are currently in creation in a circular
* reference scenario:</b> typically, references back to the caller of this method.
* check nested beans which might match the specified type as well.
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
* will get initialized. If the object created by the FactoryBean doesn't match,
* the raw FactoryBean itself will be matched against the type.
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of getBeansOfType matches all kinds of beans, be it
* singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeansOfType(type, true, true)}.
* <p>The Map returned by this method should always return bean names and
* corresponding bean instances <i>in the order of definition</i> in the
* backend configuration, as far as possible.
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
* calls for specific bean names in preference to this Map-based retrieval method.</b>
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
* @param type the class or interface to match, or {@code null} for all concrete beans
* @return a Map with the matching beans, containing the bean names as
* keys and the corresponding bean instances as values
@@ -290,9 +295,7 @@ public interface ListableBeanFactory extends BeanFactory {
* subclasses), judging from either bean definitions or the value of
* {@code getObjectType} in the case of FactoryBeans.
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
* check nested beans which might match the specified type as well. Also, it
* <b>suppresses exceptions for beans that are currently in creation in a circular
* reference scenario:</b> typically, references back to the caller of this method.
* check nested beans which might match the specified type as well.
* <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
* which means that FactoryBeans will get initialized. If the object created by the
* FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
@@ -301,12 +304,11 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>The Map returned by this method should always return bean names and
* corresponding bean instances <i>in the order of definition</i> in the
* backend configuration, as far as possible.
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
* calls for specific bean names in preference to this Map-based retrieval method.</b>
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
* @param type the class or interface to match, or {@code null} for all concrete beans
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
@@ -46,21 +46,6 @@ import org.springframework.lang.Nullable;
* Alternatively, you may implement the specific methods that your callers expect,
* for example, just {@link #getObject()} or {@link #getIfAvailable()}.
*
* <p>Note that {@link #getObject()} never returns {@code null} - it will throw a
* {@link NoSuchBeanDefinitionException} instead -, whereas {@link #getIfAvailable()}
* will return {@code null} if no matching bean is present at all. However, both
* methods will throw a {@link NoUniqueBeanDefinitionException} if more than one
* matching bean is found without a clear unique winner (see below). Last but not
* least, {@link #getIfUnique()} will return {@code null} both when no matching bean
* is found and when more than one matching bean is found without a unique winner.
*
* <p>Uniqueness is generally up to the container's candidate resolution algorithm
* but always honors the "primary" flag (with only one of the candidate beans marked
* as primary) and the "fallback" flag (with only one of the candidate beans not
* marked as fallback). The default-candidate flag is consistently taken into
* account as well, even for non-annotation-based injection points, with a single
* default candidate winning in case of no clear primary/fallback indication.
*
* @author Juergen Hoeller
* @since 4.3
* @param <T> the object type
@@ -203,7 +188,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* if unique (not called otherwise)
* @throws BeansException in case of creation errors
* @since 5.0
* @see #getIfUnique()
* @see #getIfAvailable()
*/
default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
T dependency = getIfUnique();
@@ -274,7 +259,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* @see #orderedStream(Predicate)
*/
default Stream<T> stream(Predicate<Class<?>> customFilter) {
return stream(customFilter, true);
return stream().filter(obj -> customFilter.test(obj.getClass()));
}
/**
@@ -288,44 +273,6 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* @see #stream(Predicate)
*/
default Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
return orderedStream(customFilter, true);
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* without specific ordering guarantees (but typically in registration order).
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @since 6.2.5
* @see #stream(Predicate)
* @see #orderedStream(Predicate, boolean)
*/
default Stream<T> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
if (!includeNonSingletons) {
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
}
return stream().filter(obj -> customFilter.test(obj.getClass()));
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* pre-ordered according to the factory's common order comparator.
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @since 6.2.5
* @see #orderedStream()
* @see #stream(Predicate)
*/
default Stream<T> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
if (!includeNonSingletons) {
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
}
return orderedStream().filter(obj -> customFilter.test(obj.getClass()));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2022 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.
@@ -212,9 +212,9 @@ public final class AotServices<T> implements Iterable<T> {
}
private <T> Map<String, T> loadBeans(Class<T> type) {
return (this.beanFactory != null ?
BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type, true, false) :
Collections.emptyMap());
return (this.beanFactory != null) ? BeanFactoryUtils
.beansOfTypeIncludingAncestors(this.beanFactory, type, true, false)
: Collections.emptyMap();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* 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.
@@ -68,10 +68,10 @@ public class AutowiredArgumentsCodeGenerator {
for (int i = startIndex; i < parameterTypes.length; i++) {
code.add(i > startIndex ? ", " : "");
if (!ambiguous) {
code.add("$L.get($L)", variableName, i);
code.add("$L.get($L)", variableName, i - startIndex);
}
else {
code.add("$L.get($L, $T.class)", variableName, i, parameterTypes[i]);
code.add("$L.get($L, $T.class)", variableName, i - startIndex, parameterTypes[i]);
}
}
return code.build();

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