mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90f9c0929b |
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.43
|
||||
javaFormatVersion=0.0.42
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.23.1");
|
||||
checkstyle.setToolVersion("10.23.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,8 +21,6 @@ import java.util.Map;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.api.tasks.testing.TestFrameworkOptions;
|
||||
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
|
||||
import org.gradle.testretry.TestRetryPlugin;
|
||||
import org.gradle.testretry.TestRetryTaskExtension;
|
||||
|
||||
@@ -36,7 +34,6 @@ import org.gradle.testretry.TestRetryTaskExtension;
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class TestConventions {
|
||||
|
||||
@@ -53,12 +50,7 @@ class TestConventions {
|
||||
}
|
||||
|
||||
private void configureTests(Project project, Test test) {
|
||||
TestFrameworkOptions existingOptions = test.getOptions();
|
||||
test.useJUnitPlatform(options -> {
|
||||
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
|
||||
options.copyFrom(junitPlatformOptions);
|
||||
}
|
||||
});
|
||||
test.useJUnitPlatform();
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'java-platform'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.3'
|
||||
}
|
||||
|
||||
description = "Spring Framework API Docs"
|
||||
@@ -21,7 +21,6 @@ dependencies {
|
||||
|
||||
javadoc {
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
|
||||
@@ -103,14 +103,6 @@ for details.
|
||||
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
||||
for details.
|
||||
|
||||
| `spring.placeholder.escapeCharacter.default`
|
||||
| The default escape character for property placeholder support. If not set, `'\'` will
|
||||
be used. Can be set to a custom escape character or an empty string to disable support
|
||||
for an escape character. The default escape character be explicitly overridden in
|
||||
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
|
||||
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
|
||||
for details.
|
||||
|
||||
| `spring.test.aot.processing.failOnError`
|
||||
| A boolean flag that controls whether errors encountered during AOT processing in the
|
||||
_Spring TestContext Framework_ should result in an exception that fails the overall process.
|
||||
|
||||
+2
-5
@@ -101,11 +101,8 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
||||
|
||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||
placeholder could not be resolved. It is also possible to use methods like
|
||||
`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or
|
||||
`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default
|
||||
escape character can be changed or disabled globally by setting the
|
||||
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
|
||||
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
||||
`setEscapeCharacter` to customize placeholders.
|
||||
|
||||
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||
will get properties from `application.properties` and `application.yml` files.
|
||||
|
||||
@@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the
|
||||
|
||||
|
||||
[[beans-factory-placeholderconfigurer]]
|
||||
=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer`
|
||||
=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
|
||||
|
||||
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
|
||||
from a bean definition in a separate file by using the standard Java `Properties` format.
|
||||
@@ -341,8 +341,8 @@ with placeholder values is defined:
|
||||
|
||||
The example shows properties configured from an external `Properties` file. At runtime,
|
||||
a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some
|
||||
properties of the `DataSource`. The values to replace are specified as placeholders of the
|
||||
form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style.
|
||||
properties of the DataSource. The values to replace are specified as placeholders of the
|
||||
form pass:q[`${property-name}`], which follows the Ant and log4j and JSP EL style.
|
||||
|
||||
The actual values come from another file in the standard Java `Properties` format:
|
||||
|
||||
@@ -355,15 +355,11 @@ jdbc.password=root
|
||||
----
|
||||
|
||||
Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and
|
||||
the same applies for other placeholder values that match keys in the properties file. The
|
||||
`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
||||
attributes of a bean definition. Furthermore, you can customize the placeholder prefix,
|
||||
suffix, default value separator, and escape character. In addition, the default escape
|
||||
character can be changed or disabled globally by setting the
|
||||
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
|
||||
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
the same applies for other placeholder values that match keys in the properties file.
|
||||
The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
||||
attributes of a bean definition. Furthermore, you can customize the placeholder prefix and suffix.
|
||||
|
||||
With the `context` namespace, you can configure property placeholders
|
||||
With the `context` namespace introduced in Spring 2.5, you can configure property placeholders
|
||||
with a dedicated configuration element. You can provide one or more locations as a
|
||||
comma-separated list in the `location` attribute, as the following example shows:
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use
|
||||
instead of `@Value` annotations.
|
||||
|
||||
As an alternative, you can customize the property placeholder prefix by declaring the
|
||||
following `PropertySourcesPlaceholderConfigurer` bean:
|
||||
following configuration beans:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@@ -200,10 +200,8 @@ following `PropertySourcesPlaceholderConfigurer` bean:
|
||||
}
|
||||
----
|
||||
|
||||
You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use
|
||||
the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by
|
||||
declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example
|
||||
shows:
|
||||
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
|
||||
that uses the `${...}` syntax, with configuration beans, as the following example shows:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@@ -217,9 +215,6 @@ shows:
|
||||
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
|
||||
----
|
||||
|
||||
In addition, the default escape character can be changed or disabled globally by setting
|
||||
the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or
|
||||
via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
|
||||
|
||||
[[checked-exceptions]]
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
= Spring JUnit 4 Testing Annotations
|
||||
|
||||
The following annotations are supported only when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner],
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules], or
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner], xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules]
|
||||
, or xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
|
||||
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-ifprofilevalue[`@IfProfileValue`]
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-profilevaluesourceconfiguration[`@ProfileValueSourceConfiguration`]
|
||||
|
||||
+178
-195
@@ -1,9 +1,166 @@
|
||||
[[testcontext-support-classes]]
|
||||
= TestContext Framework Support Classes
|
||||
|
||||
This section describes the various classes that support the Spring TestContext Framework
|
||||
in JUnit and TestNG.
|
||||
This section describes the various classes that support the Spring TestContext Framework.
|
||||
|
||||
[[testcontext-junit4-runner]]
|
||||
== Spring JUnit 4 Runner
|
||||
|
||||
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
|
||||
runner (supported on JUnit 4.12 or higher). By annotating test classes with
|
||||
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
|
||||
variant, developers can implement standard JUnit 4-based unit and integration tests and
|
||||
simultaneously reap the benefits of the TestContext framework, such as support for
|
||||
loading application contexts, dependency injection of test instances, transactional test
|
||||
method execution, and so on. If you want to use the Spring TestContext Framework with an
|
||||
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
|
||||
(such as the `MockitoJUnitRunner`), you can, optionally, use
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules] instead.
|
||||
|
||||
The following code listing shows the minimal requirements for configuring a test class to
|
||||
run with the custom Spring `Runner`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@TestExecutionListeners({})
|
||||
public class SimpleTest {
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@TestExecutionListeners
|
||||
class SimpleTest {
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
|
||||
disable the default listeners, which otherwise would require an `ApplicationContext` to
|
||||
be configured through `@ContextConfiguration`.
|
||||
|
||||
[[testcontext-junit4-rules]]
|
||||
== Spring JUnit 4 Rules
|
||||
|
||||
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
|
||||
4 rules (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `SpringClassRule`
|
||||
* `SpringMethodRule`
|
||||
|
||||
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
|
||||
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
|
||||
instance-level and method-level features of the Spring TestContext Framework.
|
||||
|
||||
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
|
||||
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
|
||||
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
|
||||
third-party runners (such as the `MockitoJUnitRunner`).
|
||||
|
||||
To support the full functionality of the TestContext framework, you must combine a
|
||||
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
|
||||
to declare these rules in an integration test:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
public class IntegrationTest {
|
||||
|
||||
@ClassRule
|
||||
public static final SpringClassRule springClassRule = new SpringClassRule();
|
||||
|
||||
@Rule
|
||||
public final SpringMethodRule springMethodRule = new SpringMethodRule();
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
class IntegrationTest {
|
||||
|
||||
@Rule
|
||||
val springMethodRule = SpringMethodRule()
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
val springClassRule = SpringClassRule()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[testcontext-support-classes-junit4]]
|
||||
== JUnit 4 Support Classes
|
||||
|
||||
The `org.springframework.test.context.junit4` package provides the following support
|
||||
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `AbstractJUnit4SpringContextTests`
|
||||
* `AbstractTransactionalJUnit4SpringContextTests`
|
||||
|
||||
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
|
||||
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
|
||||
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
|
||||
`protected` `applicationContext` instance variable that you can use to perform explicit
|
||||
bean lookups or to test the state of the context as a whole.
|
||||
|
||||
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
|
||||
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
|
||||
access. This class expects a `javax.sql.DataSource` bean and a
|
||||
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
|
||||
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
|
||||
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
|
||||
database. You can use such queries to confirm database state both before and after
|
||||
running database-related application code, and Spring ensures that such queries run in
|
||||
the scope of the same transaction as the application code. When used in conjunction with
|
||||
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
|
||||
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
|
||||
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
||||
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
|
||||
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
|
||||
|
||||
TIP: These classes are a convenience for extension. If you do not want your test classes
|
||||
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
|
||||
classes by using `@RunWith(SpringRunner.class)` or xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules]
|
||||
.
|
||||
|
||||
[[testcontext-junit-jupiter-extension]]
|
||||
== SpringExtension for JUnit Jupiter
|
||||
@@ -20,17 +177,14 @@ following features above and beyond the feature set that Spring supports for JUn
|
||||
TestNG:
|
||||
|
||||
* Dependency injection for test constructors, test methods, and test lifecycle callback
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
|
||||
Injection with the `SpringExtension`] for further details.
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency Injection with the `SpringExtension`] for further details.
|
||||
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
|
||||
test execution] based on SpEL expressions, environment variables, system properties,
|
||||
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
for further details and examples.
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details and examples.
|
||||
* Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See
|
||||
the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in
|
||||
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for
|
||||
further details.
|
||||
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for further details.
|
||||
|
||||
The following code listing shows how to configure a test class to use the
|
||||
`SpringExtension` in conjunction with `@ContextConfiguration`:
|
||||
@@ -153,8 +307,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
for further details.
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details.
|
||||
|
||||
[[testcontext-junit-jupiter-di]]
|
||||
=== Dependency Injection with the `SpringExtension`
|
||||
@@ -165,9 +318,10 @@ extension API from JUnit Jupiter, which lets Spring provide dependency injection
|
||||
constructors, test methods, and test lifecycle callback methods.
|
||||
|
||||
Specifically, the `SpringExtension` can inject dependencies from the test's
|
||||
`ApplicationContext` into test constructors and methods that are annotated with Spring's
|
||||
`@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`, `@AfterAll`,
|
||||
`@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and others.
|
||||
`ApplicationContext` into test constructors and methods that are annotated with
|
||||
Spring's `@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`,
|
||||
`@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`,
|
||||
and others.
|
||||
|
||||
|
||||
[[testcontext-junit-jupiter-di-constructor]]
|
||||
@@ -187,9 +341,8 @@ autowirable if one of the following conditions is met (in order of precedence).
|
||||
attribute set to `ALL`.
|
||||
* The default _test constructor autowire mode_ has been changed to `ALL`.
|
||||
|
||||
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
|
||||
for details on the use of `@TestConstructor` and how to change the global _test
|
||||
constructor autowire mode_.
|
||||
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`] for details on the use of
|
||||
`@TestConstructor` and how to change the global _test constructor autowire mode_.
|
||||
|
||||
WARNING: If the constructor for a test class is considered to be _autowirable_, Spring
|
||||
assumes the responsibility for resolving arguments for all parameters in the constructor.
|
||||
@@ -254,9 +407,8 @@ Kotlin::
|
||||
Note that this feature lets test dependencies be `final` and therefore immutable.
|
||||
|
||||
If the `spring.test.constructor.autowire.mode` property is to `all` (see
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]),
|
||||
we can omit the declaration of `@Autowired` on the constructor in the previous example,
|
||||
resulting in the following.
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]), we can omit the declaration of
|
||||
`@Autowired` on the constructor in the previous example, resulting in the following.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -401,19 +553,17 @@ honor `@NestedTestConfiguration` semantics.
|
||||
In order to allow development teams to change the default to `OVERRIDE` – for example,
|
||||
for compatibility with Spring Framework 5.0 through 5.2 – the default mode can be changed
|
||||
globally via a JVM system property or a `spring.properties` file in the root of the
|
||||
classpath. See the
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
|
||||
note for details.
|
||||
classpath. See the xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
|
||||
note for details.
|
||||
|
||||
Although the following "Hello World" example is very simplistic, it shows how to declare
|
||||
common configuration on a top-level class that is inherited by its `@Nested` test
|
||||
classes. In this particular example, only the `TestConfig` configuration class is
|
||||
inherited. Each nested test class provides its own set of active profiles, resulting in a
|
||||
distinct `ApplicationContext` for each nested test class (see
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details).
|
||||
Consult the list of
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations]
|
||||
to see which annotations can be inherited in `@Nested` test classes.
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details). Consult the list of
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations] to see
|
||||
which annotations can be inherited in `@Nested` test classes.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -476,174 +626,8 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[testcontext-junit4-support]]
|
||||
== JUnit 4 Support
|
||||
|
||||
[[testcontext-junit4-runner]]
|
||||
=== Spring JUnit 4 Runner
|
||||
|
||||
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
|
||||
runner (supported on JUnit 4.12 or higher). By annotating test classes with
|
||||
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
|
||||
variant, developers can implement standard JUnit 4-based unit and integration tests and
|
||||
simultaneously reap the benefits of the TestContext framework, such as support for
|
||||
loading application contexts, dependency injection of test instances, transactional test
|
||||
method execution, and so on. If you want to use the Spring TestContext Framework with an
|
||||
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
|
||||
(such as the `MockitoJUnitRunner`), you can, optionally, use
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules]
|
||||
instead.
|
||||
|
||||
The following code listing shows the minimal requirements for configuring a test class to
|
||||
run with the custom Spring `Runner`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@TestExecutionListeners({})
|
||||
public class SimpleTest {
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@TestExecutionListeners
|
||||
class SimpleTest {
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
|
||||
disable the default listeners, which otherwise would require an `ApplicationContext` to
|
||||
be configured through `@ContextConfiguration`.
|
||||
|
||||
[[testcontext-junit4-rules]]
|
||||
=== Spring JUnit 4 Rules
|
||||
|
||||
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
|
||||
4 rules (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `SpringClassRule`
|
||||
* `SpringMethodRule`
|
||||
|
||||
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
|
||||
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
|
||||
instance-level and method-level features of the Spring TestContext Framework.
|
||||
|
||||
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
|
||||
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
|
||||
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
|
||||
third-party runners (such as the `MockitoJUnitRunner`).
|
||||
|
||||
To support the full functionality of the TestContext framework, you must combine a
|
||||
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
|
||||
to declare these rules in an integration test:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
public class IntegrationTest {
|
||||
|
||||
@ClassRule
|
||||
public static final SpringClassRule springClassRule = new SpringClassRule();
|
||||
|
||||
@Rule
|
||||
public final SpringMethodRule springMethodRule = new SpringMethodRule();
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
class IntegrationTest {
|
||||
|
||||
@Rule
|
||||
val springMethodRule = SpringMethodRule()
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
val springClassRule = SpringClassRule()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[testcontext-support-classes-junit4]]
|
||||
=== JUnit 4 Base Classes
|
||||
|
||||
The `org.springframework.test.context.junit4` package provides the following support
|
||||
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `AbstractJUnit4SpringContextTests`
|
||||
* `AbstractTransactionalJUnit4SpringContextTests`
|
||||
|
||||
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
|
||||
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
|
||||
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
|
||||
`protected` `applicationContext` instance variable that you can use to perform explicit
|
||||
bean lookups or to test the state of the context as a whole.
|
||||
|
||||
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
|
||||
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
|
||||
access. This class expects a `javax.sql.DataSource` bean and a
|
||||
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
|
||||
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
|
||||
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
|
||||
database. You can use such queries to confirm database state both before and after
|
||||
running database-related application code, and Spring ensures that such queries run in
|
||||
the scope of the same transaction as the application code. When used in conjunction with
|
||||
an ORM tool, be sure to avoid
|
||||
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
|
||||
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
|
||||
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
||||
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
|
||||
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
|
||||
|
||||
TIP: These classes are a convenience for extension. If you do not want your test classes
|
||||
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
|
||||
classes by using `@RunWith(SpringRunner.class)` or
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules].
|
||||
|
||||
|
||||
[[testcontext-support-classes-testng]]
|
||||
== TestNG Support
|
||||
== TestNG Support Classes
|
||||
|
||||
The `org.springframework.test.context.testng` package provides the following support
|
||||
classes for TestNG based test cases:
|
||||
@@ -666,8 +650,7 @@ extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protec
|
||||
database. You can use such queries to confirm database state both before and after
|
||||
running database-related application code, and Spring ensures that such queries run in
|
||||
the scope of the same transaction as the application code. When used in conjunction with
|
||||
an ORM tool, be sure to avoid
|
||||
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
|
||||
`AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that
|
||||
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
||||
|
||||
@@ -234,8 +234,8 @@ Kotlin::
|
||||
--
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this, for example, to parameterize a base URL based on
|
||||
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this to, for example, parameterize a base URL based on
|
||||
some external configuration.
|
||||
|
||||
NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support.
|
||||
|
||||
@@ -7,17 +7,17 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.7"))
|
||||
api(platform("io.netty:netty-bom:4.1.121.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.5"))
|
||||
api(platform("io.netty:netty-bom:4.1.119.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.6"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.4"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.21"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.21"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.18"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.18"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.12.2"))
|
||||
@@ -100,7 +100,7 @@ dependencies {
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.4")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.3")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.7
|
||||
version=6.2.6
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
@@ -69,7 +69,7 @@ normalization {
|
||||
|
||||
javadoc {
|
||||
description = "Generates project-level javadoc for use in -javadoc jar"
|
||||
failOnError = true
|
||||
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+2
-2
@@ -70,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+25
-36
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,10 +20,8 @@ import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.core.env.AbstractPropertyResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for property resource configurers that resolve placeholders
|
||||
@@ -39,16 +37,16 @@ import org.springframework.util.SystemPropertyUtils;
|
||||
*
|
||||
* <pre class="code">
|
||||
* <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
* <property name="driverClassName" value="${jdbc.driver}" />
|
||||
* <property name="url" value="jdbc:${jdbc.dbname}" />
|
||||
* <property name="driverClassName" value="${driver}" />
|
||||
* <property name="url" value="jdbc:${dbname}" />
|
||||
* </bean>
|
||||
* </pre>
|
||||
*
|
||||
* Example properties file:
|
||||
*
|
||||
* <pre class="code">
|
||||
* jdbc.driver=com.mysql.jdbc.Driver
|
||||
* jdbc.dbname=mysql:mydb</pre>
|
||||
* driver=com.mysql.jdbc.Driver
|
||||
* dbname=mysql:mydb</pre>
|
||||
*
|
||||
* Annotated bean definitions may take advantage of property replacement using
|
||||
* the {@link org.springframework.beans.factory.annotation.Value @Value} annotation:
|
||||
@@ -81,12 +79,11 @@ import org.springframework.util.SystemPropertyUtils;
|
||||
* <p>Example XML property with default value:
|
||||
*
|
||||
* <pre class="code">
|
||||
* <property name="url" value="jdbc:${jdbc.dbname:defaultdb}" />
|
||||
* <property name="url" value="jdbc:${dbname:defaultdb}" />
|
||||
* </pre>
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
* @see PropertyPlaceholderConfigurer
|
||||
* @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer
|
||||
@@ -95,21 +92,16 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
implements BeanNameAware, BeanFactoryAware {
|
||||
|
||||
/** Default placeholder prefix: {@value}. */
|
||||
public static final String DEFAULT_PLACEHOLDER_PREFIX = SystemPropertyUtils.PLACEHOLDER_PREFIX;
|
||||
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
|
||||
|
||||
/** Default placeholder suffix: {@value}. */
|
||||
public static final String DEFAULT_PLACEHOLDER_SUFFIX = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
|
||||
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
|
||||
|
||||
/** Default value separator: {@value}. */
|
||||
public static final String DEFAULT_VALUE_SEPARATOR = SystemPropertyUtils.VALUE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Default escape character: {@code '\'}.
|
||||
* @since 6.2
|
||||
* @see AbstractPropertyResolver#getDefaultEscapeCharacter()
|
||||
*/
|
||||
public static final Character DEFAULT_ESCAPE_CHARACTER = SystemPropertyUtils.ESCAPE_CHARACTER;
|
||||
public static final String DEFAULT_VALUE_SEPARATOR = ":";
|
||||
|
||||
/** Default escape character: {@code '\'}. */
|
||||
public static final Character DEFAULT_ESCAPE_CHARACTER = '\\';
|
||||
|
||||
/** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX}. */
|
||||
protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
|
||||
@@ -121,11 +113,9 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
@Nullable
|
||||
protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
|
||||
*/
|
||||
/** Defaults to {@link #DEFAULT_ESCAPE_CHARACTER}. */
|
||||
@Nullable
|
||||
protected Character escapeCharacter = AbstractPropertyResolver.getDefaultEscapeCharacter();
|
||||
protected Character escapeCharacter = DEFAULT_ESCAPE_CHARACTER;
|
||||
|
||||
protected boolean trimValues = false;
|
||||
|
||||
@@ -143,7 +133,7 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
|
||||
/**
|
||||
* Set the prefix that a placeholder string starts with.
|
||||
* <p>The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}.
|
||||
* The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}.
|
||||
*/
|
||||
public void setPlaceholderPrefix(String placeholderPrefix) {
|
||||
this.placeholderPrefix = placeholderPrefix;
|
||||
@@ -151,32 +141,31 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
|
||||
/**
|
||||
* Set the suffix that a placeholder string ends with.
|
||||
* <p>The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}.
|
||||
* The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}.
|
||||
*/
|
||||
public void setPlaceholderSuffix(String placeholderSuffix) {
|
||||
this.placeholderSuffix = placeholderSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the separating character between the placeholder variable and the
|
||||
* associated default value, or {@code null} if no such special character
|
||||
* should be processed as a value separator.
|
||||
* <p>The default is {@value #DEFAULT_VALUE_SEPARATOR}.
|
||||
* Specify the separating character between the placeholder variable
|
||||
* and the associated default value, or {@code null} if no such
|
||||
* special character should be processed as a value separator.
|
||||
* The default is {@value #DEFAULT_VALUE_SEPARATOR}.
|
||||
*/
|
||||
public void setValueSeparator(@Nullable String valueSeparator) {
|
||||
this.valueSeparator = valueSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the escape character to use to ignore the
|
||||
* {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the
|
||||
* {@linkplain #setValueSeparator(String) value separator}, or {@code null}
|
||||
* if no escaping should take place.
|
||||
* <p>The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
|
||||
* Specify the escape character to use to ignore placeholder prefix
|
||||
* or value separator, or {@code null} if no escaping should take
|
||||
* place.
|
||||
* <p>Default is {@link #DEFAULT_ESCAPE_CHARACTER}.
|
||||
* @since 6.2
|
||||
*/
|
||||
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
||||
this.escapeCharacter = escapeCharacter;
|
||||
public void setEscapeCharacter(@Nullable Character escsEscapeCharacter) {
|
||||
this.escapeCharacter = escsEscapeCharacter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,14 +35,12 @@ import org.springframework.beans.factory.BeanInitializationException;
|
||||
*
|
||||
* Example properties file:
|
||||
*
|
||||
* <pre class="code">
|
||||
* dataSource.driverClassName=com.mysql.jdbc.Driver
|
||||
* <pre class="code">dataSource.driverClassName=com.mysql.jdbc.Driver
|
||||
* dataSource.url=jdbc:mysql:mydb</pre>
|
||||
*
|
||||
* <p>In contrast to {@link PropertyPlaceholderConfigurer}, the original definition
|
||||
* can have default values or no values at all for such bean properties. If an
|
||||
* overriding properties file does not have an entry for a certain bean property,
|
||||
* the default context definition is used.
|
||||
* In contrast to PropertyPlaceholderConfigurer, the original definition can have default
|
||||
* values or no values at all for such bean properties. If an overriding properties file does
|
||||
* not have an entry for a certain bean property, the default context definition is used.
|
||||
*
|
||||
* <p>Note that the context definition <i>is not</i> aware of being overridden;
|
||||
* so this is not immediately obvious when looking at the XML definition file.
|
||||
|
||||
+3
-11
@@ -997,17 +997,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
*/
|
||||
@Nullable
|
||||
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
|
||||
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
||||
if (lockFlag == null) {
|
||||
this.singletonLock.lock();
|
||||
}
|
||||
else {
|
||||
boolean locked = (lockFlag && this.singletonLock.tryLock());
|
||||
if (!locked) {
|
||||
// Avoid shortcut FactoryBean instance but allow for subsequent type-based resolution.
|
||||
resolveBeanClass(mbd, beanName);
|
||||
return null;
|
||||
}
|
||||
boolean locked = this.singletonLock.tryLock();
|
||||
if (!locked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+3
-4
@@ -1066,9 +1066,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@Nullable
|
||||
protected Boolean isCurrentThreadAllowedToHoldSingletonLock() {
|
||||
String mainThreadPrefix = this.mainThreadPrefix;
|
||||
if (mainThreadPrefix != null) {
|
||||
// We only differentiate in the preInstantiateSingletons phase, using
|
||||
// the volatile mainThreadPrefix field as an indicator for that phase.
|
||||
if (this.mainThreadPrefix != null) {
|
||||
// We only differentiate in the preInstantiateSingletons phase.
|
||||
|
||||
PreInstantiation preInstantiation = this.preInstantiationThread.get();
|
||||
if (preInstantiation != null) {
|
||||
@@ -1088,7 +1087,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
else if (this.strictLocking == null) {
|
||||
// No explicit locking configuration -> infer appropriate locking.
|
||||
if (!getThreadNamePrefix().equals(mainThreadPrefix)) {
|
||||
if (mainThreadPrefix != null && !getThreadNamePrefix().equals(mainThreadPrefix)) {
|
||||
// An unmanaged thread (assumed to be application-internal) with lenient locking,
|
||||
// and not part of the same thread pool that provided the main bootstrap thread
|
||||
// (excluding scenarios where we are hit by multiple external bootstrap threads).
|
||||
|
||||
+5
-7
@@ -271,15 +271,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
// Fallback as of 6.2: process given singleton bean outside of singleton lock.
|
||||
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
|
||||
// when triggering creation of other beans as dependencies of the current bean.
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" +
|
||||
Thread.currentThread().getName() + "\" while other thread holds " +
|
||||
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
|
||||
}
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
if (logger.isInfoEnabled()) {
|
||||
Set<String> lockedBeans = new HashSet<>(this.singletonsCurrentlyInCreation);
|
||||
lockedBeans.removeAll(this.singletonsInLenientCreation);
|
||||
logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" +
|
||||
currentThread.getName() + "\" while other thread holds singleton " +
|
||||
"lock for other beans " + lockedBeans);
|
||||
}
|
||||
this.singletonsInLenientCreation.add(beanName);
|
||||
}
|
||||
finally {
|
||||
|
||||
+8
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -118,15 +118,7 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
*/
|
||||
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
|
||||
if (factory.isSingleton() && containsSingleton(beanName)) {
|
||||
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
||||
boolean locked;
|
||||
if (lockFlag == null) {
|
||||
this.singletonLock.lock();
|
||||
locked = true;
|
||||
}
|
||||
else {
|
||||
locked = (lockFlag && this.singletonLock.tryLock());
|
||||
}
|
||||
this.singletonLock.lock();
|
||||
try {
|
||||
Object object = this.factoryBeanObjectCache.get(beanName);
|
||||
if (object == null) {
|
||||
@@ -139,13 +131,11 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
}
|
||||
else {
|
||||
if (shouldPostProcess) {
|
||||
if (locked) {
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
try {
|
||||
object = postProcessObjectFromFactoryBean(object, beanName);
|
||||
}
|
||||
@@ -154,9 +144,7 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
"Post-processing of FactoryBean's singleton object failed", ex);
|
||||
}
|
||||
finally {
|
||||
if (locked) {
|
||||
afterSingletonCreation(beanName);
|
||||
}
|
||||
afterSingletonCreation(beanName);
|
||||
}
|
||||
}
|
||||
if (containsSingleton(beanName)) {
|
||||
@@ -167,9 +155,7 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
return object;
|
||||
}
|
||||
finally {
|
||||
if (locked) {
|
||||
this.singletonLock.unlock();
|
||||
}
|
||||
this.singletonLock.unlock();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -59,10 +59,3 @@ dependencies {
|
||||
testRuntimeOnly("org.javamoney:moneta")
|
||||
testRuntimeOnly("org.junit.vintage:junit-vintage-engine") // for @Inject TCK
|
||||
}
|
||||
|
||||
test {
|
||||
description = "Runs JUnit Jupiter tests and the @Inject TCK via JUnit Vintage."
|
||||
useJUnitPlatform {
|
||||
includeEngines "junit-jupiter", "junit-vintage"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,7 +50,6 @@ public class ApplicationContextAotGenerator {
|
||||
*/
|
||||
public ClassName processAheadOfTime(GenericApplicationContext applicationContext,
|
||||
GenerationContext generationContext) {
|
||||
|
||||
return withCglibClassHandler(new CglibClassHandler(generationContext), () -> {
|
||||
applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());
|
||||
ApplicationContextInitializationCodeGenerator codeGenerator =
|
||||
|
||||
+3
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -80,9 +80,8 @@ public abstract class ContextAotProcessor extends AbstractAotProcessor<ClassName
|
||||
@Override
|
||||
protected ClassName doProcess() {
|
||||
deleteExistingOutput();
|
||||
try (GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass())) {
|
||||
return performAotProcessing(applicationContext);
|
||||
}
|
||||
GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass());
|
||||
return performAotProcessing(applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -100,7 +100,6 @@ public class ReflectiveProcessorAotContributionBuilder {
|
||||
return (!this.classes.isEmpty() ? new AotContribution(this.classes) : null);
|
||||
}
|
||||
|
||||
|
||||
private static class AotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private final Class<?>[] classes;
|
||||
@@ -114,8 +113,8 @@ public class ReflectiveProcessorAotContributionBuilder {
|
||||
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
|
||||
registrar.registerRuntimeHints(runtimeHints, this.classes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ReflectiveClassPathScanner extends ClassPathScanningCandidateComponentProvider {
|
||||
|
||||
|
||||
+28
-83
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,7 @@ import org.springframework.core.env.ConfigurablePropertyResolver;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.core.env.PropertySourcesPropertyResolver;
|
||||
@@ -48,7 +49,7 @@ import org.springframework.util.StringValueResolver;
|
||||
* XSD documentation for complete details.
|
||||
*
|
||||
* <p>Any local properties (for example, those added via {@link #setProperties}, {@link #setLocations}
|
||||
* et al.) are added as a single {@link PropertySource}. Search precedence of local properties is
|
||||
* et al.) are added as a {@code PropertySource}. Search precedence of local properties is
|
||||
* based on the value of the {@link #setLocalOverride localOverride} property, which is by
|
||||
* default {@code false} meaning that local properties are to be searched last, after all
|
||||
* environment property sources.
|
||||
@@ -100,9 +101,8 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>{@code PropertySources} from the given {@link Environment} will be searched
|
||||
* when replacing ${...} placeholders.
|
||||
* {@code PropertySources} from the given {@link Environment}
|
||||
* will be searched when replacing ${...} placeholders.
|
||||
* @see #setPropertySources
|
||||
* @see #postProcessBeanFactory
|
||||
*/
|
||||
@@ -132,11 +132,28 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
if (this.propertySources == null) {
|
||||
this.propertySources = new MutablePropertySources();
|
||||
if (this.environment != null) {
|
||||
PropertySource<?> environmentPropertySource =
|
||||
(this.environment instanceof ConfigurableEnvironment configurableEnvironment ?
|
||||
new ConfigurableEnvironmentPropertySource(configurableEnvironment) :
|
||||
new FallbackEnvironmentPropertySource(this.environment));
|
||||
this.propertySources.addLast(environmentPropertySource);
|
||||
PropertyResolver propertyResolver = this.environment;
|
||||
// If the ignoreUnresolvablePlaceholders flag is set to true, we have to create a
|
||||
// local PropertyResolver to enforce that setting, since the Environment is most
|
||||
// likely not configured with ignoreUnresolvablePlaceholders set to true.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27947
|
||||
if (this.ignoreUnresolvablePlaceholders &&
|
||||
(this.environment instanceof ConfigurableEnvironment configurableEnvironment)) {
|
||||
PropertySourcesPropertyResolver resolver =
|
||||
new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources());
|
||||
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
|
||||
propertyResolver = resolver;
|
||||
}
|
||||
PropertyResolver propertyResolverToUse = propertyResolver;
|
||||
this.propertySources.addLast(
|
||||
new PropertySource<>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
|
||||
@Override
|
||||
@Nullable
|
||||
public String getProperty(String key) {
|
||||
return propertyResolverToUse.getProperty(key);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
try {
|
||||
PropertySource<?> localPropertySource =
|
||||
@@ -159,7 +176,6 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
|
||||
/**
|
||||
* Create a {@link ConfigurablePropertyResolver} for the specified property sources.
|
||||
* <p>The default implementation creates a {@link PropertySourcesPropertyResolver}.
|
||||
* @param propertySources the property sources to use
|
||||
* @since 6.0.12
|
||||
*/
|
||||
@@ -172,7 +188,7 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
* placeholders with values from the given properties.
|
||||
*/
|
||||
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
|
||||
ConfigurablePropertyResolver propertyResolver) throws BeansException {
|
||||
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
|
||||
|
||||
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
|
||||
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
|
||||
@@ -218,75 +234,4 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
return this.appliedPropertySources;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom {@link PropertySource} that delegates to the
|
||||
* {@link ConfigurableEnvironment#getPropertySources() PropertySources} in a
|
||||
* {@link ConfigurableEnvironment}.
|
||||
* @since 6.2.7
|
||||
*/
|
||||
private static class ConfigurableEnvironmentPropertySource extends PropertySource<ConfigurableEnvironment> {
|
||||
|
||||
ConfigurableEnvironmentPropertySource(ConfigurableEnvironment environment) {
|
||||
super(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsProperty(String name) {
|
||||
for (PropertySource<?> propertySource : super.source.getPropertySources()) {
|
||||
if (propertySource.containsProperty(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getProperty(String name) {
|
||||
for (PropertySource<?> propertySource : super.source.getPropertySources()) {
|
||||
Object candidate = propertySource.getProperty(name);
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigurableEnvironmentPropertySource {propertySources=" + super.source.getPropertySources() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fallback {@link PropertySource} that delegates to a raw {@link Environment}.
|
||||
* <p>Should never apply in a regular scenario, since the {@code Environment}
|
||||
* in an {@code ApplicationContext} should always be a {@link ConfigurableEnvironment}.
|
||||
* @since 6.2.7
|
||||
*/
|
||||
private static class FallbackEnvironmentPropertySource extends PropertySource<Environment> {
|
||||
|
||||
FallbackEnvironmentPropertySource(Environment environment) {
|
||||
super(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsProperty(String name) {
|
||||
return super.source.containsProperty(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getProperty(String name) {
|
||||
return super.source.getProperty(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FallbackEnvironmentPropertySource {environment=" + super.source + "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -146,12 +146,6 @@ import org.springframework.core.Ordered;
|
||||
* compile-time weaving or load-time weaving applying the aspect to the affected classes.
|
||||
* There is no proxy involved in such a scenario; local calls will be intercepted as well.
|
||||
*
|
||||
* <p><b>Note: {@code @EnableAsync} applies to its local application context only,
|
||||
* allowing for selective activation at different levels.</b> Please redeclare
|
||||
* {@code @EnableAsync} in each individual context, for example, the common root web
|
||||
* application context and any separate {@code DispatcherServlet} application contexts,
|
||||
* if you need to apply its behavior at multiple levels.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -549,13 +550,14 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
* <p>Mark fields as disallowed, for example to avoid unwanted
|
||||
* modifications by malicious users when binding HTTP request parameters.
|
||||
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts),
|
||||
* as well as direct equality.
|
||||
* <p>The default implementation of this method stores disallowed field
|
||||
* patterns in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String)
|
||||
* canonical} form, and subsequently pattern matching in {@link #isAllowed}
|
||||
* is case-insensitive. Subclasses that override this method must therefore
|
||||
* take this transformation into account.
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality.
|
||||
* <p>The default implementation of this method stores disallowed field patterns
|
||||
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
|
||||
* form and also transforms disallowed field patterns to
|
||||
* {@linkplain String#toLowerCase() lowercase} to support case-insensitive
|
||||
* pattern matching in {@link #isAllowed}. Subclasses which override this
|
||||
* method must therefore take both of these transformations into account.
|
||||
* <p>More sophisticated matching can be implemented by overriding the
|
||||
* {@link #isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
|
||||
@@ -573,7 +575,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
else {
|
||||
String[] fieldPatterns = new String[disallowedFields.length];
|
||||
for (int i = 0; i < fieldPatterns.length; i++) {
|
||||
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]);
|
||||
String field = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]);
|
||||
fieldPatterns[i] = field.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
this.disallowedFields = fieldPatterns;
|
||||
}
|
||||
@@ -1299,9 +1302,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
* Determine if the given field is allowed for binding.
|
||||
* <p>Invoked for each passed-in property value.
|
||||
* <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts),
|
||||
* as well as direct equality, in the configured lists of allowed field
|
||||
* patterns and disallowed field patterns.
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality, in the configured lists of allowed field patterns
|
||||
* and disallowed field patterns.
|
||||
* <p>Matching against allowed field patterns is case-sensitive; whereas,
|
||||
* matching against disallowed field patterns is case-insensitive.
|
||||
* <p>A field matching a disallowed pattern will not be accepted even if it
|
||||
@@ -1317,13 +1320,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
protected boolean isAllowed(String field) {
|
||||
String[] allowed = getAllowedFields();
|
||||
String[] disallowed = getDisallowedFields();
|
||||
if (!ObjectUtils.isEmpty(allowed) && !PatternMatchUtils.simpleMatch(allowed, field)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(disallowed)) {
|
||||
return !PatternMatchUtils.simpleMatchIgnoreCase(disallowed, field);
|
||||
}
|
||||
return true;
|
||||
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
|
||||
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase(Locale.ROOT))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!DOCTYPE rmi PUBLIC "-//BEA Systems, Inc.//RMI Runtime DTD 1.0//EN" "rmi.dtd">
|
||||
|
||||
<!--
|
||||
- Special WebLogic deployment descriptor for Spring's RMI invoker.
|
||||
- Only applied by WebLogic Server, ignored on other platforms.
|
||||
-->
|
||||
<rmi name="org.springframework.remoting.rmi.RmiInvocationWrapper">
|
||||
<cluster clusterable="true"/>
|
||||
<method name="getTargetInterfaceName" idempotent="true"/>
|
||||
</rmi>
|
||||
+2
-13
@@ -24,7 +24,6 @@ import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
@@ -244,24 +243,14 @@ class BackgroundBootstrapTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FactoryBean<TestBean> testBean4() {
|
||||
public TestBean testBean4() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
TestBean testBean = new TestBean();
|
||||
return new FactoryBean<>() {
|
||||
@Override
|
||||
public TestBean getObject() {
|
||||
return testBean;
|
||||
}
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return testBean.getClass();
|
||||
}
|
||||
};
|
||||
return new TestBean();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,8 +38,7 @@ import org.springframework.context.support.GenericApplicationContext;
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
// WARNING: This class MUST be public, since it is based on JUnit 3.
|
||||
public class SpringAtInjectTckTests {
|
||||
class SpringAtInjectTckTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Test suite() {
|
||||
|
||||
+6
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,9 +45,8 @@ class ContextAotProcessorTests {
|
||||
void processGeneratesAssets(@TempDir Path directory) {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.registerBean(SampleApplication.class);
|
||||
DemoContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory);
|
||||
ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory);
|
||||
ClassName className = processor.process();
|
||||
assertThat(processor.context.isClosed()).isTrue();
|
||||
assertThat(className).isEqualTo(ClassName.get(SampleApplication.class.getPackageName(),
|
||||
"ContextAotProcessorTests_SampleApplication__ApplicationContextInitializer"));
|
||||
assertThat(directory).satisfies(hasGeneratedAssetsForSampleApplication());
|
||||
@@ -62,10 +61,9 @@ class ContextAotProcessorTests {
|
||||
Path existingSourceOutput = createExisting(sourceOutput);
|
||||
Path existingResourceOutput = createExisting(resourceOutput);
|
||||
Path existingClassOutput = createExisting(classOutput);
|
||||
DemoContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class,
|
||||
ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class,
|
||||
sourceOutput, resourceOutput, classOutput);
|
||||
processor.process();
|
||||
assertThat(processor.context.isClosed()).isTrue();
|
||||
assertThat(existingSourceOutput).doesNotExist();
|
||||
assertThat(existingResourceOutput).doesNotExist();
|
||||
assertThat(existingClassOutput).doesNotExist();
|
||||
@@ -75,14 +73,13 @@ class ContextAotProcessorTests {
|
||||
void processWithEmptyNativeImageArgumentsDoesNotCreateNativeImageProperties(@TempDir Path directory) {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.registerBean(SampleApplication.class);
|
||||
DemoContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory) {
|
||||
ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory) {
|
||||
@Override
|
||||
protected List<String> getDefaultNativeImageArguments(String application) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
processor.process();
|
||||
assertThat(processor.context.isClosed()).isTrue();
|
||||
assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/native-image.properties"))
|
||||
.doesNotExist();
|
||||
context.close();
|
||||
@@ -121,8 +118,6 @@ class ContextAotProcessorTests {
|
||||
|
||||
private static class DemoContextAotProcessor extends ContextAotProcessor {
|
||||
|
||||
AnnotationConfigApplicationContext context;
|
||||
|
||||
DemoContextAotProcessor(Class<?> application, Path rootPath) {
|
||||
this(application, rootPath.resolve("source"), rootPath.resolve("resource"), rootPath.resolve("class"));
|
||||
}
|
||||
@@ -146,11 +141,10 @@ class ContextAotProcessorTests {
|
||||
protected GenericApplicationContext prepareApplicationContext(Class<?> application) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(application);
|
||||
this.context = context;
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class SampleApplication {
|
||||
@@ -159,6 +153,7 @@ class ContextAotProcessorTests {
|
||||
public String testBean() {
|
||||
return "Hello";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-384
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,34 +16,20 @@
|
||||
|
||||
package org.springframework.context.support;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.AbstractPropertyResolver;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
@@ -52,15 +38,12 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.testfixture.env.MockPropertySource;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.util.PlaceholderResolutionException;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
|
||||
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
|
||||
import static org.springframework.core.env.AbstractPropertyResolver.DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertySourcesPlaceholderConfigurer}.
|
||||
@@ -90,43 +73,6 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
assertThat(ppc.getAppliedPropertySources()).isNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that a {@link PropertySource} added to the {@code Environment} after context
|
||||
* refresh (i.e., after {@link PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()}
|
||||
* has been invoked) can still contribute properties in late-binding scenarios.
|
||||
*/
|
||||
@Test // gh-34861
|
||||
void replacementFromEnvironmentPropertiesWithLateBinding() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
|
||||
propertySources.addFirst(new MockPropertySource("early properties").withProperty("foo", "bar"));
|
||||
|
||||
context.register(PropertySourcesPlaceholderConfigurer.class);
|
||||
context.register(PrototypeBean.class);
|
||||
context.refresh();
|
||||
|
||||
// Verify that placeholder resolution works for early binding.
|
||||
PrototypeBean prototypeBean = context.getBean(PrototypeBean.class);
|
||||
assertThat(prototypeBean.getName()).isEqualTo("bar");
|
||||
assertThat(prototypeBean.isJedi()).isFalse();
|
||||
|
||||
// Add new PropertySource after context refresh.
|
||||
propertySources.addFirst(new MockPropertySource("late properties").withProperty("jedi", "true"));
|
||||
|
||||
// Verify that placeholder resolution works for late binding: isJedi() switches to true.
|
||||
prototypeBean = context.getBean(PrototypeBean.class);
|
||||
assertThat(prototypeBean.getName()).isEqualTo("bar");
|
||||
assertThat(prototypeBean.isJedi()).isTrue();
|
||||
|
||||
// Add yet another PropertySource after context refresh.
|
||||
propertySources.addFirst(new MockPropertySource("even later properties").withProperty("foo", "enigma"));
|
||||
|
||||
// Verify that placeholder resolution works for even later binding: getName() switches to enigma.
|
||||
prototypeBean = context.getBean(PrototypeBean.class);
|
||||
assertThat(prototypeBean.getName()).isEqualTo("enigma");
|
||||
assertThat(prototypeBean.isJedi()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void localPropertiesViaResource() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
@@ -142,29 +88,14 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void localPropertiesOverride(boolean override) {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("testBean",
|
||||
genericBeanDefinition(TestBean.class)
|
||||
.addPropertyValue("name", "${foo}")
|
||||
.getBeanDefinition());
|
||||
@Test
|
||||
void localPropertiesOverrideFalse() {
|
||||
localPropertiesOverride(false);
|
||||
}
|
||||
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
|
||||
ppc.setLocalOverride(override);
|
||||
ppc.setProperties(new Properties() {{
|
||||
setProperty("foo", "local");
|
||||
}});
|
||||
ppc.setEnvironment(new MockEnvironment().withProperty("foo", "enclosing"));
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
if (override) {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("local");
|
||||
}
|
||||
else {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("enclosing");
|
||||
}
|
||||
@Test
|
||||
void localPropertiesOverrideTrue() {
|
||||
localPropertiesOverride(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -350,58 +281,28 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void withEnumerableAndNonEnumerablePropertySourcesInTheEnvironmentAndLocalProperties() {
|
||||
@SuppressWarnings("serial")
|
||||
private void localPropertiesOverride(boolean override) {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("testBean",
|
||||
genericBeanDefinition(TestBean.class)
|
||||
.addPropertyValue("name", "${foo:bogus}")
|
||||
.addPropertyValue("jedi", "${local:false}")
|
||||
.addPropertyValue("name", "${foo}")
|
||||
.getBeanDefinition());
|
||||
|
||||
// 1) MockPropertySource is an EnumerablePropertySource.
|
||||
MockPropertySource mockPropertySource = new MockPropertySource("mockPropertySource")
|
||||
.withProperty("foo", "${bar}");
|
||||
|
||||
// 2) PropertySource is not an EnumerablePropertySource.
|
||||
PropertySource<?> rawPropertySource = new PropertySource<>("rawPropertySource", new Object()) {
|
||||
@Override
|
||||
public Object getProperty(String key) {
|
||||
return ("bar".equals(key) ? "quux" : null);
|
||||
}
|
||||
};
|
||||
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.getPropertySources().addFirst(mockPropertySource);
|
||||
env.getPropertySources().addLast(rawPropertySource);
|
||||
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
// 3) Local properties are stored in a PropertiesPropertySource which is an EnumerablePropertySource.
|
||||
|
||||
ppc.setLocalOverride(override);
|
||||
ppc.setProperties(new Properties() {{
|
||||
setProperty("local", "true");
|
||||
setProperty("foo", "local");
|
||||
}});
|
||||
ppc.setEnvironment(new MockEnvironment().withProperty("foo", "enclosing"));
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
// Verify all properties can be resolved via the Environment.
|
||||
assertThat(env.getProperty("foo")).isEqualTo("quux");
|
||||
assertThat(env.getProperty("bar")).isEqualTo("quux");
|
||||
|
||||
// Verify that placeholder resolution works.
|
||||
TestBean testBean = bf.getBean(TestBean.class);
|
||||
assertThat(testBean.getName()).isEqualTo("quux");
|
||||
assertThat(testBean.isJedi()).isTrue();
|
||||
|
||||
// Verify that the presence of a non-EnumerablePropertySource does not prevent
|
||||
// accessing EnumerablePropertySources via getAppliedPropertySources().
|
||||
List<String> propertyNames = new ArrayList<>();
|
||||
for (PropertySource<?> propertySource : ppc.getAppliedPropertySources()) {
|
||||
if (propertySource instanceof EnumerablePropertySource<?> enumerablePropertySource) {
|
||||
Collections.addAll(propertyNames, enumerablePropertySource.getPropertyNames());
|
||||
}
|
||||
if (override) {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("local");
|
||||
}
|
||||
else {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("enclosing");
|
||||
}
|
||||
// Should not contain "foo" or "bar" from the Environment.
|
||||
assertThat(propertyNames).containsOnly("local");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -531,252 +432,6 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that use the escape character (or disable it) with nested placeholder
|
||||
* resolution.
|
||||
*/
|
||||
@Nested
|
||||
class EscapedNestedPlaceholdersTests {
|
||||
|
||||
@Test // gh-34861
|
||||
void singleEscapeWithDefaultEscapeCharacter() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin")
|
||||
.withProperty("my.property", "\\DOMAIN\\${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
// \DOMAIN\${user.home} resolves to \DOMAIN${user.home} instead of \DOMAIN\admin
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN${user.home}");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void singleEscapeWithCustomEscapeCharacter() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin\\~${nested}")
|
||||
.withProperty("my.property", "DOMAIN\\${user.home}\\~${enigma}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
// Set custom escape character.
|
||||
ppc.setEscapeCharacter('~');
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\admin\\${nested}\\${enigma}");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void singleEscapeWithEscapeCharacterDisabled() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin\\")
|
||||
.withProperty("my.property", "\\DOMAIN\\${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
// Disable escape character.
|
||||
ppc.setEscapeCharacter(null);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
// \DOMAIN\${user.home} resolves to \DOMAIN\admin
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN\\admin\\");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void tripleEscapeWithDefaultEscapeCharacter() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin\\\\\\")
|
||||
.withProperty("my.property", "DOMAIN\\\\\\${user.home}#${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\\\${user.home}#admin\\\\\\");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void tripleEscapeWithCustomEscapeCharacter() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin\\~${enigma}")
|
||||
.withProperty("my.property", "DOMAIN~~~${user.home}#${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
// Set custom escape character.
|
||||
ppc.setEscapeCharacter('~');
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN~~${user.home}#admin\\${enigma}");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void singleEscapeWithDefaultEscapeCharacterAndIgnoreUnresolvablePlaceholders() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "${enigma}")
|
||||
.withProperty("my.property", "\\${DOMAIN}${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.setIgnoreUnresolvablePlaceholders(true);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("${DOMAIN}${enigma}");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void singleEscapeWithCustomEscapeCharacterAndIgnoreUnresolvablePlaceholders() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "${enigma}")
|
||||
.withProperty("my.property", "~${DOMAIN}\\${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
// Set custom escape character.
|
||||
ppc.setEscapeCharacter('~');
|
||||
ppc.setIgnoreUnresolvablePlaceholders(true);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("${DOMAIN}\\${enigma}");
|
||||
}
|
||||
|
||||
@Test // gh-34861
|
||||
void tripleEscapeWithDefaultEscapeCharacterAndIgnoreUnresolvablePlaceholders() {
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "${enigma}")
|
||||
.withProperty("my.property", "X:\\\\\\${DOMAIN}${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.setIgnoreUnresolvablePlaceholders(true);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("X:\\\\${DOMAIN}${enigma}");
|
||||
}
|
||||
|
||||
private static DefaultListableBeanFactory createBeanFactory() {
|
||||
BeanDefinition beanDefinition = genericBeanDefinition(TestBean.class)
|
||||
.addPropertyValue("name", "${my.property}")
|
||||
.getBeanDefinition();
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("testBean",beanDefinition);
|
||||
return bf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that globally set the default escape character (or disable it) and
|
||||
* rely on nested placeholder resolution.
|
||||
*/
|
||||
@Nested
|
||||
class GlobalDefaultEscapeCharacterTests {
|
||||
|
||||
private static final Field defaultEscapeCharacterField =
|
||||
ReflectionUtils.findField(AbstractPropertyResolver.class, "defaultEscapeCharacter");
|
||||
|
||||
static {
|
||||
ReflectionUtils.makeAccessible(defaultEscapeCharacterField);
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void resetStateBeforeEachTest() {
|
||||
resetState();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void resetState() {
|
||||
ReflectionUtils.setField(defaultEscapeCharacterField, null, Character.MIN_VALUE);
|
||||
setSpringProperty(null);
|
||||
}
|
||||
|
||||
|
||||
@Test // gh-34865
|
||||
void defaultEscapeCharacterSetToXyz() {
|
||||
setSpringProperty("XYZ");
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(PropertySourcesPlaceholderConfigurer::new)
|
||||
.withMessage("Value [XYZ] for property [%s] must be a single character or an empty string",
|
||||
DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Test // gh-34865
|
||||
void defaultEscapeCharacterDisabled() {
|
||||
setSpringProperty("");
|
||||
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin")
|
||||
.withProperty("my.property", "\\DOMAIN\\${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN\\admin");
|
||||
}
|
||||
|
||||
@Test // gh-34865
|
||||
void defaultEscapeCharacterSetToBackslash() {
|
||||
setSpringProperty("\\");
|
||||
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin")
|
||||
.withProperty("my.property", "\\DOMAIN\\${user.home}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
// \DOMAIN\${user.home} resolves to \DOMAIN${user.home} instead of \DOMAIN\admin
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN${user.home}");
|
||||
}
|
||||
|
||||
@Test // gh-34865
|
||||
void defaultEscapeCharacterSetToTilde() {
|
||||
setSpringProperty("~");
|
||||
|
||||
MockEnvironment env = new MockEnvironment()
|
||||
.withProperty("user.home", "admin\\~${nested}")
|
||||
.withProperty("my.property", "DOMAIN\\${user.home}\\~${enigma}");
|
||||
|
||||
DefaultListableBeanFactory bf = createBeanFactory();
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\admin\\${nested}\\${enigma}");
|
||||
}
|
||||
|
||||
private static void setSpringProperty(String value) {
|
||||
SpringProperties.setProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, value);
|
||||
}
|
||||
|
||||
private static DefaultListableBeanFactory createBeanFactory() {
|
||||
BeanDefinition beanDefinition = genericBeanDefinition(TestBean.class)
|
||||
.addPropertyValue("name", "${my.property}")
|
||||
.getBeanDefinition();
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("testBean",beanDefinition);
|
||||
return bf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class OptionalTestBean {
|
||||
|
||||
private Optional<String> name;
|
||||
@@ -817,23 +472,4 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
|
||||
static class PrototypeBean {
|
||||
|
||||
@Value("${foo:bogus}")
|
||||
private String name;
|
||||
|
||||
@Value("${jedi:false}")
|
||||
private boolean jedi;
|
||||
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public boolean isJedi() {
|
||||
return this.jedi;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -463,21 +463,10 @@ public class ReflectUtils {
|
||||
c = lookup.defineClass(b);
|
||||
}
|
||||
catch (LinkageError | IllegalArgumentException ex) {
|
||||
if (ex instanceof LinkageError) {
|
||||
// Could be a ClassLoader mismatch with the class pre-existing in a
|
||||
// parent ClassLoader -> try loadClass before giving up completely.
|
||||
try {
|
||||
c = contextClass.getClassLoader().loadClass(className);
|
||||
}
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
}
|
||||
}
|
||||
if (c == null) {
|
||||
// in case of plain LinkageError (class already defined)
|
||||
// or IllegalArgumentException (class in different package):
|
||||
// fall through to traditional ClassLoader.defineClass below
|
||||
t = ex;
|
||||
}
|
||||
// in case of plain LinkageError (class already defined)
|
||||
// or IllegalArgumentException (class in different package):
|
||||
// fall through to traditional ClassLoader.defineClass below
|
||||
t = ex;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new CodeGenerationException(ex);
|
||||
|
||||
@@ -26,20 +26,18 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* Static holder for local Spring properties, i.e. defined at the Spring library level.
|
||||
*
|
||||
* <p>Reads a {@code spring.properties} file from the root of the classpath and
|
||||
* also allows for programmatically setting properties via {@link #setProperty}.
|
||||
* When retrieving properties, local entries are checked first, with JVM-level
|
||||
* system properties checked next as a fallback via {@link System#getProperty}.
|
||||
* <p>Reads a {@code spring.properties} file from the root of the Spring library classpath,
|
||||
* and also allows for programmatically setting properties through {@link #setProperty}.
|
||||
* When checking a property, local entries are being checked first, then falling back
|
||||
* to JVM-level system properties through a {@link System#getProperty} check.
|
||||
*
|
||||
* <p>This is an alternative way to set Spring-related system properties such as
|
||||
* {@code spring.getenv.ignore} and {@code spring.beaninfo.ignore}, in particular
|
||||
* for scenarios where JVM system properties are locked on the target platform
|
||||
* (for example, WebSphere). See {@link #setFlag} for a convenient way to locally
|
||||
* set such flags to {@code "true"}.
|
||||
* "spring.getenv.ignore" and "spring.beaninfo.ignore", in particular for scenarios
|
||||
* where JVM system properties are locked on the target platform (for example, WebSphere).
|
||||
* See {@link #setFlag} for a convenient way to locally set such flags to "true".
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.2.7
|
||||
* @see org.springframework.aot.AotDetector#AOT_ENABLED
|
||||
* @see org.springframework.beans.StandardBeanInfoFactory#IGNORE_BEANINFO_PROPERTY_NAME
|
||||
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#STRICT_LOCKING_PROPERTY_NAME
|
||||
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
|
||||
|
||||
+5
-1
@@ -361,12 +361,16 @@ abstract class AnnotationsScanner {
|
||||
}
|
||||
|
||||
private static boolean hasSameParameterTypes(Method rootMethod, Method candidateMethod) {
|
||||
if (candidateMethod.getParameterCount() != rootMethod.getParameterCount()) {
|
||||
return false;
|
||||
}
|
||||
Class<?>[] rootParameterTypes = rootMethod.getParameterTypes();
|
||||
Class<?>[] candidateParameterTypes = candidateMethod.getParameterTypes();
|
||||
if (Arrays.equals(candidateParameterTypes, rootParameterTypes)) {
|
||||
return true;
|
||||
}
|
||||
return hasSameGenericTypeParameters(rootMethod, candidateMethod, rootParameterTypes);
|
||||
return hasSameGenericTypeParameters(rootMethod, candidateMethod,
|
||||
rootParameterTypes);
|
||||
}
|
||||
|
||||
private static boolean hasSameGenericTypeParameters(
|
||||
|
||||
+10
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,7 +42,7 @@ import org.springframework.util.StringUtils;
|
||||
* add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute
|
||||
* property sources through the protected {@link #customizePropertySources(MutablePropertySources)}
|
||||
* hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()}
|
||||
* and work against the {@link MutablePropertySources} API.
|
||||
* and working against the {@link MutablePropertySources} API.
|
||||
* See {@link ConfigurableEnvironment} javadoc for usage examples.
|
||||
*
|
||||
* @author Chris Beams
|
||||
@@ -66,7 +66,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
|
||||
|
||||
/**
|
||||
* Name of the property to specify active profiles: {@value}.
|
||||
* Name of the property to set to specify active profiles: {@value}.
|
||||
* <p>The value may be comma delimited.
|
||||
* <p>Note that certain shell environments such as Bash disallow the use of the period
|
||||
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
|
||||
@@ -77,7 +77,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
|
||||
|
||||
/**
|
||||
* Name of the property to specify profiles that are active by default: {@value}.
|
||||
* Name of the property to set to specify profiles that are active by default: {@value}.
|
||||
* <p>The value may be comma delimited.
|
||||
* <p>Note that certain shell environments such as Bash disallow the use of the period
|
||||
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
|
||||
@@ -141,7 +141,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
|
||||
/**
|
||||
* Factory method used to create the {@link ConfigurablePropertyResolver}
|
||||
* used by this {@code Environment}.
|
||||
* instance used by the Environment.
|
||||
* @since 5.3.4
|
||||
* @see #getPropertyResolver()
|
||||
*/
|
||||
@@ -150,7 +150,8 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ConfigurablePropertyResolver} used by the {@code Environment}.
|
||||
* Return the {@link ConfigurablePropertyResolver} being used by the
|
||||
* {@link Environment}.
|
||||
* @since 5.3.4
|
||||
* @see #createPropertyResolver(MutablePropertySources)
|
||||
*/
|
||||
@@ -319,6 +320,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] getDefaultProfiles() {
|
||||
return StringUtils.toStringArray(doGetDefaultProfiles());
|
||||
@@ -326,7 +328,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
|
||||
/**
|
||||
* Return the set of default profiles explicitly set via
|
||||
* {@link #setDefaultProfiles(String...)}, or if the current set of default profiles
|
||||
* {@link #setDefaultProfiles(String...)} or if the current set of default profiles
|
||||
* consists only of {@linkplain #getReservedDefaultProfiles() reserved default
|
||||
* profiles}, then check for the presence of {@link #doGetActiveProfilesProperty()}
|
||||
* and assign its value (if any) to the set of default profiles.
|
||||
@@ -418,7 +420,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
* active or default profiles.
|
||||
* <p>Subclasses may override to impose further restrictions on profile syntax.
|
||||
* @throws IllegalArgumentException if the profile is null, empty, whitespace-only or
|
||||
* begins with the profile NOT operator (!)
|
||||
* begins with the profile NOT operator (!).
|
||||
* @see #acceptsProfiles
|
||||
* @see #addActiveProfile
|
||||
* @see #setDefaultProfiles
|
||||
|
||||
+18
-112
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,6 @@ import java.util.Set;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
@@ -38,52 +37,10 @@ import org.springframework.util.SystemPropertyUtils;
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
|
||||
|
||||
/**
|
||||
* JVM system property used to change the <em>default</em> escape character
|
||||
* for property placeholder support: {@value}.
|
||||
* <p>To configure a custom escape character, supply a string containing a
|
||||
* single character (other than {@link Character#MIN_VALUE}). For example,
|
||||
* supplying the following JVM system property via the command line sets the
|
||||
* default escape character to {@code '@'}.
|
||||
* <pre style="code">-Dspring.placeholder.escapeCharacter.default=@</pre>
|
||||
* <p>To disable escape character support, set the value to an empty string
|
||||
* — for example, by supplying the following JVM system property via
|
||||
* the command line.
|
||||
* <pre style="code">-Dspring.placeholder.escapeCharacter.default=</pre>
|
||||
* <p>If the property is not set, {@code '\'} will be used as the default
|
||||
* escape character.
|
||||
* <p>May alternatively be configured via a
|
||||
* {@link org.springframework.core.SpringProperties spring.properties} file
|
||||
* in the root of the classpath.
|
||||
* @since 6.2.7
|
||||
* @see #getDefaultEscapeCharacter()
|
||||
*/
|
||||
public static final String DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME =
|
||||
"spring.placeholder.escapeCharacter.default";
|
||||
|
||||
/**
|
||||
* Since {@code null} is a valid value for {@link #defaultEscapeCharacter},
|
||||
* this constant provides a way to represent an undefined (or not yet set)
|
||||
* value. Consequently, {@link #getDefaultEscapeCharacter()} prevents the use
|
||||
* of {@link Character#MIN_VALUE} as the actual escape character.
|
||||
* @since 6.2.7
|
||||
*/
|
||||
static final Character UNDEFINED_ESCAPE_CHARACTER = Character.MIN_VALUE;
|
||||
|
||||
|
||||
/**
|
||||
* Cached value for the default escape character.
|
||||
* @since 6.2.7
|
||||
*/
|
||||
@Nullable
|
||||
static volatile Character defaultEscapeCharacter = UNDEFINED_ESCAPE_CHARACTER;
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@Nullable
|
||||
@@ -105,7 +62,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
|
||||
|
||||
@Nullable
|
||||
private Character escapeCharacter = getDefaultEscapeCharacter();
|
||||
private Character escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER;
|
||||
|
||||
private final Set<String> requiredProperties = new LinkedHashSet<>();
|
||||
|
||||
@@ -134,9 +91,9 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The default is <code>"${"</code>.
|
||||
* @see SystemPropertyUtils#PLACEHOLDER_PREFIX
|
||||
* Set the prefix that placeholders replaced by this resolver must begin with.
|
||||
* <p>The default is "${".
|
||||
* @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX
|
||||
*/
|
||||
@Override
|
||||
public void setPlaceholderPrefix(String placeholderPrefix) {
|
||||
@@ -145,9 +102,9 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The default is <code>"}"</code>.
|
||||
* @see SystemPropertyUtils#PLACEHOLDER_SUFFIX
|
||||
* Set the suffix that placeholders replaced by this resolver must end with.
|
||||
* <p>The default is "}".
|
||||
* @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX
|
||||
*/
|
||||
@Override
|
||||
public void setPlaceholderSuffix(String placeholderSuffix) {
|
||||
@@ -156,9 +113,11 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The default is {@code ":"}.
|
||||
* @see SystemPropertyUtils#VALUE_SEPARATOR
|
||||
* Specify the separating character between the placeholders replaced by this
|
||||
* resolver and their associated default value, or {@code null} if no such
|
||||
* special character should be processed as a value separator.
|
||||
* <p>The default is ":".
|
||||
* @see org.springframework.util.SystemPropertyUtils#VALUE_SEPARATOR
|
||||
*/
|
||||
@Override
|
||||
public void setValueSeparator(@Nullable String valueSeparator) {
|
||||
@@ -166,9 +125,12 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The default is determined by {@link #getDefaultEscapeCharacter()}.
|
||||
* Specify the escape character to use to ignore placeholder prefix
|
||||
* or value separator, or {@code null} if no escaping should take
|
||||
* place.
|
||||
* <p>The default is "\".
|
||||
* @since 6.2
|
||||
* @see org.springframework.util.SystemPropertyUtils#ESCAPE_CHARACTER
|
||||
*/
|
||||
@Override
|
||||
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
||||
@@ -329,60 +291,4 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
@Nullable
|
||||
protected abstract String getPropertyAsRawString(String key);
|
||||
|
||||
|
||||
/**
|
||||
* Get the default {@linkplain #setEscapeCharacter(Character) escape character}
|
||||
* to use when parsing strings for property placeholder resolution.
|
||||
* <p>This method attempts to retrieve the default escape character configured
|
||||
* via the {@value #DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME} JVM system
|
||||
* property or Spring property.
|
||||
* <p>Falls back to {@code '\'} if the property has not been set.
|
||||
* @return the configured default escape character, {@code null} if escape character
|
||||
* support has been disabled, or {@code '\'} if the property has not been set
|
||||
* @throws IllegalArgumentException if the property is configured with an
|
||||
* invalid value, such as {@link Character#MIN_VALUE} or a string containing
|
||||
* more than one character
|
||||
* @since 6.2.7
|
||||
* @see #DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME
|
||||
* @see SystemPropertyUtils#ESCAPE_CHARACTER
|
||||
* @see SpringProperties
|
||||
*/
|
||||
@Nullable
|
||||
public static Character getDefaultEscapeCharacter() throws IllegalArgumentException {
|
||||
Character escapeCharacter = defaultEscapeCharacter;
|
||||
if (UNDEFINED_ESCAPE_CHARACTER.equals(escapeCharacter)) {
|
||||
String value = SpringProperties.getProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
|
||||
if (value != null) {
|
||||
if (value.isEmpty()) {
|
||||
// Disable escape character support by default.
|
||||
escapeCharacter = null;
|
||||
}
|
||||
else if (value.length() == 1) {
|
||||
try {
|
||||
// Use custom default escape character.
|
||||
escapeCharacter = value.charAt(0);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException("Failed to process value [%s] for property [%s]: %s"
|
||||
.formatted(value, DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, ex.getMessage()), ex);
|
||||
}
|
||||
Assert.isTrue(!escapeCharacter.equals(Character.MIN_VALUE),
|
||||
() -> "Value for property [%s] must not be Character.MIN_VALUE"
|
||||
.formatted(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME));
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Value [%s] for property [%s] must be a single character or an empty string"
|
||||
.formatted(value, DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Use standard default value for the escape character.
|
||||
escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER;
|
||||
}
|
||||
defaultEscapeCharacter = escapeCharacter;
|
||||
}
|
||||
return escapeCharacter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,10 +34,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* <p>As of Spring 4.1.2, this class extends {@link EnumerablePropertySource} instead
|
||||
* of plain {@link PropertySource}, exposing {@link #getPropertyNames()} based on the
|
||||
* accumulated property names from all contained sources - and failing with an
|
||||
* {@code IllegalStateException} against any non-{@code EnumerablePropertySource}.
|
||||
* <b>When used through the {@code EnumerablePropertySource} contract, all contained
|
||||
* sources are expected to be of type {@code EnumerablePropertySource} as well.</b>
|
||||
* accumulated property names from all contained sources (as far as possible).
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -137,13 +137,13 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper
|
||||
Map<String, Object> getSystemEnvironment();
|
||||
|
||||
/**
|
||||
* Append the given parent environment's active profiles, default profiles, and
|
||||
* Append the given parent environment's active profiles, default profiles and
|
||||
* property sources to this (child) environment's respective collections of each.
|
||||
* <p>For any identically-named {@code PropertySource} instance existing in both
|
||||
* parent and child, the child instance is to be preserved and the parent instance
|
||||
* discarded. This has the effect of allowing overriding of property sources by the
|
||||
* child as well as avoiding redundant searches through common property source types
|
||||
* — for example, system environment and system properties.
|
||||
* child as well as avoiding redundant searches through common property source types,
|
||||
* for example, system environment and system properties.
|
||||
* <p>Active and default profile names are also filtered for duplicates, to avoid
|
||||
* confusion and redundant storage.
|
||||
* <p>The parent environment remains unmodified in any case. Note that any changes to
|
||||
|
||||
+7
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -69,23 +69,21 @@ public interface ConfigurablePropertyResolver extends PropertyResolver {
|
||||
void setPlaceholderSuffix(String placeholderSuffix);
|
||||
|
||||
/**
|
||||
* Set the separating character to be honored between placeholders replaced by
|
||||
* this resolver and their associated default values, or {@code null} if no such
|
||||
* Specify the separating character between the placeholders replaced by this
|
||||
* resolver and their associated default value, or {@code null} if no such
|
||||
* special character should be processed as a value separator.
|
||||
*/
|
||||
void setValueSeparator(@Nullable String valueSeparator);
|
||||
|
||||
/**
|
||||
* Set the escape character to use to ignore the
|
||||
* {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the
|
||||
* {@linkplain #setValueSeparator(String) value separator}, or {@code null}
|
||||
* if no escaping should take place.
|
||||
* Specify the escape character to use to ignore placeholder prefix or
|
||||
* value separator, or {@code null} if no escaping should take place.
|
||||
* @since 6.2
|
||||
*/
|
||||
void setEscapeCharacter(@Nullable Character escapeCharacter);
|
||||
|
||||
/**
|
||||
* Specify whether to throw an exception when encountering an unresolvable placeholder
|
||||
* Set whether to throw an exception when encountering an unresolvable placeholder
|
||||
* nested within the value of a given property. A {@code false} value indicates strict
|
||||
* resolution, i.e. that an exception will be thrown. A {@code true} value indicates
|
||||
* that unresolvable nested placeholders should be passed through in their unresolved
|
||||
@@ -108,7 +106,7 @@ public interface ConfigurablePropertyResolver extends PropertyResolver {
|
||||
* {@link #setRequiredProperties} is present and resolves to a
|
||||
* non-{@code null} value.
|
||||
* @throws MissingRequiredPropertiesException if any of the required
|
||||
* properties are not resolvable
|
||||
* properties are not resolvable.
|
||||
*/
|
||||
void validateRequiredProperties() throws MissingRequiredPropertiesException;
|
||||
|
||||
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,13 +30,13 @@ import org.springframework.lang.Nullable;
|
||||
public interface PropertyResolver {
|
||||
|
||||
/**
|
||||
* Determine whether the given property key is available for resolution
|
||||
* — for example, if the value for the given key is not {@code null}.
|
||||
* Return whether the given property key is available for resolution,
|
||||
* i.e. if the value for the given key is not {@code null}.
|
||||
*/
|
||||
boolean containsProperty(String key);
|
||||
|
||||
/**
|
||||
* Resolve the property value associated with the given key,
|
||||
* Return the property value associated with the given key,
|
||||
* or {@code null} if the key cannot be resolved.
|
||||
* @param key the property name to resolve
|
||||
* @see #getProperty(String, String)
|
||||
@@ -47,7 +47,7 @@ public interface PropertyResolver {
|
||||
String getProperty(String key);
|
||||
|
||||
/**
|
||||
* Resolve the property value associated with the given key, or
|
||||
* Return the property value associated with the given key, or
|
||||
* {@code defaultValue} if the key cannot be resolved.
|
||||
* @param key the property name to resolve
|
||||
* @param defaultValue the default value to return if no value is found
|
||||
@@ -57,7 +57,7 @@ public interface PropertyResolver {
|
||||
String getProperty(String key, String defaultValue);
|
||||
|
||||
/**
|
||||
* Resolve the property value associated with the given key,
|
||||
* Return the property value associated with the given key,
|
||||
* or {@code null} if the key cannot be resolved.
|
||||
* @param key the property name to resolve
|
||||
* @param targetType the expected type of the property value
|
||||
@@ -67,7 +67,7 @@ public interface PropertyResolver {
|
||||
<T> T getProperty(String key, Class<T> targetType);
|
||||
|
||||
/**
|
||||
* Resolve the property value associated with the given key,
|
||||
* Return the property value associated with the given key,
|
||||
* or {@code defaultValue} if the key cannot be resolved.
|
||||
* @param key the property name to resolve
|
||||
* @param targetType the expected type of the property value
|
||||
@@ -77,14 +77,14 @@ public interface PropertyResolver {
|
||||
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
|
||||
|
||||
/**
|
||||
* Resolve the property value associated with the given key (never {@code null}).
|
||||
* Return the property value associated with the given key (never {@code null}).
|
||||
* @throws IllegalStateException if the key cannot be resolved
|
||||
* @see #getRequiredProperty(String, Class)
|
||||
*/
|
||||
String getRequiredProperty(String key) throws IllegalStateException;
|
||||
|
||||
/**
|
||||
* Resolve the property value associated with the given key, converted to the given
|
||||
* Return the property value associated with the given key, converted to the given
|
||||
* targetType (never {@code null}).
|
||||
* @throws IllegalStateException if the given key cannot be resolved
|
||||
*/
|
||||
|
||||
+1
-7
@@ -29,7 +29,6 @@ import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
@@ -45,7 +44,6 @@ import org.springframework.util.ResourceUtils;
|
||||
*/
|
||||
public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
|
||||
@SuppressWarnings("try")
|
||||
@Override
|
||||
public boolean exists() {
|
||||
try {
|
||||
@@ -88,11 +86,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
if (con instanceof JarURLConnection jarCon) {
|
||||
// For JarURLConnection, do not check content-length but rather the
|
||||
// existence of the entry (or the jar root in case of no entryName).
|
||||
// getJarFile() called for enforced presence check of the jar file,
|
||||
// throwing a NoSuchFileException otherwise (turned to false below).
|
||||
try (JarFile jarFile = jarCon.getJarFile()) {
|
||||
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
|
||||
}
|
||||
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
|
||||
}
|
||||
else if (con.getContentLengthLong() > 0) {
|
||||
return true;
|
||||
|
||||
+3
-4
@@ -36,7 +36,6 @@ import java.nio.file.FileSystemNotFoundException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
@@ -875,9 +874,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
|
||||
closeJarFile = !jarCon.getUseCaches();
|
||||
}
|
||||
catch (ZipException | FileNotFoundException | NoSuchFileException ex) {
|
||||
catch (ZipException | FileNotFoundException ex) {
|
||||
// Happens in case of a non-jar file or in case of a cached root directory
|
||||
// without the specific subdirectory present, respectively.
|
||||
// without specific subdirectory present, respectively.
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
@@ -1276,7 +1275,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an alternative form of the resource, i.e. with or without a leading slash.
|
||||
* Return a alternative form of the resource, i.e. with or without a leading slash.
|
||||
* @param path the file path (with or without a leading slash)
|
||||
* @return the alternative form or {@code null}
|
||||
*/
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -141,8 +141,8 @@ public abstract class PropertiesLoaderSupport {
|
||||
|
||||
|
||||
/**
|
||||
* Return a merged {@link Properties} instance containing both the
|
||||
* loaded properties and properties set on this component.
|
||||
* Return a merged Properties instance containing both the
|
||||
* loaded properties and properties set on this FactoryBean.
|
||||
*/
|
||||
protected Properties mergeProperties() throws IOException {
|
||||
Properties result = new Properties();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,25 +37,13 @@ public abstract class PatternMatchUtils {
|
||||
* @return whether the String matches the given pattern
|
||||
*/
|
||||
public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) {
|
||||
return simpleMatch(pattern, str, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #simpleMatch(String, String)} that ignores upper/lower case.
|
||||
* @since 6.1.20
|
||||
*/
|
||||
public static boolean simpleMatchIgnoreCase(@Nullable String pattern, @Nullable String str) {
|
||||
return simpleMatch(pattern, str, true);
|
||||
}
|
||||
|
||||
private static boolean simpleMatch(@Nullable String pattern, @Nullable String str, boolean ignoreCase) {
|
||||
if (pattern == null || str == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int firstIndex = pattern.indexOf('*');
|
||||
if (firstIndex == -1) {
|
||||
return (ignoreCase ? pattern.equalsIgnoreCase(str) : pattern.equals(str));
|
||||
return pattern.equals(str);
|
||||
}
|
||||
|
||||
if (firstIndex == 0) {
|
||||
@@ -64,43 +52,25 @@ public abstract class PatternMatchUtils {
|
||||
}
|
||||
int nextIndex = pattern.indexOf('*', 1);
|
||||
if (nextIndex == -1) {
|
||||
String part = pattern.substring(1);
|
||||
return (ignoreCase ? StringUtils.endsWithIgnoreCase(str, part) : str.endsWith(part));
|
||||
return str.endsWith(pattern.substring(1));
|
||||
}
|
||||
String part = pattern.substring(1, nextIndex);
|
||||
if (part.isEmpty()) {
|
||||
return simpleMatch(pattern.substring(nextIndex), str, ignoreCase);
|
||||
return simpleMatch(pattern.substring(nextIndex), str);
|
||||
}
|
||||
int partIndex = indexOf(str, part, 0, ignoreCase);
|
||||
int partIndex = str.indexOf(part);
|
||||
while (partIndex != -1) {
|
||||
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()), ignoreCase)) {
|
||||
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
|
||||
return true;
|
||||
}
|
||||
partIndex = indexOf(str, part, partIndex + 1, ignoreCase);
|
||||
partIndex = str.indexOf(part, partIndex + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return (str.length() >= firstIndex &&
|
||||
checkStartsWith(pattern, str, firstIndex, ignoreCase) &&
|
||||
simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex), ignoreCase));
|
||||
}
|
||||
|
||||
private static boolean checkStartsWith(String pattern, String str, int index, boolean ignoreCase) {
|
||||
String part = str.substring(0, index);
|
||||
return (ignoreCase ? StringUtils.startsWithIgnoreCase(pattern, part) : pattern.startsWith(part));
|
||||
}
|
||||
|
||||
private static int indexOf(String str, String otherStr, int startIndex, boolean ignoreCase) {
|
||||
if (!ignoreCase) {
|
||||
return str.indexOf(otherStr, startIndex);
|
||||
}
|
||||
for (int i = startIndex; i <= (str.length() - otherStr.length()); i++) {
|
||||
if (str.regionMatches(true, i, otherStr, 0, otherStr.length())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
pattern.startsWith(str.substring(0, firstIndex)) &&
|
||||
simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,19 +94,4 @@ public abstract class PatternMatchUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #simpleMatch(String[], String)} that ignores upper/lower case.
|
||||
* @since 6.1.20
|
||||
*/
|
||||
public static boolean simpleMatchIgnoreCase(@Nullable String[] patterns, @Nullable String str) {
|
||||
if (patterns != null) {
|
||||
for (String pattern : patterns) {
|
||||
if (simpleMatch(pattern, str, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ public abstract class StreamUtils {
|
||||
}
|
||||
|
||||
|
||||
private static final class NonClosingInputStream extends FilterInputStream {
|
||||
private static class NonClosingInputStream extends FilterInputStream {
|
||||
|
||||
public NonClosingInputStream(InputStream in) {
|
||||
super(in);
|
||||
@@ -248,30 +248,10 @@ public abstract class StreamUtils {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readAllBytes() throws IOException {
|
||||
return in.readAllBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readNBytes(int len) throws IOException {
|
||||
return in.readNBytes(len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readNBytes(byte[] b, int off, int len) throws IOException {
|
||||
return in.readNBytes(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long transferTo(OutputStream out) throws IOException {
|
||||
return in.transferTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class NonClosingOutputStream extends FilterOutputStream {
|
||||
private static class NonClosingOutputStream extends FilterOutputStream {
|
||||
|
||||
public NonClosingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,19 +35,16 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public abstract class SystemPropertyUtils {
|
||||
|
||||
/** Prefix for property placeholders: {@value}. */
|
||||
/** Prefix for system property placeholders: {@value}. */
|
||||
public static final String PLACEHOLDER_PREFIX = "${";
|
||||
|
||||
/** Suffix for property placeholders: {@value}. */
|
||||
/** Suffix for system property placeholders: {@value}. */
|
||||
public static final String PLACEHOLDER_SUFFIX = "}";
|
||||
|
||||
/** Value separator for property placeholders: {@value}. */
|
||||
/** Value separator for system property placeholders: {@value}. */
|
||||
public static final String VALUE_SEPARATOR = ":";
|
||||
|
||||
/**
|
||||
* Escape character for property placeholders: {@code '\'}.
|
||||
* @since 6.2
|
||||
*/
|
||||
/** Default escape character: {@code '\'}. */
|
||||
public static final Character ESCAPE_CHARACTER = '\\';
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -85,7 +85,6 @@ public class ExponentialBackOff implements BackOff {
|
||||
*/
|
||||
public static final int DEFAULT_MAX_ATTEMPTS = Integer.MAX_VALUE;
|
||||
|
||||
|
||||
private long initialInterval = DEFAULT_INITIAL_INTERVAL;
|
||||
|
||||
private double multiplier = DEFAULT_MULTIPLIER;
|
||||
@@ -205,7 +204,6 @@ public class ExponentialBackOff implements BackOff {
|
||||
return this.maxAttempts;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BackOffExecution start() {
|
||||
return new ExponentialBackOffExecution();
|
||||
@@ -227,7 +225,6 @@ public class ExponentialBackOff implements BackOff {
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
private class ExponentialBackOffExecution implements BackOffExecution {
|
||||
|
||||
private long currentInterval = -1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,7 +35,6 @@ public class FixedBackOff implements BackOff {
|
||||
*/
|
||||
public static final long UNLIMITED_ATTEMPTS = Long.MAX_VALUE;
|
||||
|
||||
|
||||
private long interval = DEFAULT_INTERVAL;
|
||||
|
||||
private long maxAttempts = UNLIMITED_ATTEMPTS;
|
||||
@@ -87,7 +86,6 @@ public class FixedBackOff implements BackOff {
|
||||
return this.maxAttempts;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BackOffExecution start() {
|
||||
return new FixedBackOffExecution();
|
||||
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.env;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.springframework.core.env.AbstractPropertyResolver.DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME;
|
||||
import static org.springframework.core.env.AbstractPropertyResolver.UNDEFINED_ESCAPE_CHARACTER;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AbstractPropertyResolver}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.7
|
||||
*/
|
||||
class AbstractPropertyResolverTests {
|
||||
|
||||
@BeforeEach
|
||||
void resetStateBeforeEachTest() {
|
||||
resetState();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void resetState() {
|
||||
AbstractPropertyResolver.defaultEscapeCharacter = UNDEFINED_ESCAPE_CHARACTER;
|
||||
setSpringProperty(null);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getDefaultEscapeCharacterWithSpringPropertySetToCharacterMinValue() {
|
||||
setSpringProperty("" + Character.MIN_VALUE);
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(AbstractPropertyResolver::getDefaultEscapeCharacter)
|
||||
.withMessage("Value for property [%s] must not be Character.MIN_VALUE",
|
||||
DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
|
||||
|
||||
assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(UNDEFINED_ESCAPE_CHARACTER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultEscapeCharacterWithSpringPropertySetToXyz() {
|
||||
setSpringProperty("XYZ");
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(AbstractPropertyResolver::getDefaultEscapeCharacter)
|
||||
.withMessage("Value [XYZ] for property [%s] must be a single character or an empty string",
|
||||
DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
|
||||
|
||||
assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(UNDEFINED_ESCAPE_CHARACTER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultEscapeCharacterWithSpringPropertySetToEmptyString() {
|
||||
setSpringProperty("");
|
||||
assertEscapeCharacter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultEscapeCharacterWithoutSpringPropertySet() {
|
||||
assertEscapeCharacter('\\');
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultEscapeCharacterWithSpringPropertySetToBackslash() {
|
||||
setSpringProperty("\\");
|
||||
assertEscapeCharacter('\\');
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultEscapeCharacterWithSpringPropertySetToTilde() {
|
||||
setSpringProperty("~");
|
||||
assertEscapeCharacter('~');
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultEscapeCharacterFromMultipleThreads() {
|
||||
setSpringProperty("~");
|
||||
|
||||
IntStream.range(1, 32).parallel().forEach(__ ->
|
||||
assertThat(AbstractPropertyResolver.getDefaultEscapeCharacter()).isEqualTo('~'));
|
||||
|
||||
assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo('~');
|
||||
}
|
||||
|
||||
|
||||
private static void setSpringProperty(String value) {
|
||||
SpringProperties.setProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, value);
|
||||
}
|
||||
|
||||
private static void assertEscapeCharacter(@Nullable Character expected) {
|
||||
assertThat(AbstractPropertyResolver.getDefaultEscapeCharacter()).isEqualTo(expected);
|
||||
assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(expected);
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+70
-96
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,7 +21,6 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
@@ -39,15 +38,18 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class PropertySourcesPropertyResolverTests {
|
||||
|
||||
private final Properties testProperties = new Properties();
|
||||
private Properties testProperties;
|
||||
|
||||
private final MutablePropertySources propertySources = new MutablePropertySources();
|
||||
private MutablePropertySources propertySources;
|
||||
|
||||
private final PropertySourcesPropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
private ConfigurablePropertyResolver propertyResolver;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
propertySources = new MutablePropertySources();
|
||||
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
testProperties = new Properties();
|
||||
propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties));
|
||||
}
|
||||
|
||||
@@ -75,12 +77,14 @@ class PropertySourcesPropertyResolverTests {
|
||||
|
||||
@Test
|
||||
void getProperty_propertySourceSearchOrderIsFIFO() {
|
||||
propertySources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value"));
|
||||
assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps1Value");
|
||||
propertySources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value"));
|
||||
assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps2Value");
|
||||
propertySources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value"));
|
||||
assertThat(propertyResolver.getProperty("pName")).isEqualTo("ps3Value");
|
||||
MutablePropertySources sources = new MutablePropertySources();
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
|
||||
sources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value"));
|
||||
assertThat(resolver.getProperty("pName")).isEqualTo("ps1Value");
|
||||
sources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value"));
|
||||
assertThat(resolver.getProperty("pName")).isEqualTo("ps2Value");
|
||||
sources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value"));
|
||||
assertThat(resolver.getProperty("pName")).isEqualTo("ps3Value");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,8 +115,8 @@ class PropertySourcesPropertyResolverTests {
|
||||
|
||||
class TestType { }
|
||||
|
||||
assertThatExceptionOfType(ConverterNotFoundException.class)
|
||||
.isThrownBy(() -> propertyResolver.getProperty("foo", TestType.class));
|
||||
assertThatExceptionOfType(ConverterNotFoundException.class).isThrownBy(() ->
|
||||
propertyResolver.getProperty("foo", TestType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,6 +127,7 @@ class PropertySourcesPropertyResolverTests {
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put(key, value1); // before construction
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MapPropertySource("testProperties", map));
|
||||
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(propertyResolver.getProperty(key)).isEqualTo(value1);
|
||||
@@ -133,6 +138,7 @@ class PropertySourcesPropertyResolverTests {
|
||||
@Test
|
||||
void getProperty_doesNotCache_addNewKeyPostConstruction() {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MapPropertySource("testProperties", map));
|
||||
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(propertyResolver.getProperty("foo")).isNull();
|
||||
@@ -142,9 +148,10 @@ class PropertySourcesPropertyResolverTests {
|
||||
|
||||
@Test
|
||||
void getPropertySources_replacePropertySource() {
|
||||
propertySources = new MutablePropertySources();
|
||||
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
propertySources.addLast(new MockPropertySource("local").withProperty("foo", "localValue"));
|
||||
propertySources.addLast(new MockPropertySource("system").withProperty("foo", "systemValue"));
|
||||
assertThat(propertySources).hasSize(3);
|
||||
|
||||
// 'local' was added first so has precedence
|
||||
assertThat(propertyResolver.getProperty("foo")).isEqualTo("localValue");
|
||||
@@ -155,7 +162,7 @@ class PropertySourcesPropertyResolverTests {
|
||||
// 'system' now has precedence
|
||||
assertThat(propertyResolver.getProperty("foo")).isEqualTo("newValue");
|
||||
|
||||
assertThat(propertySources).hasSize(3);
|
||||
assertThat(propertySources).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -163,65 +170,81 @@ class PropertySourcesPropertyResolverTests {
|
||||
testProperties.put("exists", "xyz");
|
||||
assertThat(propertyResolver.getRequiredProperty("exists")).isEqualTo("xyz");
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() -> propertyResolver.getRequiredProperty("bogus"));
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
propertyResolver.getRequiredProperty("bogus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRequiredProperty_withStringArrayConversion() {
|
||||
testProperties.put("exists", "abc,123");
|
||||
assertThat(propertyResolver.getRequiredProperty("exists", String[].class)).containsExactly("abc", "123");
|
||||
assertThat(propertyResolver.getRequiredProperty("exists", String[].class)).isEqualTo(new String[] { "abc", "123" });
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() -> propertyResolver.getRequiredProperty("bogus", String[].class));
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
propertyResolver.getRequiredProperty("bogus", String[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvePlaceholders() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvePlaceholders_withUnresolvable() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"))
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"))
|
||||
.isEqualTo("Replace this value plus ${unknown}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvePlaceholders_withDefaultValue() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
.isEqualTo("Replace this value plus defaultValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvePlaceholders_withNullInput() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolvePlaceholders(null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders_withUnresolvable() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}"));
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
|
||||
resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders_withDefaultValue() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
.isEqualTo("Replace this value plus defaultValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders_withNullInput() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders(null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -233,17 +256,17 @@ class PropertySourcesPropertyResolverTests {
|
||||
propertyResolver.setRequiredProperties("foo", "bar");
|
||||
|
||||
// neither foo nor bar properties are present -> validating should throw
|
||||
assertThatExceptionOfType(MissingRequiredPropertiesException.class)
|
||||
.isThrownBy(propertyResolver::validateRequiredProperties)
|
||||
.withMessage("The following properties were declared as required " +
|
||||
"but could not be resolved: [foo, bar]");
|
||||
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
|
||||
propertyResolver::validateRequiredProperties)
|
||||
.withMessage("The following properties were declared as required " +
|
||||
"but could not be resolved: [foo, bar]");
|
||||
|
||||
// add foo property -> validation should fail only on missing 'bar' property
|
||||
testProperties.put("foo", "fooValue");
|
||||
assertThatExceptionOfType(MissingRequiredPropertiesException.class)
|
||||
.isThrownBy(propertyResolver::validateRequiredProperties)
|
||||
.withMessage("The following properties were declared as required " +
|
||||
"but could not be resolved: [bar]");
|
||||
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
|
||||
propertyResolver::validateRequiredProperties)
|
||||
.withMessage("The following properties were declared as required " +
|
||||
"but could not be resolved: [bar]");
|
||||
|
||||
// add bar property -> validation should pass, even with an empty string value
|
||||
testProperties.put("bar", "");
|
||||
@@ -268,13 +291,13 @@ class PropertySourcesPropertyResolverTests {
|
||||
assertThat(pr.getProperty("p2")).isEqualTo("v2");
|
||||
assertThat(pr.getProperty("p3")).isEqualTo("v1:v2");
|
||||
assertThat(pr.getProperty("p4")).isEqualTo("v1:v2");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> pr.getProperty("p5"))
|
||||
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
|
||||
pr.getProperty("p5"))
|
||||
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
|
||||
assertThat(pr.getProperty("p6")).isEqualTo("v1:v2:def");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> pr.getProperty("pL"))
|
||||
.withMessageContaining("Circular");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
|
||||
pr.getProperty("pL"))
|
||||
.withMessageContaining("Circular");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -326,9 +349,9 @@ class PropertySourcesPropertyResolverTests {
|
||||
|
||||
// placeholders nested within the value of "p4" are unresolvable and cause an
|
||||
// exception by default
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> pr.getProperty("p4"))
|
||||
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
|
||||
pr.getProperty("p4"))
|
||||
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
|
||||
|
||||
// relax the treatment of unresolvable nested placeholders
|
||||
pr.setIgnoreUnresolvableNestedPlaceholders(true);
|
||||
@@ -338,58 +361,9 @@ class PropertySourcesPropertyResolverTests {
|
||||
// resolve[Nested]Placeholders methods behave as usual regardless the value of
|
||||
// ignoreUnresolvableNestedPlaceholders
|
||||
assertThat(pr.resolvePlaceholders("${p1}:${p2}:${bogus}")).isEqualTo("v1:v2:${bogus}");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}"))
|
||||
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class EscapedPlaceholderTests {
|
||||
|
||||
@Test // gh-34720
|
||||
void escapedPlaceholdersAreNotEvaluated() {
|
||||
testProperties.put("prop1", "value1");
|
||||
testProperties.put("prop2", "value2\\${prop1}");
|
||||
|
||||
assertThat(propertyResolver.getProperty("prop2")).isEqualTo("value2${prop1}");
|
||||
}
|
||||
|
||||
@Test // gh-34720
|
||||
void escapedPlaceholdersAreNotEvaluatedWithCharSequenceValues() {
|
||||
testProperties.put("prop1", "value1");
|
||||
testProperties.put("prop2", new StringBuilder("value2\\${prop1}"));
|
||||
|
||||
assertThat(propertyResolver.getProperty("prop2")).isEqualTo("value2${prop1}");
|
||||
}
|
||||
|
||||
@Test // gh-34720
|
||||
void multipleEscapedPlaceholdersArePreserved() {
|
||||
testProperties.put("prop1", "value1");
|
||||
testProperties.put("prop2", "value2");
|
||||
testProperties.put("complex", "start\\${prop1}middle\\${prop2}end");
|
||||
|
||||
assertThat(propertyResolver.getProperty("complex")).isEqualTo("start${prop1}middle${prop2}end");
|
||||
}
|
||||
|
||||
@Test // gh-34720
|
||||
void doubleBackslashesAreProcessedCorrectly() {
|
||||
testProperties.put("prop1", "value1");
|
||||
testProperties.put("doubleEscaped", "value2\\\\${prop1}");
|
||||
|
||||
assertThat(propertyResolver.getProperty("doubleEscaped")).isEqualTo("value2\\${prop1}");
|
||||
}
|
||||
|
||||
@Test // gh-34720
|
||||
void escapedPlaceholdersInNestedPropertiesAreNotEvaluated() {
|
||||
testProperties.put("p1", "v1");
|
||||
testProperties.put("p2", "v2");
|
||||
testProperties.put("escaped", "prefix-\\${p1}");
|
||||
testProperties.put("nested", "${escaped}-${p2}");
|
||||
|
||||
assertThat(propertyResolver.getProperty("nested")).isEqualTo("prefix-${p1}-v2");
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
|
||||
pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}"))
|
||||
.withMessageContaining("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-5
@@ -335,11 +335,6 @@ class PathMatchingResourcePatternResolverTests {
|
||||
}
|
||||
assertThat(new FileSystemResource(path).exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isFalse();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isFalse();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse();
|
||||
}
|
||||
|
||||
private void writeApplicationJar(Path path) throws Exception {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -53,22 +53,18 @@ class PatternMatchUtilsTests {
|
||||
assertMatches(new String[] { null, "" }, "");
|
||||
assertMatches(new String[] { null, "123" }, "123");
|
||||
assertMatches(new String[] { null, "*" }, "123");
|
||||
|
||||
testMixedCaseMatch("abC", "Abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startsWith() {
|
||||
assertMatches("get*", "getMe");
|
||||
assertDoesNotMatch("get*", "setMe");
|
||||
testMixedCaseMatch("geT*", "GetMe");
|
||||
}
|
||||
|
||||
@Test
|
||||
void endsWith() {
|
||||
assertMatches("*Test", "getMeTest");
|
||||
assertDoesNotMatch("*Test", "setMe");
|
||||
testMixedCaseMatch("*TeSt", "getMeTesT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,10 +74,6 @@ class PatternMatchUtilsTests {
|
||||
assertMatches("*stuff*", "stuffTest");
|
||||
assertMatches("*stuff*", "getstuff");
|
||||
assertMatches("*stuff*", "stuff");
|
||||
testMixedCaseMatch("*stuff*", "getStuffTest");
|
||||
testMixedCaseMatch("*stuff*", "StuffTest");
|
||||
testMixedCaseMatch("*stuff*", "getStuff");
|
||||
testMixedCaseMatch("*stuff*", "Stuff");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,8 +82,6 @@ class PatternMatchUtilsTests {
|
||||
assertMatches("on*Event", "onEvent");
|
||||
assertDoesNotMatch("3*3", "3");
|
||||
assertMatches("3*3", "33");
|
||||
testMixedCaseMatch("on*Event", "OnMyEvenT");
|
||||
testMixedCaseMatch("on*Event", "OnEvenT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -132,27 +122,18 @@ class PatternMatchUtilsTests {
|
||||
|
||||
private void assertMatches(String pattern, String str) {
|
||||
assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isTrue();
|
||||
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isTrue();
|
||||
}
|
||||
|
||||
private void assertDoesNotMatch(String pattern, String str) {
|
||||
assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isFalse();
|
||||
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isFalse();
|
||||
}
|
||||
|
||||
private void testMixedCaseMatch(String pattern, String str) {
|
||||
assertThat(PatternMatchUtils.simpleMatch(pattern, str)).isFalse();
|
||||
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(pattern, str)).isTrue();
|
||||
}
|
||||
|
||||
private void assertMatches(String[] patterns, String str) {
|
||||
assertThat(PatternMatchUtils.simpleMatch(patterns, str)).isTrue();
|
||||
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(patterns, str)).isTrue();
|
||||
}
|
||||
|
||||
private void assertDoesNotMatch(String[] patterns, String str) {
|
||||
assertThat(PatternMatchUtils.simpleMatch(patterns, str)).isFalse();
|
||||
assertThat(PatternMatchUtils.simpleMatchIgnoreCase(patterns, str)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
@@ -36,13 +36,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link PlaceholderParser}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class PlaceholderParserTests {
|
||||
|
||||
@@ -54,11 +54,11 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("placeholders")
|
||||
void placeholderIsReplaced(String text, String expected) {
|
||||
Map<String, String> properties = Map.of(
|
||||
"firstName", "John",
|
||||
"nested0", "first",
|
||||
"nested1", "Name");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("firstName", "John");
|
||||
properties.setProperty("nested0", "first");
|
||||
properties.setProperty("nested1", "Name");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> placeholders() {
|
||||
@@ -79,13 +79,13 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("nestedPlaceholders")
|
||||
void nestedPlaceholdersAreReplaced(String text, String expected) {
|
||||
Map<String, String> properties = Map.of(
|
||||
"p1", "v1",
|
||||
"p2", "v2",
|
||||
"p3", "${p1}:${p2}", // nested placeholders
|
||||
"p4", "${p3}", // deeply nested placeholders
|
||||
"p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("p1", "v1");
|
||||
properties.setProperty("p2", "v2");
|
||||
properties.setProperty("p3", "${p1}:${p2}"); // nested placeholders
|
||||
properties.setProperty("p4", "${p3}"); // deeply nested placeholders
|
||||
properties.setProperty("p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> nestedPlaceholders() {
|
||||
@@ -101,15 +101,19 @@ class PlaceholderParserTests {
|
||||
@Test
|
||||
void parseWithSinglePlaceholder() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${firstName}", resolver)).isEqualTo("John");
|
||||
verifyPlaceholderResolutions(resolver, "firstName");
|
||||
assertThat(this.parser.replacePlaceholders("${firstName}", resolver))
|
||||
.isEqualTo("John");
|
||||
verify(resolver).resolvePlaceholder("firstName");
|
||||
verifyNoMoreInteractions(resolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithPlaceholderAndPrefixText() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver)).isEqualTo("This is John");
|
||||
verifyPlaceholderResolutions(resolver, "firstName");
|
||||
assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver))
|
||||
.isEqualTo("This is John");
|
||||
verify(resolver).resolvePlaceholder("firstName");
|
||||
verifyNoMoreInteractions(resolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,25 +121,31 @@ class PlaceholderParserTests {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John", "lastName", "Smith");
|
||||
assertThat(this.parser.replacePlaceholders("User: ${firstName} - ${lastName}.", resolver))
|
||||
.isEqualTo("User: John - Smith.");
|
||||
verifyPlaceholderResolutions(resolver, "firstName", "lastName");
|
||||
verify(resolver).resolvePlaceholder("firstName");
|
||||
verify(resolver).resolvePlaceholder("lastName");
|
||||
verifyNoMoreInteractions(resolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithNestedPlaceholderInKey() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("nested", "Name", "firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${first${nested}}", resolver)).isEqualTo("John");
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver(
|
||||
"nested", "Name", "firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${first${nested}}", resolver))
|
||||
.isEqualTo("John");
|
||||
verifyPlaceholderResolutions(resolver, "nested", "firstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithMultipleNestedPlaceholdersInKey() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("nested0", "first", "nested1", "Name", "firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${${nested0}${nested1}}", resolver)).isEqualTo("John");
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver(
|
||||
"nested0", "first", "nested1", "Name", "firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${${nested0}${nested1}}", resolver))
|
||||
.isEqualTo("John");
|
||||
verifyPlaceholderResolutions(resolver, "nested0", "nested1", "firstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void placeholderValueContainingSeparatorIsHandledAsIs() {
|
||||
void placeholdersWithSeparatorAreHandledAsIs() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("my:test", "value");
|
||||
assertThat(this.parser.replacePlaceholders("${my:test}", resolver)).isEqualTo("value");
|
||||
verifyPlaceholderResolutions(resolver, "my:test");
|
||||
@@ -143,20 +153,17 @@ class PlaceholderParserTests {
|
||||
|
||||
@Test
|
||||
void placeholdersWithoutEscapeCharAreNotEscaped() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("p1", "v1", "p2", "v2", "p3", "v3", "p4", "v4");
|
||||
assertThat(this.parser.replacePlaceholders("\\${p1}", resolver)).isEqualTo("\\v1");
|
||||
assertThat(this.parser.replacePlaceholders("\\\\${p2}", resolver)).isEqualTo("\\\\v2");
|
||||
assertThat(this.parser.replacePlaceholders("\\${p3}\\", resolver)).isEqualTo("\\v3\\");
|
||||
assertThat(this.parser.replacePlaceholders("a\\${p4}\\z", resolver)).isEqualTo("a\\v4\\z");
|
||||
verifyPlaceholderResolutions(resolver, "p1", "p2", "p3", "p4");
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("test", "value");
|
||||
assertThat(this.parser.replacePlaceholders("\\${test}", resolver)).isEqualTo("\\value");
|
||||
verifyPlaceholderResolutions(resolver, "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void textWithInvalidPlaceholderSyntaxIsMerged() {
|
||||
void textWithInvalidPlaceholderIsMerged() {
|
||||
String text = "test${of${with${and${";
|
||||
ParsedValue parsedValue = this.parser.parse(text);
|
||||
assertThat(parsedValue.parts()).singleElement().isInstanceOfSatisfying(TextPart.class,
|
||||
textPart -> assertThat(textPart.text()).isEqualTo(text));
|
||||
assertThat(parsedValue.parts()).singleElement().isInstanceOfSatisfying(
|
||||
TextPart.class, textPart -> assertThat(textPart.text()).isEqualTo(text));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -169,11 +176,11 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("placeholders")
|
||||
void placeholderIsReplaced(String text, String expected) {
|
||||
Map<String, String> properties = Map.of(
|
||||
"firstName", "John",
|
||||
"nested0", "first",
|
||||
"nested1", "Name");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("firstName", "John");
|
||||
properties.setProperty("nested0", "first");
|
||||
properties.setProperty("nested1", "Name");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> placeholders() {
|
||||
@@ -192,14 +199,14 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("nestedPlaceholders")
|
||||
void nestedPlaceholdersAreReplaced(String text, String expected) {
|
||||
Map<String, String> properties = Map.of(
|
||||
"p1", "v1",
|
||||
"p2", "v2",
|
||||
"p3", "${p1}:${p2}", // nested placeholders
|
||||
"p4", "${p3}", // deeply nested placeholders
|
||||
"p5", "${p1}:${p2}:${bogus}", // unresolvable placeholder
|
||||
"p6", "${p1}:${p2}:${bogus:def}"); // unresolvable w/ default
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("p1", "v1");
|
||||
properties.setProperty("p2", "v2");
|
||||
properties.setProperty("p3", "${p1}:${p2}"); // nested placeholders
|
||||
properties.setProperty("p4", "${p3}"); // deeply nested placeholders
|
||||
properties.setProperty("p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder
|
||||
properties.setProperty("p6", "${p1}:${p2}:${bogus:def}"); // unresolvable w/ default
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> nestedPlaceholders() {
|
||||
@@ -218,11 +225,11 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("exactMatchPlaceholders")
|
||||
void placeholdersWithExactMatchAreConsidered(String text, String expected) {
|
||||
Map<String, String> properties = Map.of(
|
||||
"prefix://my-service", "example-service",
|
||||
"px", "prefix",
|
||||
"p1", "${prefix://my-service}");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("prefix://my-service", "example-service");
|
||||
properties.setProperty("px", "prefix");
|
||||
properties.setProperty("p1", "${prefix://my-service}");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> exactMatchPlaceholders() {
|
||||
@@ -235,55 +242,74 @@ class PlaceholderParserTests {
|
||||
@Test
|
||||
void parseWithKeyEqualsToText() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "Steve");
|
||||
assertThat(this.parser.replacePlaceholders("${firstName}", resolver)).isEqualTo("Steve");
|
||||
assertThat(this.parser.replacePlaceholders("${firstName}", resolver))
|
||||
.isEqualTo("Steve");
|
||||
verifyPlaceholderResolutions(resolver, "firstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithHardcodedFallback() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver();
|
||||
assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver)).isEqualTo("Steve");
|
||||
assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver))
|
||||
.isEqualTo("Steve");
|
||||
verifyPlaceholderResolutions(resolver, "firstName:Steve", "firstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithNestedPlaceholderInKeyUsingFallback() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver)).isEqualTo("John");
|
||||
assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver))
|
||||
.isEqualTo("John");
|
||||
verifyPlaceholderResolutions(resolver, "invalid:Name", "invalid", "firstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithFallbackUsingPlaceholder() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${invalid:${firstName}}", resolver)).isEqualTo("John");
|
||||
assertThat(this.parser.replacePlaceholders("${invalid:${firstName}}", resolver))
|
||||
.isEqualTo("John");
|
||||
verifyPlaceholderResolutions(resolver, "invalid", "firstName");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that use the escape character.
|
||||
*/
|
||||
@Nested
|
||||
@Nested // Tests with the use of the escape character
|
||||
class EscapedTests {
|
||||
|
||||
private final PlaceholderParser parser = new PlaceholderParser("${", "}", ":", '\\', true);
|
||||
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("escapedInNestedPlaceholders")
|
||||
void escapedSeparatorInNestedPlaceholder(String text, String expected) {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("app.environment", "qa");
|
||||
properties.setProperty("app.service", "protocol");
|
||||
properties.setProperty("protocol://host/qa/name", "protocol://example.com/qa/name");
|
||||
properties.setProperty("service/host/qa/name", "https://example.com/qa/name");
|
||||
properties.setProperty("service/host/qa/name:value", "https://example.com/qa/name-value");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> escapedInNestedPlaceholders() {
|
||||
return Stream.of(
|
||||
Arguments.of("${protocol\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
|
||||
Arguments.of("${${app.service}\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
|
||||
Arguments.of("${service/host/${app.environment}/name:\\value}", "https://example.com/qa/name"),
|
||||
Arguments.of("${service/host/${name\\:value}/}", "${service/host/${name:value}/}"));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("escapedPlaceholders")
|
||||
void escapedPlaceholderIsNotReplaced(String text, String expected) {
|
||||
Map<String, String> properties = Map.of(
|
||||
"firstName", "John",
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver(
|
||||
"firstName", "John", "nested0", "first", "nested1", "Name",
|
||||
"${test}", "John",
|
||||
"p1", "v1",
|
||||
"p2", "\\${p1:default}",
|
||||
"p3", "${p2}",
|
||||
"p1", "v1", "p2", "\\${p1:default}", "p3", "${p2}",
|
||||
"p4", "adc${p0:\\${p1}}",
|
||||
"p5", "adc${\\${p0}:${p1}}",
|
||||
"p6", "adc${p0:def\\${p1}}",
|
||||
"p7", "adc\\${");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
assertThat(this.parser.replacePlaceholders(text, resolver)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> escapedPlaceholders() {
|
||||
@@ -298,21 +324,18 @@ class PlaceholderParserTests {
|
||||
Arguments.of("${p4}", "adc${p1}"),
|
||||
Arguments.of("${p5}", "adcv1"),
|
||||
Arguments.of("${p6}", "adcdef${p1}"),
|
||||
Arguments.of("${p7}", "adc\\${"),
|
||||
// Double backslash
|
||||
Arguments.of("DOMAIN\\\\${user.name}", "DOMAIN\\${user.name}"),
|
||||
// Triple backslash
|
||||
Arguments.of("triple\\\\\\${backslash}", "triple\\\\${backslash}"),
|
||||
// Multiple escaped placeholders
|
||||
Arguments.of("start\\${prop1}middle\\${prop2}end", "start${prop1}middle${prop2}end")
|
||||
);
|
||||
Arguments.of("${p7}", "adc\\${"));
|
||||
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("escapedSeparators")
|
||||
void escapedSeparatorIsNotReplaced(String text, String expected) {
|
||||
Map<String, String> properties = Map.of("first:Name", "John");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("first:Name", "John");
|
||||
properties.setProperty("nested0", "first");
|
||||
properties.setProperty("nested1", "Name");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> escapedSeparators() {
|
||||
@@ -322,26 +345,6 @@ class PlaceholderParserTests {
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("escapedSeparatorsInNestedPlaceholders")
|
||||
void escapedSeparatorInNestedPlaceholderIsNotReplaced(String text, String expected) {
|
||||
Map<String, String> properties = Map.of(
|
||||
"app.environment", "qa",
|
||||
"app.service", "protocol",
|
||||
"protocol://host/qa/name", "protocol://example.com/qa/name",
|
||||
"service/host/qa/name", "https://example.com/qa/name",
|
||||
"service/host/qa/name:value", "https://example.com/qa/name-value");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> escapedSeparatorsInNestedPlaceholders() {
|
||||
return Stream.of(
|
||||
Arguments.of("${protocol\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
|
||||
Arguments.of("${${app.service}\\://host/${app.environment}/name}", "protocol://example.com/qa/name"),
|
||||
Arguments.of("${service/host/${app.environment}/name:\\value}", "https://example.com/qa/name"),
|
||||
Arguments.of("${service/host/${name\\:value}/}", "${service/host/${name:value}/}"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -351,38 +354,34 @@ class PlaceholderParserTests {
|
||||
|
||||
@Test
|
||||
void textWithCircularReference() {
|
||||
Map<String, String> properties = Map.of(
|
||||
"pL", "${pR}",
|
||||
"pR", "${pL}");
|
||||
assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", properties::get))
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("pL", "${pR}", "pR", "${pL}");
|
||||
assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", resolver))
|
||||
.isInstanceOf(PlaceholderResolutionException.class)
|
||||
.hasMessage("Circular placeholder reference 'pL' in value \"${pL}\" <-- \"${pR}\" <-- \"${pL}\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unresolvablePlaceholderIsReported() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver();
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> this.parser.replacePlaceholders("X${bogus}Z", placeholderName -> null))
|
||||
.withMessage("Could not resolve placeholder 'bogus' in value \"X${bogus}Z\"")
|
||||
.isThrownBy(() -> this.parser.replacePlaceholders("${bogus}", resolver))
|
||||
.withMessage("Could not resolve placeholder 'bogus' in value \"${bogus}\"")
|
||||
.withNoCause();
|
||||
}
|
||||
|
||||
@Test
|
||||
void unresolvablePlaceholderInNestedPlaceholderIsReportedWithChain() {
|
||||
Map<String, String> properties = Map.of(
|
||||
"p1", "v1",
|
||||
"p2", "v2",
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("p1", "v1", "p2", "v2",
|
||||
"p3", "${p1}:${p2}:${bogus}");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> this.parser.replacePlaceholders("${p3}", properties::get))
|
||||
.isThrownBy(() -> this.parser.replacePlaceholders("${p3}", resolver))
|
||||
.withMessage("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\" <-- \"${p3}\"")
|
||||
.withNoCause();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static PlaceholderResolver mockPlaceholderResolver(String... pairs) {
|
||||
PlaceholderResolver mockPlaceholderResolver(String... pairs) {
|
||||
if (pairs.length % 2 == 1) {
|
||||
throw new IllegalArgumentException("size must be even, it is a set of key=value pairs");
|
||||
}
|
||||
@@ -395,7 +394,7 @@ class PlaceholderParserTests {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
private static void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) {
|
||||
void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) {
|
||||
InOrder ordered = inOrder(mock);
|
||||
for (String placeholder : placeholders) {
|
||||
ordered.verify(mock).resolvePlaceholder(placeholder);
|
||||
|
||||
+1
-84
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +16,8 @@
|
||||
|
||||
package org.springframework.jdbc.core.simple;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassRelativeResourceLoader;
|
||||
@@ -147,86 +144,6 @@ class JdbcClientIntegrationTests {
|
||||
}
|
||||
|
||||
|
||||
@Nested // gh-34768
|
||||
class ReusedNamedParameterTests {
|
||||
|
||||
private static final String QUERY1 = """
|
||||
select * from users
|
||||
where
|
||||
first_name in ('Bogus', :name) or
|
||||
last_name in (:name, 'Bogus')
|
||||
order by last_name
|
||||
""";
|
||||
|
||||
private static final String QUERY2 = """
|
||||
select * from users
|
||||
where
|
||||
first_name in (:names) or
|
||||
last_name in (:names)
|
||||
order by last_name
|
||||
""";
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void insertTestUsers() {
|
||||
jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "John").update();
|
||||
jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("John", "Smith").update();
|
||||
jdbcClient.sql(INSERT_WITH_JDBC_PARAMS).params("Smith", "Smith").update();
|
||||
assertNumUsers(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithReusedNamedParameter() {
|
||||
List<User> users = jdbcClient.sql(QUERY1)
|
||||
.param("name", "John")
|
||||
.query(User.class)
|
||||
.list();
|
||||
|
||||
assertResults(users);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithReusedNamedParameterFromBeanProperties() {
|
||||
List<User> users = jdbcClient.sql(QUERY1)
|
||||
.paramSource(new Name("John"))
|
||||
.query(User.class)
|
||||
.list();
|
||||
|
||||
assertResults(users);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithReusedNamedParameterList() {
|
||||
List<User> users = jdbcClient.sql(QUERY2)
|
||||
.param("names", List.of("John", "Bogus"))
|
||||
.query(User.class)
|
||||
.list();
|
||||
|
||||
assertResults(users);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectWithReusedNamedParameterListFromBeanProperties() {
|
||||
List<User> users = jdbcClient.sql(QUERY2)
|
||||
.paramSource(new Names(List.of("John", "Bogus")))
|
||||
.query(User.class)
|
||||
.list();
|
||||
|
||||
assertResults(users);
|
||||
}
|
||||
|
||||
|
||||
private static void assertResults(List<User> users) {
|
||||
assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith"));
|
||||
}
|
||||
|
||||
record Name(String name) {}
|
||||
|
||||
record Names(List<String> names) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void assertNumUsers(long count) {
|
||||
long numUsers = this.jdbcClient.sql("select count(id) from users").query(Long.class).single();
|
||||
assertThat(numUsers).isEqualTo(count);
|
||||
|
||||
+61
-67
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,7 +49,6 @@ import org.springframework.util.Assert;
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Paluch
|
||||
* @author Anton Naydenov
|
||||
* @author Sam Brannen
|
||||
* @since 5.3
|
||||
*/
|
||||
abstract class NamedParameterUtils {
|
||||
@@ -514,17 +513,70 @@ abstract class NamedParameterUtils {
|
||||
|
||||
private final BindParameterSource parameterSource;
|
||||
|
||||
|
||||
ExpandedQuery(String expandedSql, NamedParameters parameters, BindParameterSource parameterSource) {
|
||||
this.expandedSql = expandedSql;
|
||||
this.parameters = parameters;
|
||||
this.parameterSource = parameterSource;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public void bind(BindTarget target, String identifier, Parameter parameter) {
|
||||
List<BindMarker> bindMarkers = getBindMarkers(identifier);
|
||||
if (bindMarkers == null) {
|
||||
target.bind(identifier, parameter);
|
||||
return;
|
||||
}
|
||||
if (parameter.getValue() instanceof Collection collection) {
|
||||
Iterator<Object> iterator = collection.iterator();
|
||||
Iterator<BindMarker> markers = bindMarkers.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Object valueToBind = iterator.next();
|
||||
if (valueToBind instanceof Object[] objects) {
|
||||
for (Object object : objects) {
|
||||
bind(target, markers, object);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bind(target, markers, valueToBind);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (BindMarker bindMarker : bindMarkers) {
|
||||
bindMarker.bind(target, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toQuery() {
|
||||
return this.expandedSql;
|
||||
private void bind(BindTarget target, Iterator<BindMarker> markers, Object valueToBind) {
|
||||
Assert.isTrue(markers.hasNext(), () -> String.format(
|
||||
"No bind marker for value [%s] in SQL [%s]. Check that the query was expanded using the same arguments.",
|
||||
valueToBind, toQuery()));
|
||||
markers.next().bind(target, valueToBind);
|
||||
}
|
||||
|
||||
public void bindNull(BindTarget target, String identifier, Parameter parameter) {
|
||||
List<BindMarker> bindMarkers = getBindMarkers(identifier);
|
||||
if (bindMarkers == null) {
|
||||
target.bind(identifier, parameter);
|
||||
return;
|
||||
}
|
||||
for (BindMarker bindMarker : bindMarkers) {
|
||||
bindMarker.bind(target, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
List<BindMarker> getBindMarkers(String identifier) {
|
||||
List<NamedParameters.NamedParameter> parameters = this.parameters.getMarker(identifier);
|
||||
if (parameters == null) {
|
||||
return null;
|
||||
}
|
||||
List<BindMarker> markers = new ArrayList<>();
|
||||
for (NamedParameters.NamedParameter parameter : parameters) {
|
||||
markers.addAll(parameter.placeholders);
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -545,68 +597,10 @@ abstract class NamedParameterUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private void bindNull(BindTarget target, String identifier, Parameter parameter) {
|
||||
List<List<BindMarker>> bindMarkers = getBindMarkers(identifier);
|
||||
if (bindMarkers == null) {
|
||||
target.bind(identifier, parameter);
|
||||
return;
|
||||
}
|
||||
for (List<BindMarker> outer : bindMarkers) {
|
||||
for (BindMarker bindMarker : outer) {
|
||||
bindMarker.bind(target, parameter);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toQuery() {
|
||||
return this.expandedSql;
|
||||
}
|
||||
|
||||
private void bind(BindTarget target, String identifier, Parameter parameter) {
|
||||
List<List<BindMarker>> bindMarkers = getBindMarkers(identifier);
|
||||
if (bindMarkers == null) {
|
||||
target.bind(identifier, parameter);
|
||||
return;
|
||||
}
|
||||
|
||||
for (List<BindMarker> outer : bindMarkers) {
|
||||
if (parameter.getValue() instanceof Collection<?> collection) {
|
||||
Iterator<BindMarker> markers = outer.iterator();
|
||||
for (Object valueToBind : collection) {
|
||||
if (valueToBind instanceof Object[] objects) {
|
||||
for (Object object : objects) {
|
||||
bind(target, markers, object);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bind(target, markers, valueToBind);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (BindMarker bindMarker : outer) {
|
||||
bindMarker.bind(target, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(BindTarget target, Iterator<BindMarker> markers, Object valueToBind) {
|
||||
Assert.isTrue(markers.hasNext(), () -> String.format(
|
||||
"No bind marker for value [%s] in SQL [%s]. Check that the query was expanded using the same arguments.",
|
||||
valueToBind, toQuery()));
|
||||
markers.next().bind(target, valueToBind);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<List<BindMarker>> getBindMarkers(String identifier) {
|
||||
List<NamedParameters.NamedParameter> parameters = this.parameters.getMarker(identifier);
|
||||
if (parameters == null) {
|
||||
return null;
|
||||
}
|
||||
List<List<BindMarker>> markers = new ArrayList<>();
|
||||
for (NamedParameters.NamedParameter parameter : parameters) {
|
||||
markers.add(new ArrayList<>(parameter.placeholders));
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,9 +19,8 @@ package org.springframework.r2dbc.core.binding;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
/**
|
||||
* Anonymous, index-based bind markers that use a static placeholder.
|
||||
*
|
||||
* <p>Instances are bound by the ordinal position ordered by the appearance of
|
||||
* Anonymous, index-based bind marker using a static placeholder.
|
||||
* Instances are bound by the ordinal position ordered by the appearance of
|
||||
* the placeholder. This implementation creates indexed bind markers using
|
||||
* an anonymous placeholder that correlates with an index.
|
||||
*
|
||||
@@ -47,7 +46,7 @@ class AnonymousBindMarkers implements BindMarkers {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link AnonymousBindMarkers} instance for the given {@code placeholder}.
|
||||
* Create a new {@link AnonymousBindMarkers} instance given {@code placeholder}.
|
||||
* @param placeholder parameter bind marker
|
||||
*/
|
||||
AnonymousBindMarkers(String placeholder) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,8 +20,7 @@ import io.r2dbc.spi.Statement;
|
||||
|
||||
/**
|
||||
* A bind marker represents a single bindable parameter within a query.
|
||||
*
|
||||
* <p>Bind markers are dialect-specific and provide a
|
||||
* Bind markers are dialect-specific and provide a
|
||||
* {@link #getPlaceholder() placeholder} that is used in the actual query.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
@@ -38,8 +37,7 @@ public interface BindMarker {
|
||||
String getPlaceholder();
|
||||
|
||||
/**
|
||||
* Bind the given {@code value} to the {@link Statement} using the underlying
|
||||
* binding strategy.
|
||||
* Bind the given {@code value} to the {@link Statement} using the underlying binding strategy.
|
||||
* @param bindTarget the target to bind the value to
|
||||
* @param value the actual value (must not be {@code null};
|
||||
* use {@link #bindNull(BindTarget, Class)} for {@code null} values)
|
||||
@@ -48,8 +46,7 @@ public interface BindMarker {
|
||||
void bind(BindTarget bindTarget, Object value);
|
||||
|
||||
/**
|
||||
* Bind a {@code null} value to the {@link Statement} using the underlying
|
||||
* binding strategy.
|
||||
* Bind a {@code null} value to the {@link Statement} using the underlying binding strategy.
|
||||
* @param bindTarget the target to bind the value to
|
||||
* @param valueType the value type (must not be {@code null})
|
||||
* @see Statement#bindNull
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,10 +20,10 @@ package org.springframework.r2dbc.core.binding;
|
||||
* Bind markers represent placeholders in SQL queries for substitution
|
||||
* for an actual parameter. Using bind markers allows creating safe queries
|
||||
* so query strings are not required to contain escaped values but rather
|
||||
* the driver encodes the parameter in the appropriate representation.
|
||||
* the driver encodes parameter in the appropriate representation.
|
||||
*
|
||||
* <p>{@link BindMarkers} is stateful and can be only used for a single binding
|
||||
* pass of one or more parameters. It maintains bind indexes or bind parameter names.
|
||||
* pass of one or more parameters. It maintains bind indexes/bind parameter names.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 5.3
|
||||
@@ -41,7 +41,7 @@ public interface BindMarkers {
|
||||
|
||||
/**
|
||||
* Create a new {@link BindMarker} that accepts a {@code hint}.
|
||||
* <p>Implementations are allowed to consider/ignore/filter
|
||||
* Implementations are allowed to consider/ignore/filter
|
||||
* the name hint to create more expressive bind markers.
|
||||
* @param hint an optional name hint that can be used as part of the bind marker
|
||||
* @return a new {@link BindMarker}
|
||||
|
||||
+13
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,8 +30,8 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
|
||||
/**
|
||||
* Resolves a {@link BindMarkersFactory} from a {@link ConnectionFactory} using
|
||||
* a {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's
|
||||
* {@link SpringFactoriesLoader spring.factories} file to determine available extensions.
|
||||
* {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's
|
||||
* {@link SpringFactoriesLoader spring.factories} to determine available extensions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 5.3
|
||||
@@ -45,8 +45,8 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a {@link BindMarkersFactory} by inspecting the supplied
|
||||
* {@link ConnectionFactory} and its metadata.
|
||||
* Retrieve a {@link BindMarkersFactory} by inspecting {@link ConnectionFactory}
|
||||
* and its metadata.
|
||||
* @param connectionFactory the connection factory to inspect
|
||||
* @return the resolved {@link BindMarkersFactory}
|
||||
* @throws NoBindMarkersFactoryException if no {@link BindMarkersFactory} can be resolved
|
||||
@@ -69,21 +69,18 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* SPI to extend Spring's default R2DBC {@link BindMarkersFactory} discovery
|
||||
* mechanism.
|
||||
*
|
||||
* <p>Implementations of this interface are discovered through Spring's
|
||||
* SPI to extend Spring's default R2DBC BindMarkersFactory discovery mechanism.
|
||||
* Implementations of this interface are discovered through Spring's
|
||||
* {@link SpringFactoriesLoader} mechanism.
|
||||
*
|
||||
* @see SpringFactoriesLoader
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BindMarkerFactoryProvider {
|
||||
|
||||
/**
|
||||
* Return a {@link BindMarkersFactory} for the given {@link ConnectionFactory}.
|
||||
* @param connectionFactory the connection factory to be used with the {@code BindMarkersFactory}
|
||||
* @return the {@code BindMarkersFactory} if this {@code BindMarkerFactoryProvider}
|
||||
* Return a {@link BindMarkersFactory} for a {@link ConnectionFactory}.
|
||||
* @param connectionFactory the connection factory to be used with the {@link BindMarkersFactory}
|
||||
* @return the {@link BindMarkersFactory} if the {@link BindMarkerFactoryProvider}
|
||||
* can provide a bind marker factory object, otherwise {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
@@ -92,7 +89,7 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* Exception thrown when a {@link BindMarkersFactoryResolver} cannot resolve a
|
||||
* Exception thrown when {@link BindMarkersFactoryResolver} cannot resolve a
|
||||
* {@link BindMarkersFactory}.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -109,11 +106,8 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* Built-in bind marker factories.
|
||||
*
|
||||
* <p>Typically used as the last {@link BindMarkerFactoryProvider} when other
|
||||
* providers are registered with a higher precedence.
|
||||
*
|
||||
* Built-in bind maker factories. Used typically as last {@link BindMarkerFactoryProvider}
|
||||
* when other providers register with a higher precedence.
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.annotation.AnnotationAwareOrderComparator
|
||||
*/
|
||||
|
||||
+7
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,7 @@ package org.springframework.r2dbc.core.binding;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
/**
|
||||
* Index-based bind markers. This implementation creates indexed bind
|
||||
* Index-based bind marker. This implementation creates indexed bind
|
||||
* markers using a numeric index and an optional prefix for bind markers
|
||||
* to be represented within the query string.
|
||||
*
|
||||
@@ -43,15 +43,14 @@ class IndexedBindMarkers implements BindMarkers {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link IndexedBindMarker} instance for the given {@code prefix}
|
||||
* and {@code beginWith} value.
|
||||
* @param prefix the bind parameter prefix
|
||||
* @param beginIndex the first index to use
|
||||
* Create a new {@link IndexedBindMarker} instance given {@code prefix} and {@code beginWith}.
|
||||
* @param prefix bind parameter prefix
|
||||
* @param beginWith the first index to use
|
||||
*/
|
||||
IndexedBindMarkers(String prefix, int beginIndex) {
|
||||
IndexedBindMarkers(String prefix, int beginWith) {
|
||||
this.counter = 0;
|
||||
this.prefix = prefix;
|
||||
this.offset = beginIndex;
|
||||
this.offset = beginWith;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-129
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,6 @@ import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.Parameters;
|
||||
import io.r2dbc.spi.Result;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -39,7 +38,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Mark Paluch
|
||||
* @author Mingyuan Wu
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
abstract class AbstractDatabaseClientIntegrationTests {
|
||||
|
||||
@@ -123,8 +121,7 @@ abstract class AbstractDatabaseClientIntegrationTests {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
databaseClient.sql("INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)")
|
||||
.bindValues(Map.of(
|
||||
"id", 42055,
|
||||
.bindValues(Map.of("id", 42055,
|
||||
"name", Parameters.in("SCHAUFELRADBAGGER"),
|
||||
"manual", Parameters.in(Integer.class)))
|
||||
.fetch().rowsUpdated()
|
||||
@@ -202,7 +199,8 @@ abstract class AbstractDatabaseClientIntegrationTests {
|
||||
void shouldEmitGeneratedKey() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
databaseClient.sql("INSERT INTO legoset ( name, manual) VALUES(:name, :manual)")
|
||||
databaseClient.sql(
|
||||
"INSERT INTO legoset ( name, manual) VALUES(:name, :manual)")
|
||||
.bind("name","SCHAUFELRADBAGGER")
|
||||
.bindNull("manual", Integer.class)
|
||||
.filter(statement -> statement.returnGeneratedValues("id"))
|
||||
@@ -214,129 +212,6 @@ abstract class AbstractDatabaseClientIntegrationTests {
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class ReusedNamedParameterTests {
|
||||
|
||||
@Test // gh-34768
|
||||
void executeInsertWithReusedNamedParameter() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
Lego lego = new Lego(1, 42, "Star Wars", 42);
|
||||
|
||||
// ":number" is reused.
|
||||
databaseClient.sql("INSERT INTO legoset (id, version, name, manual) VALUES(:id, :number, :name, :number)")
|
||||
.bind("id", lego.id)
|
||||
.bind("name", lego.name)
|
||||
.bind("number", lego.version)
|
||||
.fetch().rowsUpdated()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(1L)
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.sql("SELECT * FROM legoset")
|
||||
.mapProperties(Lego.class)
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(actual -> assertThat(actual).isEqualTo(lego))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-34768
|
||||
void executeSelectWithReusedNamedParameterList() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
String insertSql = "INSERT INTO legoset (id, version, name, manual) VALUES(:id, :version, :name, :manual)";
|
||||
// ":numbers" is reused.
|
||||
String selectSql = "SELECT * FROM legoset WHERE version IN (:numbers) OR manual IN (:numbers)";
|
||||
Lego lego = new Lego(1, 42, "Star Wars", 99);
|
||||
|
||||
databaseClient.sql(insertSql)
|
||||
.bind("id", lego.id)
|
||||
.bind("version", lego.version)
|
||||
.bind("name", lego.name)
|
||||
.bind("manual", lego.manual)
|
||||
.fetch().rowsUpdated()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(1L)
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.sql(selectSql)
|
||||
// match version
|
||||
.bind("numbers", List.of(2, 3, lego.version, 4))
|
||||
.mapProperties(Lego.class)
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(actual -> assertThat(actual).isEqualTo(lego))
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.sql(selectSql)
|
||||
// match manual
|
||||
.bind("numbers", List.of(2, 3, lego.manual, 4))
|
||||
.mapProperties(Lego.class)
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(actual -> assertThat(actual).isEqualTo(lego))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // gh-34768
|
||||
void executeSelectWithReusedNamedParameterListFromBeanProperties() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
String insertSql = "INSERT INTO legoset (id, version, name, manual) VALUES(:id, :version, :name, :manual)";
|
||||
// ":numbers" is reused.
|
||||
String selectSql = "SELECT * FROM legoset WHERE version IN (:numbers) OR manual IN (:numbers)";
|
||||
Lego lego = new Lego(1, 42, "Star Wars", 99);
|
||||
|
||||
databaseClient.sql(insertSql)
|
||||
.bind("id", lego.id)
|
||||
.bind("version", lego.version)
|
||||
.bind("name", lego.name)
|
||||
.bind("manual", lego.manual)
|
||||
.fetch().rowsUpdated()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(1L)
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.sql(selectSql)
|
||||
// match version
|
||||
.bindProperties(new LegoRequest(List.of(lego.version)))
|
||||
.mapProperties(Lego.class)
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(actual -> assertThat(actual).isEqualTo(lego))
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.sql(selectSql)
|
||||
// match manual
|
||||
.bindProperties(new LegoRequest(List.of(lego.manual)))
|
||||
.mapProperties(Lego.class)
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(actual -> assertThat(actual).isEqualTo(lego))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
|
||||
record Lego(int id, Integer version, String name, Integer manual) {
|
||||
}
|
||||
|
||||
static class LegoRequest {
|
||||
|
||||
private final List<Integer> numbers;
|
||||
|
||||
LegoRequest(List<Integer> numbers) {
|
||||
this.numbers = numbers;
|
||||
}
|
||||
|
||||
public List<Integer> getNumbers() {
|
||||
return numbers;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
record ParameterRecord(int id, String name, Integer manual) {
|
||||
}
|
||||
|
||||
|
||||
+128
-109
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package org.springframework.r2dbc.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.r2dbc.spi.Parameters;
|
||||
@@ -27,6 +29,8 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
import org.springframework.r2dbc.core.binding.BindTarget;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -38,13 +42,10 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Anton Naydenov
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class NamedParameterUtilsTests {
|
||||
|
||||
private static final BindMarkersFactory INDEXED_MARKERS = BindMarkersFactory.indexed("$", 1);
|
||||
|
||||
private static final BindMarkersFactory ANONYMOUS_MARKERS = BindMarkersFactory.anonymous("?");
|
||||
private final BindMarkersFactory BIND_MARKERS = BindMarkersFactory.indexed("$", 1);
|
||||
|
||||
|
||||
@Test
|
||||
@@ -72,7 +73,7 @@ class NamedParameterUtilsTests {
|
||||
namedParams.addValue("a", "a").addValue("b", "b").addValue("c", "c");
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a :b :c", INDEXED_MARKERS, namedParams);
|
||||
"xxx :a :b :c", BIND_MARKERS, namedParams);
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("xxx $1 $2 $3");
|
||||
|
||||
@@ -86,11 +87,11 @@ class NamedParameterUtilsTests {
|
||||
void substituteObjectArray() {
|
||||
MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>());
|
||||
namedParams.addValue("a",
|
||||
List.of(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
Arrays.asList(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a", INDEXED_MARKERS, namedParams);
|
||||
"xxx :a", BIND_MARKERS, namedParams);
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("xxx ($1, $2), ($3, $4)");
|
||||
}
|
||||
@@ -99,13 +100,13 @@ class NamedParameterUtilsTests {
|
||||
void shouldBindObjectArray() {
|
||||
MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>());
|
||||
namedParams.addValue("a",
|
||||
List.of(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
Arrays.asList(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
|
||||
BindTarget bindTarget = mock();
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a", INDEXED_MARKERS, namedParams);
|
||||
"xxx :a", BIND_MARKERS, namedParams);
|
||||
operation.bindTo(bindTarget);
|
||||
|
||||
verify(bindTarget).bind(0, "Walter");
|
||||
@@ -140,7 +141,7 @@ class NamedParameterUtilsTests {
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
parsedSql, INDEXED_MARKERS, new MapBindParameterSource());
|
||||
parsedSql, BIND_MARKERS, new MapBindParameterSource());
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(expectedSql);
|
||||
}
|
||||
@@ -311,139 +312,157 @@ class NamedParameterUtilsTests {
|
||||
void multipleEqualParameterReferencesBindsValueOnce() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in("foo")));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source);
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name = $1 or lastname = $1");
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameters.in("foo"))));
|
||||
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = $0 or lastname = $0");
|
||||
|
||||
operation.bindTo(trackingBindTarget);
|
||||
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(1)
|
||||
.containsEntry(0, Parameters.in("foo"));
|
||||
operation.bindTo(new BindTarget() {
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
assertThat(index).isEqualTo(0);
|
||||
assertThat(value).isEqualTo(Parameters.in("foo"));
|
||||
}
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleEqualCollectionParameterReferencesForIndexedMarkersBindsValuesOnce() {
|
||||
void multipleEqualCollectionParameterReferencesBindsValueOnce() {
|
||||
String sql = "SELECT * FROM person where name IN (:ids) or lastname IN (:ids)";
|
||||
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("ids",
|
||||
Parameters.in(List.of("foo", "bar", "baz"))));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source);
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name IN ($1, $2, $3) or lastname IN ($1, $2, $3)");
|
||||
MultiValueMap<Integer, Object> bindings = new LinkedMultiValueMap<>();
|
||||
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(Collections.singletonMap("ids",
|
||||
Parameters.in(Arrays.asList("foo", "bar", "baz")))));
|
||||
|
||||
operation.bindTo(trackingBindTarget);
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name IN ($0, $1, $2) or lastname IN ($0, $1, $2)");
|
||||
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(3)
|
||||
.containsEntry(0, "foo")
|
||||
.containsEntry(1, "bar")
|
||||
.containsEntry(2, "baz");
|
||||
}
|
||||
operation.bindTo(new BindTarget() {
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
assertThat(index).isIn(0, 1, 2);
|
||||
assertThat(value).isIn("foo", "bar", "baz");
|
||||
bindings.add(index, value);
|
||||
}
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
|
||||
@Test // gh-34768
|
||||
void multipleEqualCollectionParameterReferencesForAnonymousMarkersBindsValuesTwice() {
|
||||
String sql = "SELECT * FROM fund_info WHERE fund_code IN (:fundCodes) OR fund_code IN (:fundCodes)";
|
||||
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("fundCodes", Parameters.in(List.of("foo", "bar", "baz"))));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, ANONYMOUS_MARKERS, source);
|
||||
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM fund_info WHERE fund_code IN (?, ?, ?) OR fund_code IN (?, ?, ?)");
|
||||
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
|
||||
operation.bindTo(trackingBindTarget);
|
||||
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(6)
|
||||
.containsEntry(0, "foo")
|
||||
.containsEntry(1, "bar")
|
||||
.containsEntry(2, "baz")
|
||||
.containsEntry(3, "foo")
|
||||
.containsEntry(4, "bar")
|
||||
.containsEntry(5, "baz");
|
||||
assertThat(bindings).containsEntry(0, Collections.singletonList("foo")) //
|
||||
.containsEntry(1, Collections.singletonList("bar")) //
|
||||
.containsEntry(2, Collections.singletonList("baz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleEqualParameterReferencesForAnonymousMarkersBindsValueTwice() {
|
||||
void multipleEqualParameterReferencesForAnonymousMarkersBindsValueMultipleTimes() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in("foo")));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, ANONYMOUS_MARKERS, source);
|
||||
BindMarkersFactory factory = BindMarkersFactory.anonymous("?");
|
||||
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name = ? or lastname = ?");
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameters.in("foo"))));
|
||||
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = ? or lastname = ?");
|
||||
|
||||
operation.bindTo(trackingBindTarget);
|
||||
Map<Integer, Object> bindValues = new LinkedHashMap<>();
|
||||
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(2)
|
||||
.containsEntry(0, Parameters.in("foo"))
|
||||
.containsEntry(1, Parameters.in("foo"));
|
||||
operation.bindTo(new BindTarget() {
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
bindValues.put(index, value);
|
||||
}
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(bindValues).hasSize(2).containsEntry(0, Parameters.in("foo")).containsEntry(1, Parameters.in("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleEqualParameterReferencesBindsNullOnce() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in(String.class)));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source);
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name = $1 or lastname = $1");
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameters.in(String.class))));
|
||||
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = $0 or lastname = $0");
|
||||
|
||||
operation.bindTo(trackingBindTarget);
|
||||
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(1)
|
||||
.containsEntry(0, Parameters.in(String.class));
|
||||
operation.bindTo(new BindTarget() {
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
assertThat(index).isEqualTo(0);
|
||||
assertThat(value).isEqualTo(Parameters.in(String.class));
|
||||
}
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static String expand(ParsedSql sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS,
|
||||
private String expand(ParsedSql sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS,
|
||||
new MapBindParameterSource()).toQuery();
|
||||
}
|
||||
|
||||
private static String expand(String sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS,
|
||||
private String expand(String sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS,
|
||||
new MapBindParameterSource()).toQuery();
|
||||
}
|
||||
|
||||
|
||||
private static class TrackingBindTarget implements BindTarget {
|
||||
|
||||
final Map<Integer, Object> bindings = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {}
|
||||
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
this.bindings.put(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -105,10 +105,14 @@ test {
|
||||
description = "Runs JUnit 4, JUnit Jupiter, and TestNG tests."
|
||||
useJUnitPlatform {
|
||||
includeEngines "junit-vintage", "junit-jupiter", "testng"
|
||||
excludeTags "failing-test-case"
|
||||
}
|
||||
// `include` test filters and system properties are configured in
|
||||
// org.springframework.build.TestConventions in buildSrc.
|
||||
// We use `include` instead of `filter.includeTestsMatching`, since
|
||||
// the latter results in some tests being executed/reported
|
||||
// multiple times.
|
||||
include(["**/*Tests.class", "**/*Test.class"])
|
||||
filter.excludeTestsMatching("*TestCase")
|
||||
// Optionally configure Java Util Logging for the JUnit Platform.
|
||||
systemProperty("testGroups", project.properties.get("testGroups"))
|
||||
// Java Util Logging for the JUnit Platform.
|
||||
// systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")
|
||||
}
|
||||
|
||||
+2
-32
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.mock.web.server;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -42,19 +40,15 @@ import org.springframework.web.server.session.WebSessionManager;
|
||||
*/
|
||||
public final class MockServerWebExchange extends DefaultServerWebExchange {
|
||||
|
||||
private final Mono<Principal> principalMono;
|
||||
|
||||
|
||||
private MockServerWebExchange(
|
||||
MockServerHttpRequest request, @Nullable WebSessionManager sessionManager,
|
||||
@Nullable ApplicationContext applicationContext, @Nullable Principal principal) {
|
||||
@Nullable ApplicationContext applicationContext) {
|
||||
|
||||
super(request, new MockServerHttpResponse(),
|
||||
sessionManager != null ? sessionManager : new DefaultWebSessionManager(),
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver(),
|
||||
applicationContext);
|
||||
|
||||
this.principalMono = (principal != null) ? Mono.just(principal) : Mono.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -63,16 +57,6 @@ public final class MockServerWebExchange extends DefaultServerWebExchange {
|
||||
return (MockServerHttpResponse) super.getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user set via {@link Builder#principal(Principal)}.
|
||||
* @since 6.2.7
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return (Mono<T>) this.principalMono;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link MockServerWebExchange} from the given mock request.
|
||||
@@ -127,9 +111,6 @@ public final class MockServerWebExchange extends DefaultServerWebExchange {
|
||||
@Nullable
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Nullable
|
||||
private Principal principal;
|
||||
|
||||
public Builder(MockServerHttpRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
@@ -166,22 +147,11 @@ public final class MockServerWebExchange extends DefaultServerWebExchange {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a user to associate with the exchange.
|
||||
* @param principal the principal to use
|
||||
* @since 6.2.7
|
||||
*/
|
||||
public Builder principal(@Nullable Principal principal) {
|
||||
this.principal = principal;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@code MockServerWebExchange} instance.
|
||||
*/
|
||||
public MockServerWebExchange build() {
|
||||
return new MockServerWebExchange(
|
||||
this.request, this.sessionManager, this.applicationContext, this.principal);
|
||||
return new MockServerWebExchange(this.request, this.sessionManager, this.applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -314,7 +314,8 @@ public class TestContextAotGenerator {
|
||||
ClassName processAheadOfTime(MergedContextConfiguration mergedConfig,
|
||||
GenerationContext generationContext) throws TestContextAotException {
|
||||
|
||||
try (GenericApplicationContext gac = loadContextForAotProcessing(mergedConfig)) {
|
||||
GenericApplicationContext gac = loadContextForAotProcessing(mergedConfig);
|
||||
try {
|
||||
return this.aotGenerator.processAheadOfTime(gac, generationContext);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
@@ -332,7 +333,7 @@ public class TestContextAotGenerator {
|
||||
* context or if one of the prerequisites is not met
|
||||
* @see AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration, RuntimeHints)
|
||||
*/
|
||||
GenericApplicationContext loadContextForAotProcessing(
|
||||
private GenericApplicationContext loadContextForAotProcessing(
|
||||
MergedContextConfiguration mergedConfig) throws TestContextAotException {
|
||||
|
||||
Class<?> testClass = mergedConfig.getTestClass();
|
||||
|
||||
+8
-10
@@ -184,32 +184,30 @@ public abstract class BeanOverrideHandler {
|
||||
* @param testClass the original test class
|
||||
* @param handlers the list of handlers found
|
||||
* @param localFieldsOnly whether to search only on local fields within the type hierarchy
|
||||
* @param visitedTypes the set of types already visited
|
||||
* @param visitedEnclosingClasses the set of enclosing classes already visited
|
||||
* @since 6.2.2
|
||||
*/
|
||||
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers,
|
||||
boolean localFieldsOnly, Set<Class<?>> visitedTypes) {
|
||||
|
||||
// 0) Ensure that we do not process the same class or interface multiple times.
|
||||
if (!visitedTypes.add(clazz)) {
|
||||
return;
|
||||
}
|
||||
boolean localFieldsOnly, Set<Class<?>> visitedEnclosingClasses) {
|
||||
|
||||
// 1) Search enclosing class hierarchy.
|
||||
if (!localFieldsOnly && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
|
||||
findHandlers(clazz.getEnclosingClass(), testClass, handlers, localFieldsOnly, visitedTypes);
|
||||
Class<?> enclosingClass = clazz.getEnclosingClass();
|
||||
if (visitedEnclosingClasses.add(enclosingClass)) {
|
||||
findHandlers(enclosingClass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Search class hierarchy.
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass != null && superclass != Object.class) {
|
||||
findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedTypes);
|
||||
findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
|
||||
}
|
||||
|
||||
if (!localFieldsOnly) {
|
||||
// 3) Search interfaces.
|
||||
for (Class<?> ifc : clazz.getInterfaces()) {
|
||||
findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedTypes);
|
||||
findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
|
||||
}
|
||||
|
||||
// 4) Process current class.
|
||||
|
||||
+21
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,8 +20,9 @@ import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
import org.junit.runner.JUnitCore;
|
||||
import org.junit.runner.Result;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -31,10 +32,9 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
/**
|
||||
* Integration tests that verify proper behavior of {@link DirtiesContext @DirtiesContext}
|
||||
@@ -87,11 +87,9 @@ class ContextHierarchyDirtiesContextTests {
|
||||
private void runTestAndVerifyHierarchies(Class<? extends FooTestCase> testClass, boolean isFooContextActive,
|
||||
boolean isBarContextActive, boolean isBazContextActive) {
|
||||
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.selectors(selectClass(testClass))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));
|
||||
JUnitCore jUnitCore = new JUnitCore();
|
||||
Result result = jUnitCore.run(testClass);
|
||||
assertThat(result.wasSuccessful()).as("all tests passed").isTrue();
|
||||
|
||||
assertThat(ContextHierarchyDirtiesContextTests.context).isNotNull();
|
||||
|
||||
@@ -113,7 +111,7 @@ class ContextHierarchyDirtiesContextTests {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextHierarchy(@ContextConfiguration(name = "foo"))
|
||||
abstract static class FooTestCase implements ApplicationContextAware {
|
||||
|
||||
@@ -172,10 +170,10 @@ class ContextHierarchyDirtiesContextTests {
|
||||
* context.
|
||||
*/
|
||||
@DirtiesContext
|
||||
static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
public static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
@org.junit.Test
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,10 +184,10 @@ class ContextHierarchyDirtiesContextTests {
|
||||
* beginning from the current context hierarchy and down through all subhierarchies.
|
||||
*/
|
||||
@DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
|
||||
static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
public static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
@org.junit.Test
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,11 +199,11 @@ class ContextHierarchyDirtiesContextTests {
|
||||
* parent context, and then back down through all subhierarchies of the parent
|
||||
* context.
|
||||
*/
|
||||
static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
public static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
|
||||
@Test
|
||||
@org.junit.Test
|
||||
@DirtiesContext
|
||||
void test() {
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,11 +213,11 @@ class ContextHierarchyDirtiesContextTests {
|
||||
* <p>After running this test class, the context cache should be cleared
|
||||
* beginning from the current context hierarchy and down through all subhierarchies.
|
||||
*/
|
||||
static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
public static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
|
||||
@Test
|
||||
@org.junit.Test
|
||||
@DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
|
||||
void test() {
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.test.context;
|
||||
|
||||
import org.junit.jupiter.api.ClassOrderer;
|
||||
import org.junit.platform.suite.api.ConfigurationParameter;
|
||||
import org.junit.platform.suite.api.ExcludeTags;
|
||||
import org.junit.platform.suite.api.IncludeClassNamePatterns;
|
||||
import org.junit.platform.suite.api.SelectPackages;
|
||||
@@ -46,9 +44,5 @@ import org.junit.platform.suite.api.Suite;
|
||||
@SelectPackages("org.springframework.test.context")
|
||||
@IncludeClassNamePatterns(".*Tests?$")
|
||||
@ExcludeTags("failing-test-case")
|
||||
@ConfigurationParameter(
|
||||
key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME,
|
||||
value = "org.junit.jupiter.api.ClassOrderer$ClassName"
|
||||
)
|
||||
class SpringTestContextFrameworkTestSuite {
|
||||
}
|
||||
|
||||
+1
@@ -167,6 +167,7 @@ class AotIntegrationTests extends AbstractAotTests {
|
||||
void endToEndTestsForSelectedTestClasses() {
|
||||
List<Class<?>> testClasses = List.of(
|
||||
org.springframework.test.context.bean.override.easymock.EasyMockBeanIntegrationTests.class,
|
||||
org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests.class,
|
||||
org.springframework.test.context.junit4.ParameterizedDependencyInjectionTests.class
|
||||
);
|
||||
|
||||
|
||||
+3
-59
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,23 +16,13 @@
|
||||
|
||||
package org.springframework.test.context.aot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedFiles;
|
||||
import org.springframework.aot.generate.InMemoryGeneratedFiles;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.context.aot.TestContextAotGenerator.FAIL_ON_ERROR_PROPERTY_NAME;
|
||||
@@ -70,55 +60,9 @@ class TestContextAotGeneratorTests {
|
||||
assertThat(createGenerator().failOnError).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-34841
|
||||
void contextIsClosedAfterAotProcessing() {
|
||||
DemoTestContextAotGenerator generator = createGenerator();
|
||||
generator.processAheadOfTime(Stream.of(TestCase1.class, TestCase2.class));
|
||||
|
||||
assertThat(generator.contexts)
|
||||
.allSatisfy(context -> assertThat(context.isClosed()).as("context is closed").isTrue());
|
||||
}
|
||||
|
||||
|
||||
private static DemoTestContextAotGenerator createGenerator() {
|
||||
return new DemoTestContextAotGenerator(new InMemoryGeneratedFiles());
|
||||
}
|
||||
|
||||
|
||||
private static class DemoTestContextAotGenerator extends TestContextAotGenerator {
|
||||
|
||||
List<GenericApplicationContext> contexts = new ArrayList<>();
|
||||
|
||||
DemoTestContextAotGenerator(GeneratedFiles generatedFiles) {
|
||||
super(generatedFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
GenericApplicationContext loadContextForAotProcessing(
|
||||
MergedContextConfiguration mergedConfig) throws TestContextAotException {
|
||||
|
||||
GenericApplicationContext context = super.loadContextForAotProcessing(mergedConfig);
|
||||
this.contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
@SpringJUnitConfig
|
||||
private static class TestCase1 {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
// no beans
|
||||
}
|
||||
}
|
||||
|
||||
@SpringJUnitConfig
|
||||
private static class TestCase2 {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
// no beans
|
||||
}
|
||||
private static TestContextAotGenerator createGenerator() {
|
||||
return new TestContextAotGenerator(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||
|
||||
/**
|
||||
* Abstract top-level class and abstract inner class for integration tests for
|
||||
* {@link MockitoBean @MockitoBean} which verify that {@code @MockitoBean} fields
|
||||
* are not discovered more than once when searching intertwined enclosing class
|
||||
* hierarchies and type hierarchies, when a superclass is <em>present</em> twice
|
||||
* in the intertwined hierarchies.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.7
|
||||
* @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34844">gh-34844</a>
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
abstract class AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext enclosingContext;
|
||||
|
||||
@MockitoBean
|
||||
ExampleService service;
|
||||
|
||||
|
||||
@Test
|
||||
void topLevelTest() {
|
||||
assertIsMock(service);
|
||||
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
|
||||
}
|
||||
|
||||
|
||||
abstract class AbstractBaseClassForNestedTests {
|
||||
|
||||
@Test
|
||||
void nestedTest(ApplicationContext nestedContext) {
|
||||
assertIsMock(service);
|
||||
assertThat(enclosingContext).isSameAs(nestedContext);
|
||||
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-7
@@ -31,17 +31,14 @@ import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean @MockitoBean} which verify that
|
||||
* {@code @MockitoBean} fields are not discovered more than once when searching
|
||||
* intertwined enclosing class hierarchies and type hierarchies, when an enclosing
|
||||
* class is <em>present</em> twice in the intertwined hierarchies.
|
||||
* intertwined enclosing class hierarchies and type hierarchies.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.3
|
||||
* @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
|
||||
* @see MockitoBeanWithInterfacePresentTwiceTests
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34324">gh-34324</a>
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
class MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests {
|
||||
class MockitoBeanNestedAndTypeHierarchiesTests {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext enclosingContext;
|
||||
@@ -53,7 +50,6 @@ class MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests {
|
||||
@Test
|
||||
void topLevelTest() {
|
||||
assertIsMock(service);
|
||||
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
|
||||
|
||||
// The following are prerequisites for the reported regression.
|
||||
assertThat(NestedTests.class.getSuperclass())
|
||||
@@ -70,7 +66,6 @@ class MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests {
|
||||
void nestedTest(ApplicationContext nestedContext) {
|
||||
assertIsMock(service);
|
||||
assertThat(enclosingContext).isSameAs(nestedContext);
|
||||
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
|
||||
}
|
||||
}
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean @MockitoBean} which verify that
|
||||
* {@code @MockitoBean} fields are not discovered more than once when searching
|
||||
* intertwined enclosing class hierarchies and type hierarchies, when a superclass
|
||||
* is <em>present</em> twice in the intertwined hierarchies.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.7
|
||||
* @see MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests
|
||||
* @see MockitoBeanWithInterfacePresentTwiceTests
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34844">gh-34844</a>
|
||||
*/
|
||||
class MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
|
||||
extends AbstractMockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests {
|
||||
|
||||
@Test
|
||||
@Override
|
||||
void topLevelTest() {
|
||||
super.topLevelTest();
|
||||
|
||||
// The following are prerequisites for the reported regression.
|
||||
assertThat(NestedTests.class.getSuperclass())
|
||||
.isEqualTo(AbstractBaseClassForNestedTests.class);
|
||||
assertThat(NestedTests.class.getEnclosingClass())
|
||||
.isEqualTo(getClass());
|
||||
assertThat(NestedTests.class.getEnclosingClass().getSuperclass())
|
||||
.isEqualTo(AbstractBaseClassForNestedTests.class.getEnclosingClass())
|
||||
.isEqualTo(getClass().getSuperclass());
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class NestedTests extends AbstractBaseClassForNestedTests {
|
||||
}
|
||||
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MockitoBean @MockitoBean} which verify that type-level
|
||||
* {@code @MockitoBean} declarations are not discovered more than once when searching
|
||||
* a type hierarchy, when an interface is <em>present</em> twice in the hierarchy.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.7
|
||||
* @see MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests
|
||||
* @see MockitoBeanNestedAndTypeHierarchiesWithSuperclassPresentTwiceTests
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34844">gh-34844</a>
|
||||
*/
|
||||
class MockitoBeanWithInterfacePresentTwiceTests extends AbstractMockitoBeanWithInterfacePresentTwiceTests
|
||||
implements MockConfigInterface {
|
||||
|
||||
@Test
|
||||
void test(ApplicationContext context) {
|
||||
assertIsMock(service);
|
||||
assertThat(context.getBeanNamesForType(ExampleService.class)).hasSize(1);
|
||||
|
||||
// The following are prerequisites for the tested scenario.
|
||||
assertThat(getClass().getInterfaces()).containsExactly(MockConfigInterface.class);
|
||||
assertThat(getClass().getSuperclass().getInterfaces()).containsExactly(MockConfigInterface.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MockitoBean(types = ExampleService.class)
|
||||
interface MockConfigInterface {
|
||||
}
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
abstract class AbstractMockitoBeanWithInterfacePresentTwiceTests implements MockConfigInterface {
|
||||
|
||||
@Autowired
|
||||
ExampleService service;
|
||||
|
||||
}
|
||||
+2
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.rules;
|
||||
package org.springframework.test.context.bean.override.mockito.integration;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
@@ -23,6 +23,7 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.test.annotation.Repeat;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.context.junit4.rules.SpringMethodRule;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
+4
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,7 +31,6 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.springframework.test.context.testng.TrackingTestNGTestListener;
|
||||
@@ -163,18 +162,12 @@ class ClassLevelDirtiesContextTestNGTests {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class }, inheritListeners = false)
|
||||
@ContextConfiguration
|
||||
// Ensure that we do not include the EventPublishingTestExecutionListener
|
||||
// since it will access the ApplicationContext for each method in the
|
||||
// TestExecutionListener API, thus distorting our cache hit/miss results.
|
||||
@TestExecutionListeners({
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,
|
||||
DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class
|
||||
})
|
||||
abstract static class BaseTestCase extends AbstractTestNGSpringContextTests {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Configuration
|
||||
static class Config {
|
||||
/* no beans */
|
||||
}
|
||||
|
||||
Vendored
+33
-42
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,8 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -32,14 +31,15 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
import static org.springframework.test.context.cache.ContextCacheTestUtils.assertContextCacheStatistics;
|
||||
import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache;
|
||||
import static org.springframework.test.context.junit4.JUnitTestingUtils.runTestsAndAssertCounters;
|
||||
|
||||
/**
|
||||
* JUnit based integration test which verifies correct {@linkplain ContextCache
|
||||
@@ -131,24 +131,15 @@ class ClassLevelDirtiesContextTests {
|
||||
0, cacheHits.incrementAndGet(), cacheMisses.get());
|
||||
}
|
||||
|
||||
private void runTestClassAndAssertStats(Class<?> testClass, int expectedTestCount) throws Exception {
|
||||
runTestsAndAssertCounters(testClass, expectedTestCount, 0, expectedTestCount, 0, 0);
|
||||
}
|
||||
|
||||
private void assertBehaviorForCleanTestCase() throws Exception {
|
||||
runTestClassAndAssertStats(CleanTestCase.class, 1);
|
||||
assertContextCacheStatistics("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet());
|
||||
}
|
||||
|
||||
private void runTestClassAndAssertStats(Class<?> testClass, int expectedTestCount) throws Exception {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.selectors(selectClass(testClass))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats
|
||||
.started(expectedTestCount)
|
||||
.finished(expectedTestCount)
|
||||
.succeeded(expectedTestCount)
|
||||
.failed(0)
|
||||
.aborted(0));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void verifyFinalCacheState() {
|
||||
assertContextCacheStatistics("AfterClass", 0, cacheHits.get(), cacheMisses.get());
|
||||
@@ -157,7 +148,7 @@ class ClassLevelDirtiesContextTests {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
// Ensure that we do not include the EventPublishingTestExecutionListener
|
||||
// since it will access the ApplicationContext for each method in the
|
||||
@@ -169,7 +160,7 @@ class ClassLevelDirtiesContextTests {
|
||||
})
|
||||
abstract static class BaseTestCase {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Configuration
|
||||
static class Config {
|
||||
/* no beans */
|
||||
}
|
||||
@@ -184,75 +175,75 @@ class ClassLevelDirtiesContextTests {
|
||||
}
|
||||
}
|
||||
|
||||
static final class CleanTestCase extends BaseTestCase {
|
||||
public static final class CleanTestCase extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
void verifyContextWasAutowired() {
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@DirtiesContext
|
||||
static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
|
||||
public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
void verifyContextWasAutowired() {
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends
|
||||
public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends
|
||||
ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase {
|
||||
}
|
||||
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
|
||||
static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
|
||||
public static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
void verifyContextWasAutowired() {
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends
|
||||
public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends
|
||||
ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase {
|
||||
}
|
||||
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
|
||||
public static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
void verifyContextWasAutowired1() {
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired1() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyContextWasAutowired2() {
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired2() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyContextWasAutowired3() {
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired3() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends
|
||||
public static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends
|
||||
ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase {
|
||||
}
|
||||
|
||||
@DirtiesContext
|
||||
static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {
|
||||
public static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
@org.junit.Test
|
||||
@DirtiesContext
|
||||
void dirtyContext() {
|
||||
public void dirtyContext() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends
|
||||
public static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends
|
||||
ClassLevelDirtiesContextWithDirtyMethodsTestCase {
|
||||
}
|
||||
|
||||
|
||||
spring-test/src/test/java/org/springframework/test/context/cache/MethodLevelDirtiesContextTests.java
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.annotation.DirtiesContext.MethodMode.BEFORE_METHOD;
|
||||
|
||||
/**
|
||||
* Integration tests which verify correct interaction between the
|
||||
* Integration test which verifies correct interaction between the
|
||||
* {@link DirtiesContextBeforeModesTestExecutionListener},
|
||||
* {@link DependencyInjectionTestExecutionListener}, and
|
||||
* {@link DirtiesContextTestExecutionListener} when
|
||||
|
||||
+6
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,9 +37,10 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.asser
|
||||
import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache;
|
||||
|
||||
/**
|
||||
* JUnit based integration test which verifies correct {@linkplain ContextCache
|
||||
* application context caching} in conjunction with the {@link SpringExtension} and
|
||||
* {@link DirtiesContext @DirtiesContext} at the method level.
|
||||
* Unit tests which verify correct {@link ContextCache
|
||||
* application context caching} in conjunction with the
|
||||
* {@link SpringExtension} and the {@link DirtiesContext
|
||||
* @DirtiesContext} annotation at the method level.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
@@ -47,7 +48,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.reset
|
||||
* @see ContextCacheTests
|
||||
* @see LruContextCacheTests
|
||||
*/
|
||||
@SpringJUnitConfig(locations = "../config/CoreContextConfigurationAppCtxTests-context.xml")
|
||||
@SpringJUnitConfig(locations = "../junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml")
|
||||
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class SpringExtensionContextCacheTests {
|
||||
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.jupiter.api.ClassOrderer;
|
||||
import org.junit.platform.suite.api.ConfigurationParameter;
|
||||
import org.junit.platform.suite.api.IncludeClassNamePatterns;
|
||||
import org.junit.platform.suite.api.IncludeEngines;
|
||||
import org.junit.platform.suite.api.SelectPackages;
|
||||
import org.junit.platform.suite.api.Suite;
|
||||
|
||||
/**
|
||||
* JUnit Platform based test suite annotation-driven <em>configuration class</em>
|
||||
* support in the Spring TestContext Framework.
|
||||
*
|
||||
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
|
||||
*
|
||||
* <h3>Logging Configuration</h3>
|
||||
*
|
||||
* <p>In order for our log4j2 configuration to be used in an IDE, you must
|
||||
* set the following system property before running any tests — for
|
||||
* example, in <em>Run Configurations</em> in Eclipse.
|
||||
*
|
||||
* <pre style="code">
|
||||
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
|
||||
* </pre>
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
@Suite
|
||||
@IncludeEngines("junit-jupiter")
|
||||
@SelectPackages("org.springframework.test.context.config")
|
||||
@IncludeClassNamePatterns(".*Tests$")
|
||||
@ConfigurationParameter(
|
||||
key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME,
|
||||
value = "org.junit.jupiter.api.ClassOrderer$ClassName"
|
||||
)
|
||||
public class ContextConfigTestSuite {
|
||||
}
|
||||
+9
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,22 +21,21 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
import static org.springframework.test.context.cache.ContextCacheTestUtils.assertContextCacheStatistics;
|
||||
import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache;
|
||||
import static org.springframework.test.context.junit4.JUnitTestingUtils.runTestsAndAssertCounters;
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
@@ -71,14 +70,11 @@ class DirtiesContextInterfaceTests {
|
||||
}
|
||||
|
||||
private void runTestClassAndAssertStats(Class<?> testClass, int expectedTestCount) throws Exception {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.selectors(selectClass(testClass))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(expectedTestCount).succeeded(expectedTestCount).failed(0));
|
||||
runTestsAndAssertCounters(testClass, expectedTestCount, 0, expectedTestCount, 0, 0);
|
||||
}
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
// Ensure that we do not include the EventPublishingTestExecutionListener
|
||||
// since it will access the ApplicationContext for each method in the
|
||||
// TestExecutionListener API, thus distorting our cache hit/miss results.
|
||||
@@ -94,13 +90,13 @@ class DirtiesContextInterfaceTests {
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
|
||||
@Test
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired() {
|
||||
assertThat(this.applicationContext).as("The application context should have been autowired.").isNotNull();
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Configuration
|
||||
static class Config {
|
||||
/* no beans */
|
||||
}
|
||||
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.initializers;
|
||||
|
||||
import org.junit.jupiter.api.ClassOrderer;
|
||||
import org.junit.platform.suite.api.ConfigurationParameter;
|
||||
import org.junit.platform.suite.api.ExcludeTags;
|
||||
import org.junit.platform.suite.api.IncludeClassNamePatterns;
|
||||
import org.junit.platform.suite.api.IncludeEngines;
|
||||
import org.junit.platform.suite.api.SelectPackages;
|
||||
import org.junit.platform.suite.api.Suite;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
|
||||
/**
|
||||
* JUnit Platform based test suite for tests that involve
|
||||
* {@link ApplicationContextInitializer ApplicationContextInitializers} (ACIs)
|
||||
* in the TestContext framework.
|
||||
*
|
||||
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
|
||||
*
|
||||
* <h3>Logging Configuration</h3>
|
||||
*
|
||||
* <p>In order for our log4j2 configuration to be used in an IDE, you must
|
||||
* set the following system property before running any tests — for
|
||||
* example, in <em>Run Configurations</em> in Eclipse.
|
||||
*
|
||||
* <pre style="code">
|
||||
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
|
||||
* </pre>
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 3.2
|
||||
*/
|
||||
@Suite
|
||||
@IncludeEngines("junit-jupiter")
|
||||
@SelectPackages({
|
||||
"org.springframework.test.context.initializers.annotation",
|
||||
"org.springframework.test.context.initializers.xml"
|
||||
})
|
||||
@IncludeClassNamePatterns(".*Tests$")
|
||||
@ExcludeTags("failing-test-case")
|
||||
@ConfigurationParameter(
|
||||
key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME,
|
||||
value = "org.junit.jupiter.api.ClassOrderer$ClassName"
|
||||
)
|
||||
public class AciTestSuite {
|
||||
}
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
/**
|
||||
* Test suite to investigate claims raised in
|
||||
* <a href="https://github.com/spring-projects/spring-framework/issues/13491">gh-13491</a>.
|
||||
*
|
||||
* <h3>Work Around</h3>
|
||||
* <p>By using a SpEL expression to generate a random {@code database-name}
|
||||
* for the embedded database (see {@code datasource-config.xml}), we ensure
|
||||
* that each {@code ApplicationContext} that imports the common configuration
|
||||
* will create an embedded database with a unique name.
|
||||
*
|
||||
* <p>To reproduce the problem mentioned in gh-13491, delete the declaration
|
||||
* of the {@code database-name} attribute of the embedded database in
|
||||
* {@code datasource-config.xml} and run this <em>suite</em>.
|
||||
*
|
||||
* <h3>Solution</h3>
|
||||
* <p>As of Spring 4.2, a proper solution is possible thanks to gh-13491.
|
||||
* {@link TestClass2A} and {@link TestClass2B} both import
|
||||
* {@code datasource-config-with-auto-generated-db-name.xml} which makes
|
||||
* use of the new {@code generate-name} attribute of {@code <jdbc:embedded-database>}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Mickael Leduque
|
||||
*/
|
||||
class GeneratedDatabaseNamesTests {
|
||||
|
||||
private static final String DATASOURCE_CONFIG_XML =
|
||||
"classpath:/org/springframework/test/context/jdbc/datasource-config.xml";
|
||||
|
||||
private static final String DATASOURCE_CONFIG_WITH_AUTO_GENERATED_DB_NAME_XML =
|
||||
"classpath:/org/springframework/test/context/jdbc/datasource-config-with-auto-generated-db-name.xml";
|
||||
|
||||
|
||||
@Test
|
||||
void runTestsWithGeneratedDatabaseNames() {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.selectors(
|
||||
selectClass(TestClass1A.class),
|
||||
selectClass(TestClass1B.class),
|
||||
selectClass(TestClass2A.class),
|
||||
selectClass(TestClass2B.class)
|
||||
)
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(4).succeeded(4).failed(0));
|
||||
}
|
||||
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
abstract static class AbstractTestCase {
|
||||
|
||||
@Resource
|
||||
DataSource dataSource;
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
assertThat(dataSource).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@ContextConfiguration
|
||||
static class TestClass1A extends AbstractTestCase {
|
||||
|
||||
@Configuration
|
||||
@ImportResource(DATASOURCE_CONFIG_XML)
|
||||
static class Config {
|
||||
}
|
||||
}
|
||||
|
||||
@ContextConfiguration
|
||||
static class TestClass1B extends AbstractTestCase {
|
||||
|
||||
@Configuration
|
||||
@ImportResource(DATASOURCE_CONFIG_XML)
|
||||
static class Config {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.2
|
||||
*/
|
||||
@ContextConfiguration
|
||||
static class TestClass2A extends AbstractTestCase {
|
||||
|
||||
@Configuration
|
||||
@ImportResource(DATASOURCE_CONFIG_WITH_AUTO_GENERATED_DB_NAME_XML)
|
||||
static class Config {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.2
|
||||
*/
|
||||
@ContextConfiguration
|
||||
static class TestClass2B extends AbstractTestCase {
|
||||
|
||||
@Configuration
|
||||
@ImportResource(DATASOURCE_CONFIG_WITH_AUTO_GENERATED_DB_NAME_XML)
|
||||
static class Config {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+6
-6
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.orm.jpa;
|
||||
package org.springframework.test.context.junit.jupiter.orm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,10 +37,10 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.orm.jpa.domain.JpaPersonRepository;
|
||||
import org.springframework.test.context.orm.jpa.domain.Person;
|
||||
import org.springframework.test.context.orm.jpa.domain.PersonListener;
|
||||
import org.springframework.test.context.orm.jpa.domain.PersonRepository;
|
||||
import org.springframework.test.context.junit.jupiter.orm.domain.JpaPersonRepository;
|
||||
import org.springframework.test.context.junit.jupiter.orm.domain.Person;
|
||||
import org.springframework.test.context.junit.jupiter.orm.domain.PersonListener;
|
||||
import org.springframework.test.context.junit.jupiter.orm.domain.PersonRepository;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.18
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/28228">issue gh-28228</a>
|
||||
* @see org.springframework.test.context.orm.hibernate.HibernateSessionFlushingTests
|
||||
* @see org.springframework.test.context.junit4.orm.HibernateSessionFlushingTests
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@Transactional
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.orm.jpa.domain;
|
||||
package org.springframework.test.context.junit.jupiter.orm.domain;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.orm.jpa.domain;
|
||||
package org.springframework.test.context.junit.jupiter.orm.domain;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.orm.jpa.domain;
|
||||
package org.springframework.test.context.junit.jupiter.orm.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.orm.jpa.domain;
|
||||
package org.springframework.test.context.junit.jupiter.orm.domain;
|
||||
|
||||
/**
|
||||
* Person repository API.
|
||||
+7
-7
@@ -14,22 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.config;
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that
|
||||
* Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
|
||||
* we can specify an explicit, <em>absolute path</em> location for our
|
||||
* application context.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see CoreContextConfigurationAppCtxTests
|
||||
* @see ClassPathResourceContextConfigurationAppCtxTests
|
||||
* @see RelativePathContextConfigurationAppCtxTests
|
||||
* @see SpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
*/
|
||||
@ContextConfiguration(locations = CoreContextConfigurationAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH, inheritLocations = false)
|
||||
class AbsolutePathContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests {
|
||||
@ContextConfiguration(locations = { SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH }, inheritLocations = false)
|
||||
public class AbsolutePathSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
|
||||
/* all tests are in the parent class. */
|
||||
}
|
||||
+12
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,24 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.transaction;
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Abstract base class for verifying support of Spring's
|
||||
* {@link Transactional @Transactional} annotation.
|
||||
* Abstract base class for verifying support of Spring's {@link Transactional
|
||||
* @Transactional} annotation.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see ClassLevelTransactionalSpringTests
|
||||
* @see MethodLevelTransactionalSpringTests
|
||||
* @see ClassLevelTransactionalSpringRunnerTests
|
||||
* @see MethodLevelTransactionalSpringRunnerTests
|
||||
* @see Transactional
|
||||
*/
|
||||
@SpringJUnitConfig(locations = "transactionalTests-context.xml")
|
||||
abstract class AbstractTransactionalSpringTests {
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration("transactionalTests-context.xml")
|
||||
public abstract class AbstractTransactionalSpringRunnerTests {
|
||||
|
||||
protected static final String BOB = "bob";
|
||||
protected static final String JANE = "jane";
|
||||
+44
-41
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,21 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.transaction;
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.transaction.AfterTransaction;
|
||||
import org.springframework.test.context.transaction.BeforeTransaction;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -36,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction;
|
||||
|
||||
/**
|
||||
* JUnit based integration test which verifies
|
||||
* JUnit 4 based integration test which verifies
|
||||
* {@link BeforeTransaction @BeforeTransaction} and
|
||||
* {@link AfterTransaction @AfterTransaction} behavior.
|
||||
*
|
||||
@@ -44,31 +45,33 @@ import static org.springframework.test.transaction.TransactionAssert.assertThatT
|
||||
* @since 2.5
|
||||
*/
|
||||
@Transactional
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpringTests {
|
||||
public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpringRunnerTests {
|
||||
|
||||
private static int numBeforeTransactionCalls = 0;
|
||||
private static int numAfterTransactionCalls = 0;
|
||||
protected static JdbcTemplate jdbcTemplate;
|
||||
|
||||
JdbcTemplate jdbcTemplate;
|
||||
protected static int numBeforeTransactionCalls = 0;
|
||||
protected static int numAfterTransactionCalls = 0;
|
||||
|
||||
boolean inTransaction = false;
|
||||
protected boolean inTransaction = false;
|
||||
|
||||
@Rule
|
||||
public final TestName testName = new TestName();
|
||||
|
||||
|
||||
@Autowired
|
||||
void setDataSource(DataSource dataSource) {
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
|
||||
@BeforeAll
|
||||
void beforeClass() {
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls = 0;
|
||||
BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls = 0;
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
void afterClass() {
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(3);
|
||||
assertThat(BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls).as("Verifying the total number of calls to beforeTransaction().").isEqualTo(2);
|
||||
assertThat(BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls).as("Verifying the total number of calls to afterTransaction().").isEqualTo(2);
|
||||
@@ -92,27 +95,37 @@ class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpri
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table after a transactional test method.").isEqualTo(0);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void before(TestInfo testInfo) {
|
||||
assertShouldBeInTransaction(testInfo);
|
||||
@Before
|
||||
public void before() {
|
||||
assertShouldBeInTransaction();
|
||||
long expected = (this.inTransaction ? 1 : 0);
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table before a test method.").isEqualTo(expected);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after(TestInfo testInfo) {
|
||||
assertShouldBeInTransaction(testInfo);
|
||||
private void assertShouldBeInTransaction() {
|
||||
boolean shouldBeInTransaction = !testName.getMethodName().equals("nonTransactionalMethod");
|
||||
if (shouldBeInTransaction) {
|
||||
assertThatTransaction().isActive();
|
||||
}
|
||||
else {
|
||||
assertThatTransaction().isNotActive();
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
assertShouldBeInTransaction();
|
||||
}
|
||||
|
||||
@Test
|
||||
void transactionalMethod1() {
|
||||
public void transactionalMethod1() {
|
||||
assertThatTransaction().isActive();
|
||||
assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1);
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within transactionalMethod1().").isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void transactionalMethod2() {
|
||||
public void transactionalMethod2() {
|
||||
assertThatTransaction().isActive();
|
||||
assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1);
|
||||
assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1);
|
||||
@@ -121,7 +134,7 @@ class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpri
|
||||
|
||||
@Test
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
void nonTransactionalMethod() {
|
||||
public void nonTransactionalMethod() {
|
||||
assertThatTransaction().isNotActive();
|
||||
assertThat(addPerson(jdbcTemplate, LUKE)).as("Adding luke").isEqualTo(1);
|
||||
assertThat(addPerson(jdbcTemplate, LEIA)).as("Adding leia").isEqualTo(1);
|
||||
@@ -129,14 +142,4 @@ class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpri
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table without a transaction.").isEqualTo(3);
|
||||
}
|
||||
|
||||
|
||||
private static void assertShouldBeInTransaction(TestInfo testInfo) {
|
||||
if (!testInfo.getTestMethod().get().getName().equals("nonTransactionalMethod")) {
|
||||
assertThatTransaction().isActive();
|
||||
}
|
||||
else {
|
||||
assertThatTransaction().isNotActive();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+25
-27
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,15 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.transaction;
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
@@ -31,6 +29,7 @@ import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -38,11 +37,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction;
|
||||
|
||||
/**
|
||||
* JUnit based integration test which verifies support of Spring's
|
||||
* {@link Transactional @Transactional},
|
||||
* {@link TestExecutionListeners @TestExecutionListeners}, and
|
||||
* {@link ContextConfiguration @ContextConfiguration} annotations in conjunction
|
||||
* with the following {@link TestExecutionListener TestExecutionListeners}:
|
||||
* JUnit 4 based integration test which verifies support of Spring's
|
||||
* {@link Transactional @Transactional}, {@link TestExecutionListeners
|
||||
* @TestExecutionListeners}, and {@link ContextConfiguration
|
||||
* @ContextConfiguration} annotations in conjunction with the
|
||||
* {@link SpringRunner} and the following
|
||||
* {@link TestExecutionListener TestExecutionListeners}:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link DependencyInjectionTestExecutionListener}</li>
|
||||
@@ -55,29 +55,33 @@ import static org.springframework.test.transaction.TransactionAssert.assertThatT
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see MethodLevelTransactionalSpringTests
|
||||
* @see MethodLevelTransactionalSpringRunnerTests
|
||||
*/
|
||||
@Transactional
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class ClassLevelTransactionalSpringTests extends AbstractTransactionalSpringTests {
|
||||
public class ClassLevelTransactionalSpringRunnerTests extends AbstractTransactionalSpringRunnerTests {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
protected static JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
@Autowired
|
||||
void setDataSource(DataSource dataSource) {
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void verifyInitialTestData() {
|
||||
@AfterClass
|
||||
public static void verifyFinalTestData() {
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(4);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void verifyInitialTestData() {
|
||||
clearPersonTable(jdbcTemplate);
|
||||
assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1);
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyTestDataWithinTransaction() {
|
||||
public void modifyTestDataWithinTransaction() {
|
||||
assertThatTransaction().isActive();
|
||||
assertThat(deletePerson(jdbcTemplate, BOB)).as("Deleting bob").isEqualTo(1);
|
||||
assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1);
|
||||
@@ -87,7 +91,7 @@ class ClassLevelTransactionalSpringTests extends AbstractTransactionalSpringTest
|
||||
|
||||
@Test
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
void modifyTestDataWithoutTransaction() {
|
||||
public void modifyTestDataWithoutTransaction() {
|
||||
assertThatTransaction().isNotActive();
|
||||
assertThat(addPerson(jdbcTemplate, LUKE)).as("Adding luke").isEqualTo(1);
|
||||
assertThat(addPerson(jdbcTemplate, LEIA)).as("Adding leia").isEqualTo(1);
|
||||
@@ -95,10 +99,4 @@ class ClassLevelTransactionalSpringTests extends AbstractTransactionalSpringTest
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table without a transaction.").isEqualTo(4);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
void verifyFinalTestData() {
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,35 +14,36 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.config;
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that
|
||||
* Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
|
||||
* we can specify an explicit, <em>classpath</em> location for our application
|
||||
* context.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see CoreContextConfigurationAppCtxTests
|
||||
* @see SpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see #CLASSPATH_CONTEXT_RESOURCE_PATH
|
||||
* @see AbsolutePathContextConfigurationAppCtxTests
|
||||
* @see RelativePathContextConfigurationAppCtxTests
|
||||
* @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
*/
|
||||
@ContextConfiguration(locations = { ClassPathResourceContextConfigurationAppCtxTests.CLASSPATH_CONTEXT_RESOURCE_PATH }, inheritLocations = false)
|
||||
class ClassPathResourceContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests {
|
||||
@ContextConfiguration(locations = { ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.CLASSPATH_CONTEXT_RESOURCE_PATH }, inheritLocations = false)
|
||||
public class ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
|
||||
|
||||
/**
|
||||
* Classpath-based resource path for the application context configuration
|
||||
* for {@link CoreContextConfigurationAppCtxTests}: {@value}
|
||||
* for {@link SpringJUnit4ClassRunnerAppCtxTests}:
|
||||
* {@code "classpath:/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml"}
|
||||
*
|
||||
* @see CoreContextConfigurationAppCtxTests#DEFAULT_CONTEXT_RESOURCE_PATH
|
||||
* @see SpringJUnit4ClassRunnerAppCtxTests#DEFAULT_CONTEXT_RESOURCE_PATH
|
||||
* @see ResourceUtils#CLASSPATH_URL_PREFIX
|
||||
*/
|
||||
public static final String CLASSPATH_CONTEXT_RESOURCE_PATH = ResourceUtils.CLASSPATH_URL_PREFIX +
|
||||
CoreContextConfigurationAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH;
|
||||
SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH;
|
||||
|
||||
/* all tests are in the parent class. */
|
||||
|
||||
+18
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,21 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.transaction;
|
||||
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -45,32 +43,32 @@ import static org.springframework.test.transaction.TransactionAssert.assertThatT
|
||||
* @since 4.2
|
||||
* @see Rollback
|
||||
* @see Transactional#transactionManager
|
||||
* @see DefaultRollbackTrueRollbackAnnotationTransactionalTests
|
||||
* @see DefaultRollbackFalseTransactionalTests
|
||||
*/
|
||||
@SpringJUnitConfig(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false)
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false)
|
||||
@Transactional("txMgr")
|
||||
@Rollback(false)
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class DefaultRollbackFalseRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringTests {
|
||||
public class DefaultRollbackFalseRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringRunnerTests {
|
||||
|
||||
JdbcTemplate jdbcTemplate;
|
||||
private static JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
@Autowired
|
||||
void setDataSource(DataSource dataSource) {
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void verifyInitialTestData() {
|
||||
@Before
|
||||
public void verifyInitialTestData() {
|
||||
clearPersonTable(jdbcTemplate);
|
||||
assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1);
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyTestDataWithinTransaction() {
|
||||
public void modifyTestDataWithinTransaction() {
|
||||
assertThatTransaction().isActive();
|
||||
assertThat(deletePerson(jdbcTemplate, BOB)).as("Deleting bob").isEqualTo(1);
|
||||
assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1);
|
||||
@@ -78,8 +76,8 @@ class DefaultRollbackFalseRollbackAnnotationTransactionalTests extends AbstractT
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(2);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
void verifyFinalTestData() {
|
||||
@AfterClass
|
||||
public static void verifyFinalTestData() {
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(2);
|
||||
}
|
||||
|
||||
+19
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,21 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.transaction;
|
||||
package org.springframework.test.context.junit4;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -45,43 +43,42 @@ import static org.springframework.test.transaction.TransactionAssert.assertThatT
|
||||
* @since 4.2
|
||||
* @see Rollback
|
||||
* @see Transactional#transactionManager
|
||||
* @see DefaultRollbackFalseRollbackAnnotationTransactionalTests
|
||||
* @see DefaultRollbackTrueTransactionalTests
|
||||
*/
|
||||
@SpringJUnitConfig(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false)
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false)
|
||||
@Transactional("txMgr")
|
||||
@Rollback(true)
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class DefaultRollbackTrueRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringTests {
|
||||
public class DefaultRollbackTrueRollbackAnnotationTransactionalTests extends AbstractTransactionalSpringRunnerTests {
|
||||
|
||||
private static int originalNumRows;
|
||||
|
||||
JdbcTemplate jdbcTemplate;
|
||||
private static JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
@Autowired
|
||||
void setDataSource(DataSource dataSource) {
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void verifyInitialTestData() {
|
||||
@Before
|
||||
public void verifyInitialTestData() {
|
||||
originalNumRows = clearPersonTable(jdbcTemplate);
|
||||
assertThat(addPerson(jdbcTemplate, BOB)).as("Adding bob").isEqualTo(1);
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the initial number of rows in the person table.").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(1)
|
||||
void modifyTestDataWithinTransaction() {
|
||||
@Test(timeout = 1000)
|
||||
public void modifyTestDataWithinTransaction() {
|
||||
assertThatTransaction().isActive();
|
||||
assertThat(addPerson(jdbcTemplate, JANE)).as("Adding jane").isEqualTo(1);
|
||||
assertThat(addPerson(jdbcTemplate, SUE)).as("Adding sue").isEqualTo(1);
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the number of rows in the person table within a transaction.").isEqualTo(3);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
void verifyFinalTestData() {
|
||||
@AfterClass
|
||||
public static void verifyFinalTestData() {
|
||||
assertThat(countRowsInPersonTable(jdbcTemplate)).as("Verifying the final number of rows in the person table after all tests.").isEqualTo(originalNumRows);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user