Compare commits

..

152 Commits

Author SHA1 Message Date
Brian Clozel 502b31a7f2 Release v6.2.8 2025-06-12 11:02:39 +02:00
Brian Clozel f0e7b42704 Encode non-printable character in Content-Disposition parameter
Prior to this commit, the "filename" parameter value for the
"Content-Disposition" header would contain non-printable characters,
causing parsing issues for HTTP clients.
This commit ensures that all non-printable characters are encoded.

Fixes gh-35034
2025-06-12 08:39:29 +02:00
Johnny Lim e86dc9ad95 Apply gh-34856 to MockClientHttpRequest in testfixture package
Closes gh-35031

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-06-11 17:40:54 +02:00
Sébastien Deleuze 05c3f56ec7 Rely on default retention in @Contract
Closes gh-35027
2025-06-11 10:14:59 +02:00
Brian Clozel dee80ab1da Upgrade to Reactor 2024.0.7
Closes gh-35021
2025-06-11 09:23:17 +02:00
Brian Clozel 07fd835ea9 Upgrade to Micrometer 1.14.8
Closes gh-35020
2025-06-11 09:22:50 +02:00
Brian Clozel 0d6c6eb2d5 Use Micrometer BOM for Context Propagation dependency
Closes gh-35026
2025-06-11 09:15:30 +02:00
Sam Brannen 4d2cc4ae97 Polish contribution
See gh-35013
2025-06-10 11:45:20 +02:00
Mohammad Saeed Nouri c04902fefb Allow update of existing WebSession after max sessions limit is reached
Previously, when saving a WebSession, the system did not check whether
the session ID already existed. As a result, even if the session being
saved was an update to an existing one, it was incorrectly treated as a
new session, and a "maximum sessions exceeded" error was triggered.

This fix ensures that if a WebSession with the same ID already exists,
it will be updated rather than counted as a new session, thereby
preventing unnecessary session limit violations.

Closes gh-35013

Signed-off-by: Mohammad Saeed Nouri <msnsaeed71@gmail.com>
2025-06-10 11:44:59 +02:00
Sam Brannen 3c265e1044 Fix InMemoryWebSessionStoreTests.startsSessionImplicitly() test 2025-06-10 11:44:00 +02:00
Sam Brannen 222702f750 Polish WebSession support and tests 2025-06-10 11:43:56 +02:00
Sébastien Deleuze 7bb19fcde8 Refine Kotlin Serialization hint registration
This commit adds support for serializer methods with a parameter.

Closes gh-34979
2025-06-09 16:48:15 +02:00
Sam Brannen 18d6a55e3e Polishing and removal of "this." for method invocations 2025-06-09 14:10:30 +02:00
秦利斌 99890b6147 Fix ResourceHttpRequestHandler#setHeaders JavaDoc
Closes gh-35004
Signed-off-by: 秦利斌 <68638598+Allan-QLB@users.noreply.github.com>
2025-06-09 10:55:21 +02:00
Sam Brannen 0afdb08099 Upgrade to JUnit 5.13.1
Closes gh-34969
2025-06-07 12:03:10 +02:00
Sébastien Deleuze 40058ef875 Disable Gradle auto-provisioning
Closes gh-35007
2025-06-06 18:18:52 +02:00
rstoyanchev c769f43b3e Update docs on testing client code
Closes gh-34892
2025-06-06 15:28:28 +01:00
rstoyanchev 4782c697b8 Improve RestControllerAdvice documentation
Closes gh-34866
2025-06-06 15:28:28 +01:00
rstoyanchev de52090959 Polishing contribution
Closes gh-34554
2025-06-06 15:28:28 +01:00
Vedran Pavic 4d862b871d Improve Javadoc for @ExceptionHandler
This commit adds `ProblemDetail` and `ErrorResponse` to the list of
supported return types for `@ExceptionHandler` methods.

Closes gh-34554

Signed-off-by: Vedran Pavic <vedran@vedranpavic.com>
2025-06-06 15:28:28 +01:00
Jimmy Axenhus fa781c8390 Correct name of Selenium WebDriver artifact
The selenium-htmlunit3-driver artifact does not exist.

Closes gh-34840

Signed-off-by: Jimmy Axenhus <github@axenhus.com>
2025-06-06 15:28:28 +01:00
Sam Brannen 88cd7addda Upgrade to Gradle 8.14.2
Closes gh-34937
2025-06-06 15:06:06 +02:00
Brian Clozel 332c7f770c Remove Link to AspectJ Javadoc
Closes gh-35000
2025-06-06 12:05:44 +02:00
Juergen Hoeller 9f76ea13e3 Upgrade to Groovy 4.0.27, Mockito 5.18, Checkstyle 10.25 2025-06-06 09:21:37 +02:00
Juergen Hoeller d9c103b7ff Remove unnecessary fallback to DefaultConversionService
See gh-34936
2025-06-06 09:21:21 +02:00
Juergen Hoeller f1ddd051a3 Restore synchronization against AspectJ race condition behind PointcutExpression
Closes gh-34735
2025-06-05 20:36:10 +02:00
Juergen Hoeller a266e1b403 Close JarFile only in case of useCaches=false
Closes gh-34955
2025-06-05 20:20:47 +02:00
Sébastien Deleuze 4d09eb569b Refine RequestResponseBodyMethodProcessorKotlinTests
Improve read tests and fix copyright.

See gh-34992
2025-06-05 14:07:27 +02:00
Sébastien Deleuze a439e9030f Fix collection support in AbstractKotlinSerializationHttpMessageConverter
AbstractKotlinSerializationHttpMessageConverter#getSupportedMediaTypes(Class<?>)
currently invokes transitively supports(Class<?>) which always return false
with generic types.

This commit adds an override that just invokes getSupportedMediaTypes().

Closes gh-34992
2025-06-05 12:02:22 +02:00
James Yuzawa aa5c0dcd72 Add caching headers to unmodified static resources
per https://www.rfc-editor.org/rfc/rfc7232#section-4.1

The server generating a 304 response MUST generate any of the
following header fields that would have been sent in a 200 (OK)
response to the same request: Cache-Control, Content-Location, Date,
ETag, Expires, and Vary.

Closes gh-34614

Signed-off-by: James Yuzawa <jtyuzawa@gmail.com>
2025-06-05 10:58:24 +02:00
Brian Clozel 4a46d957f3 Fix out of bounds exception for PathPattern#combine
Prior to this commit, combining the "/*" and  "/x/y" path patterns
would result in a `StringIndexOutOfBoundsException`.

This commit fixes this problem and revisits the implementation for
better consistency:

* "/*" + "/x/y" is now "/x/y"
* "/x/*.html" + "/y/file.*" is now rejected because they don't share the
  same prefix.

This change also adds the relevant Javadoc to the `PathPattern#combine`
method.

Fixes gh-34986
2025-06-04 20:18:33 +02:00
Sam Brannen 4df93a825b Update copyright headers and fix test method name 2025-06-04 11:28:55 +02:00
rstoyanchev be17315f63 Add mention of CompletionStage in "Async Requests"
Closes gh-34991
2025-06-03 19:10:03 +01:00
DongNyoung Lee 31903a9d92 Fix typo in MockClientHttpRequest Javadoc
Closes gh-34856

Signed-off-by: DongNyoung Lee <121621378+Dongnyoung@users.noreply.github.com>
2025-06-03 19:05:32 +01:00
rstoyanchev 06f69915cb Polishing contribution
Closes gh-34885
2025-06-03 19:04:17 +01:00
addoDev d9459bdc74 Improve mvc-ann-async.adoc
Added section for WebAsyncTask return type along with java and
kotlin example code

See gh-34885

Signed-off-by: addoDev <adityaraochokkadi@gmail.com>
2025-06-03 18:57:40 +01:00
Tran Ngoc Nhan 3f0892b42c Fix typos
Closes gh-34876

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-06-03 18:55:15 +01:00
Patrick Strawderman 182d654fa8 Add optimized DataBufferInputStream overrides
Add optimized DataBufferInputStream overrides for readNBytes, skip, and transferTo; all of them
allocate byte buffers which we can either avoid (in the case of skip) or size more precisely since
the number of remaining bytes is known.

Closes gh-34799

Signed-off-by: Patrick Strawderman <pstrawderman@netflix.com>
2025-06-03 18:06:43 +02:00
Johnny Lim bbae625850 Add Javadoc since for gh-34745
Closes gh-34940

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-06-03 18:06:24 +02:00
Sam Brannen f376d1b525 Remove @⁠Contract declaration for CodeFlow.isIntegerForNumericOp()
Since the Number parameter is not @⁠Nullable, the @⁠Contract declaration
is unnecessary.

Closes gh-34985
2025-06-03 12:58:45 +02:00
Sam Brannen 707b7698ce Consistent @⁠Contract expression formatting 2025-06-02 17:00:28 +02:00
Johannes Jank 5b9cb8291e Fix exception name in ModelAttribute docs
Closes gh-34980

Signed-off-by: Johannes Jank <johannes.wengert@googlemail.com>
2025-06-02 16:30:16 +02:00
Brian Clozel 659472f9e3 Use HTTP methods in JdkClientHttpRequest when possible
Prior to this commit, we would use the
`java.net.http.HttpRequest.Builder#method(String, BodyPublisher)` to
create HTTP requests for the JDK HttpClient. This method requires a
non-null body publisher; providing an empty publisher writes a
"Content-Length: 0" header to all requests.

As of Java 19, this behavior changes for `HttpRequest.Builder#GET` and
similar methods, where the body publisher is considered as null and no
"Content-Length" header is written.

This commit aligns with this behavior and favors dedicated HTTP methods
whenever available.`

Closes gh-34971
2025-06-02 15:36:20 +02:00
Dmytro Nosan c2d678879f Fix AOT code generation for autowired inner class constructor
Prior to this commit, argument index handling in
AutowiredArgumentsCodeGenerator suffered from an off-by-one error when
generating code for an autowired inner class constructor.

Since the startIndex is already properly calculated for an inner class in
InstanceSupplierCodeGenerator.buildGetInstanceMethodForConstructor(...),
there is no need to adjust the argument indexes within
AutowiredArgumentsCodeGenerator.generateCode(...).

Closes gh-34974

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
2025-06-02 10:43:21 +02:00
Sam Brannen f207c0ed5c Polish contribution
See gh-34949
2025-06-02 10:10:46 +02:00
Mengqi Xu bbf61c74ab Fix REPLY_CHANNEL header check in MessageHeaderAccessor
Prior to this commit, the verifyType() method in MessageHeaderAccessor
checked if REPLY_CHANNEL ended with the given header name which does
not make much sense and is inconsistent with the ERROR_CHANNEL check.

This commit therefore checks if the REPLY_CHANNEL is equal to the given
header name, analogous to the ERROR_CHANNEL check.

See gh-34881
Closes gh-34949

Signed-off-by: Mengqi Xu <2663479778@qq.com>
2025-06-02 10:00:01 +02:00
Aurh1l aea66265f7 Fix syntax in @⁠SqlGroup example
Closes gh-34972
2025-05-31 12:15:50 +02:00
Sam Brannen 016294b067 Avoid overriding methods deprecated in JUnit Jupiter 5.13
See gh-34969
2025-05-30 14:54:57 +02:00
Sam Brannen 8201193650 Upgrade to JUnit 5.13
Closes gh-34969
2025-05-30 13:57:18 +02:00
Dhruv 562157d2c0 Update X-Forwarded-Proto doc to say "https / http"
Closes gh-34959

Signed-off-by: Dhruv <dhruv2015@hotmail.co.uk>
2025-05-28 16:41:16 +02:00
Dmitry Sulman 63db3d06ab Fix method links in Javadoc of WebClient
Closes gh-34938

Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
2025-05-28 11:05:00 +01:00
rstoyanchev dc52042b85 Polishing contribution
Closes gh-34939
2025-05-28 11:05:00 +01:00
dujiabao cff6e02162 Correct sample in multipart-forms.adoc
See gh-34939

Signed-off-by: dujiabao <42103826+WayneDu98@users.noreply.github.com>
2025-05-28 11:05:00 +01:00
rstoyanchev 983af78352 Configurable limit on HandlerMappingIntrospector
Closes gh-34918
2025-05-28 11:05:00 +01:00
Sébastien Deleuze 67ed64a41d Remove not null checks in BeanUtilsKotlinTests 2025-05-28 10:31:07 +02:00
Sébastien Deleuze d777b70889 Ignore DefaultConstructorMarker in BeanUtils#getParameterNames
Closes gh-34760
2025-05-28 10:12:09 +02:00
Sam Brannen 472c4012ca Remove obsolete note from Javadoc for BeanDefinitionReader 2025-05-27 11:32:05 +02:00
Sam Brannen 90be94a4a5 Use ConversionService from Environment in PropertySourcesPlaceholderConfigurer
This commit fixes a regression in PropertySourcesPlaceholderConfigurer
that was introduced in Spring Framework 6.2.7.

Specifically, this commit reinstates automatic String-conversion of
values from PropertySources in the Environment using the
ConversionService configured in the Environment.

See gh-34861
Closes gh-34936
2025-05-27 11:20:09 +02:00
Juergen Hoeller d9e261aecd Enforce backwards compatibility for null bean in shortcut code path
Closes gh-34929
2025-05-27 09:47:39 +02:00
Juergen Hoeller 15d1455acb Check for package-visible constructor in case of ClassLoader mismatch
Closes gh-34950
2025-05-27 09:47:29 +02:00
Sam Brannen 53844b068c Test conversion support in PropertySourcesPlaceholderConfigurer
This commit introduces a @⁠Disabled "regression test" which demonstrates
that PropertySourcesPlaceholderConfigurer uses the ConversionService
from the Environment.

See gh-34936
2025-05-26 18:18:52 +02:00
Sam Brannen d78264756e Support only Object property values in MockEnvironment test fixture
The setProperty() and withProperty() methods in MockEnvironment were
originally introduced with (String, String) signatures; however, they
should have always had (String, Object) signatures in order to comply
with the MockPropertySource and PropertySource APIs.

To address that, this commit changes the signatures of these methods so
that they only accept Object values for properties.

NOTE: this commit only affects the internal MockEnvironment used as a
test fixture. This commit does not affect the official, public
MockEnvironment implementation in spring-test.

See gh-34947
See gh-34948
2025-05-26 17:29:16 +02:00
Sam Brannen d0efc2230c Support Object property values in MockEnvironment
The setProperty() and withProperty() methods in MockEnvironment were
originally introduced with (String, String) signatures; however, they
should have always had (String, Object) signatures in order to comply
with the MockPropertySource and PropertySource APIs.

To address that, this commit introduces variants of these methods that
accept Object values for properties.

Closes gh-34947
2025-05-26 17:22:52 +02:00
Sam Brannen 6a6abac003 Polish Javadoc for MockPropertySource 2025-05-26 17:21:31 +02:00
Sébastien Deleuze 66f8eb02bd Upgrade to HttpComponents HttpClient 5.5
Closes gh-34941
2025-05-26 10:25:00 +02:00
Sam Brannen 274b97ca14 Upgrade to Gradle 8.14.1
Closes gh-34937
2025-05-23 17:32:53 +02:00
Sam Brannen e4b4512bcb Update code due to change in nullability contract
See gh-34933
2025-05-22 17:43:38 +02:00
Sam Brannen db5809f968 Remove obsolete @⁠Contract declaration for StreamUtils.drain()
Closes gh-34933
2025-05-22 17:23:35 +02:00
Sam Brannen d890a38f3c Support registration of non-public BeanDefinitionReader via @⁠ImportResource
Prior to this commit, a BeanDefinitionReader registered via
@⁠ImportResource was required to be public and have a public
constructor that accepts a single BeanDefinitionRegistry. However, the
public visibility requirements are not necessary, and the requirements
for the constructor's formal parameter list is not documented.

To address those issues, this commit removes the public visibility
restrictions and documents that a BeanDefinitionReader registered via
@⁠ImportResource must declare a constructor that accepts a single
BeanDefinitionRegistry.

In addition, this commit includes the cause of the instantiation
failure in case the registered BeanDefinitionReader cannot be
instantiated.

Closes gh-34928
2025-05-21 15:37:04 +02:00
Sam Brannen 98cef503fb Remove obsolete TODO
See gh-10976
2025-05-21 15:37:04 +02:00
Sam Brannen 362fe12704 Polishing 2025-05-21 15:36:58 +02:00
Brian Clozel 2af0323c21 Use Content-Type charset in JAXB message converters
Prior to this commit, the JAXB message converters would only rely on the
encoding declaration inside the XML document for reading the document.
This would then use the default UTF-8 encoding, even if the HTTP message
has the `"application/xml;charset=iso-8859-1"` Content-Type.

This commit ensures that both `Jaxb2CollectionHttpMessageConverter` and
`Jaxb2RootElementHttpMessageConverter` use the encoding declared in the
HTTP Content-Type, if present.

Fixes gh-34745
2025-05-19 16:57:57 +02:00
Brian Clozel fdab8fabd2 Avoid duplicate Content-Type in MockHttpServletRequest
Fixes gh-34913
2025-05-19 12:04:46 +02:00
Brian Clozel ae8b45a290 Next development version (v6.2.8-SNAPSHOT) 2025-05-15 11:07:25 +02:00
rstoyanchev ee62701f56 Make use of PatternMatchUtils ignoreCase option
Closes gh-34801
2025-05-15 08:18:56 +01:00
Juergen Hoeller fa168ca78a Revise FactoryBean locking behavior for strict/lenient consistency
After the bootstrap phase (and with spring.locking.strict=true during the bootstrap phase), getSingletonFactoryBeanForTypeCheck always locks. In a background bootstrap thread, it never locks. Otherwise, it tries locking and explicitly resolves the bean class for subsequent type-based resolution (even for a component-scanned class) when it fails to acquire the lock. Furthermore, getObjectFromFactoryBean follows the same locking algorithm for post-processing.

Closes gh-34902
2025-05-15 01:45:09 +02:00
rstoyanchev 3c228a5c1d Add missing @since tags in PatternMatchUtils
See: gh-34801
2025-05-14 15:15:50 +01:00
Juergen Hoeller 9bf6b8cddf Upgrade to Reactor 2024.0.6
Closes gh-34898
2025-05-14 15:03:28 +02:00
Patrick Strawderman 37ecdd1437 Forward more methods to underlying InputStream in NonClosingInputStream
NonClosingInputStream extends FilterInputStream, which does not forward some
newer InputStream methods such as transferTo and readAllBytes. Specific InputStream
implementations may have more optimized methods (e.g., FileInputStream).

Closes gh-34893

Signed-off-by: Patrick Strawderman <pstrawderman@netflix.com>
2025-05-14 09:58:23 +02:00
Juergen Hoeller 73f1c5a189 Polishing 2025-05-13 16:08:57 +02:00
Juergen Hoeller 4d296fb4ca Upgrade to Micrometer 1.14.7
Includes Jackson 2.18.4, Jetty 12.0.21, Netty 4.1.121, Apache HttpClient 5.4.4, Checkstyle 10.23.1

Closes gh-34889
2025-05-13 16:08:31 +02:00
Juergen Hoeller 6a9444473f Clarify CompositePropertySource behavior for EnumerablePropertySource contract
Closes gh-34886
2025-05-13 16:08:03 +02:00
Sam Brannen 03ae97b2eb Introduce Spring property for default escape character for placeholders
Spring Framework 6.2 introduced support for an escape character for
property placeholders (by default '\'). However, as of Spring Framework
6.2.6, there was no way to either escape the escape character or disable
escape character support.

For example, given a `username` property configured with the value of
`Jane.Smith` and a `DOMAIN\${username}` configuration string, property
placeholder replacement used to result in `DOMAIN\Jane.Smith` prior to
6.2 but now results in `DOMAIN${username}`. Similarly, an attempt to
escape the escape character via `DOMAIN\\${username}` results in
`DOMAIN\${username}`.

In theory, one should be able to disable use of an escape character
altogether, and that is currently possible by invoking
setEscapeCharacter(null) on AbstractPropertyResolver and
PlaceholderConfigurerSupport (the superclass of
PropertySourcesPlaceholderConfigurer).

However, in reality, there are two hurdles.

- As of 6.2.6, an invocation of setEscapeCharacter(null) on a
  PropertySourcesPlaceholderConfigurer applied to its internal
  top-level PropertySourcesPropertyResolver but not to any nested
  PropertySourcesPropertyResolver, which means that the `null` escape
  character could not be effectively applied.

- Users may not have an easy way to explicitly set the escape character
  to `null` for a PropertyResolver or
  PropertySourcesPlaceholderConfigurer. For example, Spring Boot
  auto-configures a PropertySourcesPlaceholderConfigurer with the
  default escape character enabled.

This first issue above has recently been addressed by gh-34861.

This commit therefore addresses the second issue as follows.

- To allow developers to easily revert to the pre-6.2 behavior without
  changes to code or configuration strings, this commit introduces a
  `spring.placeholder.escapeCharacter.default` property for use with
  SpringProperties which globally sets the default escape character that
  is automatically configured in AbstractPropertyResolver and
  PlaceholderConfigurerSupport.

- Setting the property to an empty string sets the default escape
  character to `null`, effectively disabling the default support for
  escape characters.

    spring.placeholder.escapeCharacter.default =

- Setting the property to any other character sets the default escape
  character to that specific character.

    spring.placeholder.escapeCharacter.default = ~

- Setting the property to a string containing more than one character
  results in an exception.

- Developers are still able to configure an explicit escape character
  in AbstractPropertyResolver and PlaceholderConfigurerSupport if they
  choose to do so.

- Third-party components that wish to rely on the same feature can
  invoke AbstractPropertyResolver.getDefaultEscapeCharacter() to obtain
  the globally configured default escape character.

See gh-9628
See gh-34315
See gh-34861
Closes gh-34865
2025-05-13 13:37:30 +02:00
Sam Brannen e34cdc2a55 Improve Javadoc for property placeholder support 2025-05-12 17:25:49 +02:00
Sam Brannen 8b1b9ef9e5 Stop redefining placeholder syntax constants in PlaceholderConfigurerSupport 2025-05-12 17:21:37 +02:00
Sam Brannen efdaae02e0 Add test for late binding from Environment property sources
This new test serves as a "regression test" for behavior tested in
Spring Boot.

See gh-34861
2025-05-11 16:41:19 +02:00
Sam Brannen 49ffb833a3 Sort methods to align with PropertySource declaration order
See gh-34861
2025-05-11 15:48:00 +02:00
Sam Brannen 065e50a444 Override containsProperty() in FallbackEnvironmentPropertySource
This commit overrides containsProperty() in
FallbackEnvironmentPropertySource for consistency with the
implementation of ConfigurableEnvironmentPropertySource.

See gh-34861
2025-05-11 15:44:51 +02:00
Sam Brannen 7135527765 Always obtain fresh PropertySources in ConfigurableEnvironmentPropertySource
Although it's unlikely that the implementation of getPropertySources()
in a ConfigurableEnvironment would be overridden to return a different
MutablePropertySources instance than the one that the
ConfigurableEnvironment typically acts on, it is in fact possible.

In light of that possibility, this commit refactors
ConfigurableEnvironmentPropertySource so that it always obtains a fresh
PropertySources reference.

See gh-34861
2025-05-11 15:30:09 +02:00
Sam Brannen 65e30132f3 Revert "Introduce CompositePropertySource constructor that accepts Iterable<PropertySource>"
This reverts commit 0867dfca33.
2025-05-11 14:10:54 +02:00
Sam Brannen ebb44a8368 Restore support for non-EnumerablePropertySource in PropertySourcesPlaceholderConfigurer
Commit 3295289e17 fixed a number issues with placeholder resolution in
PropertySourcesPlaceholderConfigurer. However, in doing so, it replaced
a raw PropertySource with a CompositePropertySource which implements
EnumerablePropertySource.

Consequently, all property sources registered in the Environment must
now implement EnumerablePropertySource (which is not an actual
requirement). Otherwise, invocations of getPropertyNames() on the
CompositePropertySource result in an IllegalStateException, and that is
a breaking change which resulted in numerous build failures within the
Spring portfolio.

To address that regression, this commit introduces a private
ConfigurableEnvironmentPropertySource in
PropertySourcesPlaceholderConfigurer which is a "raw" PropertySource
that delegates directly to the PropertySources in a
ConfigurableEnvironment.

This commit also extracts the raw PropertySource for direct Environment
delegation into a new FallbackEnvironmentPropertySource.

See gh-17385
Closes gh-34861
2025-05-11 13:46:11 +02:00
Sam Brannen 3096bb6d0c Polishing 2025-05-11 13:24:40 +02:00
Sam Brannen 2b2a57b4a3 Polish Javadoc 2025-05-10 15:44:08 +02:00
Sam Brannen 3295289e17 Fix placeholder resolution in PropertySourcesPlaceholderConfigurer
Currently, the placeholder resolution algorithm in
PropertySourcesPlaceholderConfigurer fails in several scenarios, and
the root cause for this category of failures has actually existed since
PropertySourcesPlaceholderConfigurer was introduced in Spring Framework
3.1.

Specifically, PropertySourcesPlaceholderConfigurer creates its own
PropertySourcesPropertyResolver that indirectly delegates to another
"nested" PropertySourcesPropertyResolver to interact with
PropertySources from the Environment, which results in double
placeholder parsing and resolution attempts, and that behavior leads to
a whole category of bugs.

For example, #27947 was addressed in Spring Framework 5.3.16, and due
to #34315 and #34326 we have recently realized that additional bugs
exist with placeholder resolution: nested placeholder resolution can
fail when escape characters are used, and it is currently impossible
to disable the escape character support for nested resolution.

To address this category of bugs, we no longer indirectly use or
directly create a "nested" PropertySourcesPropertyResolver in
PropertySourcesPlaceholderConfigurer. Instead, properties from property
sources from the Environment are now accessed directly without
duplicate/nested placeholder resolution.

See gh-27947
See gh-34326
See gh-34862
Closes gh-34861
2025-05-10 15:14:50 +02:00
Sam Brannen 457e876303 Polish reference manual regarding placeholders 2025-05-10 15:10:29 +02:00
Sam Brannen 021bf6e77d Add more tests for escape character support in PlaceholderParser 2025-05-10 14:19:41 +02:00
Sam Brannen 90453643cc Simplify and revise PlaceholderParserTests for consistency 2025-05-10 14:16:49 +02:00
Sam Brannen 5a2cbc1ab3 Polish PropertySourcesPropertyResolverTests 2025-05-09 16:43:04 +02:00
Sam Brannen 348b4cd067 Polish contribution
See gh-34720
2025-05-09 16:32:13 +02:00
Fawzi Essam 9b52cfd7d5 Test escape character support in PropertySourcesPropertyResolver
See gh-34326
See gh-34720
See gh-34861

Signed-off-by: Fawzi Essam <iifawzie@gmail.com>
2025-05-09 16:31:27 +02:00
Sam Brannen dd6eede243 Properly expand reused collection parameters in R2DBC NamedParameterUtils
Prior to this commit, NamedParameterUtils in spring-r2dbc did not
properly expand reused collection parameters. Specifically, values in a
supplied collection were only expanded in the resulting query once, for
the first occurrence of the named parameter.

To address that, this commit effectively reinstates the original logic
for ExpandedQuery from NamedParameterUtils in the Spring Data R2DBC
project.

https://github.com/spring-projects/spring-data-relational/blob/94958f5eb66cbe2e8e025155cd99abf36f6f91f4/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java#L486

Closes gh-34768
2025-05-09 14:27:19 +02:00
Sam Brannen de97e35189 Polish Javadoc and internals of R2DBC support 2025-05-09 14:04:10 +02:00
Sam Brannen 45a2c51fe1 Polish Javadoc for Environment-related components 2025-05-08 10:49:44 +02:00
Sam Brannen b98c3257af Introduce @⁠Disabled failing test for R2DBC NamedParameterUtils
The last assertion of the new test method currently fails since "foo"
is only bound once.

java.lang.AssertionError:
Expected size: 2 but was: 1 in:
{0="foo"}

See gh-34768
2025-05-07 17:30:07 +02:00
Sam Brannen ea8ae09cb7 Revise NamedParameterUtilsTests for consistency 2025-05-07 17:24:41 +02:00
Sam Brannen 0abfad870c Polish class-level Javadoc for SpringProperties 2025-05-07 16:14:23 +02:00
Sam Brannen 5a9af9e024 Document AotDetector.AOT_ENABLED flag in SpringProperties
Prior to this commit, the AotDetector.AOT_ENABLED flag was documented
in the reference manual not in the Javadoc for SpringProperties.
2025-05-07 16:14:07 +02:00
Sam Brannen 0867dfca33 Introduce CompositePropertySource constructor that accepts Iterable<PropertySource>
This commit introduces a new constructor for CompositePropertySource
that accepts a `String name` and an Iterable<PropertySource<?>>, which
allows a CompositePropertySource to be constructed from existing
property sources, such as an instance of MutablePropertySources.

Closes gh-34862
2025-05-07 14:25:13 +02:00
Sam Brannen b4355dc955 Polishing 2025-05-07 12:42:40 +02:00
Sam Brannen bc91e0ea96 Revise PropertyResolver Javadoc to highlight resolution semantics 2025-05-06 18:50:33 +02:00
Sam Brannen 8599ee6c2b Polish Javadoc 2025-05-06 16:07:46 +02:00
Sam Brannen bc466022b1 Fix typo in parameter name 2025-05-06 16:07:46 +02:00
Brian Clozel 6f11711e27 Fix HttpClient 5.3.x request config compatibility
As of gh-33806, the HttpComponents client request factory is forward
compatible with the 5.4+ versions of that library for configuring HTTP
request configuration.

This change would not tkae into account configuration set at the Spring
level because it would consider the default `RequestConfig` instance as
a custom one. This commit ensures that Spring sets its own configuration
if no custom configuration was set for all supported HttpComponents
generations.

Fixes gh-34851
2025-05-05 14:38:30 +02:00
rstoyanchev c067919173 Ensure Fragment can merge attributes
Use a new map when merging as the original may be immutable.

Closes gh-34848
2025-05-02 15:58:51 +01:00
rstoyanchev ac773d97e9 Polishing contribution
Closes gh-34828
2025-05-02 15:58:51 +01:00
Artur bd7007227c Provide a working example instead of unclear placeholders
See gh-34828

Signed-off-by: Artur <artur@vaadin.com>
2025-05-02 15:58:51 +01:00
Sam Brannen e8f873a349 Ensure Bean Overrides are discovered once in hierarchies
Prior to this commit, bean overrides (such as @⁠MockitoBean, etc.) were
discovered multiple times if they were declared:

- at the type-level on an interface that is implemented at more than
  one level in the type hierarchy, the enclosing class hierarchy, or a
  combination of the type and enclosing class hierarchies.

or

- on a field declared in a class which can be reached multiple times
  while traversing the type and enclosing class hierarchies in
  scenarios such as the following: the class (X) in which the field is
  declared is a supertype of an enclosing type of the test class, and X
  is also an enclosing type of a supertype of the test class.

Such scenarios resulted in an IllegalStateException stating that a
duplicate BeanOverrideHandler was discovered.

To address that, this commit revises the search algorithm in
BeanOverrideHandler so that all types (superclasses, enclosing classes,
and implemented interfaces) are only visited once while traversing the
type and enclosing class hierarchies in search of bean override
handlers.

See gh-33925
See gh-34324
Closes gh-34844
2025-05-02 11:26:33 +02:00
Sam Brannen b943817f3e Close ApplicationContext after test AOT processing
See commit 1c108054ee
Closes gh-34841
2025-05-01 10:50:38 +02:00
Juergen Hoeller 4466548f53 Align parameter javadoc with nullable signature
Closes gh-34845
2025-04-30 17:55:39 +02:00
Juergen Hoeller 9c183f9e77 Add explicit note on redeclaring in each application context
Closes gh-34843
2025-04-30 17:55:18 +02:00
Juergen Hoeller 03620fc530 Polishing 2025-04-29 11:47:47 +02:00
Juergen Hoeller 1c108054ee Close ApplicationContext after AOT processing
Closes gh-34841
2025-04-29 11:47:09 +02:00
Juergen Hoeller d0b186a1c7 Polishing 2025-04-28 16:13:04 +02:00
Juergen Hoeller 4172581f1b Try loadClass on LinkageError in case of same ClassLoader as well
Closes gh-34824
2025-04-28 16:12:45 +02:00
rstoyanchev c88ba6c90e Polishing contribution
Closes gh-34812
2025-04-28 14:23:30 +01:00
Yanming Zhou d7c13d6518 HttpEntity.EMPTY should be immutable
See gh-34812

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-04-28 14:22:59 +01:00
Sam Brannen 5013d6d771 Fail Gradle build whenever a javadoc task fails
To ensure that failures in javadoc tasks do not result in documentation
silently not being generated/published, this commit sets
`failOnError = true` for all javadoc tasks.

See gh-27497
See gh-34774
Closes gh-34837
2025-04-27 17:59:41 +02:00
Sam Brannen 9cd7b6a91e Upgrade spring-javaformat-gradle-plugin to version 0.0.43 2025-04-27 16:51:47 +02:00
Sam Brannen e9dcd64068 Upgrade to Gradle 8.14
Closes gh-34836
2025-04-27 16:29:46 +02:00
Sam Brannen ce7f47c962 Upgrade io.freefair.aggregate-javadoc plugin to version 8.13.1 2025-04-27 16:23:22 +02:00
Sam Brannen ef34464c94 Restructure TestContext framework support sections
This commit moves the JUnit Jupiter section above the JUnit 4 section
and groups all JUnit 4 sections under a new "JUnit 4 Support" heading.
2025-04-26 09:38:10 +02:00
Sam Brannen ef11a00c0b Polishing 2025-04-26 09:37:34 +02:00
Sam Brannen 176b0b09bf Migrate remaining JUnit 4 tests to JUnit Jupiter where feasible
In 49e5c84928 I unfortunately overlooked
several JUnit 4 based tests in the `junit4` package that should be
migrated to JUnit Jupiter.

This commit address those remaining test classes.

See gh-23451
See gh-34794
Closes gh-34813
2025-04-26 07:51:24 +02:00
rstoyanchev c48ff357dc HTTP Service proxy sets body type
Closes gh-34793
2025-04-25 21:03:00 +01:00
rstoyanchev 190dabb8e1 Polishing contribution
Closes gh-34789
2025-04-25 21:03:00 +01:00
blake_bauman d15abd58b4 Add option to set Principal in MockServerWebExchange
See gh-34789

Signed-off-by: blake_bauman <blake_bauman@apple.com>
2025-04-25 21:03:00 +01:00
Sam Brannen 44500cf868 Revise TestConventions to retain existing JUnit Platform options
This commit revises the implementation of TestConventions so that
existing JUnit Platform options from a pre-configured `test` task are
copied instead of overridden.

Closes gh-34827
2025-04-25 13:38:18 +02:00
Sam Brannen e384389790 Reinstate the @⁠Inject Technology Compatibility Kit (TCK)
In commit 05ebca8677, the `public` modifier was removed from the
SpringAtInjectTckTests class, which prevents it from being run as a
JUnit 3 test class.

To address that, this commit adds the missing `public` modifier as well
as a a code comment to help prevent this from happening again.

In addition, this commit updates spring-context.gradle to ensure that
the JUnit Vintage test engine is always applied. However, that Gradle
configuration is unfortunately ignored due to how our TestConventions
class has been implemented. Thus, that issue will have to be addressed
separately.

Closes gh-34800
2025-04-25 12:08:39 +02:00
Sam Brannen 49e5c84928 Migrate remaining JUnit 4 tests to JUnit Jupiter where feasible
In Spring Framework 5.2, we migrated most of the test suite from JUnit
4 to JUnit Jupiter; however, prior to this commit, several tests in the
spring-test module were still based on JUnit 4 unnecessarily.

Since we are now planning to deprecate our JUnit 4 support in 7.0, this
commit migrates our remaining JUnit 4 based tests to JUnit Jupiter
whenever feasible. In the process, test classes that previously resided
under the "junit4" package have been moved to new packages directly
under the "org.springframework.text.context" package, and several
classes have been renamed for greater clarity of purpose.

Consequently, the only remaining tests based on JUnit 4 are those tests
that are required to run with JUnit 4 in order to test our JUnit 4
support.

This commit also greatly simplifies exclusions for Checkstyle rules
pertaining to JUnit usage.

See gh-23451
See gh-34794
Closes gh-34813
2025-04-24 16:20:39 +02:00
Sébastien Deleuze 56eb135608 Fix AbstractJackson2HttpMessageConverter nullness
This commit makes AbstractJackson2HttpMessageConverter#getObjectMappersForType
return value non nullable as an empty map is returned in case of no
registrations.

Closes gh-34811
2025-04-24 10:37:30 +02:00
rstoyanchev 5c5cf73e11 Add ignoreCase variants to PatternMatchUtils
See gh-34801
2025-04-23 12:10:55 +01:00
rstoyanchev 858c2bd270 Polishing contribution
Closes gh-34783
2025-04-23 10:54:52 +01:00
whl 124582d910 Fix expansion of query param with same name
See gh-34783

Signed-off-by: whl <whlit.cola@gmail.com>
2025-04-23 10:54:52 +01:00
Juergen Hoeller 253f321e8b Early getJarFile() call for consistent jar file existence check
See gh-34796
2025-04-23 10:16:12 +02:00
Juergen Hoeller 0252e39409 Check for the existence of any actual jar entries in case of jar root
Closes gh-34796
2025-04-22 23:18:56 +02:00
Sam Brannen 2f60083cd5 Add integration tests for reused named parameters from bean properties
See gh-34768
2025-04-22 12:45:35 +02:00
Juergen Hoeller b83e07ff8c Ignore NoSuchFileException from getJarFile() as well
Closes gh-34764
2025-04-22 12:17:18 +02:00
Juergen Hoeller 1841ad3472 Consistently use local copy of volatile mainThreadPrefix field
Closes gh-34746
2025-04-22 12:17:03 +02:00
Sam Brannen 0477ba4de4 Remove obsolete RmiInvocationWrapperRTD.xml file
Closes gh-34779

(cherry picked from commit e7402bc365)
2025-04-22 12:10:18 +02:00
Sam Brannen 8c376e9cc5 Remove redundant parameter count check in AnnotationsScanner.hasSameParameterTypes()
The redundancy was reported by @TAKETODAY.

See gh-34717
2025-04-22 11:53:40 +02:00
Sam Brannen 018d3c9ef2 Add integration tests for reused named parameters
See gh-34768
2025-04-17 15:48:24 +02:00
Brian Clozel c70741f60d Next development version (v6.2.7-SNAPSHOT) 2025-04-17 09:50:20 +02:00
375 changed files with 5729 additions and 3619 deletions
+2 -3
View File
@@ -91,18 +91,17 @@ configure([rootProject] + javaProjects) { project ->
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.12.2/api/",
// "https://junit.org/junit5/docs/5.13.1/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
+1 -1
View File
@@ -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.25.0");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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",
+2 -1
View File
@@ -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.
@@ -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`]
@@ -166,7 +166,7 @@ following sections to make this pattern much easier to implement.
== MockMvc and WebDriver Setup
To use Selenium WebDriver with `MockMvc`, make sure that your project includes a test
dependency on `org.seleniumhq.selenium:selenium-htmlunit3-driver`.
dependency on `org.seleniumhq.selenium:htmlunit3-driver`.
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
@@ -1,10 +1,26 @@
[[spring-mvc-test-client]]
= Testing Client Applications
You can use client-side tests to test code that internally uses the `RestTemplate`. The
idea is to declare expected requests and to provide "`stub`" responses so that you can
focus on testing the code in isolation (that is, without running a server). The following
example shows how to do so:
To test code that uses the `RestClient` or `RestTemplate`, you can use a mock web server, such as
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
server, and that means you can test with the same HTTP client that is also configured in
the same way as in production, which is important because there are often subtle
differences in the way different clients handle network I/O. Another advantage of mock
web servers is the ability to simulate specific network issues and conditions at the
transport level, in combination with the client used in production.
In addition to dedicated mock web servers, historically the Spring Framework has provided
a built-in option to test `RestClient` or `RestTemplate` through `MockRestServiceServer`.
This relies on configuring the client under test with a custom `ClientHttpRequestFactory`
backed by the mock server that is in turn set up to expect requests and send "`stub`"
responses so that you can focus on testing the code in isolation, without running a server.
TIP: `MockRestServiceServer` predates the existence of mock web servers. At present, we
recommend using mock web servers for more complete testing of the transport layer and
network conditions.
The following example shows an example of using `MockRestServiceServer`:
[tabs]
======
@@ -250,7 +250,7 @@ Java::
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
})
void userTest() {
// run code that uses the test schema and test data
}
@@ -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`.
@@ -2,9 +2,16 @@
= Testing
:page-section-summary-toc: 1
To test code that uses the `WebClient`, you can use a mock web server, such as the
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see an example
of its use, check out
To test code that uses the `WebClient`, you can use a mock web server, such as
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
server, and that means you can test with the same HTTP client that is also configured in
the same way as in production, which is important because there are often subtle
differences in the way different clients handle network I/O. Another advantage of mock
web servers is the ability to simulate specific network issues and conditions at the
transport level, in combination with the client used in production.
For example use of MockWebServer, see
{spring-framework-code}/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[`WebClientIntegrationTests`]
in the Spring Framework test suite or the
https://github.com/square/okhttp/tree/master/samples/static-server[`static-server`]
@@ -19,7 +19,7 @@ Java::
private String name;
private MultipartFile file;
private FilePart file;
// ...
@@ -42,7 +42,7 @@ Kotlin::
----
class MyForm(
val name: String,
val file: MultipartFile)
val file: FilePart)
@Controller
class FileUploadController {
@@ -34,11 +34,7 @@ Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated oper
| `HttpHeaders`
| For returning a response with headers and no body.
| `ErrorResponse`
| To render an RFC 9457 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| `ErrorResponse`, `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
@@ -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.
@@ -4,11 +4,13 @@
Spring MVC has an extensive integration with Servlet asynchronous request
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-processing[processing]:
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`]
return values in controller methods provide basic support for a single asynchronous
return value.
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`],
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`], and
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-webasynctask[`WebAsyncTask`] return values
in controller methods provide support for a single asynchronous return value.
* Controllers can xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[stream] multiple values, including
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
* Controllers can use reactive clients and return
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types] for response handling.
@@ -96,6 +98,47 @@ xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[config
[[mvc-ann-async-webasynctask]]
== `WebAsyncTask`
`WebAsyncTask` is comparable to using xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[Callable]
but allows customizing additional settings such a request timeout value, and the
`AsyncTaskExecutor` to execute the `java.util.concurrent.Callable` with instead
of the defaults set up globally for Spring MVC. Below is an example of using `WebAsyncTask`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@GetMapping("/callable")
WebAsyncTask<String> handle() {
return new WebAsyncTask<String>(20000L,()->{
Thread.sleep(10000); //simulate long-running task
return "asynchronous request completed";
});
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@GetMapping("/callable")
fun handle(): WebAsyncTask<String> {
return WebAsyncTask(20000L) {
Thread.sleep(10000) // simulate long-running task
"asynchronous request completed"
}
}
----
======
[[mvc-ann-async-processing]]
== Processing
@@ -390,7 +433,7 @@ reactive types from the controller method.
Reactive return values are handled as follows:
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
include `Mono` (Reactor) or `Single` (RxJava).
include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
@@ -10,18 +10,20 @@ to any controller. Moreover, as of 5.3, `@ExceptionHandler` methods in `@Control
can be used to handle exceptions from any `@Controller` or any other handler.
`@ControllerAdvice` is meta-annotated with `@Component` and therefore can be registered as
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning]
. `@RestControllerAdvice` is meta-annotated with `@ControllerAdvice`
and `@ResponseBody`, and that means `@ExceptionHandler` methods will have their return
value rendered via response body message conversion, rather than via HTML views.
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning].
`@RestControllerAdvice` is a shortcut annotation that combines `@ControllerAdvice`
with `@ResponseBody`, in effect simply an `@ControllerAdvice` whose exception handler
methods render to the response body.
On startup, `RequestMappingHandlerMapping` and `ExceptionHandlerExceptionResolver` detect
controller advice beans and apply them at runtime. Global `@ExceptionHandler` methods,
from an `@ControllerAdvice`, are applied _after_ local ones, from the `@Controller`.
By contrast, global `@ModelAttribute` and `@InitBinder` methods are applied _before_ local ones.
The `@ControllerAdvice` annotation has attributes that let you narrow the set of controllers
and handlers that they apply to. For example:
By default, both `@ControllerAdvice` and `@RestControllerAdvice` apply to any controller,
including `@Controller` and `@RestController`. Use attributes of the annotation to narrow
the set of controllers and handlers that they apply to. For example:
[tabs]
======
@@ -177,13 +177,9 @@ the content negotiation during the error handling phase will decide which conten
be converted through `HttpMessageConverter` instances and written to the response.
See xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[ResponseEntity].
| `ErrorResponse`
| `ErrorResponse`, `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `String`
| A view name to be resolved with `ViewResolver` implementations and used together with the
@@ -243,7 +243,7 @@ Kotlin::
======
If there is no `BindingResult` parameter after the `@ModelAttribute`, then
`MethodArgumentNotValueException` is raised with the validation errors. However, if method
a `MethodArgumentNotValidException` is raised with the validation errors. However, if method
validation applies because other parameters have `@jakarta.validation.Constraint` annotations,
then `HandlerMethodValidationException` is raised instead. For more details, see the section
xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
@@ -22,11 +22,7 @@ supported for all return values.
| `HttpHeaders`
| For returning a response with headers and no body.
| `ErrorResponse`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| `ErrorResponse`, `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
@@ -38,7 +38,7 @@ to inform the server that the original port was `443`.
==== X-Forwarded-Proto
While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto[`X-Forwarded-Proto: (https|http)`]
is a de-facto standard header that is used to communicate the original protocol (for example, https / https)
is a de-facto standard header that is used to communicate the original protocol (for example, https / http)
to a downstream server. For example, if a request of `https://example.com/resource` is sent to
a proxy which forwards the request to `http://localhost:8080/resource`, then a header of
`X-Forwarded-Proto: https` can be sent to inform the server that the original protocol was `https`.
@@ -119,4 +119,4 @@ https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}
In this case, the proxy has a prefix of `/api/app1` and the server has a prefix of
`/app1`. The proxy can send `X-Forwarded-Prefix: /api/app1` to have the original prefix
`/api/app1` override the server prefix `/app1`.
`/api/app1` override the server prefix `/app1`.
+10 -11
View File
@@ -7,21 +7,21 @@ 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.8"))
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.7"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
api(platform("org.apache.groovy:groovy-bom:4.0.27"))
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"))
api(platform("org.mockito:mockito-bom:5.17.0"))
api(platform("org.junit:junit-bom:5.13.1"))
api(platform("org.mockito:mockito-bom:5.18.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
@@ -47,7 +47,6 @@ dependencies {
api("com.thoughtworks.xstream:xstream:1.4.21")
api("commons-io:commons-io:2.15.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("io.micrometer:context-propagation:1.1.1")
api("io.mockk:mockk:1.13.4")
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
@@ -100,7 +99,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.5")
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
View File
@@ -1,4 +1,4 @@
version=6.2.6
version=6.2.8
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+1 -1
View File
@@ -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
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -2
View File
@@ -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
View File
@@ -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
-1
View File
@@ -1,7 +1,6 @@
plugins {
id "com.gradle.develocity" version "3.19"
id "io.spring.ge.conventions" version "0.0.17"
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
}
include "spring-aop"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -112,12 +112,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
private BeanFactory beanFactory;
@Nullable
private transient ClassLoader pointcutClassLoader;
private transient volatile ClassLoader pointcutClassLoader;
@Nullable
private transient PointcutExpression pointcutExpression;
private transient volatile PointcutExpression pointcutExpression;
private transient boolean pointcutParsingFailed = false;
private transient volatile boolean pointcutParsingFailed;
/**
@@ -197,11 +197,14 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
* Lazily build the underlying AspectJ pointcut expression.
*/
private PointcutExpression obtainPointcutExpression() {
if (this.pointcutExpression == null) {
this.pointcutClassLoader = determinePointcutClassLoader();
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
PointcutExpression pointcutExpression = this.pointcutExpression;
if (pointcutExpression == null) {
ClassLoader pointcutClassLoader = determinePointcutClassLoader();
pointcutExpression = buildPointcutExpression(pointcutClassLoader);
this.pointcutClassLoader = pointcutClassLoader;
this.pointcutExpression = pointcutExpression;
}
return this.pointcutExpression;
return pointcutExpression;
}
/**
@@ -467,40 +470,24 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(this, targetMethod);
ShadowMatchKey key = new ShadowMatchKey(this, targetMethod);
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(key);
if (shadowMatch == null) {
PointcutExpression fallbackExpression = null;
Method methodToMatch = targetMethod;
try {
PointcutExpression pointcutExpression = obtainPointcutExpression();
synchronized (pointcutExpression) {
shadowMatch = ShadowMatchUtils.getShadowMatch(key);
if (shadowMatch != null) {
return shadowMatch;
}
PointcutExpression fallbackExpression = null;
Method methodToMatch = targetMethod;
try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
}
catch (ReflectionWorldException ex) {
// Failed to introspect target method, probably because it has been loaded
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
try {
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
if (fallbackExpression != null) {
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
}
}
catch (ReflectionWorldException ex2) {
fallbackExpression = null;
}
}
if (targetMethod != originalMethod && (shadowMatch == null ||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
// Fall back to the plain original method in case of no resolvable match or a
// negative match on a proxy class (which doesn't carry any annotations on its
// redeclared methods), as well as for annotation pointcuts.
methodToMatch = originalMethod;
try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
}
catch (ReflectionWorldException ex) {
// Could neither introspect the target class nor the proxy class ->
// let's try the original method's declaring class before we give up...
// Failed to introspect target method, probably because it has been loaded
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
try {
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
if (fallbackExpression != null) {
@@ -511,21 +498,45 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
fallbackExpression = null;
}
}
if (targetMethod != originalMethod && (shadowMatch == null ||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
// Fall back to the plain original method in case of no resolvable match or a
// negative match on a proxy class (which doesn't carry any annotations on its
// redeclared methods), as well as for annotation pointcuts.
methodToMatch = originalMethod;
try {
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
}
catch (ReflectionWorldException ex) {
// Could neither introspect the target class nor the proxy class ->
// let's try the original method's declaring class before we give up...
try {
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
if (fallbackExpression != null) {
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
}
}
catch (ReflectionWorldException ex2) {
fallbackExpression = null;
}
}
}
}
catch (Throwable ex) {
// Possibly AspectJ 1.8.10 encountering an invalid signature
logger.debug("PointcutExpression matching rejected target method", ex);
fallbackExpression = null;
}
if (shadowMatch == null) {
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
}
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
shadowMatch = new DefensiveShadowMatch(shadowMatch,
fallbackExpression.matchesMethodExecution(methodToMatch));
}
shadowMatch = ShadowMatchUtils.setShadowMatch(key, shadowMatch);
}
catch (Throwable ex) {
// Possibly AspectJ 1.8.10 encountering an invalid signature
logger.debug("PointcutExpression matching rejected target method", ex);
fallbackExpression = null;
}
if (shadowMatch == null) {
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
}
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
shadowMatch = new DefensiveShadowMatch(shadowMatch,
fallbackExpression.matchesMethodExecution(methodToMatch));
}
shadowMatch = ShadowMatchUtils.setShadowMatch(this, targetMethod, shadowMatch);
}
return shadowMatch;
}
@@ -720,4 +731,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
private record ShadowMatchKey(AspectJExpressionPointcut expression, Method method) {
}
}
@@ -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,24 +16,48 @@
package org.springframework.aop.aspectj;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.aspectj.weaver.tools.ShadowMatch;
import org.springframework.aop.support.ExpressionPointcut;
import org.springframework.lang.Nullable;
/**
* Internal {@link ShadowMatch} utilities.
*
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 6.2
*/
public abstract class ShadowMatchUtils {
private static final Map<Key, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
private static final Map<Object, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
/**
* Find a {@link ShadowMatch} for the specified key.
* @param key the key to use
* @return the {@code ShadowMatch} to use for the specified key,
* or {@code null} if none found
*/
@Nullable
static ShadowMatch getShadowMatch(Object key) {
return shadowMatchCache.get(key);
}
/**
* Associate the {@link ShadowMatch} with the specified key.
* If an entry already exists, the given {@code shadowMatch} is ignored.
* @param key the key to use
* @param shadowMatch the shadow match to use for this key
* if none already exists
* @return the shadow match to use for the specified key
*/
static ShadowMatch setShadowMatch(Object key, ShadowMatch shadowMatch) {
ShadowMatch existing = shadowMatchCache.putIfAbsent(key, shadowMatch);
return (existing != null ? existing : shadowMatch);
}
/**
* Clear the cache of computed {@link ShadowMatch} instances.
@@ -42,34 +66,4 @@ public abstract class ShadowMatchUtils {
shadowMatchCache.clear();
}
/**
* Return the {@link ShadowMatch} for the specified {@link ExpressionPointcut}
* and {@link Method} or {@code null} if none is found.
* @param expression the expression
* @param method the method
* @return the {@code ShadowMatch} to use for the specified expression and method
*/
@Nullable
static ShadowMatch getShadowMatch(ExpressionPointcut expression, Method method) {
return shadowMatchCache.get(new Key(expression, method));
}
/**
* Associate the {@link ShadowMatch} to the specified {@link ExpressionPointcut}
* and method. If an entry already exists, the given {@code shadowMatch} is
* ignored.
* @param expression the expression
* @param method the method
* @param shadowMatch the shadow match to use for this expression and method
* if none already exists
* @return the shadow match to use for the specified expression and method
*/
static ShadowMatch setShadowMatch(ExpressionPointcut expression, Method method, ShadowMatch shadowMatch) {
ShadowMatch existing = shadowMatchCache.putIfAbsent(new Key(expression, method), shadowMatch);
return (existing != null ? existing : shadowMatch);
}
private record Key(ExpressionPointcut expression, Method method) {}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -24,7 +24,7 @@ import java.util.Map;
import org.springframework.lang.Nullable;
/**
* Abstract superclass for counting advices etc.
* Abstract superclass for counting advice, etc.
*
* @author Rod Johnson
* @author Chris Beams
@@ -62,7 +62,7 @@ public class MethodCounter implements Serializable {
*/
@Override
public boolean equals(@Nullable Object other) {
return (other != null && other.getClass() == this.getClass());
return (other != null && getClass() == other.getClass());
}
@Override
@@ -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.
@@ -33,6 +33,7 @@ import java.util.Map;
import java.util.Set;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.reflect.KClass;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
@@ -669,7 +670,9 @@ public abstract class BeanUtils {
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
Assert.state(paramNames.length == ctor.getParameterCount(),
int parameterCount = (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasDefaultConstructorMarker(ctor) ?
ctor.getParameterCount() - 1 : ctor.getParameterCount());
Assert.state(paramNames.length == parameterCount,
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
return paramNames;
}
@@ -939,6 +942,11 @@ public abstract class BeanUtils {
}
return kotlinConstructor.callBy(argParameters);
}
public static boolean hasDefaultConstructorMarker(Constructor<?> ctor) {
int parameterCount = ctor.getParameterCount();
return parameterCount > 0 && ctor.getParameters()[parameterCount -1].getType() == DefaultConstructorMarker.class;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -68,10 +68,10 @@ public class AutowiredArgumentsCodeGenerator {
for (int i = startIndex; i < parameterTypes.length; i++) {
code.add(i > startIndex ? ", " : "");
if (!ambiguous) {
code.add("$L.get($L)", variableName, i - startIndex);
code.add("$L.get($L)", variableName, i);
}
else {
code.add("$L.get($L, $T.class)", variableName, i - startIndex, parameterTypes[i]);
code.add("$L.get($L, $T.class)", variableName, i, parameterTypes[i]);
}
}
return code.build();
@@ -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">
* &lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt;
* &lt;property name="driverClassName" value="${driver}" /&gt;
* &lt;property name="url" value="jdbc:${dbname}" /&gt;
* &lt;property name="driverClassName" value="${jdbc.driver}" /&gt;
* &lt;property name="url" value="jdbc:${jdbc.dbname}" /&gt;
* &lt;/bean&gt;
* </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">
* &lt;property name="url" value="jdbc:${dbname:defaultdb}" /&gt;
* &lt;property name="url" value="jdbc:${jdbc.dbname:defaultdb}" /&gt;
* </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;
}
/**
@@ -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.
@@ -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 {
@@ -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.
@@ -29,10 +29,6 @@ import org.springframework.lang.Nullable;
* load and register methods for bean definitions, specific to
* their bean definition format.
*
* <p>Note that a bean definition reader does not have to implement
* this interface. It only serves as a suggestion for bean definition
* readers that want to follow standard naming conventions.
*
* @author Juergen Hoeller
* @since 1.1
* @see org.springframework.core.io.Resource
@@ -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).
@@ -1678,7 +1679,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (autowiredBeanNames != null) {
autowiredBeanNames.add(dependencyName);
}
boolean preExisting = containsSingleton(dependencyName);
Object dependencyBean = getBean(dependencyName);
if (preExisting && dependencyBean instanceof NullBean) {
// for backwards compatibility with addCandidateEntry in the regular code path
dependencyBean = null;
}
return resolveInstance(dependencyBean, descriptor, type, dependencyName);
}
}
@@ -1759,7 +1765,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
throw new BeanNotOfRequiredTypeException(name, type, candidate.getClass());
}
return result;
}
@Nullable
@@ -2244,7 +2249,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* i.e. whether the candidate points back to the original bean or to a factory method
* on the original bean.
*/
@Contract("null, _ -> false;_, null -> false;")
@Contract("null, _ -> false; _, null -> false;")
private boolean isSelfReference(@Nullable String beanName, @Nullable String candidateName) {
return (beanName != null && candidateName != null &&
(beanName.equals(candidateName) || (containsBeanDefinition(candidateName) &&
@@ -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 {
@@ -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 {
@@ -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.
@@ -33,13 +33,14 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Chris Beams
*/
class FactoryBeanLookupTests {
private BeanFactory beanFactory;
private final BeanFactory beanFactory = new DefaultListableBeanFactory();
@BeforeEach
void setUp() {
beanFactory = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory).loadBeanDefinitions(
new ClassPathResource("FactoryBeanLookupTests-context.xml", this.getClass()));
new ClassPathResource("FactoryBeanLookupTests-context.xml", getClass()));
}
@Test
@@ -71,6 +72,7 @@ class FactoryBeanLookupTests {
Foo foo = beanFactory.getBean("fooFactory", Foo.class);
assertThat(foo).isNotNull();
}
}
class FooFactoryBean extends AbstractFactoryBean<Foo> {
@@ -960,11 +960,34 @@ class AutowiredAnnotationBeanPostProcessorTests {
@Test
void constructorResourceInjectionWithNoCandidatesAndNoFallback() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> bf.getBean("annotatedBean"))
.satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class));
}
@Test
void constructorResourceInjectionWithCandidateAndNoFallback() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class));
RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
bf.registerBeanDefinition("testBean", tb);
bf.getBean("testBean");
assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull();
}
@Test
void constructorResourceInjectionWithNameMatchingCandidateAndNoFallback() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class));
RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
bf.registerBeanDefinition("testBean3", tb);
bf.getBean("testBean3");
assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull();
}
@Test
void constructorResourceInjectionWithSometimesNullBeanEarly() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
@@ -1193,6 +1216,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
@Test
void singleConstructorInjectionWithMissingDependency() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorOptionalCollectionBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> bf.getBean("annotatedBean"));
}
@@ -1203,6 +1227,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
bf.registerBeanDefinition("testBean", tb);
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> bf.getBean("annotatedBean"));
}
@@ -2428,7 +2453,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
}
@Test
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "rawtypes" })
void genericsBasedConstructorInjectionWithNonTypedTarget() {
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
@@ -3060,7 +3085,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
protected ITestBean testBean3;
@Autowired(required = false)
public ConstructorWithoutFallbackBean(ITestBean testBean3) {
this.testBean3 = testBean3;
}
@@ -3075,7 +3099,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
protected ITestBean testBean3;
@Autowired(required = false)
public ConstructorWithNullableArgument(@Nullable ITestBean testBean3) {
this.testBean3 = testBean3;
}
@@ -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.
@@ -67,7 +67,7 @@ class AutowiredArgumentsCodeGeneratorTests {
AutowiredArgumentsCodeGenerator generator = new AutowiredArgumentsCodeGenerator(
Outer.Nested.class, constructor);
assertThat(generator.generateCode(constructor.getParameterTypes(), 1))
.hasToString("args.get(0), args.get(1)");
.hasToString("args.get(1), args.get(2)");
}
@Test
@@ -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.
@@ -36,6 +36,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.support.AnnotationConsumer;
import org.junit.jupiter.params.support.ParameterDeclarations;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.ObjectProvider;
@@ -691,7 +692,7 @@ class BeanInstanceSupplierTests {
}
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
return this.source.provideArguments(context);
}
}
@@ -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.
@@ -133,11 +133,13 @@ class InstanceSupplierCodeGeneratorTests {
@Test
void generateWhenHasConstructorWithInnerClassAndParameter() {
BeanDefinition beanDefinition = new RootBeanDefinition(EnvironmentAwareComponent.class);
StandardEnvironment environment = new StandardEnvironment();
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
this.beanFactory.registerSingleton("environment", new StandardEnvironment());
this.beanFactory.registerSingleton("environment", environment);
compile(beanDefinition, (instanceSupplier, compiled) -> {
Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(EnvironmentAwareComponent.class);
assertThat(bean).hasFieldOrPropertyWithValue("environment", environment);
assertThat(compiled.getSourceFile()).contains(
"getBeanFactory().getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent(");
});
@@ -162,11 +164,13 @@ class InstanceSupplierCodeGeneratorTests {
@Test
void generateWhenHasNonPublicConstructorWithInnerClassAndParameter() {
BeanDefinition beanDefinition = new RootBeanDefinition(EnvironmentAwareComponentWithoutPublicConstructor.class);
StandardEnvironment environment = new StandardEnvironment();
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
this.beanFactory.registerSingleton("environment", new StandardEnvironment());
this.beanFactory.registerSingleton("environment", environment);
compile(beanDefinition, (instanceSupplier, compiled) -> {
Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(EnvironmentAwareComponentWithoutPublicConstructor.class);
assertThat(bean).hasFieldOrPropertyWithValue("environment", environment);
assertThat(compiled.getSourceFile()).doesNotContain(
"getBeanFactory().getBean(InnerComponentConfiguration.class)");
});
@@ -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.
@@ -84,7 +84,7 @@ class PropertyPlaceholderConfigurerTests {
.getBeanDefinition());
PropertyPlaceholderConfigurer pc = new PropertyPlaceholderConfigurer();
Resource resource = new ClassPathResource("PropertyPlaceholderConfigurerTests.properties", this.getClass());
Resource resource = new ClassPathResource("PropertyPlaceholderConfigurerTests.properties", getClass());
pc.setLocation(resource);
pc.postProcessBeanFactory(bf);
}
@@ -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.
@@ -46,7 +46,7 @@ class DuplicateBeanIdTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
assertThatException().as("duplicate ids in same nesting level").isThrownBy(() ->
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-sameLevel-context.xml", this.getClass())));
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-sameLevel-context.xml", getClass())));
}
@Test
@@ -54,7 +54,7 @@ class DuplicateBeanIdTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAllowBeanDefinitionOverriding(true);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-multiLevel-context.xml", this.getClass()));
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-multiLevel-context.xml", getClass()));
TestBean testBean = bf.getBean(TestBean.class); // there should be only one
assertThat(testBean.getName()).isEqualTo("nested");
}
@@ -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.
@@ -36,7 +36,7 @@ class NestedBeansElementAttributeRecursionTests {
void defaultLazyInit() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", this.getClass()));
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", getClass()));
assertLazyInits(bf);
}
@@ -47,7 +47,7 @@ class NestedBeansElementAttributeRecursionTests {
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(bf);
xmlBeanDefinitionReader.setValidating(false);
xmlBeanDefinitionReader.loadBeanDefinitions(
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", this.getClass()));
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", getClass()));
assertLazyInits(bf);
}
@@ -70,7 +70,7 @@ class NestedBeansElementAttributeRecursionTests {
void defaultMerge() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", this.getClass()));
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", getClass()));
assertMerge(bf);
}
@@ -81,7 +81,7 @@ class NestedBeansElementAttributeRecursionTests {
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(bf);
xmlBeanDefinitionReader.setValidating(false);
xmlBeanDefinitionReader.loadBeanDefinitions(
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", this.getClass()));
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", getClass()));
assertMerge(bf);
}
@@ -109,7 +109,7 @@ class NestedBeansElementAttributeRecursionTests {
void defaultAutowireCandidates() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", this.getClass()));
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", getClass()));
assertAutowireCandidates(bf);
}
@@ -120,7 +120,7 @@ class NestedBeansElementAttributeRecursionTests {
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(bf);
xmlBeanDefinitionReader.setValidating(false);
xmlBeanDefinitionReader.loadBeanDefinitions(
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", this.getClass()));
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", getClass()));
assertAutowireCandidates(bf);
}
@@ -149,7 +149,7 @@ class NestedBeansElementAttributeRecursionTests {
void initMethod() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("NestedBeansElementAttributeRecursionTests-init-destroy-context.xml", this.getClass()));
new ClassPathResource("NestedBeansElementAttributeRecursionTests-init-destroy-context.xml", getClass()));
InitDestroyBean beanA = bf.getBean("beanA", InitDestroyBean.class);
InitDestroyBean beanB = bf.getBean("beanB", InitDestroyBean.class);
@@ -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.
@@ -26,15 +26,14 @@ import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for new nested beans element support in Spring XML
*
* @author Chris Beams
*/
class NestedBeansElementTests {
private final Resource XML =
new ClassPathResource("NestedBeansElementTests-context.xml", this.getClass());
private final Resource XML = new ClassPathResource("NestedBeansElementTests-context.xml", getClass());
@Test
void getBean_withoutActiveProfile() {
@@ -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.
@@ -93,7 +93,6 @@ class BeanUtilsKotlinTests {
@Test
fun `Instantiate value class`() {
val constructor = BeanUtils.findPrimaryConstructor(ValueClass::class.java)!!
assertThat(constructor).isNotNull()
val value = "Hello value class!"
val instance = BeanUtils.instantiateClass(constructor, value)
assertThat(instance).isEqualTo(ValueClass(value))
@@ -102,7 +101,6 @@ class BeanUtilsKotlinTests {
@Test
fun `Instantiate value class with multiple constructors`() {
val constructor = BeanUtils.findPrimaryConstructor(ValueClassWithMultipleConstructors::class.java)!!
assertThat(constructor).isNotNull()
val value = "Hello value class!"
val instance = BeanUtils.instantiateClass(constructor, value)
assertThat(instance).isEqualTo(ValueClassWithMultipleConstructors(value))
@@ -111,7 +109,6 @@ class BeanUtilsKotlinTests {
@Test
fun `Instantiate class with value class parameter`() {
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithValueClass::class.java)!!
assertThat(constructor).isNotNull()
val value = ValueClass("Hello value class!")
val instance = BeanUtils.instantiateClass(constructor, value)
assertThat(instance).isEqualTo(ConstructorWithValueClass(value))
@@ -120,7 +117,6 @@ class BeanUtilsKotlinTests {
@Test
fun `Instantiate class with nullable value class parameter`() {
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithNullableValueClass::class.java)!!
assertThat(constructor).isNotNull()
val value = ValueClass("Hello value class!")
var instance = BeanUtils.instantiateClass(constructor, value)
assertThat(instance).isEqualTo(ConstructorWithNullableValueClass(value))
@@ -131,7 +127,6 @@ class BeanUtilsKotlinTests {
@Test
fun `Instantiate primitive value class`() {
val constructor = BeanUtils.findPrimaryConstructor(PrimitiveValueClass::class.java)!!
assertThat(constructor).isNotNull()
val value = 0
val instance = BeanUtils.instantiateClass(constructor, value)
assertThat(instance).isEqualTo(PrimitiveValueClass(value))
@@ -140,7 +135,6 @@ class BeanUtilsKotlinTests {
@Test
fun `Instantiate class with primitive value class parameter`() {
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithPrimitiveValueClass::class.java)!!
assertThat(constructor).isNotNull()
val value = PrimitiveValueClass(0)
val instance = BeanUtils.instantiateClass(constructor, value)
assertThat(instance).isEqualTo(ConstructorWithPrimitiveValueClass(value))
@@ -149,7 +143,6 @@ class BeanUtilsKotlinTests {
@Test
fun `Instantiate class with nullable primitive value class parameter`() {
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithNullablePrimitiveValueClass::class.java)!!
assertThat(constructor).isNotNull()
val value = PrimitiveValueClass(0)
var instance = BeanUtils.instantiateClass(constructor, value)
assertThat(instance).isEqualTo(ConstructorWithNullablePrimitiveValueClass(value))
@@ -157,6 +150,48 @@ class BeanUtilsKotlinTests {
assertThat(instance).isEqualTo(ConstructorWithNullablePrimitiveValueClass(null))
}
@Test
fun `Get parameter names with Foo`() {
val ctor = BeanUtils.findPrimaryConstructor(Foo::class.java)!!
val names = BeanUtils.getParameterNames(ctor)
assertThat(names).containsExactly("param1", "param2")
}
@Test
fun `Get parameter names filters out DefaultConstructorMarker with ConstructorWithValueClass`() {
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithValueClass::class.java)!!
val names = BeanUtils.getParameterNames(ctor)
assertThat(names).containsExactly("value")
}
@Test
fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithNullableValueClass`() {
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithNullableValueClass::class.java)!!
val names = BeanUtils.getParameterNames(ctor)
assertThat(names).containsExactly("value")
}
@Test
fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithPrimitiveValueClass`() {
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithPrimitiveValueClass::class.java)!!
val names = BeanUtils.getParameterNames(ctor)
assertThat(names).containsExactly("value")
}
@Test
fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithNullablePrimitiveValueClass`() {
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithNullablePrimitiveValueClass::class.java)!!
val names = BeanUtils.getParameterNames(ctor)
assertThat(names).containsExactly("value")
}
@Test
fun `getParameterNames with ClassWithZeroParameterCtor`() {
val ctor = BeanUtils.findPrimaryConstructor(ClassWithZeroParameterCtor::class.java)!!
val names = BeanUtils.getParameterNames(ctor)
assertThat(names).isEmpty()
}
class Foo(val param1: String, val param2: Int)
@@ -216,4 +251,6 @@ class BeanUtilsKotlinTests {
data class ConstructorWithNullablePrimitiveValueClass(val value: PrimitiveValueClass?)
class ClassWithZeroParameterCtor()
}
@@ -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.
@@ -28,7 +28,10 @@ public class InnerComponentConfiguration {
public class EnvironmentAwareComponent {
final Environment environment;
public EnvironmentAwareComponent(Environment environment) {
this.environment = environment;
}
}
@@ -40,7 +43,10 @@ public class InnerComponentConfiguration {
public class EnvironmentAwareComponentWithoutPublicConstructor {
final Environment environment;
EnvironmentAwareComponentWithoutPublicConstructor(Environment environment) {
this.environment = environment;
}
}
+7
View File
@@ -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"
}
}
@@ -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,6 +16,7 @@
package org.springframework.context.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
@@ -27,6 +28,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
@@ -286,8 +288,8 @@ class ConfigurationClassBeanDefinitionReader {
}
if (logger.isTraceEnabled()) {
logger.trace(String.format("Registering bean definition for @Bean method %s.%s()",
configClass.getMetadata().getClassName(), beanName));
logger.trace("Registering bean definition for @Bean method %s.%s()"
.formatted(configClass.getMetadata().getClassName(), beanName));
}
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
@@ -344,9 +346,8 @@ class ConfigurationClassBeanDefinitionReader {
"@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " +
"already exists. This top-level bean definition is considered as an override.",
beanMethod, beanName));
logger.debug("Skipping bean definition for %s: a definition for bean '%s' already exists. " +
"This top-level bean definition is considered as an override.".formatted(beanMethod, beanName));
}
return true;
}
@@ -372,9 +373,11 @@ class ConfigurationClassBeanDefinitionReader {
BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
if (reader == null) {
try {
Constructor<? extends BeanDefinitionReader> constructor =
readerClass.getDeclaredConstructor(BeanDefinitionRegistry.class);
// Instantiate the specified BeanDefinitionReader
reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
// Delegate the current ResourceLoader to it if possible
reader = BeanUtils.instantiateClass(constructor, this.registry);
// Delegate the current ResourceLoader and Environment to it if possible
if (reader instanceof AbstractBeanDefinitionReader abdr) {
abdr.setResourceLoader(this.resourceLoader);
abdr.setEnvironment(this.environment);
@@ -383,11 +386,9 @@ class ConfigurationClassBeanDefinitionReader {
}
catch (Throwable ex) {
throw new IllegalStateException(
"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]", ex);
}
}
// TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
reader.loadBeanDefinitions(resource);
});
}
@@ -16,6 +16,7 @@
package org.springframework.context.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -139,14 +140,20 @@ class ConfigurationClassEnhancer {
}
/**
* Checks whether the given config class relies on package visibility,
* either for the class itself or for any of its {@code @Bean} methods.
* Checks whether the given config class relies on package visibility, either for
* the class and any of its constructors or for any of its {@code @Bean} methods.
*/
private boolean reliesOnPackageVisibility(Class<?> configSuperClass) {
int mod = configSuperClass.getModifiers();
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
return true;
}
for (Constructor<?> ctor : configSuperClass.getDeclaredConstructors()) {
mod = ctor.getModifiers();
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
return true;
}
}
for (Method method : ReflectionUtils.getDeclaredMethods(configSuperClass)) {
if (BeanAnnotationHelper.isBeanAnnotated(method)) {
mod = method.getModifiers();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 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,14 +29,15 @@ import org.springframework.core.annotation.AliasFor;
* Indicates one or more resources containing bean definitions to import.
*
* <p>Like {@link Import @Import}, this annotation provides functionality similar to
* the {@code <import/>} element in Spring XML. It is typically used when designing
* {@link Configuration @Configuration} classes to be bootstrapped by an
* {@link AnnotationConfigApplicationContext}, but where some XML functionality such
* as namespaces is still necessary.
* the {@code <import/>} element in Spring XML configuration. It is typically used
* when designing {@link Configuration @Configuration} classes to be bootstrapped by
* an {@link AnnotationConfigApplicationContext}, but where some XML functionality
* such as namespaces is still necessary.
*
* <p>By default, arguments to the {@link #value} attribute will be processed using a
* <p>By default, arguments to the {@link #locations() locations} or {@link #value() value}
* attribute will be processed using a
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader}
* if ending in {@code ".groovy"}; otherwise, an
* for resource locations ending in {@code ".groovy"}; otherwise, an
* {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}
* will be used to parse Spring {@code <beans/>} XML files. Optionally, the {@link #reader}
* attribute may be declared, allowing the user to choose a custom {@link BeanDefinitionReader}
@@ -77,12 +78,19 @@ public @interface ImportResource {
/**
* {@link BeanDefinitionReader} implementation to use when processing
* resources specified via the {@link #value} attribute.
* resources specified via the {@link #locations() locations} or
* {@link #value() value} attribute.
* <p>The configured {@code BeanDefinitionReader} type must declare a
* constructor that accepts a single
* {@link org.springframework.beans.factory.support.BeanDefinitionRegistry
* BeanDefinitionRegistry} argument.
* <p>By default, the reader will be adapted to the resource path specified:
* {@code ".groovy"} files will be processed with a
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader};
* whereas, all other resources will be processed with an
* {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}.
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader
* GroovyBeanDefinitionReader}; whereas, all other resources will be processed
* with an {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* XmlBeanDefinitionReader}.
* @see #locations
* @see #value
*/
Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
@@ -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 =
@@ -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);
}
}
/**
@@ -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
@@ -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.
@@ -24,12 +24,12 @@ import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
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 +49,7 @@ import org.springframework.util.StringValueResolver;
* XSD documentation for complete details.
*
* <p>Any local properties (for example, those added via {@link #setProperties}, {@link #setLocations}
* et al.) are added as a {@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 +101,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 +133,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 +160,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 +173,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 +219,94 @@ 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
// Declare String as covariant return type, since a String is actually required.
public String getProperty(String name) {
for (PropertySource<?> propertySource : super.source.getPropertySources()) {
Object candidate = propertySource.getProperty(name);
if (candidate != null) {
return convertToString(candidate);
}
}
return null;
}
/**
* Convert the supplied value to a {@link String} using the {@link ConversionService}
* from the {@link Environment}.
* <p>This is a modified version of
* {@link org.springframework.core.env.AbstractPropertyResolver#convertValueIfNecessary(Object, Class)}.
* @param value the value to convert
* @return the converted value, or the original value if no conversion is necessary
* @since 6.2.8
*/
@Nullable
private String convertToString(Object value) {
if (value instanceof String string) {
return string;
}
return super.source.getConversionService().convert(value, String.class);
}
@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
// Declare String as covariant return type, since a String is actually required.
public String getProperty(String name) {
return super.source.getProperty(name);
}
@Override
public String toString() {
return "FallbackEnvironmentPropertySource {environment=" + super.source + "}";
}
}
}
@@ -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;
}
/**
@@ -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.
@@ -250,7 +250,7 @@ public abstract class ValidationUtils {
Assert.notNull(errors, "Errors object must not be null");
Object value = errors.getFieldValue(field);
if (value == null ||!StringUtils.hasText(value.toString())) {
if (value == null || !StringUtils.hasText(value.toString())) {
errors.rejectValue(field, errorCode, errorArgs, defaultMessage);
}
}
@@ -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>
@@ -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.
@@ -130,7 +130,7 @@ class SimpleKeyGeneratorTests {
private Object generateKey(Object[] arguments) {
Method method = ReflectionUtils.findMethod(this.getClass(), "generateKey", Object[].class);
Method method = ReflectionUtils.findMethod(getClass(), "generateKey", Object[].class);
return this.generator.generate(this, method, arguments);
}
@@ -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();
}
};
}
}
@@ -104,6 +104,31 @@ class ConfigurationClassEnhancerTests {
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
}
@Test
void withNonPublicConstructor() {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new OverridingClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
}
@Test
void withNonPublicMethod() {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
@@ -160,6 +185,19 @@ class ConfigurationClassEnhancerTests {
}
@Configuration
public static class MyConfigWithNonPublicConstructor {
MyConfigWithNonPublicConstructor() {
}
@Bean
public String myBean() {
return "bean";
}
}
@Configuration
public static class MyConfigWithNonPublicMethod {
@@ -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,6 @@
package org.springframework.context.annotation.configuration;
import java.util.Collections;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.junit.jupiter.api.Test;
@@ -25,18 +23,18 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
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.ImportResource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.testfixture.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ImportResource} support.
* Integration tests for {@link ImportResource @ImportResource} support.
*
* @author Chris Beams
* @author Juergen Hoeller
@@ -45,81 +43,88 @@ import static org.assertj.core.api.Assertions.assertThat;
class ImportResourceTests {
@Test
void importXml() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlConfig.class);
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
assertThat(tb.getName()).isEqualTo("myName");
ctx.close();
void importResource() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlConfig.class)) {
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
assertThat(tb.getName()).isEqualTo("myName");
}
}
@Test
void importXmlIsInheritedFromSuperclassDeclarations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FirstLevelSubConfig.class);
assertThat(ctx.containsBean("xmlDeclaredBean")).isTrue();
ctx.close();
void importResourceIsInheritedFromSuperclassDeclarations() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FirstLevelSubConfig.class)) {
assertThat(ctx.containsBean("xmlDeclaredBean")).isTrue();
}
}
@Test
void importXmlIsMergedFromSuperclassDeclarations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SecondLevelSubConfig.class);
assertThat(ctx.containsBean("secondLevelXmlDeclaredBean")).as("failed to pick up second-level-declared XML bean").isTrue();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("failed to pick up parent-declared XML bean").isTrue();
ctx.close();
void importResourceIsMergedFromSuperclassDeclarations() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SecondLevelSubConfig.class)) {
assertThat(ctx.containsBean("secondLevelXmlDeclaredBean")).as("failed to pick up second-level-declared XML bean").isTrue();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("failed to pick up parent-declared XML bean").isTrue();
}
}
@Test
void importXmlWithNamespaceConfig() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithAopNamespaceConfig.class);
Object bean = ctx.getBean("proxiedXmlBean");
assertThat(AopUtils.isAopProxy(bean)).isTrue();
ctx.close();
void importResourceWithNamespaceConfig() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithAopNamespaceConfig.class)) {
Object bean = ctx.getBean("proxiedXmlBean");
assertThat(AopUtils.isAopProxy(bean)).isTrue();
}
}
@Test
void importXmlWithOtherConfigurationClass() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithConfigurationClass.class);
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
assertThat(tb.getName()).isEqualTo("myName");
ctx.close();
void importResourceWithOtherConfigurationClass() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithConfigurationClass.class)) {
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
assertThat(tb.getName()).isEqualTo("myName");
}
}
@Test
void importWithPlaceholder() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
PropertySource<?> propertySource = new MapPropertySource("test",
Collections.<String, Object> singletonMap("test", "springframework"));
ctx.getEnvironment().getPropertySources().addFirst(propertySource);
ctx.register(ImportXmlConfig.class);
ctx.refresh();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
ctx.close();
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()) {
ctx.getEnvironment().getPropertySources().addFirst(new MockPropertySource("test").withProperty("test", "springframework"));
ctx.register(ImportXmlConfig.class);
ctx.refresh();
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
}
}
@Test
void importXmlWithAutowiredConfig() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlAutowiredConfig.class);
String name = ctx.getBean("xmlBeanName", String.class);
assertThat(name).isEqualTo("xml.declared");
ctx.close();
void importResourceWithAutowiredConfig() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlAutowiredConfig.class)) {
String name = ctx.getBean("xmlBeanName", String.class);
assertThat(name).isEqualTo("xml.declared");
}
}
@Test
void importNonXmlResource() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportNonXmlResourceConfig.class);
assertThat(ctx.containsBean("propertiesDeclaredBean")).isTrue();
ctx.close();
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportNonXmlResourceConfig.class)) {
assertThat(ctx.containsBean("propertiesDeclaredBean")).isTrue();
}
}
@Test
void importResourceWithPrivateReader() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportWithPrivateReaderConfig.class)) {
assertThat(ctx.containsBean("propertiesDeclaredBean")).isTrue();
}
}
@Configuration
@ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
static class ImportXmlConfig {
@Value("${name}")
private String name;
@Bean public TestBean javaDeclaredBean() {
return new TestBean(this.name);
}
@@ -146,6 +151,7 @@ class ImportResourceTests {
@Aspect
static class AnAspect {
@Before("execution(* org.springframework.beans.testfixture.beans.TestBean.*(..))")
public void advice() { }
}
@@ -158,18 +164,37 @@ class ImportResourceTests {
@Configuration
@ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
static class ImportXmlAutowiredConfig {
@Autowired TestBean xmlDeclaredBean;
@Bean public String xmlBeanName() {
@Autowired
TestBean xmlDeclaredBean;
@Bean
public String xmlBeanName() {
return xmlDeclaredBean.getName();
}
}
@SuppressWarnings("deprecation")
@Configuration
@ImportResource(locations = "classpath:org/springframework/context/annotation/configuration/ImportNonXmlResourceConfig-context.properties",
@ImportResource(locations = "org/springframework/context/annotation/configuration/ImportNonXmlResourceConfig.properties",
reader = org.springframework.beans.factory.support.PropertiesBeanDefinitionReader.class)
static class ImportNonXmlResourceConfig {
}
@SuppressWarnings("deprecation")
@Configuration
@ImportResource(locations = "org/springframework/context/annotation/configuration/ImportNonXmlResourceConfig.properties",
reader = PrivatePropertiesBeanDefinitionReader.class)
static class ImportWithPrivateReaderConfig {
}
@SuppressWarnings("deprecation")
private static class PrivatePropertiesBeanDefinitionReader
extends org.springframework.beans.factory.support.PropertiesBeanDefinitionReader {
PrivatePropertiesBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
}
}
@@ -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() {
@@ -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";
}
}
}
@@ -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,36 @@
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.ConversionService;
import org.springframework.core.convert.converter.Converter;
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 +54,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 +92,75 @@ class PropertySourcesPlaceholderConfigurerTests {
assertThat(ppc.getAppliedPropertySources()).isNotNull();
}
/**
* Ensure that a {@link Converter} registered in the {@link ConversionService}
* used by the {@code Environment} is applied during placeholder resolution
* against a {@link PropertySource} registered in the {@code Environment}.
*/
@Test // gh-34936
void replacementFromEnvironmentPropertiesWithConversion() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("testBean",
genericBeanDefinition(TestBean.class)
.addPropertyValue("name", "${my.name}")
.getBeanDefinition());
record Point(int x, int y) {
}
Converter<Point, String> pointToStringConverter =
point -> "(%d,%d)".formatted(point.x, point.y);
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(Point.class, String.class, pointToStringConverter);
MockEnvironment env = new MockEnvironment();
env.setConversionService(conversionService);
env.setProperty("my.name", new Point(4,5));
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
ppc.setEnvironment(env);
ppc.postProcessBeanFactory(bf);
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("(4,5)");
}
/**
* 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();
@@ -82,20 +170,35 @@ class PropertySourcesPlaceholderConfigurerTests {
.getBeanDefinition());
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
Resource resource = new ClassPathResource("PropertySourcesPlaceholderConfigurerTests.properties", this.getClass());
Resource resource = new ClassPathResource("PropertySourcesPlaceholderConfigurerTests.properties", getClass());
ppc.setLocation(resource);
ppc.postProcessBeanFactory(bf);
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 +384,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 +565,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 +851,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;
}
}
}
@@ -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.
@@ -32,6 +32,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.support.ParameterDeclarations;
import static java.time.Instant.MAX;
import static java.time.Instant.MIN;
@@ -91,7 +92,7 @@ class InstantFormatterTests {
private static final Random random = new Random();
@Override
public final Stream<Arguments> provideArguments(ExtensionContext context) {
public final Stream<Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
return provideArguments().map(Arguments::of).limit(DATA_SET_SIZE);
}
@@ -137,7 +138,7 @@ class InstantFormatterTests {
private static final Random random = new Random();
@Override
public Stream<Arguments> provideArguments(ExtensionContext context) {
public Stream<Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
return random.longs(DATA_SET_SIZE, Long.MIN_VALUE, Long.MAX_VALUE)
.mapToObj(Instant::ofEpochMilli)
.map(instant -> instant.truncatedTo(ChronoUnit.MILLIS))
@@ -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.
@@ -27,7 +27,7 @@ import org.springframework.core.testfixture.env.MockPropertySource;
* @author Chris Beams
* @author Sam Brannen
* @since 3.2
* @see org.springframework.core.testfixture.env.MockPropertySource
* @see MockPropertySource
*/
public class MockEnvironment extends AbstractEnvironment {
@@ -44,19 +44,23 @@ public class MockEnvironment extends AbstractEnvironment {
/**
* Set a property on the underlying {@link MockPropertySource} for this environment.
* @since 6.2.8
* @see MockPropertySource#setProperty(String, Object)
*/
public void setProperty(String key, String value) {
this.propertySource.setProperty(key, value);
public void setProperty(String name, Object value) {
this.propertySource.setProperty(name, value);
}
/**
* Convenient synonym for {@link #setProperty} that returns the current instance.
* Useful for method chaining and fluent-style use.
* Convenient synonym for {@link #setProperty(String, Object)} that returns
* the current instance.
* <p>Useful for method chaining and fluent-style use.
* @return this {@link MockEnvironment} instance
* @see MockPropertySource#withProperty
* @since 6.2.8
* @see MockPropertySource#withProperty(String, Object)
*/
public MockEnvironment withProperty(String key, String value) {
setProperty(key, value);
public MockEnvironment withProperty(String name, Object value) {
setProperty(name, value);
return this;
}
@@ -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.
@@ -81,7 +81,7 @@ public abstract class AbstractTypeReference implements TypeReference {
@Override
public int compareTo(TypeReference other) {
return this.getCanonicalName().compareToIgnoreCase(other.getCanonicalName());
return getCanonicalName().compareToIgnoreCase(other.getCanonicalName());
}
@Override
@@ -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.
@@ -150,7 +150,8 @@ public class BindingReflectionHintsRegistrar {
String companionClassName = clazz.getCanonicalName() + KOTLIN_COMPANION_SUFFIX;
if (ClassUtils.isPresent(companionClassName, null)) {
Class<?> companionClass = ClassUtils.resolveClassName(companionClassName, null);
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer");
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer",
(Class<?>[]) null);
if (serializerMethod != null) {
hints.registerMethod(serializerMethod, ExecutableMode.INVOKE);
}
@@ -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
@@ -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(
@@ -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
@@ -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
* &mdash; 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) {
@@ -160,7 +198,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
if (getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
@@ -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;
}
}
@@ -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
@@ -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
* &mdash; 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
@@ -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;
@@ -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
* &mdash; 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
*/
@@ -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;
@@ -86,7 +87,17 @@ 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).
JarFile jarFile = jarCon.getJarFile();
try {
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
}
finally {
if (!jarCon.getUseCaches()) {
jarFile.close();
}
}
}
else if (con.getContentLengthLong() > 0) {
return true;
@@ -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.
@@ -18,6 +18,8 @@ package org.springframework.core.io.buffer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;
import org.springframework.util.Assert;
@@ -103,10 +105,44 @@ final class DataBufferInputStream extends InputStream {
this.closed = true;
}
@Override
public byte[] readNBytes(int len) throws IOException {
if (len < 0) {
throw new IllegalArgumentException("len < 0");
}
checkClosed();
int size = Math.min(available(), len);
byte[] out = new byte[size];
this.dataBuffer.read(out);
return out;
}
@Override
public long skip(long n) throws IOException {
checkClosed();
if (n <= 0) {
return 0L;
}
int skipped = Math.min(available(), n > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) n);
this.dataBuffer.readPosition(Math.min(this.end, this.dataBuffer.readPosition() + skipped));
return skipped;
}
@Override
public long transferTo(OutputStream out) throws IOException {
Objects.requireNonNull(out, "out");
checkClosed();
if (available() == 0) {
return 0L;
}
byte[] buf = readAllBytes();
out.write(buf);
return buf.length;
}
private void checkClosed() throws IOException {
if (this.closed) {
throw new IOException("DataBufferInputStream is closed");
}
}
}
@@ -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}
*/
@@ -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-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.
@@ -18,8 +18,6 @@ package org.springframework.lang;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
@@ -77,7 +75,6 @@ import java.lang.annotation.Target;
* NullAway custom contract annotations</a>
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Contract {
@@ -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;
}
}
@@ -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.
@@ -25,13 +25,14 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import org.springframework.lang.Contract;
import org.springframework.lang.Nullable;
/**
* Simple utility methods for dealing with streams. The copy methods of this class are
* similar to those defined in {@link FileCopyUtils} except that all affected streams are
* left open when done. All copy methods use a block size of 8192 bytes.
* Simple utility methods for dealing with streams.
*
* <p>The copy methods of this class are similar to those defined in
* {@link FileCopyUtils} except that all affected streams are left open when done.
* All copy methods use a block size of {@value #BUFFER_SIZE} bytes.
*
* <p>Mainly for use within the framework, but also useful for application code.
*
@@ -190,14 +191,14 @@ public abstract class StreamUtils {
}
/**
* Drain the remaining content of the given InputStream.
* <p>Leaves the InputStream open when done.
* @param in the InputStream to drain
* @return the number of bytes read
* Drain the remaining content of the given {@link InputStream}.
* <p>Leaves the {@code InputStream} open when done.
* @param in the {@code InputStream} to drain
* @return the number of bytes read, or {@code 0} if the supplied
* {@code InputStream} is {@code null} or empty
* @throws IOException in case of I/O errors
* @since 4.3
*/
@Contract("null -> fail")
public static int drain(@Nullable InputStream in) throws IOException {
if (in == null) {
return 0;
@@ -239,7 +240,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 +249,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();
@@ -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.
@@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class AnnotationBackCompatibilityTests {
@Test
void multiplRoutesToMetaAnnotation() {
void multipleRoutesToMetaAnnotation() {
Class<?> source = WithMetaMetaTestAnnotation1AndMetaTestAnnotation2.class;
// Merged annotation chooses lowest depth
MergedAnnotation<TestAnnotation> mergedAnnotation = MergedAnnotations.from(source).get(TestAnnotation.class);
@@ -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.
@@ -983,7 +983,7 @@ class MergedAnnotationsTests {
}
@Test
void getDirectFromClassgetDirectFromClassMetaMetaAnnotatedClass() {
void getDirectFromClassWithMetaMetaAnnotatedClass() {
MergedAnnotation<?> annotation = MergedAnnotations.from(
MetaMetaAnnotatedClass.class, SearchStrategy.TYPE_HIERARCHY).get(Component.class);
assertThat(annotation.getString("value")).isEqualTo("meta2");

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