Compare commits

..
1 Commits
Author SHA1 Message Date
Brian Clozel fe00848be2 Release v7.0.0 2025-11-13 11:06:28 +01:00
324 changed files with 1889 additions and 5003 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ bin
.springBeans
spring-*/src/main/java/META-INF/MANIFEST.MF
# IntelliJ IDEA artifacts and output dirs
# IDEA artifacts and output dirs
*.iml
*.ipr
*.iws
+1 -1
View File
@@ -120,7 +120,7 @@ source code into your IDE.
The wiki pages
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
define the source file coding standards we use along with some IntelliJ editor settings we customize.
define the source file coding standards we use along with some IDEA editor settings we customize.
### Reference Docs
@@ -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("12.2.0");
checkstyle.setToolVersion("12.1.2");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
@@ -18,7 +18,6 @@ package org.springframework.build.multirelease;
import javax.inject.Inject;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
@@ -65,12 +64,10 @@ public class MultiReleaseJarPlugin implements Plugin<Project> {
dependencies,
objects);
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
});
tasks.named("check", task -> task.dependsOn(validateJarTask));
}
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
});
tasks.named("check", task -> task.dependsOn(validateJarTask));
}
}
@@ -29,8 +29,6 @@ import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
@@ -45,13 +43,10 @@ public class MultiReleaseJarPluginTests {
private File buildFile;
private File propertiesFile;
@BeforeEach
void setup(@TempDir File projectDir) {
this.projectDir = projectDir;
this.buildFile = new File(this.projectDir, "build.gradle");
this.propertiesFile = new File(this.projectDir, "gradle.properties");
}
@Test
@@ -124,7 +119,6 @@ public class MultiReleaseJarPluginTests {
}
@Test
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
void validateJar() throws IOException {
writeBuildFile("""
plugins {
@@ -137,9 +131,6 @@ public class MultiReleaseJarPluginTests {
}
multiRelease { releaseVersions 17 }
""");
writeGradleProperties("""
org.gradle.jvmargs=-Duser.language=en
""");
writeClass("src/main/java17", "Main.java", """
public class Main {
@@ -161,12 +152,6 @@ public class MultiReleaseJarPluginTests {
}
}
private void writeGradleProperties(String properties) throws IOException {
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
out.print(properties);
}
}
private void writeClass(String path, String fileName, String fileContent) throws IOException {
Path folder = this.projectDir.toPath().resolve(path);
Files.createDirectories(folder);
+2 -4
View File
@@ -13,9 +13,7 @@ content:
- url: https://github.com/spring-projects/spring-framework
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
branches: ['main', '{6..9}.+({1..9}).x']
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
start_path: framework-docs
asciidoc:
@@ -38,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.19/ui-bundle.zip
@@ -50,8 +50,9 @@ XML configuration:
The preceding XML is more succinct. However, typos are discovered at runtime rather than
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
completion when you create bean definitions. Such IDE assistance is highly recommended.
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
that supports automatic property completion when you create bean definitions. Such IDE
assistance is highly recommended.
You can also configure a `java.util.Properties` instance, as follows:
@@ -338,11 +338,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
modularity, but determining exactly where the autowired bean definitions are declared is
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
explicit in the code, and this may be just fine. Note that the
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
showing how everything is wired, which may be all you need. Also, your Java IDE can
easily find all declarations and uses of the `AccountRepository` type and quickly show
you the location of `@Bean` methods that return that type.
explicit in the code, and this may be just fine. Remember that the
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
can render graphs showing how everything is wired, which may be all you need. Also,
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
and quickly show you the location of `@Bean` methods that return that type.
In cases where this ambiguity is not acceptable and you wish to have direct navigation
from within your IDE from one `@Configuration` class to another, consider autowiring the
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
Language] but offers additional features, most notably method invocation and basic string
templating functionality.
While there are several other Java expression languages available -- OGNL, MVEL, and
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
Spring community with a single well supported expression language that can be used across
all the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements for
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
is based on a technology-agnostic API that lets other expression language implementations
be integrated, should the need arise.
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
EL, to name a few -- the Spring Expression Language was created to provide the Spring
community with a single well supported expression language that can be used across all
the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
That said, SpEL is based on a technology-agnostic API that lets other expression language
implementations be integrated, should the need arise.
While SpEL serves as the foundation for expression evaluation within the Spring
portfolio, it is not directly tied to Spring and can be used independently. To
@@ -319,17 +319,9 @@ progresses.
== Testing
This section addresses testing with the combination of Kotlin and Spring Framework.
The recommended testing framework is https://junit.org/[JUnit] along with
The recommended testing framework is https://junit.org/junit5/[JUnit] along with
https://mockk.io/[Mockk] for mocking.
[TIP]
====
Kotlin lets you specify meaningful test function names between backticks (```).
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
in this section.
====
NOTE: If you are using Spring Boot, see
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
@@ -360,6 +352,7 @@ file with a `spring.test.constructor.autowire.mode = all` property.
[[per_class-lifecycle]]
=== `PER_CLASS` Lifecycle
Kotlin lets you specify meaningful test function names between backticks (```).
With JUnit Jupiter, Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
@@ -237,8 +237,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
RSocketStrategies strategies = RSocketStrategies.builder()
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
.build();
RSocketRequester requester = RSocketRequester.builder()
@@ -251,8 +251,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val strategies = RSocketStrategies.builder()
.encoders { it.add(JacksonCborEncoder()) }
.decoders { it.add(JacksonCborDecoder()) }
.encoders { it.add(Jackson2CborEncoder()) }
.decoders { it.add(Jackson2CborDecoder()) }
.build()
val requester = RSocketRequester.builder()
@@ -681,8 +681,8 @@ Java::
@Bean
public RSocketStrategies rsocketStrategies() {
return RSocketStrategies.builder()
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
.routeMatcher(new PathPatternRouteMatcher())
.build();
}
@@ -703,8 +703,8 @@ Kotlin::
@Bean
fun rsocketStrategies() = RSocketStrategies.builder()
.encoders { it.add(JacksonCborEncoder()) }
.decoders { it.add(JacksonCborDecoder()) }
.encoders { it.add(Jackson2CborEncoder()) }
.decoders { it.add(Jackson2CborDecoder()) }
.routeMatcher(PathPatternRouteMatcher())
.build()
}
@@ -187,7 +187,7 @@ default mode may be set via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
The default mode may also be configured as a
https://docs.junit.org/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
If the `spring.test.constructor.autowire.mode` property is not set, test class
constructors will not be automatically autowired.
@@ -2,12 +2,11 @@
= Meta-Annotation Support for Testing
You can use most test-related annotations as
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to
create custom composed annotations and reduce configuration duplication across a test
suite.
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to create custom composed
annotations and reduce configuration duplication across a test suite.
For example, you can use each of the following as a meta-annotation in conjunction with
the xref:testing/testcontext-framework.adoc[TestContext framework].
You can use each of the following as a meta-annotation in conjunction with the
xref:testing/testcontext-framework.adoc[TestContext framework].
* `@BootstrapWith`
* `@ContextConfiguration`
@@ -38,7 +37,111 @@ the xref:testing/testcontext-framework.adoc[TestContext framework].
* `@EnabledIf` _(only supported on JUnit Jupiter)_
* `@DisabledIf` _(only supported on JUnit Jupiter)_
Consider the following test classes that use the `SpringExtension` with JUnit Jupiter:
Consider the following example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
----
======
If we discover that we are repeating the preceding configuration across our JUnit 4-based
test suite, we can reduce the duplication by introducing a custom composed annotation
that centralizes the common test configuration for Spring, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
----
======
Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the
configuration of individual JUnit 4 based test classes, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class OrderRepositoryTests
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class UserRepositoryTests
----
======
If we write tests that use JUnit Jupiter, we can reduce code duplication even further,
since annotations in JUnit Jupiter can also be used as meta-annotations. Consider the
following example:
[tabs]
======
@@ -47,13 +150,13 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
@@ -64,22 +167,23 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
----
======
If we discover that we are repeating the preceding configuration across our test suite,
we can reduce the duplication by introducing a custom composed annotation that
centralizes the common test configuration for Spring and JUnit Jupiter, as follows:
If we discover that we are repeating the preceding configuration across our JUnit
Jupiter-based test suite, we can reduce the duplication by introducing a custom composed
annotation that centralizes the common test configuration for Spring and JUnit Jupiter,
as follows:
[tabs]
======
@@ -90,7 +194,7 @@ Java::
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
@@ -103,7 +207,7 @@ Kotlin::
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
@@ -5,25 +5,24 @@ It is important to be able to perform some integration testing without requiring
deployment to your application server or connecting to other enterprise infrastructure.
Doing so lets you test things such as:
* The correct wiring of your Spring components.
* Data access using JDBC or an ORM tool.
** This can include such things as the correctness of SQL statements, Hibernate queries,
JPA entity mappings, and so forth.
* The correct wiring of your Spring IoC container contexts.
* Data access using JDBC or an ORM tool. This can include such things as the correctness
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
The Spring Framework provides first-class support for integration testing in the
`spring-test` module. The name of the actual JAR file might include the release version,
depending on where you get it from (see the
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
for details). This library includes the `org.springframework.test` package, which
`spring-test` module. The name of the actual JAR file might include the release version
and might also be in the long `org.springframework.test` form, depending on where you get
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
for an explanation). This library includes the `org.springframework.test` package, which
contains valuable classes for integration testing with a Spring container. This testing
does not rely on an application server or other deployment environment. Such tests are
slower to run than unit tests but much faster than the equivalent Selenium tests or
remote tests that rely on deployment to an application server.
Unit and integration testing support is provided in the form of the annotation-driven
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
framework is agnostic of the actual testing framework in use, which allows
instrumentation of tests in various environments, including JUnit, TestNG, and others.
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
agnostic of the actual testing framework in use, which allows instrumentation of tests
in various environments, including JUnit, TestNG, and others.
The following section provides an overview of the high-level goals of Spring's
integration support, and the rest of this chapter then focuses on dedicated topics:
@@ -184,10 +184,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
standaloneSetup(SimpleController())
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -18,7 +18,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
mockMvc = standaloneSetup(PersonController()).addFilters<StandaloneMockMvcBuilder>(CharacterEncodingFilter()).build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -159,18 +159,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyWebTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup() {
mockMvc = standaloneSetup(AccountController())
.defaultRequest<StandaloneMockMvcBuilder>(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build()
}
}
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -25,13 +25,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// static import of MockMvcBuilders.standaloneSetup
val mockMvc = standaloneSetup(MusicController())
.defaultRequest<StandaloneMockMvcBuilder>(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -59,13 +53,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// static import of SharedHttpSessionConfigurer.sharedHttpSession
val mockMvc = MockMvcBuilders.standaloneSetup(TestController())
.apply<StandaloneMockMvcBuilder>(sharedHttpSession())
.build()
// Use mockMvc to perform requests...
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -8,9 +8,9 @@ use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
== Why Geb and MockMvc?
Geb is backed by WebDriver, so it offers many of the
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[same benefits]
that we get from WebDriver. However, Geb makes things even easier by taking care of some
of the boilerplate code for us.
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[same benefits] that we get from
WebDriver. However, Geb makes things even easier by taking care of some of the
boilerplate code for us.
[[mockmvc-server-htmlunit-geb-setup]]
== MockMvc and Geb Setup
@@ -28,8 +28,7 @@ def setup() {
----
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
This ensures that any URL referencing `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
@@ -63,10 +62,10 @@ forwarded to the current page object. This removes a lot of the boilerplate code
needed when using WebDriver directly.
As with direct WebDriver usage, this improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
by using the Page Object Pattern. As mentioned previously, we can use the Page Object
Pattern with HtmlUnit and WebDriver, but it is even easier with Geb. Consider our new
Groovy-based `CreateMessagePage` implementation:
xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] by using the Page Object
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
`CreateMessagePage` implementation:
[source,groovy]
----
@@ -7,7 +7,8 @@ to use the raw HtmlUnit libraries.
[[mockmvc-server-htmlunit-mah-setup]]
== MockMvc and HtmlUnit Setup
First, make sure that you have included a test dependency on `org.htmlunit:htmlunit`.
First, make sure that you have included a test dependency on
`org.htmlunit:htmlunit`.
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
`MockMvcWebClientBuilder`, as follows:
@@ -44,7 +45,7 @@ Kotlin::
======
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
This ensures that any URL that references `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
@@ -76,7 +77,7 @@ Kotlin::
======
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
to create a message, as the following example shows:
@@ -143,10 +144,10 @@ Kotlin::
======
The preceding code improves on our
xref:testing/mockmvc/htmlunit/why.adoc#mockmvc-server-htmlunit-why[MockMvc test] in a
number of ways. First, we no longer have to explicitly verify our form and then create a
request that looks like the form. Instead, we request the form, fill it out, and submit
it, thereby significantly reducing the overhead.
xref:testing/mockmvc/htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways.
First, we no longer have to explicitly verify our form and then create a request that
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
significantly reducing the overhead.
Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
@@ -266,19 +267,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply<DefaultMockMvcBuilder>(springSecurity())
.build()
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com", "example.org")
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -203,7 +203,7 @@ Kotlin::
======
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
The preceding example ensures that any URL that references `localhost` as the server is
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
@@ -259,11 +259,10 @@ Kotlin::
======
--
This improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
by leveraging the Page Object Pattern. As we mentioned in
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
HtmlUnit, but it is much easier with WebDriver. Consider the following
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
with HtmlUnit, but it is much easier with WebDriver. Consider the following
`CreateMessagePage` implementation:
--
@@ -563,19 +562,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val mockMvc: MockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply<DefaultMockMvcBuilder>(springSecurity())
.build()
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com", "example.org")
.build()
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
</form>
----
How do we ensure that our form produces the correct request to create a new message? A
How do we ensure that our form produce the correct request to create a new message? A
naive attempt might resemble the following:
[tabs]
@@ -154,7 +154,7 @@ validation.
[[mockmvc-server-htmlunit-why-integration]]
== Integration Testing to the Rescue?
To resolve the issues mentioned above, we could perform end-to-end integration testing,
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
but this has some drawbacks. Consider testing the view that lets us page through the
messages. We might need the following tests:
@@ -171,7 +171,7 @@ leads to a number of additional challenges:
* Testing can become slow, since each test would need to ensure that the database is in
the correct state.
* Since our database needs to be in a specific state, we cannot run tests in parallel.
* Performing assertions on items such as auto-generated IDs, timestamps, and others can
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
be difficult.
These challenges do not mean that we should abandon end-to-end integration testing
@@ -8,15 +8,12 @@ in JUnit and TestNG.
[[testcontext-junit-jupiter-extension]]
== SpringExtension for JUnit Jupiter
The `SpringExtension` integrates the Spring TestContext Framework into the JUnit Jupiter
testing framework.
NOTE: As of Spring Framework 7.0, the `SpringExtension` requires JUnit Jupiter 6.0 or higher.
By annotating test classes with `@ExtendWith(SpringExtension.class)`, you can implement
standard JUnit Jupiter-based unit and integration tests and simultaneously reap the
benefits of the TestContext framework, such as support for loading application contexts,
dependency injection of test instances, transactional test method execution, and so on.
The Spring TestContext Framework offers full integration with the JUnit Jupiter testing
framework, originally introduced in JUnit 5. By annotating test classes with
`@ExtendWith(SpringExtension.class)`, you can implement standard JUnit Jupiter-based unit
and integration tests and simultaneously reap the benefits of the TestContext framework,
such as support for loading application contexts, dependency injection of test instances,
transactional test method execution, and so on.
Furthermore, thanks to the rich extension API in JUnit Jupiter, Spring provides the
following features above and beyond the feature set that Spring supports for JUnit 4 and
@@ -25,7 +22,7 @@ TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://docs.junit.org/current/user-guide/#extensions-conditions[conditional
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
@@ -163,7 +160,7 @@ for further details.
=== Dependency Injection with the `SpringExtension`
The `SpringExtension` implements the
link:https://docs.junit.org/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
constructors, test methods, and test lifecycle callback methods.
@@ -362,7 +362,7 @@ of `PlatformTransactionManager` within the test's `ApplicationContext`, you can
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
`@Configuration` class. Consult the
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager(org.springframework.test.context.TestContext,java.lang.String)[javadoc
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
@@ -75,7 +75,7 @@ infrastructure and controller declarations and use it to handle requests via moc
and response objects, without a running server.
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext(org.springframework.context.ApplicationContext)[WebHttpHandlerBuilder]
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
requests:
@@ -44,7 +44,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the WebFlux Java configuration to declare such mappings, which results in a single,
global map passed to all `HandlerMapping` implementations.
@@ -57,7 +57,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
@@ -123,7 +123,7 @@ Both request and response provide {reactive-streams-site}[Reactive Streams] back
against the body streams.
The request body is represented with a Reactor `Flux` or `Mono`.
The response body is represented with any Reactive Streams `Publisher`, including `Flux` and `Mono`.
For more on that, see xref:web/webflux-reactive-libraries.adoc[Reactive Libraries].
For more on that, see xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries].
[[webflux-fn-request]]
=== ServerRequest
@@ -296,8 +296,7 @@ allPartsEvents.windowUntil(PartEvent::isLast)
NOTE: The body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
The following shows how to bind request parameters, URI variables, or headers via `DataBinder`,
and also shows how to customize the `DataBinder`:
The following shows how to bind request parameters, including an optional `DataBinder` customization:
[tabs]
======
@@ -375,14 +374,14 @@ Java::
+
[source,java]
----
ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
----
Kotlin::
+
[source,kotlin]
----
ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
----
======
@@ -482,7 +482,7 @@ purposes, it is useful to be able to alternate between rendering a model with an
or as other formats (such as JSON or XML), depending on the content type requested by the client.
To support doing so, Spring WebFlux provides the `HttpMessageWriterView`, which you can use to
plug in any of the available xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from
`spring-web`, such as `JacksonJsonEncoder`, `JacksonSmileEncoder`, or `Jaxb2XmlEncoder`.
`spring-web`, such as `Jackson2JsonEncoder`, `Jackson2SmileEncoder`, or `Jaxb2XmlEncoder`.
Unlike other view technologies, `HttpMessageWriterView` does not require a `ViewResolver` but is
instead xref:web/webflux/config.adoc#webflux-config-view-resolvers[configured] as a default view.
@@ -135,10 +135,6 @@ Message codes and arguments for each error are also resolved via `MessageSource`
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
Message codes and arguments for each error are also resolved via `MessageSource`.
| `NoResourceFoundException`
| (default)
| `+{0}+` the request path (or portion of) used to find a resource
|===
NOTE: Unlike other exceptions, the message arguments for
@@ -334,8 +334,19 @@ Kotlin::
`ServerCodecConfigurer` provides a set of default readers and writers. You can use it to add
more readers and writers, customize the default ones, or replace the default ones completely.
For Jackson, consider using a Jackson format-specific builder like `JsonMapper.Builder` to configure Jackson's default
properties.
For Jackson JSON and XML, consider using
{spring-framework-api}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`],
which customizes Jackson's default properties with the following ones:
* {jackson-docs}/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES[`DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`] is disabled.
* {jackson-docs}/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html#DEFAULT_VIEW_INCLUSION[`MapperFeature.DEFAULT_VIEW_INCLUSION`] is disabled.
It also automatically registers the following well-known modules if they are detected on the classpath:
* {jackson-github-org}/jackson-datatype-jsr310[`jackson-datatype-jsr310`]: Support for Java 8 Date and Time API types.
* {jackson-github-org}/jackson-datatype-jdk8[`jackson-datatype-jdk8`]: Support for other Java 8 types, such as `Optional`.
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
[[webflux-config-view-resolvers]]
== View Resolvers
@@ -478,7 +489,7 @@ Java::
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
JacksonJsonEncoder encoder = new JacksonJsonEncoder();
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
registry.defaultViews(new HttpMessageWriterView(encoder));
}
@@ -497,7 +508,7 @@ Kotlin::
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
val encoder = JacksonJsonEncoder()
val encoder = Jackson2JsonEncoder()
registry.defaultViews(HttpMessageWriterView(encoder))
}
@@ -690,7 +701,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim"]
----
@Configuration
class WebConfiguration : WebFluxConfigurer {
class WebConfiguration : WebMvcConfigurer {
override fun configureApiVersioning(configurer: ApiVersionConfigurer) {
configurer.useRequestHeader("API-Version")
@@ -5,7 +5,7 @@
The following table shows the supported controller method arguments.
Reactive types (Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other]) are
Reactive types (Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other]) are
supported on arguments that require blocking I/O (for example, reading the request body) to
be resolved. This is marked in the Description column. Reactive types are not expected
on arguments that do not require blocking.
@@ -114,6 +114,6 @@ and others) and is equivalent to `required=false`.
| Any other argument
| If a method argument is not matched to any of the above, it is, by default, resolved as
a `@RequestParam` if it is a simple type, as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
or as a `@ModelAttribute`, otherwise.
|===
@@ -205,7 +205,7 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
@@ -74,6 +74,6 @@ When a `@RequestParam` annotation is declared on a `Map<String, String>` or
Note that use of `@RequestParam` is optional -- for example, to set its attributes. By
default, any argument that is a simple value type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
and is not resolved by any other argument resolver is treated as if it were annotated
with `@RequestParam`.
@@ -33,7 +33,7 @@ Kotlin::
----
======
WebFlux supports using a single value xref:web/webflux-reactive-libraries.adoc[reactive type] to
WebFlux supports using a single value xref:web-reactive.adoc#webflux-reactive-libraries[reactive type] to
produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive types
for the body. This allows a variety of async responses with `ResponseEntity` as follows:
@@ -87,6 +87,6 @@ Reactor provides a dedicated operator for that, `Flux#collectList()`.
| Other return values
| If a return value remains unresolved in any other way, it is treated as a model
attribute, unless it is a simple type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
in which case it remains unresolved.
|===
@@ -207,7 +207,7 @@ was not provided (for example, model attribute was returned) or an async return
view resolution scenarios. Explore the options in your IDE with code completion.
* `Model`, `Map`: Extra model attributes to be added to the model for the request.
* Any other: Any other return value (except for simple types, as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
is treated as a model attribute to be added to the model. The attribute name is derived
from the class name by using {spring-framework-api}/core/Conventions.html[conventions],
unless a handler method `@ModelAttribute` annotation is present.
@@ -77,7 +77,7 @@ as input, adapts it to a Reactor type internally, uses that, and returns either
`Flux` or a `Mono` as output. So, you can pass any `Publisher` as input and you can apply
operations on the output, but you need to adapt the output for use with another reactive library.
Whenever feasible (for example, annotated controllers), WebFlux adapts transparently to the use
of RxJava or another reactive library. See xref:web/webflux-reactive-libraries.adoc[Reactive Libraries] for more details.
of RxJava or another reactive library. See xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries] for more details.
NOTE: In addition to Reactive APIs, WebFlux can also be used with
xref:languages/kotlin/coroutines.adoc[Coroutines] APIs in Kotlin which provides a more imperative style of programming.
@@ -148,7 +148,7 @@ RxJava to perform blocking calls on a separate thread but you would not be makin
most of a non-blocking web stack.
* If you have a Spring MVC application with calls to remote services, try the reactive `WebClient`.
You can return reactive types (Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other])
You can return reactive types (Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other])
directly from Spring MVC controller methods. The greater the latency per call or the
interdependency among calls, the more dramatic the benefits. Spring MVC controllers
can call other reactive components too.
@@ -459,22 +459,22 @@ xref:web/webflux/config.adoc#webflux-config-message-codecs[HTTP message codecs].
JSON and binary JSON ({jackson-github-org}/smile-format-specification[Smile]) are
both supported when the Jackson library is present.
The `JacksonJsonDecoder` works as follows:
The `Jackson2Decoder` works as follows:
* Jackson's asynchronous, non-blocking parser is used to aggregate a stream of byte chunks
into ``TokenBuffer``'s each representing a JSON object.
* Each `TokenBuffer` is passed to Jackson's `JsonMapper` to create a higher level object.
* Each `TokenBuffer` is passed to Jackson's `ObjectMapper` to create a higher level object.
* When decoding to a single-value publisher (for example, `Mono`), there is one `TokenBuffer`.
* When decoding to a multi-value publisher (for example, `Flux`), each `TokenBuffer` is passed to
the `JsonMapper` as soon as enough bytes are received for a fully formed object. The
the `ObjectMapper` as soon as enough bytes are received for a fully formed object. The
input content can be a JSON array, or any
https://en.wikipedia.org/wiki/JSON_streaming[line-delimited JSON] format such as NDJSON,
JSON Lines, or JSON Text Sequences.
The `JacksonJsonEncoder` works as follows:
The `Jackson2Encoder` works as follows:
* For a single value publisher (for example, `Mono`), simply serialize it through the
`JsonMapper`.
`ObjectMapper`.
* For a multi-value publisher with `application/json`, by default collect the values with
`Flux#collectToList()` and then serialize the resulting collection.
* For a multi-value publisher with a streaming media type such as
@@ -482,12 +482,12 @@ The `JacksonJsonEncoder` works as follows:
flush each value individually using a
https://en.wikipedia.org/wiki/JSON_streaming[line-delimited JSON] format. Other
streaming media types may be registered with the encoder.
* For SSE the `JacksonJsonEncoder` is invoked per event and the output is flushed to ensure
* For SSE the `Jackson2Encoder` is invoked per event and the output is flushed to ensure
delivery without delay.
[NOTE]
====
By default both `JacksonJsonEncoder` and `JacksonJsonDecoder` do not support elements of type
By default both `Jackson2Encoder` and `Jackson2Decoder` do not support elements of type
`String`. Instead the default assumption is that a string or a sequence of strings
represent serialized JSON content, to be rendered by the `CharSequenceEncoder`. If what
you need is to render a JSON array from `Flux<String>`, use `Flux#collectToList()` and
@@ -71,7 +71,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the MVC Java configuration or the XML namespace to declare such mappings, which results
in a single global map being passed to all `HandlerMapping` instances.
@@ -84,7 +84,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, for example, `allowCredentials` and `maxAge`, the local overrides the global value. See
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
@@ -184,8 +184,7 @@ val map = request.params()
----
======
The following shows how to bind request parameters, URI variables, or headers via `DataBinder`,
and also shows how to customize the `DataBinder`:
The following shows how to bind request parameters, including an optional `DataBinder` customization:
[tabs]
======
@@ -10,7 +10,7 @@ Spring offers support for the Jackson JSON library.
== Jackson-based JSON MVC Views
[.small]#xref:web/webflux-view.adoc#webflux-view-httpmessagewriter[See equivalent in the Reactive stack]#
The `JacksonJsonView` uses the Jackson library's `JsonMapper` to render the response
The `MappingJackson2JsonView` uses the Jackson library's `ObjectMapper` to render the response
content as JSON. By default, the entire contents of the model map (with the exception of
framework-specific classes) are encoded as JSON. For cases where the contents of the
map need to be filtered, you can specify a specific set of model attributes to encode
@@ -18,17 +18,17 @@ by using the `modelKeys` property. You can also use the `extractValueFromSingleK
property to have the value in single-key models extracted and serialized directly rather
than as a map of model attributes.
You can customize JSON mapping as needed by using Jackson's provided annotations. When
you need further control, you can inject a custom `JsonMapper` through the `JsonMapper`
or `JsonMapper.Builder` constructor parameters, for cases where you need to provide
custom JSON serializers and deserializers for specific types.
You can customize JSON mapping as needed by using Jackson's provided
annotations. When you need further control, you can inject a custom `ObjectMapper`
through the `ObjectMapper` property, for cases where you need to provide custom JSON
serializers and deserializers for specific types.
[[mvc-view-xml-mapping]]
== Jackson-based XML Views
[.small]#xref:web/webflux-view.adoc#webflux-view-httpmessagewriter[See equivalent in the Reactive stack]#
`JacksonXmlView` uses the
`MappingJackson2XmlView` uses the
{jackson-github-org}/jackson-dataformat-xml[Jackson XML extension's] `XmlMapper`
to render the response content as XML. If the model contains multiple entries, you should
explicitly set the object to be serialized by using the `modelKey` bean property. If the
@@ -36,5 +36,5 @@ model contains a single entry, it is serialized automatically.
You can customize XML mapping as needed by using JAXB or Jackson's provided
annotations. When you need further control, you can inject a custom `XmlMapper`
created via `XmlMapper.Builder` for cases where custom XML you need to provide
serializers and deserializers for specific types.
through the `ObjectMapper` property, for cases where custom XML
you need to provide serializers and deserializers for specific types.
@@ -42,25 +42,25 @@ This converter requires a `Marshaller` and `Unmarshaller` before it can be used.
You can inject these through constructor or bean properties.
By default, this converter supports `text/xml` and `application/xml`.
| `JacksonJsonHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's `JsonMapper`.
| `MappingJackson2HttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's `ObjectMapper`.
You can customize JSON mapping as needed through the use of Jackson's provided annotations.
When you need further control (for cases where custom JSON serializers/deserializers need to be provided for specific types), you can inject a custom `JsonMapper` through the `JsonMapper` or `JsonMapper.Builder ` constructor parameters.
By default, this converter supports `application/json`. This requires the `tools.jackson.core:jackson-databind` dependency.
When you need further control (for cases where custom JSON serializers/deserializers need to be provided for specific types), you can inject a custom `ObjectMapper` through the `ObjectMapper` property.
By default, this converter supports `application/json`. This requires the `com.fasterxml.jackson.core:jackson-databind` dependency.
| `JacksonXmlHttpMessageConverter`
| `MappingJackson2XmlHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write XML by using {jackson-github-org}/jackson-dataformat-xml[Jackson XML] extension's `XmlMapper`.
You can customize XML mapping as needed through the use of JAXB or Jackson's provided annotations.
When you need further control (for cases where custom XML serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper` through the `JsonMapper` or `JsonMapper.Builder` constructor parameters.
By default, this converter supports `application/xml`. This requires the `tools.jackson.dataformat:jackson-dataformat-xml` dependency.
When you need further control (for cases where custom XML serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper` through the `ObjectMapper` property.
By default, this converter supports `application/xml`. This requires the `com.fasterxml.jackson.dataformat:jackson-dataformat-xml` dependency.
| `KotlinSerializationJsonHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write JSON using `kotlinx.serialization`.
This converter is not configured by default, as this conflicts with Jackson.
Developers must configure it as an additional converter ahead of the Jackson one.
| `JacksonCborHttpMessageConverter`
| `tools.jackson.dataformat:jackson-dataformat-cbor`
| `MappingJackson2CborHttpMessageConverter`
| `com.fasterxml.jackson.dataformat:jackson-dataformat-cbor`
| `SourceHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write `javax.xml.transform.Source` from the HTTP request and response.
@@ -414,7 +414,7 @@ customize the status and headers of the response.
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs-streaming[See equivalent in the Reactive stack]#
Spring MVC supports use of reactive client libraries in a controller (also read
xref:web/webflux-reactive-libraries.adoc[Reactive Libraries] in the WebFlux section).
xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries] in the WebFlux section).
This includes the `WebClient` from `spring-webflux` and others, such as Spring Data
reactive data repositories. In such scenarios, it is convenient to be able to return
reactive types from the controller method.
@@ -171,11 +171,11 @@ Message codes and arguments for each error are also resolved via `MessageSource`
| `NoResourceFoundException`
| (default)
| `+{0}+` the request path (or portion of) used to find a resource
|
| `TypeMismatchException`
| (default)
| `+{0}+` property name, `+{1}+` property value, `+{2}+` simple name of required type
| `+{0}+` property name, `+{1}+` property value
| `UnsatisfiedServletRequestParameterException`
| (default)
@@ -215,7 +215,7 @@ the content negotiation during the error handling phase will decide which conten
| Any other return value
| If a return value is not matched to any of the above and is not a simple type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
by default, it is treated as a model attribute to be added to the model. If it is a simple type,
it remains unresolved.
|===
@@ -134,6 +134,6 @@ and others) and is equivalent to `required=false`.
| Any other argument
| If a method argument is not matched to any of the earlier values in this table and it is
a simple type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
it is resolved as a `@RequestParam`. Otherwise, it is resolved as a `@ModelAttribute`.
|===
@@ -250,7 +250,7 @@ xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
@@ -117,6 +117,6 @@ Kotlin::
Note that use of `@RequestParam` is optional (for example, to set its attributes).
By default, any argument that is a simple value type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
and is not resolved by any other argument resolver, is treated as if it were annotated
with `@RequestParam`.
@@ -98,6 +98,6 @@ supported for all return values.
| Other return values
| If a return value remains unresolved in any other way, it is treated as a model
attribute, unless it is a simple type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
in which case it remains unresolved.
|===
@@ -214,7 +214,7 @@ When multiple patterns match a URL, the best match must be selected. This is don
one of the following depending on whether use of parsed `PathPattern` is enabled for use or not:
* {spring-framework-api}/web/util/pattern/PathPattern.html#SPECIFICITY_COMPARATOR[`PathPattern.SPECIFICITY_COMPARATOR`]
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator(java.lang.String)[`AntPathMatcher.getPatternComparator(String path)`]
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator-java.lang.String-[`AntPathMatcher.getPatternComparator(String path)`]
Both help to sort patterns with more specific ones on top. A pattern is more specific if
it has a lower count of URI variables (counted as 1), single wildcards (counted as 1),
+1 -1
View File
@@ -4,7 +4,7 @@
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.7",
"@springio/antora-extensions": "1.14.2",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
@@ -20,7 +20,7 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.docs.testing.mockmvc.assertj.mockmvctestersetup.ApplicationWebConfiguration;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter ;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
@@ -34,7 +34,7 @@ class AccountControllerIntegrationTests {
AccountControllerIntegrationTests(@Autowired WebApplicationContext wac) {
this.mockMvc = MockMvcTester.from(wac).withHttpMessageConverters(
List.of(wac.getBean(AbstractJacksonHttpMessageConverter.class)));
List.of(wac.getBean(AbstractJackson2HttpMessageConverter.class)));
}
// ...
@@ -21,7 +21,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.json.JacksonJsonView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
@SuppressWarnings("removal")
// tag::snippet[]
@@ -30,7 +30,7 @@ public class FreeMarkerConfiguration implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new JacksonJsonView());
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.freeMarker().cache(false);
}
@@ -19,7 +19,7 @@ package org.springframework.docs.web.webmvc.mvcconfig.mvcconfigviewresolvers;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.json.JacksonJsonView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
@SuppressWarnings("removal")
// tag::snippet[]
@@ -28,7 +28,7 @@ public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new JacksonJsonView());
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
}
@@ -20,7 +20,7 @@ package org.springframework.docs.testing.mockmvc.assertj.mockmvctestersetup.conv
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.docs.testing.mockmvc.assertj.mockmvctestersetup.ApplicationWebConfiguration
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig
import org.springframework.test.web.servlet.assertj.MockMvcTester
import org.springframework.web.context.WebApplicationContext
@@ -30,7 +30,7 @@ import org.springframework.web.context.WebApplicationContext
class AccountControllerIntegrationTests(@Autowired wac: WebApplicationContext) {
private val mockMvc = MockMvcTester.from(wac).withHttpMessageConverters(
listOf(wac.getBean(AbstractJacksonHttpMessageConverter::class.java)))
listOf(wac.getBean(AbstractJackson2HttpMessageConverter::class.java)))
// ...
@@ -7,14 +7,14 @@ import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer
import org.springframework.web.servlet.view.json.JacksonJsonView
import org.springframework.web.servlet.view.json.MappingJackson2JsonView
// tag::snippet[]
@Configuration
class FreeMarkerConfiguration : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.enableContentNegotiation(JacksonJsonView())
registry.enableContentNegotiation(MappingJackson2JsonView())
registry.freeMarker().cache(false)
}
@@ -21,13 +21,13 @@ package org.springframework.docs.web.webmvc.mvcconfig.mvcconfigviewresolvers
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.view.json.JacksonJsonView
import org.springframework.web.servlet.view.json.MappingJackson2JsonView
// tag::snippet[]
@Configuration
class WebConfiguration : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.enableContentNegotiation(JacksonJsonView())
registry.enableContentNegotiation(MappingJackson2JsonView())
registry.jsp()
}
}
@@ -12,7 +12,7 @@
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.JacksonJsonView"/>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:freemarker cache-views="false"/>
@@ -12,7 +12,7 @@
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.JacksonJsonView"/>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:jsp/>
+10 -10
View File
@@ -8,15 +8,15 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.20.1"))
api(platform("io.micrometer:micrometer-bom:1.16.1"))
api(platform("io.micrometer:micrometer-bom:1.16.0"))
api(platform("io.netty:netty-bom:4.2.7.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.1"))
api(platform("io.projectreactor:reactor-bom:2025.0.0"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:5.0.3"))
api(platform("org.apache.logging.log4j:log4j-bom:2.25.2"))
api(platform("org.apache.groovy:groovy-bom:5.0.2"))
api(platform("org.apache.logging.log4j:log4j-bom:2.25.1"))
api(platform("org.assertj:assertj-bom:3.27.6"))
api(platform("org.eclipse.jetty:jetty-bom:12.1.5"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.5"))
api(platform("org.eclipse.jetty:jetty-bom:12.1.4"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.4"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0"))
api(platform("org.junit:junit-bom:6.0.1"))
@@ -33,7 +33,7 @@ dependencies {
api("com.google.code.gson:gson:2.13.2")
api("com.google.protobuf:protobuf-java-util:4.32.1")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.10.0")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.networknt:json-schema-validator:1.5.3")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
api("com.rometools:rome:1.19.0")
@@ -111,7 +111,7 @@ dependencies {
api("org.dom4j:dom4j:2.2.0")
api("org.easymock:easymock:5.6.0")
api("org.eclipse.angus:angus-mail:2.0.3")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.1.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.1.0")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-B11")
api("org.eclipse:yasson:3.0.4")
api("org.ehcache:ehcache:3.10.8")
@@ -121,7 +121,7 @@ dependencies {
api("org.glassfish:jakarta.el:4.0.2")
api("org.graalvm.sdk:graal-sdk:22.3.1")
api("org.hamcrest:hamcrest:3.0")
api("org.hibernate.orm:hibernate-core:7.2.0.CR3")
api("org.hibernate.orm:hibernate-core:7.2.0.CR2")
api("org.hibernate.validator:hibernate-validator:9.1.0.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.18.0")
@@ -129,7 +129,7 @@ dependencies {
api("org.jboss.logging:jboss-logging:3.6.1.Final")
api("org.jruby:jruby:10.0.2.0")
api("org.jspecify:jspecify:1.0.0")
api("org.junit.support:testng-engine:1.1.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")
+1 -1
View File
@@ -1,4 +1,4 @@
version=7.0.2
version=7.0.0
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+1 -1
View File
@@ -54,4 +54,4 @@ _When instructed to execute `./gradlew` from the command line, be sure to execut
In any case, please do not check in your own generated `.classpath` file, `.project`
file, or `.settings` folder. You'll notice these files are already intentionally in
`.gitignore`. The same policy holds for IntelliJ IDEA metadata.
`.gitignore`. The same policy holds for IDEA metadata.
@@ -48,6 +48,7 @@ import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConvertingComparator;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -83,10 +84,10 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
// @AfterThrowing methods due to the fact that AspectJAfterAdvice.invoke(MethodInvocation)
// invokes proceed() in a `try` block and only invokes the @After advice method
// in a corresponding `finally` block.
Comparator<Method> adviceKindComparator = new ConvertingComparator<Method, @Nullable Annotation>(
Comparator<Method> adviceKindComparator = new ConvertingComparator<>(
new InstanceComparator<>(
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
method -> {
(Converter<Method, Annotation>) method -> {
AspectJAnnotation ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
return (ann != null ? ann.getAnnotation() : null);
});
@@ -44,6 +44,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
@@ -295,8 +296,10 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
/**
* Build a cache key for the given bean class and bean name.
* <p>Note: As of 7.0.2, this implementation returns a composed cache key
* for bean class plus bean name; or if no bean name specified, then the
* <p>Note: As of 4.2.3, this implementation does not return a concatenated
* class/name String anymore but rather the most efficient cache key possible:
* a plain bean name, prepended with {@link BeanFactory#FACTORY_BEAN_PREFIX}
* in case of a {@code FactoryBean}; or if no bean name specified, then the
* given bean {@code Class} as-is.
* @param beanClass the bean class
* @param beanName the bean name
@@ -304,7 +307,8 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
*/
protected Object getCacheKey(Class<?> beanClass, @Nullable String beanName) {
if (StringUtils.hasLength(beanName)) {
return new ComposedCacheKey(beanClass, beanName);
return (FactoryBean.class.isAssignableFrom(beanClass) ?
BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
}
else {
return beanClass;
@@ -611,12 +615,4 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
protected abstract Object @Nullable [] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,
@Nullable TargetSource customTargetSource) throws BeansException;
/**
* Composed cache key for bean class plus bean name.
* @see #getCacheKey(Class, String)
*/
private record ComposedCacheKey(Class<?> beanClass, String beanName) {
}
}
@@ -365,12 +365,9 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
}
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
for (Annotation annotation : methodParam.getMethodAnnotations()) {
if (isQualifier(annotation.annotationType())) {
return true;
}
for (Annotation annotation : methodParam.getMethodAnnotations()) {
if (isQualifier(annotation.annotationType())) {
return true;
}
}
}
@@ -203,8 +203,8 @@ public abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
public @Nullable CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
if (value instanceof RuntimeBeanReference runtimeBeanReference &&
runtimeBeanReference.getBeanType() != null) {
return CodeBlock.of("new $T($S, $T.class)", RuntimeBeanReference.class,
runtimeBeanReference.getBeanName(), runtimeBeanReference.getBeanType());
return CodeBlock.of("new $T($T.class)", RuntimeBeanReference.class,
runtimeBeanReference.getBeanType());
}
else if (value instanceof BeanReference beanReference) {
return CodeBlock.of("new $T($S)", RuntimeBeanReference.class,
@@ -18,7 +18,6 @@ package org.springframework.beans.factory.aot;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.MethodReference;
import org.springframework.javapoet.ClassName;
/**
* Interface that can be used to configure the code that will be generated to
@@ -26,7 +25,6 @@ import org.springframework.javapoet.ClassName;
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 6.0
* @see BeanFactoryInitializationAotContribution
*/
@@ -43,13 +41,6 @@ public interface BeanFactoryInitializationCode {
*/
GeneratedMethods getMethods();
/**
* Return the name of the class used by the initializing code.
* @return the generated class name
* @since 7.0.2
*/
ClassName getClassName();
/**
* Add an initializer method call. An initializer can use a flexible signature,
* using any of the following:
@@ -384,7 +384,7 @@ public class InstanceSupplierCodeGenerator {
Visibility visibility = AccessControl.lowest(classAccessControl, memberAccessControl).getVisibility();
return (visibility == Visibility.PUBLIC || (visibility != Visibility.PRIVATE &&
member.getDeclaringClass().getPackageName().equals(this.className.packageName())));
}
}
private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes) {
CodeBlock.Builder code = CodeBlock.builder();
@@ -33,6 +33,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Rob Harrop
* @since 1.1
* @see PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
*/
public abstract class BeanDefinitionReaderUtils {
@@ -43,6 +43,7 @@ import org.springframework.core.AliasRegistry;
* @see DefaultListableBeanFactory
* @see org.springframework.context.support.GenericApplicationContext
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see PropertiesBeanDefinitionReader
*/
public interface BeanDefinitionRegistry extends AliasRegistry {
@@ -1632,7 +1632,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
if (Optional.class == descriptor.getDependencyType()) {
return createOptionalDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
return createOptionalDependency(descriptor, requestingBeanName);
}
else if (ObjectFactory.class == descriptor.getDependencyType() ||
ObjectProvider.class == descriptor.getDependencyType()) {
@@ -2330,8 +2330,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/**
* Create an {@link Optional} wrapper for the specified dependency.
*/
private Optional<?> createOptionalDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable Object @Nullable [] args) {
private Optional<?> createOptionalDependency(
DependencyDescriptor descriptor, @Nullable String beanName, final @Nullable Object... args) {
DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) {
@Override
@@ -2348,7 +2348,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return ObjectUtils.isEmpty(args);
}
};
Object result = doResolveDependency(descriptorToUse, beanName, autowiredBeanNames, null);
Object result = doResolveDependency(descriptorToUse, beanName, null, null);
return (result instanceof Optional<?> optional ? optional : Optional.ofNullable(result));
}
@@ -2501,18 +2501,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
*/
private class DependencyObjectProvider implements BeanObjectProvider<Object> {
private static final Object NOT_CACHEABLE = new Object();
private static final Object NULL_VALUE = new Object();
private final DependencyDescriptor descriptor;
private final boolean optional;
private final @Nullable String beanName;
private transient volatile @Nullable Object cachedValue;
public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable String beanName) {
this.descriptor = new NestedDependencyDescriptor(descriptor);
this.optional = (this.descriptor.getDependencyType() == Optional.class);
@@ -2521,17 +2515,22 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@Override
public Object getObject() throws BeansException {
Object result = getValue();
if (result == null) {
throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType());
if (this.optional) {
return createOptionalDependency(this.descriptor, this.beanName);
}
else {
Object result = doResolveDependency(this.descriptor, this.beanName, null, null);
if (result == null) {
throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType());
}
return result;
}
return result;
}
@Override
public Object getObject(final @Nullable Object... args) throws BeansException {
if (this.optional) {
return createOptionalDependency(this.descriptor, this.beanName, null, args);
return createOptionalDependency(this.descriptor, this.beanName, args);
}
else {
DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {
@@ -2552,7 +2551,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
public @Nullable Object getIfAvailable() throws BeansException {
try {
if (this.optional) {
return createOptionalDependency(this.descriptor, this.beanName, null, null);
return createOptionalDependency(this.descriptor, this.beanName);
}
else {
DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {
@@ -2605,7 +2604,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
};
try {
if (this.optional) {
return createOptionalDependency(descriptorToUse, this.beanName, null, null);
return createOptionalDependency(descriptorToUse, this.beanName);
}
else {
return doResolveDependency(descriptorToUse, this.beanName, null, null);
@@ -2631,41 +2630,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
protected @Nullable Object getValue() throws BeansException {
Object value = this.cachedValue;
if (value == null) {
if (isConfigurationFrozen()) {
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
value = resolveValue(autowiredBeanNames);
boolean cacheable = false;
if (!autowiredBeanNames.isEmpty()) {
cacheable = true;
for (String autowiredBeanName : autowiredBeanNames) {
if (!containsBean(autowiredBeanName) || !isSingleton(autowiredBeanName)) {
cacheable = false;
}
}
}
this.cachedValue = (cacheable ? (value != null ? value : NULL_VALUE) : NOT_CACHEABLE);
return value;
}
}
else if (value == NULL_VALUE) {
return null;
}
else if (value != NOT_CACHEABLE) {
return value;
}
// Not cacheable -> fresh resolution.
return resolveValue(null);
}
private @Nullable Object resolveValue(@Nullable Set<String> autowiredBeanNames) {
if (this.optional) {
return createOptionalDependency(this.descriptor, this.beanName, autowiredBeanNames, null);
return createOptionalDependency(this.descriptor, this.beanName);
}
else {
return doResolveDependency(this.descriptor, this.beanName, autowiredBeanNames, null);
return doResolveDependency(this.descriptor, this.beanName, null, null);
}
}
@@ -532,7 +532,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
/**
* Callback before singleton creation.
* <p>The default implementation registers the singleton as currently in creation.
* <p>The default implementation register the singleton as currently in creation.
* @param beanName the name of the singleton about to be created
* @see #isSingletonCurrentlyInCreation
*/
@@ -582,7 +582,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
public void registerContainedBean(String containedBeanName, String containingBeanName) {
synchronized (this.containedBeanMap) {
Set<String> containedBeans =
this.containedBeanMap.computeIfAbsent(containingBeanName, key -> new LinkedHashSet<>(8));
this.containedBeanMap.computeIfAbsent(containingBeanName, k -> new LinkedHashSet<>(8));
if (!containedBeans.add(containedBeanName)) {
return;
}
@@ -601,7 +601,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, key -> new LinkedHashSet<>(8));
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
@@ -609,7 +609,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, key -> new LinkedHashSet<>(8));
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
@@ -76,7 +76,7 @@ import org.springframework.util.StringUtils;
* @since 26.11.2003
* @see DefaultListableBeanFactory
* @deprecated in favor of Spring's common bean definition formats and/or
* custom BeanDefinitionReader implementations
* custom reader implementations
*/
@Deprecated(since = "5.3")
public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
@@ -1587,18 +1587,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
ObjectFactoryFieldInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryFieldInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
void objectFactoryFieldInjectionAgainstFrozen() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class));
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
bf.freezeConfiguration();
ObjectFactoryFieldInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryFieldInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
@@ -1608,18 +1596,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
ObjectFactoryConstructorInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryConstructorInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
void objectFactoryConstructorInjectionAgainstFrozen() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryConstructorInjectionBean.class));
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
bf.freezeConfiguration();
ObjectFactoryConstructorInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryConstructorInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
@@ -2140,8 +2116,8 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -2752,11 +2728,9 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("nonNullBean", "Test");
bf.registerBeanDefinition("mixedNullableInjectionBean",
new RootBeanDefinition(MixedNullableInjectionBean.class));
MixedNullableInjectionBean mixedNullableInjectionBean = bf.getBean(MixedNullableInjectionBean.class);
assertThat(mixedNullableInjectionBean.nonNullBean).isNotNull();
assertThat(mixedNullableInjectionBean.nullableBean).isNull();
assertThat(bf.getDependentBeans("nonNullBean")).contains("mixedNullableInjectionBean");
}
@Test
@@ -2764,11 +2738,9 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("nonNullBean", "Test");
bf.registerBeanDefinition("mixedOptionalInjectionBean",
new RootBeanDefinition(MixedOptionalInjectionBean.class));
MixedOptionalInjectionBean mixedOptionalInjectionBean = bf.getBean(MixedOptionalInjectionBean.class);
assertThat(mixedOptionalInjectionBean.nonNullBean).isNotNull();
assertThat(mixedOptionalInjectionBean.nullableBean).isNull();
assertThat(bf.getDependentBeans("nonNullBean")).contains("mixedOptionalInjectionBean");
}
@@ -370,25 +370,8 @@ class InjectAnnotationBeanPostProcessorTests {
ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
bean = SerializationTestUtils.serializeAndDeserialize(bean);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
void testObjectFactoryWithBeanFieldAgainstFrozen() throws Exception {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class));
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
bf.setSerializationId("test");
bf.freezeConfiguration();
ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
bean = SerializationTestUtils.serializeAndDeserialize(bean);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
@@ -399,25 +382,8 @@ class InjectAnnotationBeanPostProcessorTests {
ObjectFactoryMethodInjectionBean bean = (ObjectFactoryMethodInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
bean = SerializationTestUtils.serializeAndDeserialize(bean);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
void testObjectFactoryWithBeanMethodAgainstFrozen() throws Exception {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMethodInjectionBean.class));
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
bf.setSerializationId("test");
bf.freezeConfiguration();
ObjectFactoryMethodInjectionBean bean = (ObjectFactoryMethodInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
bean = SerializationTestUtils.serializeAndDeserialize(bean);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
}
@Test
@@ -478,8 +444,8 @@ class InjectAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -493,7 +459,6 @@ class InjectAnnotationBeanPostProcessorTests {
NullableFieldInjectionBean bean = (NullableFieldInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
}
@Test
@@ -511,7 +476,6 @@ class InjectAnnotationBeanPostProcessorTests {
NullableMethodInjectionBean bean = (NullableMethodInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
}
@Test
@@ -530,7 +494,6 @@ class InjectAnnotationBeanPostProcessorTests {
OptionalFieldInjectionBean bean = (OptionalFieldInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isPresent();
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
}
@Test
@@ -549,7 +512,6 @@ class InjectAnnotationBeanPostProcessorTests {
OptionalMethodInjectionBean bean = (OptionalMethodInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isPresent();
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
}
@Test
@@ -568,7 +530,6 @@ class InjectAnnotationBeanPostProcessorTests {
OptionalListFieldInjectionBean bean = (OptionalListFieldInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).hasValueSatisfying(list ->
assertThat(list).containsExactly(bf.getBean("testBean", TestBean.class)));
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
}
@Test
@@ -587,7 +548,6 @@ class InjectAnnotationBeanPostProcessorTests {
OptionalListMethodInjectionBean bean = (OptionalListMethodInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).hasValueSatisfying(list ->
assertThat(list).containsExactly(bf.getBean("testBean", TestBean.class)));
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
}
@Test
@@ -606,7 +566,6 @@ class InjectAnnotationBeanPostProcessorTests {
ProviderOfOptionalFieldInjectionBean bean = (ProviderOfOptionalFieldInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isPresent();
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
assertThat(bf.getDependentBeans("testBean")).doesNotContain("annotatedBean");
}
@Test
@@ -625,7 +584,6 @@ class InjectAnnotationBeanPostProcessorTests {
ProviderOfOptionalMethodInjectionBean bean = (ProviderOfOptionalMethodInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isPresent();
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
assertThat(bf.getDependentBeans("testBean")).doesNotContain("annotatedBean");
}
@Test
@@ -830,6 +788,7 @@ class InjectAnnotationBeanPostProcessorTests {
private ConfigurableListableBeanFactory beanFactory;
public ConstructorResourceInjectionBean() {
throw new UnsupportedOperationException();
}
@@ -458,42 +458,30 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
class BeanReferenceTests {
@Test
void generatedWhenRuntimeBeanNameReference() {
BeanReference beanReference = new RuntimeBeanNameReference("test");
void generatedWhenBeanNameReference() {
RuntimeBeanNameReference beanReference = new RuntimeBeanNameReference("test");
compile(beanReference, (instance, compiler) -> {
RuntimeBeanReference actual = (RuntimeBeanReference) instance;
assertThat(actual.getBeanName()).as("name").isEqualTo("test");
assertThat(actual.getBeanType()).as("type").isNull();
assertThat(actual.getBeanName()).isEqualTo(beanReference.getBeanName());
});
}
@Test
void generatedWhenRuntimeBeanReferenceByName() {
BeanReference beanReference = new RuntimeBeanReference("test");
void generatedWhenBeanReferenceByName() {
RuntimeBeanReference beanReference = new RuntimeBeanReference("test");
compile(beanReference, (instance, compiler) -> {
RuntimeBeanReference actual = (RuntimeBeanReference) instance;
assertThat(actual.getBeanName()).as("name").isEqualTo("test");
assertThat(actual.getBeanType()).as("type").isNull();
assertThat(actual.getBeanName()).isEqualTo(beanReference.getBeanName());
assertThat(actual.getBeanType()).isEqualTo(beanReference.getBeanType());
});
}
@Test
void generatedWhenRuntimeBeanReferenceByType() {
void generatedWhenBeanReferenceByType() {
BeanReference beanReference = new RuntimeBeanReference(String.class);
compile(beanReference, (instance, compiler) -> {
RuntimeBeanReference actual = (RuntimeBeanReference) instance;
assertThat(actual.getBeanName()).as("name").isEqualTo(String.class.getName());
assertThat(actual.getBeanType()).as("type").isEqualTo(String.class);
});
}
@Test // gh-35913
void generatedWhenRuntimeBeanReferenceByNameAndType() {
BeanReference beanReference = new RuntimeBeanReference("test", String.class);
compile(beanReference, (instance, compiler) -> {
RuntimeBeanReference actual = (RuntimeBeanReference) instance;
assertThat(actual.getBeanName()).as("name").isEqualTo("test");
assertThat(actual.getBeanType()).as("type").isEqualTo(String.class);
assertThat(actual.getBeanType()).isEqualTo(String.class);
});
}
@@ -46,14 +46,14 @@ class YamlMapFactoryBeanTests {
@Test
void testSetIgnoreResourceNotFound() {
this.factory.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE);
this.factory.setResources(new FileSystemResource("non-existent-file.yml"));
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
assertThat(this.factory.getObject()).isEmpty();
}
@Test
void testSetBarfOnResourceNotFound() {
assertThatIllegalStateException().isThrownBy(() -> {
this.factory.setResources(new FileSystemResource("non-existent-file.yml"));
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
this.factory.getObject().size();
});
}
@@ -51,7 +51,7 @@ class MetadataAttachmentTests {
void metadataIsInherited() {
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("testBean2");
assertThat(beanDefinition.getAttribute("foo")).as("Metadata not inherited").isEqualTo("bar");
assertThat(beanDefinition.getAttribute("abc")).as("Child metadata not attached").isEqualTo("123");
assertThat(beanDefinition.getAttribute("abc")).as("Child metdata not attached").isEqualTo("123");
}
@Test
@@ -76,7 +76,7 @@ public class CaffeineCacheManager implements CacheManager {
private boolean allowNullValues = true;
private volatile boolean dynamic = true;
private boolean dynamic = true;
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
@@ -101,15 +101,10 @@ public class CaffeineCacheManager implements CacheManager {
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call
* to this method, with no creation of further cache regions at runtime.
* <p>Note that this method replaces existing caches of the given names
* and prevents the creation of further cache regions from here on - but
* does <i>not</i> remove unrelated existing caches. For a full reset,
* consider calling {@link #resetCaches()} before calling this method.
* <p>Calling this method with a {@code null} collection argument resets
* the mode to 'dynamic', allowing for further creation of caches again.
* @see #resetCaches()
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -249,6 +244,11 @@ public class CaffeineCacheManager implements CacheManager {
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
public @Nullable Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
@@ -258,34 +258,6 @@ public class CaffeineCacheManager implements CacheManager {
return cache;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
/**
* Reset this cache manager's caches, removing them completely for on-demand
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
* @since 6.2.14
*/
@Override
public void resetCaches() {
this.cacheMap.values().forEach(Cache::clear);
if (this.dynamic) {
this.cacheMap.keySet().retainAll(this.customCacheNames);
}
}
/**
* Remove the specified cache from this cache manager, applying to
* custom caches as well as dynamically registered caches at runtime.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.customCacheNames.remove(name);
this.cacheMap.remove(name);
}
/**
* Register the given native Caffeine Cache instance with this cache manager,
@@ -329,6 +301,16 @@ public class CaffeineCacheManager implements CacheManager {
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
}
/**
* Remove the specified cache from this cache manager, applying to
* custom caches as well as dynamically registered caches at runtime.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.customCacheNames.remove(name);
this.cacheMap.remove(name);
}
/**
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
@@ -130,17 +130,4 @@ public class JCacheCacheManager extends AbstractTransactionSupportingCacheManage
return null;
}
@Override
public void resetCaches() {
CacheManager cacheManager = getCacheManager();
if (cacheManager != null && !cacheManager.isClosed()) {
for (String cacheName : cacheManager.getCacheNames()) {
javax.cache.Cache<Object, Object> jcache = cacheManager.getCache(cacheName);
if (jcache != null && !jcache.isClosed()) {
jcache.clear();
}
}
}
}
}
@@ -26,7 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.util.ReflectionUtils;
@@ -95,8 +95,8 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
return null;
}
@@ -24,6 +24,7 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec;
import org.junit.jupiter.api.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,7 +42,7 @@ class CaffeineCacheManagerTests {
@Test
@SuppressWarnings("cast")
void dynamicMode() {
CaffeineCacheManager cm = new CaffeineCacheManager();
CacheManager cm = new CaffeineCacheManager();
Cache cache1 = cm.getCache("c1");
assertThat(cache1).isInstanceOf(CaffeineCache.class);
@@ -75,14 +76,6 @@ class CaffeineCacheManagerTests {
cache1.evict("key3");
assertThat(cache1.get("key3", () -> (String) null)).isNull();
assertThat(cache1.get("key3", () -> (String) null)).isNull();
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isSameAs(cache2);
cm.resetCaches();
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
}
@Test
@@ -138,24 +131,11 @@ class CaffeineCacheManagerTests {
cm.setAllowNullValues(true);
Cache cache1y = cm.getCache("c1");
Cache cache2y = cm.getCache("c2");
cache1y.put("key3", null);
assertThat(cache1y.get("key3").get()).isNull();
cache1y.evict("key3");
assertThat(cache1y.get("key3")).isNull();
cache2y.put("key4", "value4");
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.resetCaches();
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4")).isNull();
}
@Test
@@ -51,7 +51,8 @@ class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCacheCache>
this.cacheManager.createCache(CACHE_NAME_NO_NULL, new MutableConfiguration<>());
this.nativeCache = this.cacheManager.getCache(CACHE_NAME);
this.cache = new JCacheCache(this.nativeCache);
Cache<Object, Object> nativeCacheNoNull = this.cacheManager.getCache(CACHE_NAME_NO_NULL);
Cache<Object, Object> nativeCacheNoNull =
this.cacheManager.getCache(CACHE_NAME_NO_NULL);
this.cacheNoNull = new JCacheCache(nativeCacheNoNull, false);
}
@@ -99,18 +100,4 @@ class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCacheCache>
assertThat(cache.get(key).get()).isEqualTo(value);
}
@Test
void resetCaches() {
JCacheCacheManager cm = new JCacheCacheManager(cacheManager);
org.springframework.cache.Cache cache = cm.getCache(CACHE_NAME);
cache.put("key", "value");
assertThat(cm.getCacheNames()).contains(CACHE_NAME);
assertThat(cm.getCache(CACHE_NAME)).isNotNull().isSameAs(cache);
assertThat(cacheManager.getCache(CACHE_NAME).iterator()).hasNext();
cm.resetCaches();
assertThat(cm.getCacheNames()).contains(CACHE_NAME);
assertThat(cm.getCache(CACHE_NAME)).isNotNull().isSameAs(cache);
assertThat(cacheManager.getCache(CACHE_NAME).iterator()).isExhausted();
}
}
@@ -27,7 +27,6 @@ import org.jspecify.annotations.Nullable;
*
* @author Costin Leau
* @author Sam Brannen
* @author Juergen Hoeller
* @since 3.1
*/
public interface CacheManager {
@@ -48,31 +47,4 @@ public interface CacheManager {
*/
Collection<String> getCacheNames();
/**
* Remove all registered caches from this cache manager if possible,
* re-creating them on demand. After this call, {@link #getCacheNames()}
* will possibly be empty and the cache provider will have dropped all
* cache management state.
* <p>Alternatively, an implementation may perform an equivalent reset
* on fixed existing cache regions without actually dropping the cache.
* This behavior will be indicated by {@link #getCacheNames()} still
* exposing a non-empty set of names, whereas the corresponding cache
* regions will not contain cache entries anymore.
* <p>The default implementation calls {@link Cache#clear} on all
* registered caches, retaining all caches as registered, satisfying
* the alternative implementation path above. Custom implementations
* may either drop the actual caches (re-creating them on demand) or
* perform a more exhaustive reset at the actual cache provider level.
* @since 7.0.2
* @see Cache#clear()
*/
default void resetCaches() {
for (String cacheName : getCacheNames()) {
Cache cache = getCache(cacheName);
if (cache != null) {
cache.clear();
}
}
}
}
@@ -177,11 +177,6 @@ public @interface EnableCaching {
* be upgraded to subclass proxying at the same time. This approach has no negative
* impact in practice unless one is explicitly expecting one type of proxy vs another,
* for example, in tests.
* <p>It is usually recommendable to rely on a global default proxy configuration
* instead, with specific proxy requirements for certain beans expressed through
* a {@link org.springframework.context.annotation.Proxyable} annotation on
* the affected bean classes.
* @see org.springframework.aop.config.AopConfigUtils#forceAutoProxyCreatorToUseClassProxying
*/
boolean proxyTargetClass() default false;
@@ -55,7 +55,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private volatile boolean dynamic = true;
private boolean dynamic = true;
private boolean allowNullValues = true;
@@ -82,15 +82,10 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call
* to this method, with no creation of further cache regions at runtime.
* <p>Note that this method replaces existing caches of the given names
* and prevents the creation of further cache regions from here on - but
* does <i>not</i> remove unrelated existing caches. For a full reset,
* consider calling {@link #resetCaches()} before calling this method.
* <p>Calling this method with a {@code null} collection argument resets
* the mode to 'dynamic', allowing for further creation of caches again.
* @see #resetCaches()
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -165,6 +160,11 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
public @Nullable Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
@@ -174,24 +174,6 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
return cache;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
/**
* Reset this cache manager's caches, removing them completely for on-demand
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
* @since 6.2.14
*/
@Override
public void resetCaches() {
this.cacheMap.values().forEach(Cache::clear);
if (this.dynamic) {
this.cacheMap.clear();
}
}
/**
* Remove the specified cache from this cache manager.
* @param name the name of the cache
@@ -113,7 +113,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
builder.setUnless(getAttributeValue(opElement, "unless", ""));
builder.setSync(Boolean.parseBoolean(getAttributeValue(opElement, "sync", "false")));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -136,7 +136,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
builder.setBeforeInvocation(Boolean.parseBoolean(after.trim()));
}
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -150,7 +150,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
parserContext.getReaderContext(), new CachePutOperation.Builder());
builder.setUnless(getAttributeValue(opElement, "unless", ""));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -28,7 +28,7 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -137,8 +137,8 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
return null;
}
@@ -115,13 +115,6 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing
return this.cacheNames;
}
@Override
public void resetCaches() {
synchronized (this.cacheMap) {
this.cacheMap.values().forEach(Cache::clear);
}
}
// Common cache initialization delegates for subclasses
@@ -119,11 +119,4 @@ public class CompositeCacheManager implements CacheManager, InitializingBean {
return Collections.unmodifiableSet(names);
}
@Override
public void resetCaches() {
for (CacheManager manager : this.cacheManagers) {
manager.resetCaches();
}
}
}
@@ -18,6 +18,8 @@ package org.springframework.cache.support;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -31,33 +33,44 @@ import org.springframework.cache.CacheManager;
* for disabling caching, typically used for backing cache declarations
* without an actual backing store.
*
* <p>This implementation will simply accept any items into the cache,
* not actually storing them.
* <p>Will simply accept any items into the cache not actually storing them.
*
* @author Costin Leau
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 3.1
* @see NoOpCache
*/
public class NoOpCacheManager implements CacheManager {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(16);
private final Set<String> cacheNames = new LinkedHashSet<>(16);
/**
* This implementation always returns a {@link Cache} implementation that will not store items.
* Additionally, the request cache will be remembered by the manager for consistency.
*/
@Override
public @Nullable Cache getCache(String name) {
return this.cacheMap.computeIfAbsent(name, NoOpCache::new);
Cache cache = this.caches.get(name);
if (cache == null) {
this.caches.computeIfAbsent(name, NoOpCache::new);
synchronized (this.cacheNames) {
this.cacheNames.add(name);
}
}
return this.caches.get(name);
}
/**
* This implementation returns the name of the caches previously requested.
*/
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
public void resetCaches() {
this.cacheMap.clear();
synchronized (this.cacheNames) {
return Collections.unmodifiableSet(this.cacheNames);
}
}
}
@@ -61,9 +61,9 @@ public interface ApplicationContext extends EnvironmentCapable, ListableBeanFact
/**
* Return the unique id of this application context.
* @return the unique id of the context (never null as of 7.0.2)
* @return the unique id of the context, or {@code null} if none
*/
String getId();
@Nullable String getId();
/**
* Return a name for the deployed application that this context belongs to.
@@ -42,20 +42,16 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aot.generate.AccessControl;
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.ResourceHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -876,14 +872,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
@Override
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
GeneratedMethod generatedMethod = beanFactoryInitializationCode.getMethods().add(
"applyBeanRegistrars", builder -> this.generateApplyBeanRegistrarsMethod(builder,
generationContext, beanFactoryInitializationCode.getClassName()));
"applyBeanRegistrars", builder -> this.generateApplyBeanRegistrarsMethod(builder, generationContext));
beanFactoryInitializationCode.addInitializer(generatedMethod.toMethodReference());
}
private void generateApplyBeanRegistrarsMethod(MethodSpec.Builder method, GenerationContext generationContext,
ClassName className) {
private void generateApplyBeanRegistrarsMethod(MethodSpec.Builder method, GenerationContext generationContext) {
ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection();
method.addJavadoc("Apply bean registrars.");
method.addModifiers(Modifier.PRIVATE);
@@ -922,7 +915,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
}
}
method.addCode(generateRegisterCode(className, generationContext));
method.addCode(generateRegisterCode());
}
private void checkUnsupportedFeatures(AbstractBeanDefinition beanDefinition) {
@@ -944,79 +937,37 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
return code.build();
}
private CodeBlock generateRegisterCode(ClassName className, GenerationContext generationContext) {
private CodeBlock generateRegisterCode() {
Builder code = CodeBlock.builder();
Builder metadataReaderFactoryCode = null;
NameAllocator nameAllocator = new NameAllocator();
for (Map.Entry<String, List<BeanRegistrar>> beanRegistrarEntry : this.beanRegistrars.entrySet()) {
for (BeanRegistrar beanRegistrar : beanRegistrarEntry.getValue()) {
String beanRegistrarName = nameAllocator.newName(StringUtils.uncapitalize(beanRegistrar.getClass().getSimpleName()));
Constructor<?> constructor = BeanUtils.getResolvableConstructor(beanRegistrar.getClass());
boolean visible = isVisible(constructor, className);
if (visible) {
code.addStatement("$T $L = new $T()", beanRegistrar.getClass(), beanRegistrarName, beanRegistrar.getClass());
}
else {
try {
Class<?> configClass = ClassUtils.forName(beanRegistrarEntry.getKey(), beanRegistrar.getClass().getClassLoader());
GeneratedClass generatedClass = generationContext.getGeneratedClasses()
.getOrAddForFeatureComponent("BeanRegistrars", configClass, type ->
type.addJavadoc("Bean registrars for {@link $T}.", configClass)
.addModifiers(Modifier.PUBLIC));
GeneratedMethod generatedMethod = generatedClass.getMethods().add(
"get" + beanRegistrar.getClass().getSimpleName(),
method -> method
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(BeanRegistrar.class)
.addStatement("return new $T()", beanRegistrar.getClass()));
code.addStatement("$T $L = $L", BeanRegistrar.class, beanRegistrarName,
generatedMethod.toMethodReference().toInvokeCodeBlock(ArgumentCodeGenerator.none()));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
code.addStatement("$T $L = new $T()", beanRegistrar.getClass(), beanRegistrarName, beanRegistrar.getClass());
if (beanRegistrar instanceof ImportAware) {
if (metadataReaderFactoryCode == null) {
metadataReaderFactoryCode = CodeBlock.builder();
metadataReaderFactoryCode.addStatement("$T metadataReaderFactory = new $T()",
MetadataReaderFactory.class, CachingMetadataReaderFactory.class);
}
CodeBlock setImportMetadataCode;
if (visible) {
setImportMetadataCode = CodeBlock.builder()
.addStatement("$L.setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())",
beanRegistrarName, beanRegistrarEntry.getKey()).build();
}
else {
setImportMetadataCode = CodeBlock.builder()
.addStatement("(($T)$L).setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())",
ImportAware.class, beanRegistrarName, beanRegistrarEntry.getKey()).build();
}
code.beginControlFlow("try")
.add(setImportMetadataCode)
.addStatement("$L.setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())",
beanRegistrarName, beanRegistrarEntry.getKey())
.nextControlFlow("catch ($T ex)", IOException.class)
.addStatement("throw new $T(\"Failed to read metadata for '$L'\", ex)",
IllegalStateException.class, beanRegistrarEntry.getKey())
.endControlFlow();
}
code.addStatement("$L.register(new $T(($T)$L, $L, $L, $L.getClass(), $L), $L)", beanRegistrarName,
code.addStatement("$L.register(new $T(($T)$L, $L, $L, $T.class, $L), $L)", beanRegistrarName,
BeanRegistryAdapter.class, BeanDefinitionRegistry.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE,
BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, ENVIRONMENT_VARIABLE, beanRegistrarName,
BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, ENVIRONMENT_VARIABLE, beanRegistrar.getClass(),
CUSTOMIZER_MAP_VARIABLE, ENVIRONMENT_VARIABLE);
}
}
return (metadataReaderFactoryCode == null ? code.build() : metadataReaderFactoryCode.add(code.build()).build());
}
private boolean isVisible(Constructor<?> ctor, ClassName className) {
AccessControl classAccessControl = AccessControl.forClass(ctor.getDeclaringClass());
AccessControl memberAccessControl = AccessControl.forMember(ctor);
AccessControl.Visibility visibility = AccessControl.lowest(classAccessControl, memberAccessControl).getVisibility();
return (visibility == AccessControl.Visibility.PUBLIC || (visibility != AccessControl.Visibility.PRIVATE &&
ctor.getDeclaringClass().getPackageName().equals(className.packageName())));
}
private CodeBlock generateInitDestroyMethods(String beanName, AbstractBeanDefinition beanDefinition,
String[] methodNames, String method, ReflectionHints reflectionHints) {
@@ -168,25 +168,22 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
}
}
boolean cacheable = false;
if (!autowiredBeanNames.isEmpty()) {
cacheable = true;
for (String autowiredBeanName : autowiredBeanNames) {
if (!this.beanFactory.containsBean(autowiredBeanName)) {
boolean cacheable = true;
for (String autowiredBeanName : autowiredBeanNames) {
if (!this.beanFactory.containsBean(autowiredBeanName)) {
cacheable = false;
}
else {
if (this.beanName != null) {
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
}
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
cacheable = false;
}
else {
if (this.beanName != null) {
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
}
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
cacheable = false;
}
}
}
}
if (cacheable) {
this.cachedTarget = target;
if (cacheable) {
this.cachedTarget = target;
}
}
return target;
@@ -54,4 +54,5 @@ public @interface Proxyable {
*/
Class<?>[] interfaces() default {};
}
@@ -57,7 +57,7 @@ public class ApplicationContextAotGenerator {
new ApplicationContextInitializationCodeGenerator(applicationContext, generationContext);
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
new BeanFactoryInitializationAotContributions(beanFactory).applyTo(generationContext, codeGenerator);
return codeGenerator.getClassName();
return codeGenerator.getGeneratedClass().getName();
});
}
@@ -127,9 +127,8 @@ class ApplicationContextInitializationCodeGenerator implements BeanFactoryInitia
return ArgumentCodeGenerator.from(new InitializerMethodArgumentCodeGenerator());
}
@Override
public ClassName getClassName() {
return this.generatedClass.getName();
GeneratedClass getGeneratedClass() {
return this.generatedClass;
}
@Override
@@ -623,22 +623,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
finishRefresh();
}
catch (RuntimeException | Error ex) {
catch (RuntimeException | Error ex ) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Stop already started Lifecycle beans to avoid dangling resources.
if (this.lifecycleProcessor != null && this.lifecycleProcessor.isRunning()) {
try {
this.lifecycleProcessor.stop();
}
catch (Throwable ex2) {
logger.warn("Exception thrown from LifecycleProcessor on cancelled refresh", ex2);
}
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();

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