mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba590ac9e4 | |||
| ee62701f56 | |||
| fa168ca78a | |||
| 3c228a5c1d | |||
| 9bf6b8cddf | |||
| 37ecdd1437 | |||
| 73f1c5a189 | |||
| 4d296fb4ca | |||
| 6a9444473f | |||
| 03ae97b2eb | |||
| e34cdc2a55 | |||
| 8b1b9ef9e5 | |||
| efdaae02e0 | |||
| 49ffb833a3 | |||
| 065e50a444 | |||
| 7135527765 | |||
| 65e30132f3 | |||
| ebb44a8368 | |||
| 3096bb6d0c | |||
| 2b2a57b4a3 | |||
| 3295289e17 | |||
| 457e876303 | |||
| 021bf6e77d | |||
| 90453643cc | |||
| 5a2cbc1ab3 | |||
| 348b4cd067 | |||
| 9b52cfd7d5 | |||
| dd6eede243 | |||
| de97e35189 | |||
| 45a2c51fe1 | |||
| b98c3257af | |||
| ea8ae09cb7 | |||
| 0abfad870c | |||
| 5a9af9e024 | |||
| 0867dfca33 | |||
| b4355dc955 | |||
| bc91e0ea96 | |||
| 8599ee6c2b | |||
| bc466022b1 | |||
| 6f11711e27 | |||
| c067919173 | |||
| ac773d97e9 | |||
| bd7007227c | |||
| e8f873a349 | |||
| b943817f3e | |||
| 4466548f53 | |||
| 9c183f9e77 | |||
| 03620fc530 | |||
| 1c108054ee | |||
| d0b186a1c7 | |||
| 4172581f1b | |||
| c88ba6c90e | |||
| d7c13d6518 | |||
| 5013d6d771 | |||
| 9cd7b6a91e | |||
| e9dcd64068 | |||
| ce7f47c962 | |||
| ef34464c94 | |||
| ef11a00c0b | |||
| 176b0b09bf | |||
| c48ff357dc | |||
| 190dabb8e1 | |||
| d15abd58b4 | |||
| 44500cf868 | |||
| e384389790 | |||
| 49e5c84928 | |||
| 56eb135608 | |||
| 5c5cf73e11 | |||
| 858c2bd270 | |||
| 124582d910 | |||
| 253f321e8b | |||
| 0252e39409 | |||
| 2f60083cd5 | |||
| b83e07ff8c | |||
| 1841ad3472 | |||
| 0477ba4de4 | |||
| 8c376e9cc5 | |||
| 018d3c9ef2 | |||
| c70741f60d |
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.42
|
||||
javaFormatVersion=0.0.43
|
||||
|
||||
@@ -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.0");
|
||||
checkstyle.setToolVersion("10.23.1");
|
||||
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-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -21,6 +21,8 @@ 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;
|
||||
|
||||
@@ -34,6 +36,7 @@ import org.gradle.testretry.TestRetryTaskExtension;
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class TestConventions {
|
||||
|
||||
@@ -50,7 +53,12 @@ class TestConventions {
|
||||
}
|
||||
|
||||
private void configureTests(Project project, Test test) {
|
||||
test.useJUnitPlatform();
|
||||
TestFrameworkOptions existingOptions = test.getOptions();
|
||||
test.useJUnitPlatform(options -> {
|
||||
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
|
||||
options.copyFrom(junitPlatformOptions);
|
||||
}
|
||||
});
|
||||
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.3'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
}
|
||||
|
||||
description = "Spring Framework API Docs"
|
||||
@@ -21,6 +21,7 @@ dependencies {
|
||||
|
||||
javadoc {
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
|
||||
@@ -103,6 +103,14 @@ 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.
|
||||
|
||||
+5
-2
@@ -101,8 +101,11 @@ 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 placeholders.
|
||||
`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).
|
||||
|
||||
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: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
|
||||
=== Example: Property Placeholder Substitution with `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 and 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, log4j, and JSP EL style.
|
||||
|
||||
The actual values come from another file in the standard Java `Properties` format:
|
||||
|
||||
@@ -355,11 +355,15 @@ 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 and suffix.
|
||||
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).
|
||||
|
||||
With the `context` namespace introduced in Spring 2.5, you can configure property placeholders
|
||||
With the `context` namespace, 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 configuration beans:
|
||||
following `PropertySourcesPlaceholderConfigurer` bean:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@@ -200,8 +200,10 @@ following configuration beans:
|
||||
}
|
||||
----
|
||||
|
||||
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
|
||||
that uses the `${...}` syntax, with configuration beans, as the following example shows:
|
||||
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:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl
|
||||
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,8 +2,9 @@
|
||||
= 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`]
|
||||
|
||||
+195
-178
@@ -1,166 +1,9 @@
|
||||
[[testcontext-support-classes]]
|
||||
= TestContext Framework Support Classes
|
||||
|
||||
This section describes the various classes that support the Spring TestContext Framework.
|
||||
This section describes the various classes that support the Spring TestContext Framework
|
||||
in JUnit and TestNG.
|
||||
|
||||
[[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
|
||||
@@ -177,14 +20,17 @@ 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`:
|
||||
@@ -307,7 +153,8 @@ 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`
|
||||
@@ -318,10 +165,9 @@ 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]]
|
||||
@@ -341,8 +187,9 @@ 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.
|
||||
@@ -407,8 +254,9 @@ 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]
|
||||
======
|
||||
@@ -553,17 +401,19 @@ 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]
|
||||
======
|
||||
@@ -626,8 +476,174 @@ 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 Classes
|
||||
== TestNG Support
|
||||
|
||||
The `org.springframework.test.context.testng` package provides the following support
|
||||
classes for TestNG based test cases:
|
||||
@@ -650,7 +666,8 @@ 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
|
||||
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this to, for example, parameterize a base URL based on
|
||||
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this, for example, to parameterize a base URL based on
|
||||
some external configuration.
|
||||
|
||||
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.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.5"))
|
||||
api(platform("io.netty:netty-bom:4.1.119.Final"))
|
||||
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("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.4"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.6"))
|
||||
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.18"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.18"))
|
||||
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.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.3")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.4")
|
||||
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.6-SNAPSHOT
|
||||
version=6.2.7
|
||||
|
||||
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.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# 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" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+2
-2
@@ -70,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%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" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+36
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,8 +20,10 @@ 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
|
||||
@@ -37,16 +39,16 @@ import org.springframework.util.StringValueResolver;
|
||||
*
|
||||
* <pre class="code">
|
||||
* <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
* <property name="driverClassName" value="${driver}" />
|
||||
* <property name="url" value="jdbc:${dbname}" />
|
||||
* <property name="driverClassName" value="${jdbc.driver}" />
|
||||
* <property name="url" value="jdbc:${jdbc.dbname}" />
|
||||
* </bean>
|
||||
* </pre>
|
||||
*
|
||||
* Example properties file:
|
||||
*
|
||||
* <pre class="code">
|
||||
* driver=com.mysql.jdbc.Driver
|
||||
* dbname=mysql:mydb</pre>
|
||||
* jdbc.driver=com.mysql.jdbc.Driver
|
||||
* jdbc.dbname=mysql:mydb</pre>
|
||||
*
|
||||
* Annotated bean definitions may take advantage of property replacement using
|
||||
* the {@link org.springframework.beans.factory.annotation.Value @Value} annotation:
|
||||
@@ -79,11 +81,12 @@ import org.springframework.util.StringValueResolver;
|
||||
* <p>Example XML property with default value:
|
||||
*
|
||||
* <pre class="code">
|
||||
* <property name="url" value="jdbc:${dbname:defaultdb}" />
|
||||
* <property name="url" value="jdbc:${jdbc.dbname:defaultdb}" />
|
||||
* </pre>
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
* @see PropertyPlaceholderConfigurer
|
||||
* @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer
|
||||
@@ -92,16 +95,21 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
implements BeanNameAware, BeanFactoryAware {
|
||||
|
||||
/** Default placeholder prefix: {@value}. */
|
||||
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
|
||||
public static final String DEFAULT_PLACEHOLDER_PREFIX = SystemPropertyUtils.PLACEHOLDER_PREFIX;
|
||||
|
||||
/** Default placeholder suffix: {@value}. */
|
||||
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
|
||||
public static final String DEFAULT_PLACEHOLDER_SUFFIX = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
|
||||
|
||||
/** Default value separator: {@value}. */
|
||||
public static final String DEFAULT_VALUE_SEPARATOR = ":";
|
||||
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;
|
||||
|
||||
/** Default escape character: {@code '\'}. */
|
||||
public static final Character DEFAULT_ESCAPE_CHARACTER = '\\';
|
||||
|
||||
/** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX}. */
|
||||
protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
|
||||
@@ -113,9 +121,11 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
@Nullable
|
||||
protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
|
||||
|
||||
/** Defaults to {@link #DEFAULT_ESCAPE_CHARACTER}. */
|
||||
/**
|
||||
* The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
|
||||
*/
|
||||
@Nullable
|
||||
protected Character escapeCharacter = DEFAULT_ESCAPE_CHARACTER;
|
||||
protected Character escapeCharacter = AbstractPropertyResolver.getDefaultEscapeCharacter();
|
||||
|
||||
protected boolean trimValues = false;
|
||||
|
||||
@@ -133,7 +143,7 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
|
||||
/**
|
||||
* Set the prefix that a placeholder string starts with.
|
||||
* The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}.
|
||||
* <p>The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}.
|
||||
*/
|
||||
public void setPlaceholderPrefix(String placeholderPrefix) {
|
||||
this.placeholderPrefix = placeholderPrefix;
|
||||
@@ -141,31 +151,32 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
|
||||
/**
|
||||
* Set the suffix that a placeholder string ends with.
|
||||
* The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}.
|
||||
* <p>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.
|
||||
* 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.
|
||||
* <p>The default is {@value #DEFAULT_VALUE_SEPARATOR}.
|
||||
*/
|
||||
public void setValueSeparator(@Nullable String valueSeparator) {
|
||||
this.valueSeparator = valueSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* 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()}.
|
||||
* @since 6.2
|
||||
*/
|
||||
public void setEscapeCharacter(@Nullable Character escsEscapeCharacter) {
|
||||
this.escapeCharacter = escsEscapeCharacter;
|
||||
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
||||
this.escapeCharacter = escapeCharacter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -35,12 +35,14 @@ 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>
|
||||
*
|
||||
* 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>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.
|
||||
*
|
||||
* <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.
|
||||
|
||||
+11
-3
@@ -997,9 +997,17 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
*/
|
||||
@Nullable
|
||||
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
|
||||
boolean locked = this.singletonLock.tryLock();
|
||||
if (!locked) {
|
||||
return null;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+4
-3
@@ -1066,8 +1066,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@Nullable
|
||||
protected Boolean isCurrentThreadAllowedToHoldSingletonLock() {
|
||||
String mainThreadPrefix = this.mainThreadPrefix;
|
||||
if (this.mainThreadPrefix != null) {
|
||||
// We only differentiate in the preInstantiateSingletons phase.
|
||||
if (mainThreadPrefix != null) {
|
||||
// We only differentiate in the preInstantiateSingletons phase, using
|
||||
// the volatile mainThreadPrefix field as an indicator for that phase.
|
||||
|
||||
PreInstantiation preInstantiation = this.preInstantiationThread.get();
|
||||
if (preInstantiation != null) {
|
||||
@@ -1087,7 +1088,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
else if (this.strictLocking == null) {
|
||||
// No explicit locking configuration -> infer appropriate locking.
|
||||
if (mainThreadPrefix != null && !getThreadNamePrefix().equals(mainThreadPrefix)) {
|
||||
if (!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).
|
||||
|
||||
+7
-5
@@ -271,13 +271,15 @@ 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 {
|
||||
|
||||
+22
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -118,7 +118,15 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
*/
|
||||
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
|
||||
if (factory.isSingleton() && containsSingleton(beanName)) {
|
||||
this.singletonLock.lock();
|
||||
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
||||
boolean locked;
|
||||
if (lockFlag == null) {
|
||||
this.singletonLock.lock();
|
||||
locked = true;
|
||||
}
|
||||
else {
|
||||
locked = (lockFlag && this.singletonLock.tryLock());
|
||||
}
|
||||
try {
|
||||
Object object = this.factoryBeanObjectCache.get(beanName);
|
||||
if (object == null) {
|
||||
@@ -131,11 +139,13 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
}
|
||||
else {
|
||||
if (shouldPostProcess) {
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
if (locked) {
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
try {
|
||||
object = postProcessObjectFromFactoryBean(object, beanName);
|
||||
}
|
||||
@@ -144,7 +154,9 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
"Post-processing of FactoryBean's singleton object failed", ex);
|
||||
}
|
||||
finally {
|
||||
afterSingletonCreation(beanName);
|
||||
if (locked) {
|
||||
afterSingletonCreation(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (containsSingleton(beanName)) {
|
||||
@@ -155,7 +167,9 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
return object;
|
||||
}
|
||||
finally {
|
||||
this.singletonLock.unlock();
|
||||
if (locked) {
|
||||
this.singletonLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -59,3 +59,10 @@ 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"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -50,6 +50,7 @@ public class ApplicationContextAotGenerator {
|
||||
*/
|
||||
public ClassName processAheadOfTime(GenericApplicationContext applicationContext,
|
||||
GenerationContext generationContext) {
|
||||
|
||||
return withCglibClassHandler(new CglibClassHandler(generationContext), () -> {
|
||||
applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());
|
||||
ApplicationContextInitializationCodeGenerator codeGenerator =
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -80,8 +80,9 @@ public abstract class ContextAotProcessor extends AbstractAotProcessor<ClassName
|
||||
@Override
|
||||
protected ClassName doProcess() {
|
||||
deleteExistingOutput();
|
||||
GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass());
|
||||
return performAotProcessing(applicationContext);
|
||||
try (GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass())) {
|
||||
return performAotProcessing(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -100,6 +100,7 @@ public class ReflectiveProcessorAotContributionBuilder {
|
||||
return (!this.classes.isEmpty() ? new AotContribution(this.classes) : null);
|
||||
}
|
||||
|
||||
|
||||
private static class AotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private final Class<?>[] classes;
|
||||
@@ -113,9 +114,9 @@ public class ReflectiveProcessorAotContributionBuilder {
|
||||
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
|
||||
registrar.registerRuntimeHints(runtimeHints, this.classes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class ReflectiveClassPathScanner extends ClassPathScanningCandidateComponentProvider {
|
||||
|
||||
@Nullable
|
||||
|
||||
+83
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -29,7 +29,6 @@ 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;
|
||||
@@ -49,7 +48,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 {@code PropertySource}. Search precedence of local properties is
|
||||
* et al.) are added as a single {@link 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.
|
||||
@@ -101,8 +100,9 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code PropertySources} from the given {@link Environment}
|
||||
* will be searched when replacing ${...} placeholders.
|
||||
* {@inheritDoc}
|
||||
* <p>{@code PropertySources} from the given {@link Environment} will be searched
|
||||
* when replacing ${...} placeholders.
|
||||
* @see #setPropertySources
|
||||
* @see #postProcessBeanFactory
|
||||
*/
|
||||
@@ -132,28 +132,11 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
if (this.propertySources == null) {
|
||||
this.propertySources = new MutablePropertySources();
|
||||
if (this.environment != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
PropertySource<?> environmentPropertySource =
|
||||
(this.environment instanceof ConfigurableEnvironment configurableEnvironment ?
|
||||
new ConfigurableEnvironmentPropertySource(configurableEnvironment) :
|
||||
new FallbackEnvironmentPropertySource(this.environment));
|
||||
this.propertySources.addLast(environmentPropertySource);
|
||||
}
|
||||
try {
|
||||
PropertySource<?> localPropertySource =
|
||||
@@ -176,6 +159,7 @@ 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
|
||||
*/
|
||||
@@ -188,7 +172,7 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
* placeholders with values from the given properties.
|
||||
*/
|
||||
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
|
||||
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
|
||||
ConfigurablePropertyResolver propertyResolver) throws BeansException {
|
||||
|
||||
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
|
||||
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
|
||||
@@ -234,4 +218,75 @@ 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 + "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* 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.
|
||||
@@ -146,6 +146,12 @@ 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,7 +27,6 @@ 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;
|
||||
@@ -550,14 +549,13 @@ 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 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.
|
||||
* {@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.
|
||||
* <p>More sophisticated matching can be implemented by overriding the
|
||||
* {@link #isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
|
||||
@@ -575,8 +573,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
else {
|
||||
String[] fieldPatterns = new String[disallowedFields.length];
|
||||
for (int i = 0; i < fieldPatterns.length; i++) {
|
||||
String field = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]);
|
||||
fieldPatterns[i] = field.toLowerCase(Locale.ROOT);
|
||||
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]);
|
||||
}
|
||||
this.disallowedFields = fieldPatterns;
|
||||
}
|
||||
@@ -1302,9 +1299,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
|
||||
@@ -1320,8 +1317,13 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
protected boolean isAllowed(String field) {
|
||||
String[] allowed = getAllowedFields();
|
||||
String[] disallowed = getDisallowedFields();
|
||||
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
|
||||
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase(Locale.ROOT))));
|
||||
if (!ObjectUtils.isEmpty(allowed) && !PatternMatchUtils.simpleMatch(allowed, field)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(disallowed)) {
|
||||
return !PatternMatchUtils.simpleMatchIgnoreCase(disallowed, field);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<?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>
|
||||
+13
-2
@@ -24,6 +24,7 @@ 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;
|
||||
@@ -243,14 +244,24 @@ class BackgroundBootstrapTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestBean testBean4() {
|
||||
public FactoryBean<TestBean> testBean4() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return new TestBean();
|
||||
TestBean testBean = new TestBean();
|
||||
return new FactoryBean<>() {
|
||||
@Override
|
||||
public TestBean getObject() {
|
||||
return testBean;
|
||||
}
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return testBean.getClass();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -38,7 +38,8 @@ import org.springframework.context.support.GenericApplicationContext;
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
class SpringAtInjectTckTests {
|
||||
// WARNING: This class MUST be public, since it is based on JUnit 3.
|
||||
public class SpringAtInjectTckTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Test suite() {
|
||||
|
||||
+11
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -45,8 +45,9 @@ class ContextAotProcessorTests {
|
||||
void processGeneratesAssets(@TempDir Path directory) {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.registerBean(SampleApplication.class);
|
||||
ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory);
|
||||
DemoContextAotProcessor 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());
|
||||
@@ -61,9 +62,10 @@ class ContextAotProcessorTests {
|
||||
Path existingSourceOutput = createExisting(sourceOutput);
|
||||
Path existingResourceOutput = createExisting(resourceOutput);
|
||||
Path existingClassOutput = createExisting(classOutput);
|
||||
ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class,
|
||||
DemoContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class,
|
||||
sourceOutput, resourceOutput, classOutput);
|
||||
processor.process();
|
||||
assertThat(processor.context.isClosed()).isTrue();
|
||||
assertThat(existingSourceOutput).doesNotExist();
|
||||
assertThat(existingResourceOutput).doesNotExist();
|
||||
assertThat(existingClassOutput).doesNotExist();
|
||||
@@ -73,13 +75,14 @@ class ContextAotProcessorTests {
|
||||
void processWithEmptyNativeImageArgumentsDoesNotCreateNativeImageProperties(@TempDir Path directory) {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.registerBean(SampleApplication.class);
|
||||
ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory) {
|
||||
DemoContextAotProcessor 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();
|
||||
@@ -118,6 +121,8 @@ 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"));
|
||||
}
|
||||
@@ -141,11 +146,12 @@ 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 {
|
||||
|
||||
@@ -153,7 +159,6 @@ class ContextAotProcessorTests {
|
||||
public String testBean() {
|
||||
return "Hello";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+384
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,20 +16,34 @@
|
||||
|
||||
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;
|
||||
@@ -38,12 +52,15 @@ 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}.
|
||||
@@ -73,6 +90,43 @@ 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();
|
||||
@@ -88,14 +142,29 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void localPropertiesOverrideFalse() {
|
||||
localPropertiesOverride(false);
|
||||
}
|
||||
@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 localPropertiesOverrideTrue() {
|
||||
localPropertiesOverride(true);
|
||||
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
|
||||
@@ -281,28 +350,58 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private void localPropertiesOverride(boolean override) {
|
||||
@Test // gh-34861
|
||||
void withEnumerableAndNonEnumerablePropertySourcesInTheEnvironmentAndLocalProperties() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("testBean",
|
||||
genericBeanDefinition(TestBean.class)
|
||||
.addPropertyValue("name", "${foo}")
|
||||
.addPropertyValue("name", "${foo:bogus}")
|
||||
.addPropertyValue("jedi", "${local:false}")
|
||||
.getBeanDefinition());
|
||||
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
// 1) MockPropertySource is an EnumerablePropertySource.
|
||||
MockPropertySource mockPropertySource = new MockPropertySource("mockPropertySource")
|
||||
.withProperty("foo", "${bar}");
|
||||
|
||||
ppc.setLocalOverride(override);
|
||||
// 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.setProperties(new Properties() {{
|
||||
setProperty("foo", "local");
|
||||
setProperty("local", "true");
|
||||
}});
|
||||
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");
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
// Should not contain "foo" or "bar" from the Environment.
|
||||
assertThat(propertyNames).containsOnly("local");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -432,6 +531,252 @@ 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;
|
||||
@@ -472,4 +817,23 @@ 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,10 +463,21 @@ public class ReflectUtils {
|
||||
c = lookup.defineClass(b);
|
||||
}
|
||||
catch (LinkageError | IllegalArgumentException ex) {
|
||||
// in case of plain LinkageError (class already defined)
|
||||
// or IllegalArgumentException (class in different package):
|
||||
// fall through to traditional ClassLoader.defineClass below
|
||||
t = 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;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new CodeGenerationException(ex);
|
||||
|
||||
@@ -26,18 +26,20 @@ 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 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>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>This is an alternative way to set Spring-related system properties such as
|
||||
* "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".
|
||||
* {@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"}.
|
||||
*
|
||||
* @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
|
||||
|
||||
+1
-5
@@ -361,16 +361,12 @@ 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(
|
||||
|
||||
+8
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -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 working against the {@link MutablePropertySources} API.
|
||||
* and work 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 set to specify active profiles: {@value}.
|
||||
* Name of the property 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 set to specify profiles that are active by default: {@value}.
|
||||
* Name of the property 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}
|
||||
* instance used by the Environment.
|
||||
* used by this {@code Environment}.
|
||||
* @since 5.3.4
|
||||
* @see #getPropertyResolver()
|
||||
*/
|
||||
@@ -150,8 +150,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ConfigurablePropertyResolver} being used by the
|
||||
* {@link Environment}.
|
||||
* Return the {@link ConfigurablePropertyResolver} used by the {@code Environment}.
|
||||
* @since 5.3.4
|
||||
* @see #createPropertyResolver(MutablePropertySources)
|
||||
*/
|
||||
@@ -320,7 +319,6 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] getDefaultProfiles() {
|
||||
return StringUtils.toStringArray(doGetDefaultProfiles());
|
||||
@@ -328,7 +326,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.
|
||||
@@ -420,7 +418,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
|
||||
|
||||
+112
-18
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -37,10 +38,52 @@ 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
|
||||
@@ -62,7 +105,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
|
||||
|
||||
@Nullable
|
||||
private Character escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER;
|
||||
private Character escapeCharacter = getDefaultEscapeCharacter();
|
||||
|
||||
private final Set<String> requiredProperties = new LinkedHashSet<>();
|
||||
|
||||
@@ -91,9 +134,9 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prefix that placeholders replaced by this resolver must begin with.
|
||||
* <p>The default is "${".
|
||||
* @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX
|
||||
* {@inheritDoc}
|
||||
* <p>The default is <code>"${"</code>.
|
||||
* @see SystemPropertyUtils#PLACEHOLDER_PREFIX
|
||||
*/
|
||||
@Override
|
||||
public void setPlaceholderPrefix(String placeholderPrefix) {
|
||||
@@ -102,9 +145,9 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the suffix that placeholders replaced by this resolver must end with.
|
||||
* <p>The default is "}".
|
||||
* @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX
|
||||
* {@inheritDoc}
|
||||
* <p>The default is <code>"}"</code>.
|
||||
* @see SystemPropertyUtils#PLACEHOLDER_SUFFIX
|
||||
*/
|
||||
@Override
|
||||
public void setPlaceholderSuffix(String placeholderSuffix) {
|
||||
@@ -113,11 +156,9 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@inheritDoc}
|
||||
* <p>The default is {@code ":"}.
|
||||
* @see SystemPropertyUtils#VALUE_SEPARATOR
|
||||
*/
|
||||
@Override
|
||||
public void setValueSeparator(@Nullable String valueSeparator) {
|
||||
@@ -125,12 +166,9 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 "\".
|
||||
* {@inheritDoc}
|
||||
* <p>The default is determined by {@link #getDefaultEscapeCharacter()}.
|
||||
* @since 6.2
|
||||
* @see org.springframework.util.SystemPropertyUtils#ESCAPE_CHARACTER
|
||||
*/
|
||||
@Override
|
||||
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
||||
@@ -291,4 +329,60 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -34,7 +34,10 @@ 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 (as far as possible).
|
||||
* 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>
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -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
|
||||
|
||||
+9
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -69,21 +69,23 @@ public interface ConfigurablePropertyResolver extends PropertyResolver {
|
||||
void setPlaceholderSuffix(String placeholderSuffix);
|
||||
|
||||
/**
|
||||
* Specify the separating character between the placeholders replaced by this
|
||||
* resolver and their associated default value, or {@code null} if no such
|
||||
* Set the separating character to be honored between placeholders replaced by
|
||||
* this resolver and their associated default values, or {@code null} if no such
|
||||
* special character should be processed as a value separator.
|
||||
*/
|
||||
void setValueSeparator(@Nullable String valueSeparator);
|
||||
|
||||
/**
|
||||
* Specify the escape character to use to ignore placeholder prefix or
|
||||
* value separator, or {@code null} if no escaping should take place.
|
||||
* 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.
|
||||
* @since 6.2
|
||||
*/
|
||||
void setEscapeCharacter(@Nullable Character escapeCharacter);
|
||||
|
||||
/**
|
||||
* Set whether to throw an exception when encountering an unresolvable placeholder
|
||||
* Specify 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
|
||||
@@ -106,7 +108,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-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -30,13 +30,13 @@ import org.springframework.lang.Nullable;
|
||||
public interface PropertyResolver {
|
||||
|
||||
/**
|
||||
* Return whether the given property key is available for resolution,
|
||||
* i.e. if the value for the given key is not {@code null}.
|
||||
* Determine whether the given property key is available for resolution
|
||||
* — for example, if the value for the given key is not {@code null}.
|
||||
*/
|
||||
boolean containsProperty(String key);
|
||||
|
||||
/**
|
||||
* Return the property value associated with the given key,
|
||||
* Resolve 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);
|
||||
|
||||
/**
|
||||
* Return the property value associated with the given key, or
|
||||
* Resolve 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);
|
||||
|
||||
/**
|
||||
* Return the property value associated with the given key,
|
||||
* Resolve 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);
|
||||
|
||||
/**
|
||||
* Return the property value associated with the given key,
|
||||
* Resolve 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);
|
||||
|
||||
/**
|
||||
* Return the property value associated with the given key (never {@code null}).
|
||||
* Resolve 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;
|
||||
|
||||
/**
|
||||
* Return the property value associated with the given key, converted to the given
|
||||
* Resolve 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
|
||||
*/
|
||||
|
||||
+7
-1
@@ -29,6 +29,7 @@ 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;
|
||||
|
||||
@@ -44,6 +45,7 @@ import org.springframework.util.ResourceUtils;
|
||||
*/
|
||||
public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
|
||||
@SuppressWarnings("try")
|
||||
@Override
|
||||
public boolean exists() {
|
||||
try {
|
||||
@@ -86,7 +88,11 @@ 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).
|
||||
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
else if (con.getContentLengthLong() > 0) {
|
||||
return true;
|
||||
|
||||
+4
-3
@@ -36,6 +36,7 @@ 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;
|
||||
@@ -874,9 +875,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
|
||||
closeJarFile = !jarCon.getUseCaches();
|
||||
}
|
||||
catch (ZipException | FileNotFoundException ex) {
|
||||
catch (ZipException | FileNotFoundException | NoSuchFileException ex) {
|
||||
// Happens in case of a non-jar file or in case of a cached root directory
|
||||
// without specific subdirectory present, respectively.
|
||||
// without the specific subdirectory present, respectively.
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
@@ -1275,7 +1276,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a alternative form of the resource, i.e. with or without a leading slash.
|
||||
* Return an 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-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -141,8 +141,8 @@ public abstract class PropertiesLoaderSupport {
|
||||
|
||||
|
||||
/**
|
||||
* Return a merged Properties instance containing both the
|
||||
* loaded properties and properties set on this FactoryBean.
|
||||
* Return a merged {@link Properties} instance containing both the
|
||||
* loaded properties and properties set on this component.
|
||||
*/
|
||||
protected Properties mergeProperties() throws IOException {
|
||||
Properties result = new Properties();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -37,13 +37,25 @@ 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 pattern.equals(str);
|
||||
return (ignoreCase ? pattern.equalsIgnoreCase(str) : pattern.equals(str));
|
||||
}
|
||||
|
||||
if (firstIndex == 0) {
|
||||
@@ -52,25 +64,43 @@ public abstract class PatternMatchUtils {
|
||||
}
|
||||
int nextIndex = pattern.indexOf('*', 1);
|
||||
if (nextIndex == -1) {
|
||||
return str.endsWith(pattern.substring(1));
|
||||
String part = pattern.substring(1);
|
||||
return (ignoreCase ? StringUtils.endsWithIgnoreCase(str, part) : str.endsWith(part));
|
||||
}
|
||||
String part = pattern.substring(1, nextIndex);
|
||||
if (part.isEmpty()) {
|
||||
return simpleMatch(pattern.substring(nextIndex), str);
|
||||
return simpleMatch(pattern.substring(nextIndex), str, ignoreCase);
|
||||
}
|
||||
int partIndex = str.indexOf(part);
|
||||
int partIndex = indexOf(str, part, 0, ignoreCase);
|
||||
while (partIndex != -1) {
|
||||
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
|
||||
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()), ignoreCase)) {
|
||||
return true;
|
||||
}
|
||||
partIndex = str.indexOf(part, partIndex + 1);
|
||||
partIndex = indexOf(str, part, partIndex + 1, ignoreCase);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return (str.length() >= firstIndex &&
|
||||
pattern.startsWith(str.substring(0, firstIndex)) &&
|
||||
simpleMatch(pattern.substring(firstIndex), str.substring(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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,4 +124,19 @@ 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 class NonClosingInputStream extends FilterInputStream {
|
||||
private static final class NonClosingInputStream extends FilterInputStream {
|
||||
|
||||
public NonClosingInputStream(InputStream in) {
|
||||
super(in);
|
||||
@@ -248,10 +248,30 @@ 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 class NonClosingOutputStream extends FilterOutputStream {
|
||||
private static final class NonClosingOutputStream extends FilterOutputStream {
|
||||
|
||||
public NonClosingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -35,16 +35,19 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public abstract class SystemPropertyUtils {
|
||||
|
||||
/** Prefix for system property placeholders: {@value}. */
|
||||
/** Prefix for property placeholders: {@value}. */
|
||||
public static final String PLACEHOLDER_PREFIX = "${";
|
||||
|
||||
/** Suffix for system property placeholders: {@value}. */
|
||||
/** Suffix for property placeholders: {@value}. */
|
||||
public static final String PLACEHOLDER_SUFFIX = "}";
|
||||
|
||||
/** Value separator for system property placeholders: {@value}. */
|
||||
/** Value separator for property placeholders: {@value}. */
|
||||
public static final String VALUE_SEPARATOR = ":";
|
||||
|
||||
/** Default escape character: {@code '\'}. */
|
||||
/**
|
||||
* Escape character for property placeholders: {@code '\'}.
|
||||
* @since 6.2
|
||||
*/
|
||||
public static final Character ESCAPE_CHARACTER = '\\';
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -85,6 +85,7 @@ 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;
|
||||
@@ -204,6 +205,7 @@ public class ExponentialBackOff implements BackOff {
|
||||
return this.maxAttempts;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BackOffExecution start() {
|
||||
return new ExponentialBackOffExecution();
|
||||
@@ -225,6 +227,7 @@ public class ExponentialBackOff implements BackOff {
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
private class ExponentialBackOffExecution implements BackOffExecution {
|
||||
|
||||
private long currentInterval = -1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* 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.
|
||||
@@ -35,6 +35,7 @@ public class FixedBackOff implements BackOff {
|
||||
*/
|
||||
public static final long UNLIMITED_ATTEMPTS = Long.MAX_VALUE;
|
||||
|
||||
|
||||
private long interval = DEFAULT_INTERVAL;
|
||||
|
||||
private long maxAttempts = UNLIMITED_ATTEMPTS;
|
||||
@@ -86,6 +87,7 @@ public class FixedBackOff implements BackOff {
|
||||
return this.maxAttempts;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BackOffExecution start() {
|
||||
return new FixedBackOffExecution();
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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
+96
-70
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -21,6 +21,7 @@ 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;
|
||||
@@ -38,18 +39,15 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class PropertySourcesPropertyResolverTests {
|
||||
|
||||
private Properties testProperties;
|
||||
private final Properties testProperties = new Properties();
|
||||
|
||||
private MutablePropertySources propertySources;
|
||||
private final MutablePropertySources propertySources = new MutablePropertySources();
|
||||
|
||||
private ConfigurablePropertyResolver propertyResolver;
|
||||
private final PropertySourcesPropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
propertySources = new MutablePropertySources();
|
||||
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
testProperties = new Properties();
|
||||
propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties));
|
||||
}
|
||||
|
||||
@@ -77,14 +75,12 @@ class PropertySourcesPropertyResolverTests {
|
||||
|
||||
@Test
|
||||
void getProperty_propertySourceSearchOrderIsFIFO() {
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,8 +111,8 @@ class PropertySourcesPropertyResolverTests {
|
||||
|
||||
class TestType { }
|
||||
|
||||
assertThatExceptionOfType(ConverterNotFoundException.class).isThrownBy(() ->
|
||||
propertyResolver.getProperty("foo", TestType.class));
|
||||
assertThatExceptionOfType(ConverterNotFoundException.class)
|
||||
.isThrownBy(() -> propertyResolver.getProperty("foo", TestType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,7 +123,6 @@ 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);
|
||||
@@ -138,7 +133,6 @@ 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();
|
||||
@@ -148,10 +142,9 @@ 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");
|
||||
@@ -162,7 +155,7 @@ class PropertySourcesPropertyResolverTests {
|
||||
// 'system' now has precedence
|
||||
assertThat(propertyResolver.getProperty("foo")).isEqualTo("newValue");
|
||||
|
||||
assertThat(propertySources).hasSize(2);
|
||||
assertThat(propertySources).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,81 +163,65 @@ 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)).isEqualTo(new String[] { "abc", "123" });
|
||||
assertThat(propertyResolver.getRequiredProperty("exists", String[].class)).containsExactly("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"));
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvePlaceholders_withUnresolvable() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"))
|
||||
assertThat(propertyResolver.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"));
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
assertThat(propertyResolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
.isEqualTo("Replace this value plus defaultValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvePlaceholders_withNullInput() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolvePlaceholders(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key}")).isEqualTo("Replace this value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders_withUnresolvable() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class).isThrownBy(() ->
|
||||
resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}"));
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders_withDefaultValue() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
|
||||
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
|
||||
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
assertThat(propertyResolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"))
|
||||
.isEqualTo("Replace this value plus defaultValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredPlaceholders_withNullInput() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> propertyResolver.resolveRequiredPlaceholders(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -256,17 +233,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", "");
|
||||
@@ -291,13 +268,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
|
||||
@@ -349,9 +326,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);
|
||||
@@ -361,9 +338,58 @@ 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}\"");
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -335,6 +335,11 @@ 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-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -53,18 +53,22 @@ 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
|
||||
@@ -74,6 +78,10 @@ class PatternMatchUtilsTests {
|
||||
assertMatches("*stuff*", "stuffTest");
|
||||
assertMatches("*stuff*", "getstuff");
|
||||
assertMatches("*stuff*", "stuff");
|
||||
testMixedCaseMatch("*stuff*", "getStuffTest");
|
||||
testMixedCaseMatch("*stuff*", "StuffTest");
|
||||
testMixedCaseMatch("*stuff*", "getStuff");
|
||||
testMixedCaseMatch("*stuff*", "Stuff");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -82,6 +90,8 @@ class PatternMatchUtilsTests {
|
||||
assertMatches("on*Event", "onEvent");
|
||||
assertDoesNotMatch("3*3", "3");
|
||||
assertMatches("3*3", "33");
|
||||
testMixedCaseMatch("on*Event", "OnMyEvenT");
|
||||
testMixedCaseMatch("on*Event", "OnEvenT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,18 +132,27 @@ 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.Properties;
|
||||
import java.util.Map;
|
||||
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) {
|
||||
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);
|
||||
Map<String, String> properties = Map.of(
|
||||
"firstName", "John",
|
||||
"nested0", "first",
|
||||
"nested1", "Name");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> placeholders() {
|
||||
@@ -79,13 +79,13 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("nestedPlaceholders")
|
||||
void nestedPlaceholdersAreReplaced(String text, String 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);
|
||||
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);
|
||||
}
|
||||
|
||||
static Stream<Arguments> nestedPlaceholders() {
|
||||
@@ -101,19 +101,15 @@ class PlaceholderParserTests {
|
||||
@Test
|
||||
void parseWithSinglePlaceholder() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("${firstName}", resolver))
|
||||
.isEqualTo("John");
|
||||
verify(resolver).resolvePlaceholder("firstName");
|
||||
verifyNoMoreInteractions(resolver);
|
||||
assertThat(this.parser.replacePlaceholders("${firstName}", resolver)).isEqualTo("John");
|
||||
verifyPlaceholderResolutions(resolver, "firstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithPlaceholderAndPrefixText() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
|
||||
assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver))
|
||||
.isEqualTo("This is John");
|
||||
verify(resolver).resolvePlaceholder("firstName");
|
||||
verifyNoMoreInteractions(resolver);
|
||||
assertThat(this.parser.replacePlaceholders("This is ${firstName}", resolver)).isEqualTo("This is John");
|
||||
verifyPlaceholderResolutions(resolver, "firstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,31 +117,25 @@ class PlaceholderParserTests {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John", "lastName", "Smith");
|
||||
assertThat(this.parser.replacePlaceholders("User: ${firstName} - ${lastName}.", resolver))
|
||||
.isEqualTo("User: John - Smith.");
|
||||
verify(resolver).resolvePlaceholder("firstName");
|
||||
verify(resolver).resolvePlaceholder("lastName");
|
||||
verifyNoMoreInteractions(resolver);
|
||||
verifyPlaceholderResolutions(resolver, "firstName", "lastName");
|
||||
}
|
||||
|
||||
@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 placeholdersWithSeparatorAreHandledAsIs() {
|
||||
void placeholderValueContainingSeparatorIsHandledAsIs() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("my:test", "value");
|
||||
assertThat(this.parser.replacePlaceholders("${my:test}", resolver)).isEqualTo("value");
|
||||
verifyPlaceholderResolutions(resolver, "my:test");
|
||||
@@ -153,17 +143,20 @@ class PlaceholderParserTests {
|
||||
|
||||
@Test
|
||||
void placeholdersWithoutEscapeCharAreNotEscaped() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("test", "value");
|
||||
assertThat(this.parser.replacePlaceholders("\\${test}", resolver)).isEqualTo("\\value");
|
||||
verifyPlaceholderResolutions(resolver, "test");
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void textWithInvalidPlaceholderIsMerged() {
|
||||
void textWithInvalidPlaceholderSyntaxIsMerged() {
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -176,11 +169,11 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("placeholders")
|
||||
void placeholderIsReplaced(String text, String 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);
|
||||
Map<String, String> properties = Map.of(
|
||||
"firstName", "John",
|
||||
"nested0", "first",
|
||||
"nested1", "Name");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> placeholders() {
|
||||
@@ -199,14 +192,14 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("nestedPlaceholders")
|
||||
void nestedPlaceholdersAreReplaced(String text, String 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);
|
||||
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);
|
||||
}
|
||||
|
||||
static Stream<Arguments> nestedPlaceholders() {
|
||||
@@ -225,11 +218,11 @@ class PlaceholderParserTests {
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("exactMatchPlaceholders")
|
||||
void placeholdersWithExactMatchAreConsidered(String text, String 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);
|
||||
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);
|
||||
}
|
||||
|
||||
static Stream<Arguments> exactMatchPlaceholders() {
|
||||
@@ -242,74 +235,55 @@ 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested // Tests with the use of the escape character
|
||||
/**
|
||||
* Tests that use the escape character.
|
||||
*/
|
||||
@Nested
|
||||
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) {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver(
|
||||
"firstName", "John", "nested0", "first", "nested1", "Name",
|
||||
Map<String, String> properties = Map.of(
|
||||
"firstName", "John",
|
||||
"${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, resolver)).isEqualTo(expected);
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> escapedPlaceholders() {
|
||||
@@ -324,18 +298,21 @@ class PlaceholderParserTests {
|
||||
Arguments.of("${p4}", "adc${p1}"),
|
||||
Arguments.of("${p5}", "adcv1"),
|
||||
Arguments.of("${p6}", "adcdef${p1}"),
|
||||
Arguments.of("${p7}", "adc\\${"));
|
||||
|
||||
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")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0} -> {1}")
|
||||
@MethodSource("escapedSeparators")
|
||||
void escapedSeparatorIsNotReplaced(String text, String 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);
|
||||
Map<String, String> properties = Map.of("first:Name", "John");
|
||||
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> escapedSeparators() {
|
||||
@@ -345,6 +322,26 @@ 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
|
||||
@@ -354,34 +351,38 @@ class PlaceholderParserTests {
|
||||
|
||||
@Test
|
||||
void textWithCircularReference() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("pL", "${pR}", "pR", "${pL}");
|
||||
assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", resolver))
|
||||
Map<String, String> properties = Map.of(
|
||||
"pL", "${pR}",
|
||||
"pR", "${pL}");
|
||||
assertThatThrownBy(() -> this.parser.replacePlaceholders("${pL}", properties::get))
|
||||
.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("${bogus}", resolver))
|
||||
.withMessage("Could not resolve placeholder 'bogus' in value \"${bogus}\"")
|
||||
.isThrownBy(() -> this.parser.replacePlaceholders("X${bogus}Z", placeholderName -> null))
|
||||
.withMessage("Could not resolve placeholder 'bogus' in value \"X${bogus}Z\"")
|
||||
.withNoCause();
|
||||
}
|
||||
|
||||
@Test
|
||||
void unresolvablePlaceholderInNestedPlaceholderIsReportedWithChain() {
|
||||
PlaceholderResolver resolver = mockPlaceholderResolver("p1", "v1", "p2", "v2",
|
||||
Map<String, String> properties = Map.of(
|
||||
"p1", "v1",
|
||||
"p2", "v2",
|
||||
"p3", "${p1}:${p2}:${bogus}");
|
||||
assertThatExceptionOfType(PlaceholderResolutionException.class)
|
||||
.isThrownBy(() -> this.parser.replacePlaceholders("${p3}", resolver))
|
||||
.isThrownBy(() -> this.parser.replacePlaceholders("${p3}", properties::get))
|
||||
.withMessage("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\" <-- \"${p3}\"")
|
||||
.withNoCause();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PlaceholderResolver mockPlaceholderResolver(String... pairs) {
|
||||
|
||||
private static PlaceholderResolver mockPlaceholderResolver(String... pairs) {
|
||||
if (pairs.length % 2 == 1) {
|
||||
throw new IllegalArgumentException("size must be even, it is a set of key=value pairs");
|
||||
}
|
||||
@@ -394,7 +395,7 @@ class PlaceholderParserTests {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) {
|
||||
private static void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) {
|
||||
InOrder ordered = inOrder(mock);
|
||||
for (String placeholder : placeholders) {
|
||||
ordered.verify(mock).resolvePlaceholder(placeholder);
|
||||
|
||||
+84
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
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;
|
||||
@@ -144,6 +147,86 @@ 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);
|
||||
|
||||
+67
-61
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -49,6 +49,7 @@ import org.springframework.util.Assert;
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Paluch
|
||||
* @author Anton Naydenov
|
||||
* @author Sam Brannen
|
||||
* @since 5.3
|
||||
*/
|
||||
abstract class NamedParameterUtils {
|
||||
@@ -513,70 +514,17 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
public String toQuery() {
|
||||
return this.expandedSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -597,10 +545,68 @@ abstract class NamedParameterUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toQuery() {
|
||||
return this.expandedSql;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -19,8 +19,9 @@ package org.springframework.r2dbc.core.binding;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
/**
|
||||
* Anonymous, index-based bind marker using a static placeholder.
|
||||
* Instances are bound by the ordinal position ordered by the appearance of
|
||||
* Anonymous, index-based bind markers that use a static placeholder.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
@@ -46,7 +47,7 @@ class AnonymousBindMarkers implements BindMarkers {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link AnonymousBindMarkers} instance given {@code placeholder}.
|
||||
* Create a new {@link AnonymousBindMarkers} instance for the given {@code placeholder}.
|
||||
* @param placeholder parameter bind marker
|
||||
*/
|
||||
AnonymousBindMarkers(String placeholder) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,7 +20,8 @@ import io.r2dbc.spi.Statement;
|
||||
|
||||
/**
|
||||
* A bind marker represents a single bindable parameter within a query.
|
||||
* Bind markers are dialect-specific and provide a
|
||||
*
|
||||
* <p>Bind markers are dialect-specific and provide a
|
||||
* {@link #getPlaceholder() placeholder} that is used in the actual query.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
@@ -37,7 +38,8 @@ 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)
|
||||
@@ -46,7 +48,8 @@ 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-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -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 parameter in the appropriate representation.
|
||||
* the driver encodes the 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/bind parameter names.
|
||||
* pass of one or more parameters. It maintains bind indexes or 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}.
|
||||
* Implementations are allowed to consider/ignore/filter
|
||||
* <p>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}
|
||||
|
||||
+19
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -30,8 +30,8 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
|
||||
/**
|
||||
* Resolves a {@link BindMarkersFactory} from a {@link ConnectionFactory} using
|
||||
* {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's
|
||||
* {@link SpringFactoriesLoader spring.factories} to determine available extensions.
|
||||
* a {@link BindMarkerFactoryProvider}. Dialect resolution uses Spring's
|
||||
* {@link SpringFactoriesLoader spring.factories} file to determine available extensions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 5.3
|
||||
@@ -45,8 +45,8 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a {@link BindMarkersFactory} by inspecting {@link ConnectionFactory}
|
||||
* and its metadata.
|
||||
* Retrieve a {@link BindMarkersFactory} by inspecting the supplied
|
||||
* {@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,18 +69,21 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* SPI to extend Spring's default R2DBC BindMarkersFactory discovery mechanism.
|
||||
* Implementations of this interface are discovered through Spring's
|
||||
* SPI to extend Spring's default R2DBC {@link BindMarkersFactory} discovery
|
||||
* mechanism.
|
||||
*
|
||||
* <p>Implementations of this interface are discovered through Spring's
|
||||
* {@link SpringFactoriesLoader} mechanism.
|
||||
*
|
||||
* @see SpringFactoriesLoader
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface 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}
|
||||
* 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}
|
||||
* can provide a bind marker factory object, otherwise {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
@@ -89,7 +92,7 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* Exception thrown when {@link BindMarkersFactoryResolver} cannot resolve a
|
||||
* Exception thrown when a {@link BindMarkersFactoryResolver} cannot resolve a
|
||||
* {@link BindMarkersFactory}.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -106,8 +109,11 @@ public final class BindMarkersFactoryResolver {
|
||||
|
||||
|
||||
/**
|
||||
* Built-in bind maker factories. Used typically as last {@link BindMarkerFactoryProvider}
|
||||
* when other providers register with a higher precedence.
|
||||
* Built-in bind marker factories.
|
||||
*
|
||||
* <p>Typically used as the last {@link BindMarkerFactoryProvider} when other
|
||||
* providers are registered with a higher precedence.
|
||||
*
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.annotation.AnnotationAwareOrderComparator
|
||||
*/
|
||||
|
||||
+8
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -19,7 +19,7 @@ package org.springframework.r2dbc.core.binding;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
/**
|
||||
* Index-based bind marker. This implementation creates indexed bind
|
||||
* Index-based bind markers. 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,14 +43,15 @@ class IndexedBindMarkers implements BindMarkers {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link IndexedBindMarker} instance given {@code prefix} and {@code beginWith}.
|
||||
* @param prefix bind parameter prefix
|
||||
* @param beginWith the first index to use
|
||||
* 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
|
||||
*/
|
||||
IndexedBindMarkers(String prefix, int beginWith) {
|
||||
IndexedBindMarkers(String prefix, int beginIndex) {
|
||||
this.counter = 0;
|
||||
this.prefix = prefix;
|
||||
this.offset = beginWith;
|
||||
this.offset = beginIndex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+129
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Mark Paluch
|
||||
* @author Mingyuan Wu
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
abstract class AbstractDatabaseClientIntegrationTests {
|
||||
|
||||
@@ -121,7 +123,8 @@ 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()
|
||||
@@ -199,8 +202,7 @@ 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"))
|
||||
@@ -212,6 +214,129 @@ 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) {
|
||||
}
|
||||
|
||||
|
||||
+109
-128
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
package org.springframework.r2dbc.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.r2dbc.spi.Parameters;
|
||||
@@ -29,8 +27,6 @@ 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;
|
||||
@@ -42,10 +38,13 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Anton Naydenov
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class NamedParameterUtilsTests {
|
||||
|
||||
private final BindMarkersFactory BIND_MARKERS = BindMarkersFactory.indexed("$", 1);
|
||||
private static final BindMarkersFactory INDEXED_MARKERS = BindMarkersFactory.indexed("$", 1);
|
||||
|
||||
private static final BindMarkersFactory ANONYMOUS_MARKERS = BindMarkersFactory.anonymous("?");
|
||||
|
||||
|
||||
@Test
|
||||
@@ -73,7 +72,7 @@ class NamedParameterUtilsTests {
|
||||
namedParams.addValue("a", "a").addValue("b", "b").addValue("c", "c");
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a :b :c", BIND_MARKERS, namedParams);
|
||||
"xxx :a :b :c", INDEXED_MARKERS, namedParams);
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("xxx $1 $2 $3");
|
||||
|
||||
@@ -87,11 +86,11 @@ class NamedParameterUtilsTests {
|
||||
void substituteObjectArray() {
|
||||
MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>());
|
||||
namedParams.addValue("a",
|
||||
Arrays.asList(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
List.of(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a", BIND_MARKERS, namedParams);
|
||||
"xxx :a", INDEXED_MARKERS, namedParams);
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("xxx ($1, $2), ($3, $4)");
|
||||
}
|
||||
@@ -100,13 +99,13 @@ class NamedParameterUtilsTests {
|
||||
void shouldBindObjectArray() {
|
||||
MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>());
|
||||
namedParams.addValue("a",
|
||||
Arrays.asList(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
List.of(new Object[] {"Walter", "Heisenberg"},
|
||||
new Object[] {"Walt Jr.", "Flynn"}));
|
||||
|
||||
BindTarget bindTarget = mock();
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a", BIND_MARKERS, namedParams);
|
||||
"xxx :a", INDEXED_MARKERS, namedParams);
|
||||
operation.bindTo(bindTarget);
|
||||
|
||||
verify(bindTarget).bind(0, "Walter");
|
||||
@@ -141,7 +140,7 @@ class NamedParameterUtilsTests {
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
parsedSql, BIND_MARKERS, new MapBindParameterSource());
|
||||
parsedSql, INDEXED_MARKERS, new MapBindParameterSource());
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(expectedSql);
|
||||
}
|
||||
@@ -312,157 +311,139 @@ class NamedParameterUtilsTests {
|
||||
void multipleEqualParameterReferencesBindsValueOnce() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in("foo")));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source);
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameters.in("foo"))));
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name = $1 or lastname = $1");
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = $0 or lastname = $0");
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
operation.bindTo(trackingBindTarget);
|
||||
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(1)
|
||||
.containsEntry(0, Parameters.in("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleEqualCollectionParameterReferencesBindsValueOnce() {
|
||||
void multipleEqualCollectionParameterReferencesForIndexedMarkersBindsValuesOnce() {
|
||||
String sql = "SELECT * FROM person where name IN (:ids) or lastname IN (:ids)";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("ids",
|
||||
Parameters.in(List.of("foo", "bar", "baz"))));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source);
|
||||
|
||||
MultiValueMap<Integer, Object> bindings = new LinkedMultiValueMap<>();
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name IN ($1, $2, $3) or lastname IN ($1, $2, $3)");
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(Collections.singletonMap("ids",
|
||||
Parameters.in(Arrays.asList("foo", "bar", "baz")))));
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name IN ($0, $1, $2) or lastname IN ($0, $1, $2)");
|
||||
operation.bindTo(trackingBindTarget);
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(3)
|
||||
.containsEntry(0, "foo")
|
||||
.containsEntry(1, "bar")
|
||||
.containsEntry(2, "baz");
|
||||
}
|
||||
|
||||
assertThat(bindings).containsEntry(0, Collections.singletonList("foo")) //
|
||||
.containsEntry(1, Collections.singletonList("bar")) //
|
||||
.containsEntry(2, Collections.singletonList("baz"));
|
||||
@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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleEqualParameterReferencesForAnonymousMarkersBindsValueMultipleTimes() {
|
||||
void multipleEqualParameterReferencesForAnonymousMarkersBindsValueTwice() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.anonymous("?");
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in("foo")));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, ANONYMOUS_MARKERS, source);
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameters.in("foo"))));
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name = ? or lastname = ?");
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = ? or lastname = ?");
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
|
||||
Map<Integer, Object> bindValues = new LinkedHashMap<>();
|
||||
operation.bindTo(trackingBindTarget);
|
||||
|
||||
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"));
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.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";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
MapBindParameterSource source = new MapBindParameterSource(Map.of("id", Parameters.in(String.class)));
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS, source);
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameters.in(String.class))));
|
||||
assertThat(operation.toQuery())
|
||||
.isEqualTo("SELECT * FROM person where name = $1 or lastname = $1");
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = $0 or lastname = $0");
|
||||
TrackingBindTarget trackingBindTarget = new TrackingBindTarget();
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
operation.bindTo(trackingBindTarget);
|
||||
|
||||
assertThat(trackingBindTarget.bindings)
|
||||
.hasSize(1)
|
||||
.containsEntry(0, Parameters.in(String.class));
|
||||
}
|
||||
|
||||
|
||||
private String expand(ParsedSql sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS,
|
||||
private static String expand(ParsedSql sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, INDEXED_MARKERS,
|
||||
new MapBindParameterSource()).toQuery();
|
||||
}
|
||||
|
||||
private String expand(String sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS,
|
||||
private static String expand(String sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, INDEXED_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,14 +105,10 @@ test {
|
||||
description = "Runs JUnit 4, JUnit Jupiter, and TestNG tests."
|
||||
useJUnitPlatform {
|
||||
includeEngines "junit-vintage", "junit-jupiter", "testng"
|
||||
excludeTags "failing-test-case"
|
||||
}
|
||||
// We use `include` instead of `filter.includeTestsMatching`, since
|
||||
// the latter results in some tests being executed/reported
|
||||
// multiple times.
|
||||
include(["**/*Tests.class", "**/*Test.class"])
|
||||
// `include` test filters and system properties are configured in
|
||||
// org.springframework.build.TestConventions in buildSrc.
|
||||
filter.excludeTestsMatching("*TestCase")
|
||||
systemProperty("testGroups", project.properties.get("testGroups"))
|
||||
// Java Util Logging for the JUnit Platform.
|
||||
// Optionally configure Java Util Logging for the JUnit Platform.
|
||||
// systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")
|
||||
}
|
||||
|
||||
+32
-2
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.mock.web.server;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -40,15 +42,19 @@ 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 ApplicationContext applicationContext, @Nullable Principal principal) {
|
||||
|
||||
super(request, new MockServerHttpResponse(),
|
||||
sessionManager != null ? sessionManager : new DefaultWebSessionManager(),
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver(),
|
||||
applicationContext);
|
||||
|
||||
this.principalMono = (principal != null) ? Mono.just(principal) : Mono.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +63,16 @@ 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.
|
||||
@@ -111,6 +127,9 @@ public final class MockServerWebExchange extends DefaultServerWebExchange {
|
||||
@Nullable
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Nullable
|
||||
private Principal principal;
|
||||
|
||||
public Builder(MockServerHttpRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
@@ -147,11 +166,22 @@ 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);
|
||||
return new MockServerWebExchange(
|
||||
this.request, this.sessionManager, this.applicationContext, this.principal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -314,8 +314,7 @@ public class TestContextAotGenerator {
|
||||
ClassName processAheadOfTime(MergedContextConfiguration mergedConfig,
|
||||
GenerationContext generationContext) throws TestContextAotException {
|
||||
|
||||
GenericApplicationContext gac = loadContextForAotProcessing(mergedConfig);
|
||||
try {
|
||||
try (GenericApplicationContext gac = loadContextForAotProcessing(mergedConfig)) {
|
||||
return this.aotGenerator.processAheadOfTime(gac, generationContext);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
@@ -333,7 +332,7 @@ public class TestContextAotGenerator {
|
||||
* context or if one of the prerequisites is not met
|
||||
* @see AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration, RuntimeHints)
|
||||
*/
|
||||
private GenericApplicationContext loadContextForAotProcessing(
|
||||
GenericApplicationContext loadContextForAotProcessing(
|
||||
MergedContextConfiguration mergedConfig) throws TestContextAotException {
|
||||
|
||||
Class<?> testClass = mergedConfig.getTestClass();
|
||||
|
||||
+10
-8
@@ -184,30 +184,32 @@ 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 visitedEnclosingClasses the set of enclosing classes already visited
|
||||
* @param visitedTypes the set of types already visited
|
||||
* @since 6.2.2
|
||||
*/
|
||||
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers,
|
||||
boolean localFieldsOnly, Set<Class<?>> visitedEnclosingClasses) {
|
||||
boolean localFieldsOnly, Set<Class<?>> visitedTypes) {
|
||||
|
||||
// 0) Ensure that we do not process the same class or interface multiple times.
|
||||
if (!visitedTypes.add(clazz)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) Search enclosing class hierarchy.
|
||||
if (!localFieldsOnly && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
|
||||
Class<?> enclosingClass = clazz.getEnclosingClass();
|
||||
if (visitedEnclosingClasses.add(enclosingClass)) {
|
||||
findHandlers(enclosingClass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
|
||||
}
|
||||
findHandlers(clazz.getEnclosingClass(), testClass, handlers, localFieldsOnly, visitedTypes);
|
||||
}
|
||||
|
||||
// 2) Search class hierarchy.
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass != null && superclass != Object.class) {
|
||||
findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
|
||||
findHandlers(superclass, testClass, handlers, localFieldsOnly, visitedTypes);
|
||||
}
|
||||
|
||||
if (!localFieldsOnly) {
|
||||
// 3) Search interfaces.
|
||||
for (Class<?> ifc : clazz.getInterfaces()) {
|
||||
findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedEnclosingClasses);
|
||||
findHandlers(ifc, testClass, handlers, localFieldsOnly, visitedTypes);
|
||||
}
|
||||
|
||||
// 4) Process current class.
|
||||
|
||||
+23
-21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,9 +20,8 @@ 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.runner.JUnitCore;
|
||||
import org.junit.runner.Result;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -32,9 +31,10 @@ 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.junit4.SpringRunner;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Integration tests that verify proper behavior of {@link DirtiesContext @DirtiesContext}
|
||||
@@ -87,9 +87,11 @@ class ContextHierarchyDirtiesContextTests {
|
||||
private void runTestAndVerifyHierarchies(Class<? extends FooTestCase> testClass, boolean isFooContextActive,
|
||||
boolean isBarContextActive, boolean isBazContextActive) {
|
||||
|
||||
JUnitCore jUnitCore = new JUnitCore();
|
||||
Result result = jUnitCore.run(testClass);
|
||||
assertThat(result.wasSuccessful()).as("all tests passed").isTrue();
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.selectors(selectClass(testClass))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));
|
||||
|
||||
assertThat(ContextHierarchyDirtiesContextTests.context).isNotNull();
|
||||
|
||||
@@ -111,7 +113,7 @@ class ContextHierarchyDirtiesContextTests {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextHierarchy(@ContextConfiguration(name = "foo"))
|
||||
abstract static class FooTestCase implements ApplicationContextAware {
|
||||
|
||||
@@ -170,10 +172,10 @@ class ContextHierarchyDirtiesContextTests {
|
||||
* context.
|
||||
*/
|
||||
@DirtiesContext
|
||||
public static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
public void test() {
|
||||
@Test
|
||||
void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,10 +186,10 @@ class ContextHierarchyDirtiesContextTests {
|
||||
* beginning from the current context hierarchy and down through all subhierarchies.
|
||||
*/
|
||||
@DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
|
||||
public static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
public void test() {
|
||||
@Test
|
||||
void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,11 +201,11 @@ class ContextHierarchyDirtiesContextTests {
|
||||
* parent context, and then back down through all subhierarchies of the parent
|
||||
* context.
|
||||
*/
|
||||
public static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void test() {
|
||||
void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,11 +215,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.
|
||||
*/
|
||||
public static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
@Test
|
||||
@DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
|
||||
public void test() {
|
||||
void test() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
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;
|
||||
@@ -44,5 +46,9 @@ 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,7 +167,6 @@ 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
|
||||
);
|
||||
|
||||
|
||||
+59
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,13 +16,23 @@
|
||||
|
||||
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;
|
||||
@@ -60,9 +70,55 @@ class TestContextAotGeneratorTests {
|
||||
assertThat(createGenerator().failOnError).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-34841
|
||||
void contextIsClosedAfterAotProcessing() {
|
||||
DemoTestContextAotGenerator generator = createGenerator();
|
||||
generator.processAheadOfTime(Stream.of(TestCase1.class, TestCase2.class));
|
||||
|
||||
private static TestContextAotGenerator createGenerator() {
|
||||
return new TestContextAotGenerator(null);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+7
-2
@@ -31,14 +31,17 @@ 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.
|
||||
* intertwined enclosing class hierarchies and type hierarchies, when an enclosing
|
||||
* class is <em>present</em> twice in the intertwined 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 MockitoBeanNestedAndTypeHierarchiesTests {
|
||||
class MockitoBeanNestedAndTypeHierarchiesWithEnclosingClassPresentTwiceTests {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext enclosingContext;
|
||||
@@ -50,6 +53,7 @@ class MockitoBeanNestedAndTypeHierarchiesTests {
|
||||
@Test
|
||||
void topLevelTest() {
|
||||
assertIsMock(service);
|
||||
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
|
||||
|
||||
// The following are prerequisites for the reported regression.
|
||||
assertThat(NestedTests.class.getSuperclass())
|
||||
@@ -66,6 +70,7 @@ class MockitoBeanNestedAndTypeHierarchiesTests {
|
||||
void nestedTest(ApplicationContext nestedContext) {
|
||||
assertIsMock(service);
|
||||
assertThat(enclosingContext).isSameAs(nestedContext);
|
||||
assertThat(enclosingContext.getBeanNamesForType(ExampleService.class)).hasSize(1);
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
}
|
||||
+11
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -31,6 +31,7 @@ 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;
|
||||
@@ -162,12 +163,18 @@ 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
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
/* no beans */
|
||||
}
|
||||
|
||||
Vendored
+42
-33
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -21,7 +21,8 @@ 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.runner.RunWith;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -31,15 +32,14 @@ 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,15 +131,24 @@ 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());
|
||||
@@ -148,7 +157,7 @@ class ClassLevelDirtiesContextTests {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
// Ensure that we do not include the EventPublishingTestExecutionListener
|
||||
// since it will access the ApplicationContext for each method in the
|
||||
@@ -160,7 +169,7 @@ class ClassLevelDirtiesContextTests {
|
||||
})
|
||||
abstract static class BaseTestCase {
|
||||
|
||||
@Configuration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
/* no beans */
|
||||
}
|
||||
@@ -175,75 +184,75 @@ class ClassLevelDirtiesContextTests {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CleanTestCase extends BaseTestCase {
|
||||
static final class CleanTestCase extends BaseTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired() {
|
||||
@Test
|
||||
void verifyContextWasAutowired() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@DirtiesContext
|
||||
public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
|
||||
static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired() {
|
||||
@Test
|
||||
void verifyContextWasAutowired() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends
|
||||
static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends
|
||||
ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase {
|
||||
}
|
||||
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
|
||||
public static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
|
||||
static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired() {
|
||||
@Test
|
||||
void verifyContextWasAutowired() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends
|
||||
static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends
|
||||
ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase {
|
||||
}
|
||||
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
|
||||
static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired1() {
|
||||
@Test
|
||||
void verifyContextWasAutowired1() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired2() {
|
||||
@Test
|
||||
void verifyContextWasAutowired2() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
|
||||
@org.junit.Test
|
||||
public void verifyContextWasAutowired3() {
|
||||
@Test
|
||||
void verifyContextWasAutowired3() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends
|
||||
static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends
|
||||
ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase {
|
||||
}
|
||||
|
||||
@DirtiesContext
|
||||
public static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {
|
||||
static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {
|
||||
|
||||
@org.junit.Test
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void dirtyContext() {
|
||||
void dirtyContext() {
|
||||
assertApplicationContextWasAutowired();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends
|
||||
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-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.annotation.DirtiesContext.MethodMode.BEFORE_METHOD;
|
||||
|
||||
/**
|
||||
* Integration test which verifies correct interaction between the
|
||||
* Integration tests which verify correct interaction between the
|
||||
* {@link DirtiesContextBeforeModesTestExecutionListener},
|
||||
* {@link DependencyInjectionTestExecutionListener}, and
|
||||
* {@link DirtiesContextTestExecutionListener} when
|
||||
|
||||
+5
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -37,10 +37,9 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.asser
|
||||
import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
@@ -48,7 +47,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.reset
|
||||
* @see ContextCacheTests
|
||||
* @see LruContextCacheTests
|
||||
*/
|
||||
@SpringJUnitConfig(locations = "../junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml")
|
||||
@SpringJUnitConfig(locations = "../config/CoreContextConfigurationAppCtxTests-context.xml")
|
||||
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class SpringExtensionContextCacheTests {
|
||||
|
||||
+7
-7
@@ -14,22 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
|
||||
* Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that
|
||||
* we can specify an explicit, <em>absolute path</em> location for our
|
||||
* application context.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see SpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see CoreContextConfigurationAppCtxTests
|
||||
* @see ClassPathResourceContextConfigurationAppCtxTests
|
||||
* @see RelativePathContextConfigurationAppCtxTests
|
||||
*/
|
||||
@ContextConfiguration(locations = { SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH }, inheritLocations = false)
|
||||
public class AbsolutePathSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
|
||||
@ContextConfiguration(locations = CoreContextConfigurationAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH, inheritLocations = false)
|
||||
class AbsolutePathContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests {
|
||||
/* all tests are in the parent class. */
|
||||
}
|
||||
+5
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,19 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests;
|
||||
|
||||
/**
|
||||
* Integration tests that verify support for configuration classes in
|
||||
* the Spring TestContext Framework.
|
||||
*
|
||||
* <p>Furthermore, by extending {@link SpringJUnit4ClassRunnerAppCtxTests},
|
||||
* <p>Furthermore, by extending {@link CoreContextConfigurationAppCtxTests},
|
||||
* this class also verifies support for several basic features of the
|
||||
* Spring TestContext Framework. See JavaDoc in
|
||||
* {@code SpringJUnit4ClassRunnerAppCtxTests} for details.
|
||||
* {@link CoreContextConfigurationAppCtxTests} for details.
|
||||
*
|
||||
* <p>Configuration will be loaded from {@link PojoAndStringConfig}.
|
||||
*
|
||||
@@ -34,6 +33,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTest
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration(classes = PojoAndStringConfig.class, inheritLocations = false)
|
||||
public class AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
|
||||
class AnnotationConfigContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests {
|
||||
/* all tests are in the parent class. */
|
||||
}
|
||||
+29
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,43 +14,59 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.spr6128;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests to verify claims made in <a
|
||||
* href="https://jira.springframework.org/browse/SPR-6128"
|
||||
* target="_blank">SPR-6128</a>.
|
||||
* Integration tests to verify claims made in
|
||||
* <a href="https://github.com/spring-projects/spring-framework/issues/10796">gh-10796</a>.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Chris Beams
|
||||
* @since 3.0
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class AutowiredQualifierTests {
|
||||
class AutowiredQualifierTests {
|
||||
|
||||
@Autowired
|
||||
private String foo;
|
||||
String foo;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("customFoo")
|
||||
private String customFoo;
|
||||
String customFoo;
|
||||
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
void test() {
|
||||
assertThat(foo).isEqualTo("normal");
|
||||
assertThat(customFoo).isEqualTo("custom");
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
String foo() {
|
||||
return "normal";
|
||||
}
|
||||
|
||||
@Bean
|
||||
String customFoo() {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+14
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.testfixture.beans.Employee;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -36,13 +36,21 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
public class BeanOverridingDefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests {
|
||||
class BeanOverridingDefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests {
|
||||
|
||||
@Configuration
|
||||
@Test
|
||||
@Override
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda");
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
public Employee employee() {
|
||||
Employee employee() {
|
||||
Employee employee = new Employee();
|
||||
employee.setName("Yoda");
|
||||
employee.setAge(900);
|
||||
@@ -51,12 +59,4 @@ public class BeanOverridingDefaultConfigClassesInheritedTests extends DefaultCon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda");
|
||||
}
|
||||
|
||||
}
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@@ -33,11 +33,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration(classes = BeanOverridingDefaultConfigClassesInheritedTests.ContextConfiguration.class)
|
||||
public class BeanOverridingExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests {
|
||||
class BeanOverridingExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests {
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda");
|
||||
}
|
||||
+11
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,36 +14,35 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
|
||||
* Extension of {@link CoreContextConfigurationAppCtxTests}, which verifies that
|
||||
* we can specify an explicit, <em>classpath</em> location for our application
|
||||
* context.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see SpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see CoreContextConfigurationAppCtxTests
|
||||
* @see #CLASSPATH_CONTEXT_RESOURCE_PATH
|
||||
* @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see AbsolutePathContextConfigurationAppCtxTests
|
||||
* @see RelativePathContextConfigurationAppCtxTests
|
||||
*/
|
||||
@ContextConfiguration(locations = { ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.CLASSPATH_CONTEXT_RESOURCE_PATH }, inheritLocations = false)
|
||||
public class ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
|
||||
@ContextConfiguration(locations = { ClassPathResourceContextConfigurationAppCtxTests.CLASSPATH_CONTEXT_RESOURCE_PATH }, inheritLocations = false)
|
||||
class ClassPathResourceContextConfigurationAppCtxTests extends CoreContextConfigurationAppCtxTests {
|
||||
|
||||
/**
|
||||
* Classpath-based resource path for the application context configuration
|
||||
* for {@link SpringJUnit4ClassRunnerAppCtxTests}:
|
||||
* {@code "classpath:/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml"}
|
||||
* for {@link CoreContextConfigurationAppCtxTests}: {@value}
|
||||
*
|
||||
* @see SpringJUnit4ClassRunnerAppCtxTests#DEFAULT_CONTEXT_RESOURCE_PATH
|
||||
* @see CoreContextConfigurationAppCtxTests#DEFAULT_CONTEXT_RESOURCE_PATH
|
||||
* @see ResourceUtils#CLASSPATH_URL_PREFIX
|
||||
*/
|
||||
public static final String CLASSPATH_CONTEXT_RESOURCE_PATH = ResourceUtils.CLASSPATH_URL_PREFIX +
|
||||
SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH;
|
||||
CoreContextConfigurationAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH;
|
||||
|
||||
/* all tests are in the parent class. */
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 {
|
||||
}
|
||||
+29
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -33,18 +33,18 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
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.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.GenericXmlContextLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* SpringJUnit4ClassRunnerAppCtxTests serves as a <em>proof of concept</em>
|
||||
* JUnit 4 based test class, which verifies the expected functionality of
|
||||
* {@link SpringRunner} in conjunction with the following:
|
||||
* {@code CoreContextConfigurationAppCtxTests} serves as a <em>core</em> test class, which
|
||||
* verifies the expected functionality of {@link ContextConfiguration @ContextConfiguration}
|
||||
* in conjunction with the following:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link ContextConfiguration @ContextConfiguration}</li>
|
||||
* <li>{@link Autowired @Autowired}</li>
|
||||
* <li>{@link Qualifier @Qualifier}</li>
|
||||
* <li>{@link Resource @Resource}</li>
|
||||
@@ -67,21 +67,24 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see InheritedConfigSpringJUnit4ClassRunnerAppCtxTests
|
||||
* @see AbsolutePathContextConfigurationAppCtxTests
|
||||
* @see AnnotationConfigContextConfigurationAppCtxTests
|
||||
* @see ClassPathResourceContextConfigurationAppCtxTests
|
||||
* @see InheritedConfigContextConfigurationAppCtxTests
|
||||
* @see MultipleResourcesContextConfigurationAppCtxTests
|
||||
* @see RelativePathContextConfigurationAppCtxTests
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@TestExecutionListeners(DependencyInjectionTestExecutionListener.class)
|
||||
public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAware, BeanNameAware, InitializingBean {
|
||||
class CoreContextConfigurationAppCtxTests implements ApplicationContextAware, BeanNameAware, InitializingBean {
|
||||
|
||||
/**
|
||||
* Default resource path for the application context configuration for
|
||||
* {@link SpringJUnit4ClassRunnerAppCtxTests}: {@value}
|
||||
* {@link CoreContextConfigurationAppCtxTests}: {@value}
|
||||
*/
|
||||
public static final String DEFAULT_CONTEXT_RESOURCE_PATH =
|
||||
"/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml";
|
||||
"/org/springframework/test/context/config/CoreContextConfigurationAppCtxTests-context.xml";
|
||||
|
||||
|
||||
private Employee employee;
|
||||
@@ -136,12 +139,12 @@ public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAwa
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setLiteralParameterValue(@Value("enigma") String literalParameterValue) {
|
||||
void setLiteralParameterValue(@Value("enigma") String literalParameterValue) {
|
||||
this.literalParameterValue = literalParameterValue;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setSpelParameterValue(@Value("#{2 == (1+1)}") Boolean spelParameterValue) {
|
||||
void setSpelParameterValue(@Value("#{2 == (1+1)}") Boolean spelParameterValue) {
|
||||
this.spelParameterValue = spelParameterValue;
|
||||
}
|
||||
|
||||
@@ -162,23 +165,23 @@ public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAwa
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyBeanNameSet() {
|
||||
void verifyBeanNameSet() {
|
||||
assertThat(this.beanName).as("The bean name of this test instance should have been set due to BeanNameAware semantics.")
|
||||
.startsWith(getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyApplicationContextSet() {
|
||||
void verifyApplicationContextSet() {
|
||||
assertThat(this.applicationContext).as("The application context should have been set due to ApplicationContextAware semantics.").isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyBeanInitialized() {
|
||||
void verifyBeanInitialized() {
|
||||
assertThat(this.beanInitialized).as("This test bean should have been initialized due to InitializingBean semantics.").isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyAnnotationAutowiredAndInjectedFields() {
|
||||
void verifyAnnotationAutowiredAndInjectedFields() {
|
||||
assertThat(this.nonrequiredLong).as("The nonrequiredLong field should NOT have been autowired.").isNull();
|
||||
assertThat(this.quux).as("The quux field should have been autowired via @Autowired and @Qualifier.").isEqualTo("Quux");
|
||||
assertThat(this.namedQuux).as("The namedFoo field should have been injected via @Inject and @Named.").isEqualTo("Quux");
|
||||
@@ -192,13 +195,13 @@ public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAwa
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyAnnotationAutowiredMethods() {
|
||||
void verifyAnnotationAutowiredMethods() {
|
||||
assertThat(this.employee).as("The employee setter method should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).isEqualTo("John Smith");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyAutowiredAtValueFields() {
|
||||
void verifyAutowiredAtValueFields() {
|
||||
assertThat(this.literalFieldValue).as("Literal @Value field should have been autowired").isNotNull();
|
||||
assertThat(this.spelFieldValue).as("SpEL @Value field should have been autowired.").isNotNull();
|
||||
assertThat(this.literalFieldValue).isEqualTo("enigma");
|
||||
@@ -206,7 +209,7 @@ public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAwa
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyAutowiredAtValueMethods() {
|
||||
void verifyAutowiredAtValueMethods() {
|
||||
assertThat(this.literalParameterValue).as("Literal @Value method parameter should have been autowired.").isNotNull();
|
||||
assertThat(this.spelParameterValue).as("SpEL @Value method parameter should have been autowired.").isNotNull();
|
||||
assertThat(this.literalParameterValue).isEqualTo("enigma");
|
||||
@@ -214,12 +217,12 @@ public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAwa
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyResourceAnnotationInjectedFields() {
|
||||
void verifyResourceAnnotationInjectedFields() {
|
||||
assertThat(this.foo).as("The foo field should have been injected via @Resource.").isEqualTo("Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyResourceAnnotationInjectedMethods() {
|
||||
void verifyResourceAnnotationInjectedMethods() {
|
||||
assertThat(this.bar).as("The bar method should have been wired via @Resource.").isEqualTo("Bar");
|
||||
}
|
||||
|
||||
+19
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,17 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.testfixture.beans.Employee;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -39,15 +37,25 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
* @see DefaultLoaderDefaultConfigClassesBaseTests
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
|
||||
public class DefaultConfigClassesBaseTests {
|
||||
@SpringJUnitConfig(loader = AnnotationConfigContextLoader.class)
|
||||
class DefaultConfigClassesBaseTests {
|
||||
|
||||
@Configuration
|
||||
@Autowired
|
||||
Employee employee;
|
||||
|
||||
|
||||
@Test
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee field should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).isEqualTo("John Smith");
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
public Employee employee() {
|
||||
Employee employee() {
|
||||
Employee employee = new Employee();
|
||||
employee.setName("John Smith");
|
||||
employee.setAge(42);
|
||||
@@ -56,15 +64,4 @@ public class DefaultConfigClassesBaseTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
protected Employee employee;
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee field should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).isEqualTo("John Smith");
|
||||
}
|
||||
|
||||
}
|
||||
+16
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.testfixture.beans.Pet;
|
||||
@@ -37,26 +37,26 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
public class DefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests {
|
||||
|
||||
@Configuration
|
||||
static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
public Pet pet() {
|
||||
return new Pet("Fido");
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests {
|
||||
|
||||
@Autowired
|
||||
private Pet pet;
|
||||
Pet pet;
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyPetSetFromExtendedContextConfig() {
|
||||
void verifyPetSetFromExtendedContextConfig() {
|
||||
assertThat(this.pet).as("The pet should have been autowired.").isNotNull();
|
||||
assertThat(this.pet.getName()).isEqualTo("Fido");
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
Pet pet() {
|
||||
return new Pet("Fido");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+14
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.testfixture.beans.Employee;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -35,14 +35,22 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
public class DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests extends
|
||||
class DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests extends
|
||||
DefaultLoaderDefaultConfigClassesBaseTests {
|
||||
|
||||
@Configuration
|
||||
@Test
|
||||
@Override
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda");
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Employee employee() {
|
||||
Employee employee() {
|
||||
Employee employee = new Employee();
|
||||
employee.setName("Yoda");
|
||||
employee.setAge(900);
|
||||
@@ -51,12 +59,4 @@ public class DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests exten
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda");
|
||||
}
|
||||
|
||||
}
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.support.DelegatingSmartContextLoader;
|
||||
@@ -32,12 +32,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration(classes = DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.Config.class)
|
||||
public class DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests extends
|
||||
class DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests extends
|
||||
DefaultLoaderExplicitConfigClassesBaseTests {
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).as("The employee bean should have been overridden.").isEqualTo("Yoda");
|
||||
}
|
||||
+19
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,17 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.testfixture.beans.Employee;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.support.DelegatingSmartContextLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -38,15 +36,25 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
* @see DefaultConfigClassesBaseTests
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class DefaultLoaderDefaultConfigClassesBaseTests {
|
||||
@SpringJUnitConfig
|
||||
class DefaultLoaderDefaultConfigClassesBaseTests {
|
||||
|
||||
@Configuration
|
||||
@Autowired
|
||||
Employee employee;
|
||||
|
||||
|
||||
@Test
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee field should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).isEqualTo("John Smith");
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Employee employee() {
|
||||
Employee employee() {
|
||||
Employee employee = new Employee();
|
||||
employee.setName("John Smith");
|
||||
employee.setAge(42);
|
||||
@@ -55,15 +63,4 @@ public class DefaultLoaderDefaultConfigClassesBaseTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
protected Employee employee;
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee field should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).isEqualTo("John Smith");
|
||||
}
|
||||
|
||||
}
|
||||
+16
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.testfixture.beans.Pet;
|
||||
@@ -36,26 +36,26 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
public class DefaultLoaderDefaultConfigClassesInheritedTests extends DefaultLoaderDefaultConfigClassesBaseTests {
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Pet pet() {
|
||||
return new Pet("Fido");
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultLoaderDefaultConfigClassesInheritedTests extends DefaultLoaderDefaultConfigClassesBaseTests {
|
||||
|
||||
@Autowired
|
||||
private Pet pet;
|
||||
Pet pet;
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyPetSetFromExtendedContextConfig() {
|
||||
void verifyPetSetFromExtendedContextConfig() {
|
||||
assertThat(this.pet).as("The pet should have been autowired.").isNotNull();
|
||||
assertThat(this.pet.getName()).isEqualTo("Fido");
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
Pet pet() {
|
||||
return new Pet("Fido");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+8
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,15 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.testfixture.beans.Employee;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.support.DelegatingSmartContextLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -35,16 +33,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = DefaultLoaderDefaultConfigClassesBaseTests.Config.class)
|
||||
public class DefaultLoaderExplicitConfigClassesBaseTests {
|
||||
@SpringJUnitConfig(DefaultLoaderDefaultConfigClassesBaseTests.Config.class)
|
||||
class DefaultLoaderExplicitConfigClassesBaseTests {
|
||||
|
||||
@Autowired
|
||||
protected Employee employee;
|
||||
Employee employee;
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).isEqualTo("John Smith");
|
||||
}
|
||||
+8
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,15 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.testfixture.beans.Pet;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.support.DelegatingSmartContextLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -35,16 +33,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = DefaultLoaderDefaultConfigClassesInheritedTests.Config.class)
|
||||
public class DefaultLoaderExplicitConfigClassesInheritedTests extends DefaultLoaderExplicitConfigClassesBaseTests {
|
||||
@SpringJUnitConfig(DefaultLoaderDefaultConfigClassesInheritedTests.Config.class)
|
||||
class DefaultLoaderExplicitConfigClassesInheritedTests extends DefaultLoaderExplicitConfigClassesBaseTests {
|
||||
|
||||
@Autowired
|
||||
private Pet pet;
|
||||
Pet pet;
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyPetSetFromExtendedContextConfig() {
|
||||
void verifyPetSetFromExtendedContextConfig() {
|
||||
assertThat(this.pet).as("The pet should have been autowired.").isNotNull();
|
||||
assertThat(this.pet.getName()).isEqualTo("Fido");
|
||||
}
|
||||
+8
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,15 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.junit4.annotation;
|
||||
package org.springframework.test.context.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.testfixture.beans.Employee;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -36,16 +34,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesBaseTests.ContextConfiguration.class)
|
||||
public class ExplicitConfigClassesBaseTests {
|
||||
@SpringJUnitConfig(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesBaseTests.ContextConfiguration.class)
|
||||
class ExplicitConfigClassesBaseTests {
|
||||
|
||||
@Autowired
|
||||
protected Employee employee;
|
||||
Employee employee;
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyEmployeeSetFromBaseContextConfig() {
|
||||
void verifyEmployeeSetFromBaseContextConfig() {
|
||||
assertThat(this.employee).as("The employee should have been autowired.").isNotNull();
|
||||
assertThat(this.employee.getName()).isEqualTo("John Smith");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user