Compare commits

...

136 Commits

Author SHA1 Message Date
Brian Clozel 0a471c95c1 Release v6.2.13 2025-11-13 09:42:26 +01:00
Brian Clozel da31750e5f Upgrade to Micrometer 1.14.13
Closes gh-35810
2025-11-13 09:15:09 +01:00
Brian Clozel b853203846 Upgrade to Reactor 2024.0.12
Closes gh-35809
2025-11-13 09:14:04 +01:00
Brian Clozel 2b04df045a Upgrade to Jetty 12.0.30
Closes gh-35806
2025-11-12 21:57:31 +01:00
Juergen Hoeller 40544e096f Fix typo in ProxyMethodInvocation javadoc 2025-11-11 19:42:02 +01:00
Juergen Hoeller 23354b0155 Fix doTask method visibility (referring to private Task type)
See gh-35794
2025-11-11 19:41:44 +01:00
Juergen Hoeller 5c5367a1be Polishing 2025-11-11 15:40:08 +01:00
Juergen Hoeller 12dd758158 Provide compute method implementations in ConcurrentReferenceHashMap
Closes gh-35794
2025-11-11 15:39:21 +01:00
Sébastien Deleuze 5aec239261 Add hints for entities package-private methods
Closes gh-35711
2025-11-11 13:52:59 +01:00
Dmitry Sulman d0ff8f9243 Fix HttpServiceMethod for suspending functions returning Flow
Closes gh-35718
Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
2025-11-11 12:58:42 +01:00
Brian Clozel ba39385cce Use executor for blocking I/O in Reactor request factory
Prior to this commit, the `ReactorClientHttpRequestFactory` and the
`ReactorClientHttpRequest` would use the `Executor` from the current
event loop for performing write operations.
Depending on I/O demand, this work could be blocked and would result in
blocked Netty event loop executors and the HTTP client hanging.

This commit ensures that the client uses a separate Executor for such
operations. If the application does not provide one on the request
factory, a `Schedulers#boundedElastic` instance will be used.

Fixes gh-34707
2025-11-10 16:12:14 +01:00
rstoyanchev e735c2d9c5 Improve Filter overview in reference docs
Closes gh-30454
2025-11-10 14:43:34 +00:00
rstoyanchev 3cb498fa98 Improve docs on streaming via StreamingHttpOutputMessage
Closes gh-35700
2025-11-10 14:43:34 +00:00
Sam Brannen 335a2c4e21 Support response encoding in select and options JSP form tags
Prior to this commit, Spring Framework's JSP form tags supported the
response encoding in most places; however, <form:select> and
<form:options> still did not support the response character encoding.

To address that, this commit updates SelectTag, OptionsTag, and
OptionWriter to provide support for response character encoding in the
`select` and `options` JSP form tags.

See gh-33023
Closes gh-35783
2025-11-10 15:35:13 +01:00
Sam Brannen 1714a00492 Suppress warnings in Gradle build 2025-11-10 14:13:34 +01:00
Chandra Bhan Singh ddb45a9302 Correct documentation formatting for Mono type
Closes gh-35786
Signed-off-by: Chandra Bhan Singh <cbhansingh1@gmail.com>
2025-11-10 12:03:37 +01:00
Juergen Hoeller 38555df3d8 Fix Aware exclusion check to apply to interface method only
See gh-35780
2025-11-08 09:53:17 +01:00
Juergen Hoeller 6f81cb625c Avoid unnecessary bridge method resolution around getMostSpecificMethod
Closes gh-35780
2025-11-08 09:16:31 +01:00
Juergen Hoeller 566078b4e4 Add explicit note on empty constructor
See gh-35773
2025-11-08 09:14:53 +01:00
Brian Clozel f0cf7f19f6 Fix multi-release JAR issue with VirtualThreadDelegate
This commit ensures that both `VirtualThreadDelegate` implementations
expose the same public API. If not, JAR verification fails with the
following message:

```
jar --validate --file spring-core-6.2.13-SNAPSHOT.jar
entry: META-INF/versions/21/org/springframework/core/task/VirtualThreadDelegate.class, contains a class with different api from earlier version
```

Fixes gh-35773
2025-11-07 14:01:51 +01:00
Juergen Hoeller b5008d33ee Upgrade to ASM 9.9 plus lenient version check patch
Closes gh-35763
2025-11-06 10:41:34 +01:00
Juergen Hoeller bce1445d92 Accept fallback match for bean name or method-level qualifier as well
Closes gh-35690
2025-11-05 19:31:53 +01:00
Brian Clozel 285182be27 Relax multiple segment matching constraints in PathPattern
Prior to this commit, gh-35213 allowed wildcard path elments at the
start of path patterns. This came with an additional constraint that
rejected such patterns if the pattern segment following the wildcard one
was not a literal:

* `/**/{name}` was rejected
* `/**/something/{name}` was accepted

The motivation here was to make the performance impact of wildard
patterns as small as possible at runtime.

This commit relaxes this constraint because `/**/*.js` patterns are very
popular in the security space for request matchers.

Closes gh-35686
2025-11-05 18:32:44 +01:00
Brian Clozel 8bb63081a8 Document PathPattern matching for single/multiple segments
This commit improves the reference document to better reflect the
different between `*` or `{name}` on one side, and `**` or `{*path}` on
the other.

The former patterns only consider a single path segment and its content,
while the latter variants consider zero or more path segments. This
explains why `/test/{*path}` can match `/test`.

Closes gh-35727
2025-11-05 14:58:58 +01:00
rstoyanchev a698b1bc0d Refine validation section for controllers
Closes gh-35759
2025-11-05 12:23:43 +00:00
Juergen Hoeller 2060c340ce Upgrade to Caffeine 3.2.3, Selenium 4.38, HtmlUnit 4.18 2025-11-05 12:44:28 +01:00
Juergen Hoeller 4f97c41e58 Avoid javadoc failure for line break with annotation reference
See gh-35736
2025-11-05 12:35:09 +01:00
Juergen Hoeller 0243059f53 Polishing 2025-11-05 12:22:56 +01:00
Juergen Hoeller b027b73a7a Relax configuration class requirement in configurer callback javadoc
Closes gh-35736
2025-11-05 12:22:44 +01:00
Brian Clozel c0429dbb09 Fix file extension resolution for media types with q parameter
Prior to this commit, the `MappingMediaTypeFileExtensionResolver` would
resolve file extensions for a given media type by using a direct lookup
using the given media type provided by the request.
If the request contains a quality parameter like
"application/json;q=0.9", this would not resolve configured file
extensions for this media type.
While other media type parameters can be meaningful, the quality
parameter should not be used for lookups. This commit ensures that the
quality parameter is dropped before performing lookups.

Fixes gh-35754
2025-11-05 10:48:24 +01:00
Brian Clozel d65de19e7d Deprecate setConnectTimeout on HttpComponents client factory
Prior to this commit, the `HttpComponentsClientHttpRequestFactory` would
set the connection timeout on the request configuration. This has been
deprecated by the client itself and this value should be set while
creating the client on the connection manager itself.

This commit deprecates this method, as there is no way for the factory
to set this value anymore.

Closes gh-35748
2025-11-03 16:53:54 +01:00
Sébastien Deleuze 16822c2fd0 Polishing
Closes gh-35746
2025-11-03 11:14:30 +01:00
Moad Elfatihi 983d39a970 Update CI documentation reference in README
Replace obsolete Concourse pipeline reference with GitHub Actions.
The Concourse link returns 404.

See gh-35746
Signed-off-by: Moad ELFATIHI <elfatihi.moad@gmail.com>
2025-11-03 11:14:14 +01:00
Juergen Hoeller 4b7cf85d71 Retrieve cached response body in a thread-safe manner
Closes gh-35745
2025-11-02 20:40:02 +01:00
Juergen Hoeller a33027703d Preserve Connection readOnly state for defaultReadOnly DataSource
Includes DataSourceTransactionManagerTests alignment with main branch.

Closes gh-35743
2025-11-02 17:28:12 +01:00
Sam Brannen ae804cb2d0 Improve @⁠NumberFormat Javadoc
See gh-35742
2025-11-02 16:23:47 +01:00
Stefano Cordio 46503d6a45 Fix typo in @⁠NumberFormat Javadoc
Closes gh-35742

Signed-off-by: Stefano Cordio <stefano.cordio@gmail.com>
2025-11-02 16:14:11 +01:00
Sam Brannen b4a13dddfc Fix JUnit Jupiter discovery issues on 6.2.x 2025-11-01 14:46:23 +01:00
Sam Brannen 661dca5e54 Remove accidental use of SpringExtension in ServletContextAwareBeanWacTests 2025-11-01 14:26:08 +01:00
Sam Brannen b976010a9e Simplify MockMvcClientHttpRequestFactoryTests 2025-11-01 14:26:07 +01:00
Juergen Hoeller 15563ee54e Polishing 2025-11-01 12:43:51 +01:00
Artur Signell 97586b525f fix: Fix PathMatchingResourcePatternResolver to handle absolute paths in JAR manifests
When JAR manifest Class-Path entries contain absolute paths (as Gradle
creates on Windows for long classpaths), PathMatchingResourcePatternResolver
incorrectly rejected them.

Fixes #35730

Signed-off-by: Artur Signell <artur@vaadin.com>
2025-11-01 12:25:20 +01:00
Sam Brannen f89737e398 Upgrade to JUnit 5.14.1
Closes gh-35739
2025-11-01 11:05:43 +01:00
Stéphane Nicoll ecdf069c5d Add AOT support for AutowiredPropertyMarker
Closes gh-35731
2025-10-31 15:30:54 +01:00
Anton Xu 01a58a253b Fix minor typo in RestClient
Closes gh-35723
Signed-off-by: Anton Xu <anton-xu@hotmail.com>
2025-10-30 19:41:56 +01:00
Juergen Hoeller f28e245baa Upgrade to Groovy 4.0.29, Jetty 12.0.29, Jackson 2.18.5 2025-10-30 17:30:21 +01:00
Park Juhyeong 14579b7848 Fix concurrency permit leak causing permanent deadlock in SimpleAsyncTaskExecutor
When concurrency limiting is enabled via setConcurrencyLimit() and
thread creation fails in doExecute() (e.g., OutOfMemoryError from
Thread.start()), the concurrency permit acquired by beforeAccess()
is never released because TaskTrackingRunnable.run() never executes.

This causes the concurrency count to permanently remain at the limit,
causing all subsequent task submissions to block forever in
ConcurrencyThrottleSupport.onLimitReached().

Root cause:
- beforeAccess() increments concurrencyCount
- doExecute() throws Error before thread starts
- TaskTrackingRunnable.run() never executes
- afterAccess() in finally block never called
- Concurrency permit permanently leaked

Solution:
Wrap doExecute() in try-catch block in the concurrency throttle path
and call afterAccess() in catch block to ensure permit is always
released, even when thread creation fails.

The fix only applies to the concurrency throttle path. The
activeThreads-only path does not need fixing because it never calls
beforeAccess(), so there is no permit to leak.

Test approach:
The test simulates thread creation failure and verifies that a
subsequent execution does not deadlock. The first execution should
fail with some exception (type doesn't matter), and the second
execution should complete within timeout if the permit was properly
released.

Signed-off-by: Park Juhyeong <wngud5957@naver.com>
2025-10-30 17:29:39 +01:00
Juergen Hoeller 5471961f06 Restore exposure of original BatchUpdateException as root cause
Closes gh-35717
See gh-35547
2025-10-30 14:59:00 +01:00
rstoyanchev 715bc68c16 Rename methods in FragmentsRendering
Previous commit 81ea35c726 in main for 7.0
should have been applied in 6.2.x first for 6.2.1.

This commit applies the changes in 6.2.x as intended,
effective as of 6.2.13.

Closes gh-33974
2025-10-30 12:17:51 +00:00
Sam Brannen 9c24f7ba8c Document test-method scoped TestContext semantics
Although gh-35680 introduced support for JUnit Jupiter's TEST_METHOD
ExtensionContextScope in the SpringExtension and
BeanOverrideTestExecutionListener, those were internal changes that are
not directly visible by TestExecutionListener authors.

However, in order to be compatible with a test-method scoped
TestContext (which takes its values from the current Jupiter
ExtensionContext), existing third-party TestExecutionListener
implementations may need to be modified similar to how
BeanOverrideTestExecutionListener was modified in commit d24a31d469.

To raise awareness of how to deal with such issues, this commit
documents test-method TestContext semantics for TestExecutionListener
authors.

Closes gh-35716
2025-10-30 11:22:25 +01:00
Juergen Hoeller a40647a62f Remove empty ConstraintValidator#initialize implementations 2025-10-28 20:38:08 +01:00
Juergen Hoeller b53fb13f30 Add tests for name-based dependency resolution against util:map
See gh-35690
2025-10-28 20:34:49 +01:00
Park Juhyeong 4b7b280ac3 Optimize resource URL resolution in SortedResourcesFactoryBean
Cache resource URLs before sorting to eliminate repeated I/O calls
during comparator operations. The previous implementation called
getURL() multiple times per resource during sorting (O(n log n)
calls), and silently swallowed IOExceptions by returning 0,
potentially causing unstable sort results.

This change:
- Caches URLs once per resource before sorting (O(n) I/O calls)
- Removes unnecessary ArrayList conversions
- Provides clear exception handling with context
- Improves performance by ~70% for typical use cases

Signed-off-by: Park Juhyeong <wngud5957@naver.com>
2025-10-28 20:30:33 +01:00
Sébastien Deleuze b3aefac9c3 Remove jibx-marshaller element from spring-oxm.xsd
Closes gh-35699
2025-10-28 15:38:53 +01:00
rstoyanchev b19059f6cb Handle null from JDK HttpResponse#body
Closes gh-35692
2025-10-28 11:44:16 +00:00
Sam Brannen b1f5b61bcd Abort search for static methods in getPubliclyAccessibleMethodIfPossible()
Prior to this commit, getPubliclyAccessibleMethodIfPossible() in
ClassUtils incorrectly returned a hidden static method as an
"equivalent" method for a static method with the same signature;
however, a static method cannot be overridden and therefore has no
"equivalent" method in a super type.

To fix that bug, this commit immediately aborts the search for an
"equivalent" publicly accessible method when the original method is a
static method.

See gh-33216
See gh-35189
See gh-35556
Closes gh-35667
2025-10-26 15:21:17 +01:00
rstoyanchev 810e069bcc FieldError uses HexFormat to format byte[]
See gh-35675
2025-10-24 13:01:28 +01:00
Elijah Mock 62f42cab6b Fix minor typo in JDBC Core Classes documentation
This commit adds a missing closing parenthesis in an example.

Closes gh-35684

Signed-off-by: Elijah Mock <28277163+ekcom@users.noreply.github.com>
2025-10-24 13:56:25 +02:00
Sam Brannen 9ee16b8ea3 Switch back to HSQL for JpaEntityListenerTests on main 2025-10-22 19:14:26 +02:00
Sam Brannen e61884e4bd Introduce tests for "No transaction in progress for @⁠Nested test class"
Since we now officially support the TEST_METHOD ExtensionContextScope
(see gh-35676 and gh-35680), this commit introduces tests which
demonstrate that the issue raised in gh-34576 is no longer an "issue" if
the user indirectly configures the SpringExtension to use the TEST_METHOD
scope via the "junit.jupiter.extensions.testinstantiation.extensioncontextscope.default"
configuration parameter.
2025-10-22 18:58:08 +02:00
Sam Brannen 0f2fc79fb7 Update exception message to use correct test class
See gh-35680
2025-10-22 18:04:13 +02:00
Sam Brannen ba70c1384a Polish SpringExtension internals 2025-10-22 17:07:53 +02:00
Sam Brannen d24a31d469 Support JUnit Jupiter ExtensionContextScope.TEST_METHOD
Historically, @⁠Autowired fields in an enclosing class of a @⁠Nested
test class have been injected from the ApplicationContext for the
enclosing class. If the enclosing test class and @⁠Nested test class
share the same ApplicationContext configuration, things work as
developers expect. However, if the enclosing class and @⁠Nested test
class have different ApplicationContexts, that can lead to
difficult-to-debug scenarios. For example, a bean injected into the
enclosing test class will not participate in a test-managed transaction
in the @⁠Nested test class (see gh-34576).

JUnit Jupiter 5.12 introduced a new ExtensionContextScope feature which
allows the SpringExtension to behave the same for @⁠Autowired fields as
it already does for @⁠Autowired arguments in lifecycle and test
methods. Specifically, if a developer sets the ExtensionContextScope to
TEST_METHOD — for example, by configuring the following configuration
parameter as a JVM system property or in a `junit-platform.properties`
file — the SpringExtension already supports dependency injection from
the current, @⁠Nested ApplicationContext in @⁠Autowired fields in an
enclosing class of the @⁠Nested test class.

junit.jupiter.extensions.testinstantiation.extensioncontextscope.default=test_method

However, there are two scenarios that fail as of Spring Framework
6.2.12.

1. @⁠TestConstructor configuration in @⁠Nested class hierarchies.
2. Field injection for bean overrides (such as @⁠MockitoBean) in
   @⁠Nested class hierarchies.

Commit 82c34f7b51 fixed the SpringExtension to support scenario #2
above.

To fix scenario #1, this commit revises
BeanOverrideTestExecutionListener's injectField() implementation to
look up the fields to inject for the "current test instance" instead of
for the "current test class".

This commit also introduces tests for both scenarios.

See gh-34576
See gh-35676
Closes gh-35680
2025-10-22 17:00:44 +02:00
Brian Clozel a5141b187a Fix '**' parsing within a PathPattern segment
Prior to this commit, a regexp path segment ending with a double wilcard
(like "/path**") would be incorrectly parsed as a double wildcard
segment ("/**").

This commit fixes the incorrect parsing.

See gh-35679
2025-10-22 12:21:57 +02:00
Brian Clozel d3c1e678c2 Support wildcard path elements at the start of path patterns
Prior to this commit, the `PathPattern` and `PathPatternParser` would
allow multiple-segments matching and capturing with the following:

* "/files/**" (matching 0-N segments until the end)
* "/files/{*path}" (matching 0-N segments until the end and capturing
  the value as the "path" variable)

This would be only allowed as the last path element in the pattern and
the parser would reject other combinations.

This commit expands the support and allows multiple segments matching at
the beginning of the path:

* "/**/index.html" (matching 0-N segments from the start)
* "/{*path}/index.html" (matching 0-N segments until the end and capturing
  the value as the "path" variable)

This does come with additional restrictions:

1. "/files/**/file.txt" and "/files/{*path}/file.txt" are invalid,
   as multiple segment matching is not allowed in the middle of the
   pattern.
2. "/{*path}/files/**" is not allowed, as a single "{*path}" or "/**"
   element is allowed in a pattern
3. "/{*path}/{folder}/file.txt"  "/**/{folder:[a-z]+}/file.txt" are
   invalid because only a literal pattern is allowed right after
   multiple segments path elements.

Closes gh-35679
2025-10-22 12:21:57 +02:00
Sam Brannen 82c34f7b51 Introduce isAutowirableConstructor(Executable,PropertyProvider) in TestConstructorUtils
This commit introduces a new isAutowirableConstructor(Executable, PropertyProvider)
overload in TestConstructorUtils and deprecates all other existing variants
for removal in 7.1.

Closes gh-35676
2025-10-21 17:37:04 +02:00
Sam Brannen cb0f26a152 Use consistent naming for Bean Override test classes
cherry-picked from c6e5cfe03d
2025-10-21 16:25:12 +02:00
Sam Brannen bccfbfd630 Fix previous commit for 6.2.x compatibility
See gh-35674
2025-10-21 15:37:35 +02:00
Brian Clozel 506b76032f Add missing reflection hints for JdbcUtils
This commit adds the missing reflection hints for `JdbcUtils`, as this
class reflects on `java.sql.Types` public fields.

Fixes gh-35674
2025-10-21 13:50:13 +02:00
Juergen Hoeller 7adcd99ea2 Polishing 2025-10-21 00:59:56 +02:00
Juergen Hoeller 765e30c3a6 Polishing 2025-10-18 15:30:15 +02:00
HJC96 d7fc07e73c Fix typos
Closes gh-35656
Signed-off-by: HJC96 <gkswlcjs2@naver.com>
2025-10-17 15:46:27 +02:00
Brian Clozel 18a879779e Next development version (v6.2.13-SNAPSHOT) 2025-10-16 09:32:20 +02:00
Juergen Hoeller c53132ecdf Polishing 2025-10-15 19:40:09 +02:00
rstoyanchev 03c3ec1577 Fall back on remoteHost if remoteAddress is null
See gh-35589
2025-10-15 16:42:40 +01:00
rstoyanchev c88bfc54c9 Refactor state management in StompSubProtocolHandler
Closes gh-35591
2025-10-15 12:31:42 +01:00
Sam Brannen a96558c965 Revise commit 01acb80501
See gh-35631
2025-10-15 12:48:52 +02:00
Sam Brannen 1612b7c5db Remove test prefixes 2025-10-15 11:28:49 +02:00
Sam Brannen 01acb80501 Improve exception handling in ConfigurationClassBeanDefinitionReader
Thanks to a proposal from @wilkinsona, this commit introduces a
try-catch block in loadBeanDefinitions(...) which throws an
IllegalStateException that provides context regarding the configuration
class and cause of the failure.

Closes gh-35631

Co-authored-by: Andy Wilkinson <andy.wilkinson@broadcom.com>
2025-10-15 11:26:06 +02:00
Juergen Hoeller f4438ce9e3 Upgrade to Reactor 2024.0.11 and Micrometer 1.14.12
Includes Netty 4.1.128

Closes gh-35638
Closes gh-35640
2025-10-14 19:21:34 +02:00
Juergen Hoeller 5033b9d3c5 Reset by-type cache for Object after registering a new singleton
Closes gh-35634
2025-10-14 19:20:57 +02:00
Juergen Hoeller d733023a29 Consistently apply fallback attribute to proxy definition as well
See gh-35627
2025-10-14 19:17:57 +02:00
Schäfer, H.H. (Hans Hosea) df67c1cf2d 35626: defaultCandidate for scoped proxies
Signed-off-by: Schäfer, H.H. (Hans Hosea) <HansHosea.Schaefer@ing.de>
2025-10-14 19:07:03 +02:00
rstoyanchev 05814f7a42 Polishing contribution
Closes gh-35625
2025-10-14 16:49:10 +01:00
Marius Lichtblau ba2bb08589 Release DataBuffer in AbstractCharSequenceDecoder
if String creation fails

See gh-35625

Signed-off-by: Marius Lichtblau <marius@lichtblau.io>
2025-10-14 16:49:10 +01:00
Edgar Asatryan 141df5291d Prevent NoClassDefFoundError when Jetty Reactive HttpClient not on classpath
Closes gh-35608

Signed-off-by: Edgar Asatryan <nstdio@gmail.com>
2025-10-14 16:49:10 +01:00
Sébastien Deleuze dcfe33f427 Fix Hibernate Javadoc to avoid a redirect 2025-10-14 17:40:44 +02:00
Juergen Hoeller 3a61460f91 Use JarFile#entries() instead of #stream() for consistent entry paths
Closes gh-35617
2025-10-14 11:23:34 +02:00
Brian Clozel ee284f2ee6 Add "forEachByte" variant to DataBuffer
As reported in gh-34651, `DataBuffer#getByte` can be inefficient for
some implementations, as bound checks are performed for each call.

This commit introduces a new `forEachByte` method that helps with
traversing operations without paying the bound check cost for each byte.

Closes gh-35623
2025-10-13 18:28:34 +02:00
Sam Brannen 2591cab561 Polishing 2025-10-13 15:12:21 +02:00
Sam Brannen 8456cd1e74 Polish contribution
See gh-35587
2025-10-13 15:12:21 +02:00
Igor Murzich 7699b4af9c Retain order of produces media types in @⁠ExceptionHandler
Closes gh-35587

Signed-off-by: Igor Murzich <imurzich@gmail.com>
2025-10-13 15:12:21 +02:00
Juergen Hoeller 6c5de48059 Ignore SQL state 3B001 in HSQLDB exception message as well
Closes gh-35618
See gh-35564
2025-10-13 15:10:20 +02:00
Juergen Hoeller 9f678ce698 Upgrade to Jetty 12.0.28, Undertow 2.3.20, RxJava 3.1.12, Aalto 1.3.4, Gson 2.13.2, Selenium 4.36, HtmlUnit 4.17, Mockito 5.20, AssertJ 3.27.6 2025-10-11 15:12:21 +02:00
Juergen Hoeller 467a484df6 Polishing
See gh-35460
2025-10-11 15:11:16 +02:00
Juergen Hoeller 83efe8cff4 Remove deprecation on CandidateComponentsIndex(Loader)
See gh-35472
2025-10-11 15:10:42 +02:00
rstoyanchev 2c83144946 Use getRemoteAddr in ServletServerHttpRequest
Closes gh-35589
2025-10-10 17:24:48 +01:00
rstoyanchev c165dd5e0e Update Javadoc of reactive WebSocketHandler
Replace suggestion to use zip with and operator.

Closes gh-35572
2025-10-10 17:23:03 +01:00
Juergen Hoeller 449b85f446 Avoid overhead for parsing plain values and simple placeholders
Closes gh-35594
2025-10-10 17:03:34 +02:00
Juergen Hoeller c2a66e723f Polishing 2025-10-09 20:34:03 +02:00
Juergen Hoeller 2ee34a5632 Add MySQLTableMetaDataProvider for correct generated-keys support
Closes gh-35593
2025-10-09 20:30:25 +02:00
Juergen Hoeller 1bc82d241a Remove deprecation on CandidateComponentsIndex(Loader)
Closes gh-35472
2025-10-09 20:26:59 +02:00
Juergen Hoeller 80e7ee321e Mark bootstrap thread for entire finishBeanFactoryInitialization phase
Closes gh-35398
2025-10-06 14:08:03 +02:00
Juergen Hoeller ecd3dd8883 Consistent local synchronization in getObjectFromFactoryBean
Closes gh-35545
2025-10-03 17:29:14 +02:00
Juergen Hoeller 332953c9a4 Align BatchUpdateException handling among SQLExceptionTranslator variants
Closes gh-35547
2025-10-03 14:51:15 +02:00
Sam Brannen 1cdd56bf02 Log multiple primary bean detection in DefaultListableBeanFactory
Prior to this commit, a NoUniqueBeanDefinitionException was thrown when
multiple primary beans were detected within a given set of beans, but
nothing was logged. For use cases where the exception is handled by
infrastructure code, it may not be obvious to the developer what the
problem is.

To address that, a TRACE message is now logged whenever multiple
competing primary beans are detected in DefaultListableBeanFactory.

Closes gh-35550
2025-10-03 14:09:52 +02:00
Juergen Hoeller 3041071269 Consistently apply useCaches for single Resource lookup
Closes gh-35465
2025-10-02 21:53:47 +02:00
Juergen Hoeller 2da821389c Remove redundant object allocation in cglib proxy method calls
Reformatted with Spring patch markers and backported to 6.2.12

Closes gh-35543
2025-10-02 21:53:34 +02:00
Juergen Hoeller d484e4f3ff Ignore SQL state 3B001 in releaseSavepoint (for HSQLDB)
Closes gh-35564
2025-10-02 13:01:30 +02:00
Juergen Hoeller 74dc61b8c4 Fall back to local synchronization in getObjectFromFactoryBean
Closes gh-35545
2025-10-02 13:01:03 +02:00
Sam Brannen 717358b56b Short circuit ClassUtils.findPubliclyAccessibleMethodIfPossible(...)
Once we find a publicly accessible method, there is no need to continue
traversing the type hierarchy.

See gh-35556
2025-10-02 11:44:54 +02:00
Sébastien Deleuze 836634c47f Upgrade CI workflow from Java 24 to Java 25 2025-10-02 10:19:42 +02:00
Juergen Hoeller a6f6ecfe6c Revise getPubliclyAccessibleMethodIfPossible to rely on Module#isExported
This avoids reflection and cache access for regular public and exported types.

Closes gh-35556
2025-10-01 19:56:23 +02:00
Juergen Hoeller e3da26ebbd Clarify event parameter type for multiple mapped classes
Closes gh-35506
2025-10-01 19:55:42 +02:00
Maziyar Bahramian e1c008f5a3 Fix URI Patterns docs for WebMVC and WebFlux
Closes gh-35551
Signed-off-by: Maziyar Bahramian <maziyar.bahramian@gmail.com>
2025-10-01 19:39:57 +02:00
Sébastien Deleuze cb849a7071 Add a test for CGLIB Enhancer Kotlin refinements
This commit adds a reproducer for the change of behavior introduced via
https://youtrack.jetbrains.com/issue/KT-76667. The test is only broken
with Kotlin 2.2.20+ without the related fix (see previous commit).

Closes gh-35487
2025-10-01 19:21:40 +02:00
Juergen Hoeller 3e37279db6 Make CGLIB Enhancer compatible with Kotlin 2.2.20+
This commit refines Enhancer#emitMethods to support the changes
introduced by https://youtrack.jetbrains.com/issue/KT-76667.

See gh-35487
2025-10-01 19:14:14 +02:00
rstoyanchev b3264ec2a8 Fix typo 2025-10-01 16:13:40 +01:00
rstoyanchev df860fd3cd Polishing contribution
Closes gh-35493
2025-10-01 14:45:18 +01:00
Réda Housni Alaoui 636523a2f5 AbstractMockHttpServletRequestBuilder#buildRequest is not idempotent
See gh-35493

Signed-off-by: Réda Housni Alaoui <reda-alaoui@hey.com>
2025-10-01 14:44:23 +01:00
rstoyanchev a19b51b7e0 Handle invalid position in ResourceHttpMessageWriter
Closes gh-35536
2025-10-01 14:44:05 +01:00
rstoyanchev fbdece6759 Polishing in ResourceHttpMessageWriter
See gh-35536
2025-10-01 14:42:01 +01:00
Sam Brannen 64d42fefda Fix idref example in reference manual
Closes gh-35560
2025-10-01 13:19:54 +02:00
Sam Brannen 447cfa18e9 Polishing 2025-10-01 13:19:25 +02:00
Sam Brannen ec3d9d6253 Upgrade to JUnit 5.14
Closes gh-35555
2025-09-30 12:43:53 +02:00
Sam Brannen 5cd2cb38e1 Make AbstractTestNGSpringContextTests thread-safe regarding tracked exceptions
Prior to this commit, AbstractTestNGSpringContextTests was not
thread-safe with regard to tracked exceptions.

To address that, AbstractTestNGSpringContextTests now tracks the test
exception via a ThreadLocal.

Closes gh-35528
2025-09-25 17:51:39 +02:00
rstoyanchev e9fb5eb38a Respect forwarded path in UrlHandlerFilter
Closes gh-35509
2025-09-25 12:19:56 +01:00
rstoyanchev 5a858915ea Revise parsed path handling in UrlHandlerFilter
Closes gh-35538
2025-09-24 19:08:55 +01:00
Brian Clozel d85a020e4e Improve Task Javadoc about Runnable wrapping
Closes gh-35394
2025-09-19 18:05:02 +02:00
Brian Clozel 0cc79ba366 Catch EOFException in IntrospectingClientHttpResponse
Prior to this commit, the `IntrospectingClientHttpResponse` would try
and read the HTTP response stream in order to check for the presence of
a non-empty message body.

Developers reported that in some cases, an `EOFException` is thrown
instead of returning -1 from the `read()` method. This commit ensures
that this case is taken into account and that we report the response as
an empty body in these cases.

Closes gh-35361
2025-09-19 17:49:12 +02:00
Sam Brannen 1e29911292 Upgrade to AssertJ 3.27.5 2025-09-19 11:23:08 +02:00
Juergen Hoeller 931686a5ee Upgrade to SnakeYAML 2.5, Protobuf 4.32.1, ActiveMQ 5.17.7 and Artemis 2.42, EasyMock 5.6, AssertJ 3.27.4, XMLUnit 2.10.4, Dom4J 2.2 2025-09-17 18:18:35 +02:00
Juergen Hoeller bf715ac23e Polishing 2025-09-17 18:10:19 +02:00
Brian Clozel b213344d25 Fix synchronization in ResponseBodyEmitter
See gh-35423
Fixes gh-35466

(cherry picked from commit 20e1149dde)
2025-09-17 18:07:45 +02:00
Stéphane Nicoll 0a48984fab Suppress deprecating warnings for getInstanceSupplier() method as well
This commit fixes code generation for a bean produced by a protected
factory method. Previously only the code generated for public methods,
i.e. without a dedicated instance supplier method, was handled.

Closes gh-35486
2025-09-16 16:08:15 +02:00
Brian Clozel cbdd107799 Next development version (v6.2.12-SNAPSHOT) 2025-09-11 09:40:23 +02:00
245 changed files with 8076 additions and 4086 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
toolchain: false
- version: 21
toolchain: true
- version: 24
- version: 25
toolchain: true
exclude:
- os:
+1 -1
View File
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
## Continuous Integration Builds
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
CI builds are defined with [GitHub Actions workflows](.github/workflows).
## Stay in Touch
+2 -2
View File
@@ -86,7 +86,7 @@ configure([rootProject] + javaProjects) { project ->
ext.javadocLinks = [
"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://docs.hibernate.org/orm/5.6/javadocs/",
"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/",
@@ -97,7 +97,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.13.4/api/",
// "https://junit.org/junit5/docs/5.14.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/",
@@ -85,11 +85,11 @@ element. The following example shows how to use it:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="theTargetBean" class="..."/>
<bean id="collaborator" class="..." />
<bean id="theClientBean" class="...">
<bean id="client" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
<idref bean="collaborator" />
</property>
</bean>
----
@@ -99,28 +99,24 @@ following snippet:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="theTargetBean" class="..." />
<bean id="collaborator" class="..." />
<bean id="theClientBean" class="...">
<property name="targetName" ref="theTargetBean"/>
<bean id="client" class="...">
<property name="targetName" value="collaborator" />
</bean>
----
The first form is preferable to the second, because using the `idref` tag lets the
container validate at deployment time that the referenced, named bean actually
exists. In the second variation, no validation is performed on the value that is passed
to the `targetName` property of the `client` bean. Typos are only discovered (with most
container validate at deployment time that the referenced, named bean actually exists. In
the second variation, no validation is performed on the value that is passed to the
`targetName` property of the `client` bean. Typos are therefore only discovered (with most
likely fatal results) when the `client` bean is actually instantiated. If the `client`
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting exception
may only be discovered long after the container is deployed.
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting
exception may only be discovered long after the container is deployed.
NOTE: The `local` attribute on the `idref` element is no longer supported in the 4.0 beans
XSD, since it does not provide value over a regular `bean` reference any more. Change
your existing `idref local` references to `idref bean` when upgrading to the 4.0 schema.
A common place (at least in versions earlier than Spring 2.0) where the `<idref/>` element
brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors] in a
`ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
NOTE: A common place (at least in versions earlier than Spring 2.0) where the `<idref/>`
element brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors]
in a `ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
interceptor names prevents you from misspelling an interceptor ID.
@@ -676,7 +676,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
[source,java,indent=0,subs="verbatim,quotes"]
----
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
.paramSource(new Actor("Leonor", "Watling")
.paramSource(new Actor("Leonor", "Watling"))
.update();
----
@@ -448,7 +448,7 @@ Java::
----
@GetMapping
FragmentsRendering handle() {
return FragmentsRendering.with("posts").fragment("comments").build();
return FragmentsRendering.fragment("posts").fragment("comments").build();
}
----
@@ -458,7 +458,7 @@ Kotlin::
----
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.with("posts").fragment("comments").build()
return FragmentsRendering.fragment("posts").fragment("comments").build()
}
----
======
@@ -310,7 +310,7 @@ Java::
Flux<String> source = ... ;
Mono<Void> output = session.send(source.map(session::textMessage)); <2>
return Mono.zip(input, output).then(); <3>
return input.and(output); <3>
}
}
----
@@ -338,7 +338,7 @@ Kotlin::
val source: Flux<String> = ...
val output = session.send(source.map(session::textMessage)) // <2>
return Mono.zip(input, output).then() // <3>
return input.and(output) // <3>
}
}
----
@@ -89,39 +89,7 @@ Kotlin::
You can map requests by using glob patterns and wildcards:
[cols="2,3,5"]
|===
|Pattern |Description |Example
| `+?+`
| Matches one character
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
| `+*+`
| Matches zero or more characters within a path segment
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`
| `+**+`
| Matches zero or more path segments until the end of the path
| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
`+"/resources/**/file.png"+` is invalid as `+**+` is only allowed at the end of the path.
| `+{name}+`
| Matches a path segment and captures it as a variable named "name"
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
| `+{name:[a-z]+}+`
| Matches the regexp `+"[a-z]+"+` as a path variable named "name"
| `+"/projects/{project:[a-z]+}/versions"+` matches `+"/projects/spring/versions"+` but not `+"/projects/spring1/versions"+`
| `+{*path}+`
| Matches zero or more path segments until the end of the path and captures it as a variable named "path"
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
|===
include::partial$web/uri-patterns.adoc[leveloffset=+1]
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
@@ -7,19 +7,23 @@ Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
1. Java Bean Validation is applied individually to an
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
resolvers validate a method argument individually if the method parameter is annotated
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
discussed next). The exception raised in this case is `WebExchangeBindException`.
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
it is a command object rather than a container such as `Map` or `Collection`, it does not
have `Errors` or `BindingResult` immediately after in the method signature, and does not
otherwise require method validation (see next). `WebExchangeBindException` is the
exception raised when validating a method parameter individually.
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
directly on method parameters, or on the method (for the return value), then method
validation must be applied, and that supersedes validation at the method argument level
because method validation covers both method parameter constraints and nested constraints
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
method for the return value, and it supersedes any validation that would be applied
otherwise to a method parameter individually because method validation covers both
method parameter constraints and nested constraints via `@Valid`.
`HandlerMethodValidationException` is the exception raised validation is applied
to the method.
Applications must handle both `WebExchangeBindException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
@@ -48,7 +48,7 @@ Java::
----
@GetMapping
FragmentsRendering handle() {
return FragmentsRendering.with("posts").fragment("comments").build();
return FragmentsRendering.fragment("posts").fragment("comments").build();
}
----
@@ -58,7 +58,7 @@ Kotlin::
----
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.with("posts").fragment("comments").build()
return FragmentsRendering.fragment("posts").fragment("comments").build()
}
----
======
@@ -3,7 +3,10 @@
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-filters[See equivalent in the Reactive stack]#
The `spring-web` module provides some useful filters:
In the Servlet API, you can add a `jakarta.servlet.Filter` to apply interception-style logic
before and after the rest of the processing chain of filters and the target `Servlet`.
The `spring-web` module has a number of built-in `Filter` implementations:
* xref:web/webmvc/filters.adoc#filters-http-put[Form Data]
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
@@ -11,9 +14,19 @@ The `spring-web` module provides some useful filters:
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations.
If you are using Spring Boot, you can
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare them as beans and configure them as part of your application].
There are also base class implementations for use in Spring applications:
* `GenericFilterBean` -- base class for a `Filter` configured as a Spring bean;
integrates with the Spring `ApplicationContext` lifecycle.
* `OncePerRequestFilter` -- extension of `GenericFilterBean` that supports a single
invocation at the start of a request, i.e. during the `REQUEST` dispatch phase, and
ignoring further handling via `FORWARD` dispatches. The filter also provides control
over whether the `Filter` gets involved in `ASYNC` and `ERROR` dispatches.
Servlet filters can be configured in `web.xml` or via Servlet annotations.
In a Spring Boot application , you can
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare Filter's as beans]
and Boot will have them configured.
[[filters-http-put]]
@@ -422,7 +422,7 @@ reactive types from the controller method.
Reactive return values are handled as follows:
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
include `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).
@@ -103,22 +103,9 @@ Spring WebFlux. It was enabled for use in Spring MVC from version 5.3 and is ena
default from version 6.0. See xref:web/webmvc/mvc-config/path-matching.adoc[MVC config] for
customizations of path matching options.
`PathPattern` supports the same pattern syntax as `AntPathMatcher`. In addition, it also
supports the capturing pattern, for example, `+{*spring}+`, for matching 0 or more path segments
at the end of a path. `PathPattern` also restricts the use of `+**+` for matching multiple
path segments such that it's only allowed at the end of a pattern. This eliminates many
cases of ambiguity when choosing the best matching pattern for a given request.
For full pattern syntax please refer to
{spring-framework-api}/web/util/pattern/PathPattern.html[PathPattern] and
{spring-framework-api}/util/AntPathMatcher.html[AntPathMatcher].
You can map requests by using glob patterns and wildcards:
Some example patterns:
* `+"/resources/ima?e.png"+` - match one character in a path segment
* `+"/resources/*.png"+` - match zero or more characters in a path segment
* `+"/resources/**"+` - match multiple path segments
* `+"/projects/{project}/versions"+` - match a path segment and capture it as a variable
* `++"/projects/{project:[a-z]+}/versions"++` - match and capture a variable with a regex
include::partial$web/uri-patterns.adoc[leveloffset=+1]
Captured URI variables can be accessed with `@PathVariable`. For example:
@@ -7,22 +7,26 @@ Spring MVC has built-in xref:core/validation/validator.adoc[validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
1. Java Bean Validation is applied individually to an
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
resolvers validate a method argument individually if the method parameter is annotated
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
it is a command object rather than a container such as `Map` or `Collection`, it does not
have `Errors` or `BindingResult` immediately after in the method signature, and does not
otherwise require method validation (see next). `MethodArgumentNotValidException` is the
exception raised when validating a method parameter individually.
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
directly on method parameters, or on the method (for the return value), then method
validation must be applied, and that supersedes validation at the method argument level
because method validation covers both method parameter constraints and nested constraints
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
method for the return value, and it supersedes any validation that would be applied
otherwise to a method parameter individually because method validation covers both
method parameter constraints and nested constraints via `@Valid`.
`HandlerMethodValidationException` is the exception raised validation is applied
to the method.
Applications must handle both `MethodArgumentNotValidException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
Applications should handle both `MethodArgumentNotValidException` and
`HandlerMethodValidationException` since either may be raised depending on the controller
method signature. The two exceptions, however are designed to be very similar, and can be
handled with almost identical code. The main difference is that the former is for a single
object while the latter is for a list of method parameters.
@@ -0,0 +1,51 @@
[cols="2,3,5"]
|===
|Pattern |Description |Example
| `spring`
| Literal pattern
| `+"/spring"+` matches `+"/spring"+`
| `+?+`
| Matches one character
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
| `+*+`
| Matches zero or more characters within a path segment
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`.
`+"/projects/*"+` matches `+"/projects/spring"+` but does not match `+"/projects"+` as the path segment is not present.
| `+**+`
| Matches zero or more path segments
| `+"/resources/**"+` matches `+"/resources"+`, `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
`+"/**/info"+` matches `+"/info"+`, `+"/spring/info"+` and `+"/spring/framework/info"+`
`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path.
`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
| `+{name}+`
| Similar to `+*+`, but also captures the path segment as a variable named "name"
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment.
| `{name:[a-z]+}`
| Matches the regexp `"[a-z]+"` as a path variable named "name"
| `"/projects/{project:[a-z]+}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"`
| `+{*path}+`
| Similar to `+**+`, but also captures the path segments as a variable named "path"
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+`
`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path.
`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
|===
+32 -32
View File
@@ -7,31 +7,31 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4.1"))
api(platform("io.micrometer:micrometer-bom:1.14.11"))
api(platform("io.netty:netty-bom:4.1.127.Final"))
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
api(platform("io.micrometer:micrometer-bom:1.14.13"))
api(platform("io.netty:netty-bom:4.1.128.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.10"))
api(platform("io.projectreactor:reactor-bom:2024.0.12"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.28"))
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
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.26"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.26"))
api(platform("org.assertj:assertj-bom:3.27.6"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.30"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.30"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.13.4"))
api(platform("org.mockito:mockito-bom:5.19.0"))
api(platform("org.junit:junit-bom:5.14.1"))
api(platform("org.mockito:mockito-bom:5.20.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.3")
api("com.fasterxml:aalto-xml:1.3.4")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.2.2")
api("com.github.ben-manes.caffeine:caffeine:3.2.3")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.13.1")
api("com.google.protobuf:protobuf-java-util:4.32.0")
api("com.google.code.gson:gson:2.13.2")
api("com.google.protobuf:protobuf-java-util:4.32.1")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
@@ -53,11 +53,11 @@ dependencies {
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.11")
api("io.reactivex.rxjava3:rxjava:3.1.12")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.19.Final")
api("io.undertow:undertow-servlet:2.3.19.Final")
api("io.undertow:undertow-websockets-jsr:2.3.19.Final")
api("io.undertow:undertow-core:2.3.20.Final")
api("io.undertow:undertow-servlet:2.3.20.Final")
api("io.undertow:undertow-websockets-jsr:2.3.20.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -90,11 +90,11 @@ dependencies {
api("junit:junit:4.13.2")
api("net.sf.jopt-simple:jopt-simple:5.0.4")
api("org.apache-extras.beanshell:bsh:2.0b6")
api("org.apache.activemq:activemq-broker:5.17.6")
api("org.apache.activemq:activemq-kahadb-store:5.17.6")
api("org.apache.activemq:activemq-stomp:5.17.6")
api("org.apache.activemq:artemis-jakarta-client:2.31.2")
api("org.apache.activemq:artemis-junit-5:2.31.2")
api("org.apache.activemq:activemq-broker:5.17.7")
api("org.apache.activemq:activemq-kahadb-store:5.17.7")
api("org.apache.activemq:activemq-stomp:5.17.7")
api("org.apache.activemq:artemis-jakarta-client:2.42.0")
api("org.apache.activemq:artemis-junit-5:2.42.0")
api("org.apache.commons:commons-pool2:2.9.0")
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
@@ -113,9 +113,9 @@ dependencies {
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
api("org.dom4j:dom4j:2.1.4")
api("org.easymock:easymock:5.5.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.11")
api("org.dom4j:dom4j:2.2.0")
api("org.easymock:easymock:5.6.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.12")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
@@ -129,7 +129,7 @@ dependencies {
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.16.0")
api("org.htmlunit:htmlunit:4.18.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.13.0")
api("org.junit.support:testng-engine:1.0.5")
@@ -137,16 +137,16 @@ dependencies {
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.35.0")
api("org.seleniumhq.selenium:selenium-java:4.35.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
api("org.seleniumhq.selenium:selenium-java:4.38.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.17")
api("org.testng:testng:7.11.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.59")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.3")
api("org.xmlunit:xmlunit-matchers:2.10.3")
api("org.yaml:snakeyaml:2.4")
api("org.xmlunit:xmlunit-assertj:2.10.4")
api("org.xmlunit:xmlunit-matchers:2.10.4")
api("org.yaml:snakeyaml:2.5")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.11-SNAPSHOT
version=6.2.13
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
@@ -62,7 +62,7 @@ public interface ProxyMethodInvocation extends MethodInvocation {
MethodInvocation invocableClone(Object... arguments);
/**
* Set the arguments to be used on subsequent invocations in the any advice
* Set the arguments to be used on subsequent invocations in any advice
* in this chain.
* @param arguments the argument array
*/
@@ -41,8 +41,8 @@ abstract class CoroutinesUtils {
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
@SuppressWarnings({"unchecked", "rawtypes"})
static Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
(Continuation<Object>) continuation);
@@ -81,13 +81,19 @@ public abstract class ScopedProxyUtils {
// Copy autowire settings from original bean definition.
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
proxyDefinition.setFallback(targetDefinition.isFallback());
if (targetDefinition instanceof AbstractBeanDefinition abd) {
proxyDefinition.setDefaultCandidate(abd.isDefaultCandidate());
proxyDefinition.copyQualifiersFrom(abd);
}
// The target bean should be ignored in favor of the scoped proxy.
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
targetDefinition.setFallback(false);
if (targetDefinition instanceof AbstractBeanDefinition abd) {
abd.setDefaultCandidate(false);
}
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
@@ -37,7 +37,6 @@ import static org.mockito.Mockito.verify;
* Tests for {@link AsyncExecutionInterceptor}.
*
* @author Bao Ngo
* @since 7.0
*/
class AsyncExecutionInterceptorTests {
@@ -62,11 +61,13 @@ class AsyncExecutionInterceptorTests {
O run();
}
static class FutureRunner implements GenericRunner<Future<Void>> {
@Override
public Future<Void> run() {
return CompletableFuture.runAsync(() -> {
});
return CompletableFuture.runAsync(() -> {});
}
}
}
@@ -18,6 +18,15 @@ package org.springframework.aop.scope;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -25,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Tests for {@link ScopedProxyUtils}.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 5.1.10
*/
class ScopedProxyUtilsTests {
@@ -53,15 +63,79 @@ class ScopedProxyUtilsTests {
@Test
void getOriginalBeanNameForNullTargetBean() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName(null))
.withMessage("bean name 'null' does not refer to the target of a scoped proxy");
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName(null))
.withMessage("bean name 'null' does not refer to the target of a scoped proxy");
}
@Test
void getOriginalBeanNameForNonScopedTarget() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName("myBean"))
.withMessage("bean name 'myBean' does not refer to the target of a scoped proxy");
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName("myBean"))
.withMessage("bean name 'myBean' does not refer to the target of a scoped proxy");
}
@Test
void createScopedProxyTargetAppliesAutowireSettingsToProxyBeanDefinition() {
AbstractBeanDefinition targetDefinition = new GenericBeanDefinition();
// Opposite of defaults
targetDefinition.setAutowireCandidate(false);
targetDefinition.setDefaultCandidate(false);
targetDefinition.setPrimary(true);
targetDefinition.setFallback(true);
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(
new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false);
AbstractBeanDefinition proxyBeanDefinition = (AbstractBeanDefinition) proxyHolder.getBeanDefinition();
assertThat(proxyBeanDefinition.isAutowireCandidate()).isFalse();
assertThat(proxyBeanDefinition.isDefaultCandidate()).isFalse();
assertThat(proxyBeanDefinition.isPrimary()).isTrue();
assertThat(proxyBeanDefinition.isFallback()).isTrue();
}
@Test
void createScopedProxyTargetAppliesBeanAttributesToProxyBeanDefinition() {
GenericBeanDefinition targetDefinition = new GenericBeanDefinition();
// Opposite of defaults
targetDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
targetDefinition.setSource("theSource");
targetDefinition.addQualifier(new AutowireCandidateQualifier("myQualifier"));
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(
new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false);
BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition();
assertThat(proxyBeanDefinition.getRole()).isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE);
assertThat(proxyBeanDefinition).isInstanceOf(RootBeanDefinition.class);
assertThat(proxyBeanDefinition.getPropertyValues()).hasSize(2);
assertThat(proxyBeanDefinition.getPropertyValues().get("proxyTargetClass")).isEqualTo(false);
assertThat(proxyBeanDefinition.getPropertyValues().get("targetBeanName")).isEqualTo(
ScopedProxyUtils.getTargetBeanName("myBean"));
RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) proxyBeanDefinition;
assertThat(rootBeanDefinition.getQualifiers()).hasSize(1);
assertThat(rootBeanDefinition.hasQualifier("myQualifier")).isTrue();
assertThat(rootBeanDefinition.getSource()).isEqualTo("theSource");
}
@Test
void createScopedProxyTargetCleansAutowireSettingsInTargetDefinition() {
AbstractBeanDefinition targetDefinition = new GenericBeanDefinition();
targetDefinition.setAutowireCandidate(true);
targetDefinition.setDefaultCandidate(true);
targetDefinition.setPrimary(true);
targetDefinition.setFallback(true);
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
ScopedProxyUtils.createScopedProxy(
new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false);
assertThat(targetDefinition.isAutowireCandidate()).isFalse();
assertThat(targetDefinition.isDefaultCandidate()).isFalse();
assertThat(targetDefinition.isPrimary()).isFalse();
assertThat(targetDefinition.isFallback()).isFalse();
}
}
@@ -19,6 +19,7 @@ package org.springframework.aop.framework
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
/**
* Tests for Kotlin support in [CglibAopProxy].
@@ -48,6 +49,13 @@ class CglibAopProxyKotlinTests {
assertThatThrownBy { proxy.checkedException() }.isInstanceOf(CheckedException::class.java)
}
@Test // gh-35487
fun jvmDefault() {
val proxyFactory = ProxyFactory()
proxyFactory.setTarget(AddressRepo())
proxyFactory.proxy
}
open class MyKotlinBean {
@@ -63,4 +71,24 @@ class CglibAopProxyKotlinTests {
}
class CheckedException() : Exception()
open class AddressRepo(): CrudRepo<Address, Int>
interface CrudRepo<E : Any, ID : Any> {
fun save(e: E): E {
return e
}
fun delete(id: ID): Long {
return 0L
}
}
data class Address(
val id: Int = 0,
val street: String,
val version: Int = 0,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null,
)
}
@@ -566,7 +566,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
Class<?> targetClass = ClassUtils.getUserClass(clazz);
do {
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
@@ -586,12 +586,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
if (method.isBridge()) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
MergedAnnotation<?> ann = findAutowiredAnnotation(method);
if (ann != null && method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
@@ -609,7 +608,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
methodElements.add(new AutowiredMethodElement(method, required, pd));
}
});
@@ -180,7 +180,6 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
* {@code true} if a qualifier has been found and matched,
* {@code null} if no qualifier has been found at all
*/
@Nullable
protected Boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
boolean qualifierFound = false;
@@ -375,6 +374,14 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
return true;
}
}
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
for (Annotation annotation : methodParam.getMethodAnnotations()) {
if (isQualifier(annotation.annotationType())) {
return true;
}
}
}
return false;
}
@@ -29,6 +29,7 @@ import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.CollectionDelegate;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.MapDelegate;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
@@ -57,6 +58,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
* <li>{@link LinkedHashMap}</li>
* <li>{@link BeanReference}</li>
* <li>{@link TypedStringValue}</li>
* <li>{@link AutowiredPropertyMarker}</li>
* </ul>
* When combined with {@linkplain ValueCodeGeneratorDelegates#INSTANCES the
* delegates for common value types}, this should be added first as they have
@@ -68,7 +70,8 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
new ManagedMapDelegate(),
new LinkedHashMapDelegate(),
new BeanReferenceDelegate(),
new TypedStringValueDelegate()
new TypedStringValueDelegate(),
new AutowiredPropertyMarkerDelegate()
);
@@ -216,4 +219,20 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
return valueCodeGenerator.generateCode(value);
}
}
/**
* {@link Delegate} for {@link AutowiredPropertyMarker} types.
*/
private static class AutowiredPropertyMarkerDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
if (value instanceof AutowiredPropertyMarker) {
return CodeBlock.of("$T.INSTANCE", AutowiredPropertyMarker.class);
}
return null;
}
}
}
@@ -294,9 +294,12 @@ public class InstanceSupplierCodeGenerator {
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
CodeWarnings codeWarnings = new CodeWarnings();
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
codeWarnings.detectDeprecation(suppliedType, factoryMethod);
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC);
codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
method.addStatement(generateInstanceSupplierForFactoryMethod(
factoryMethod, suppliedType, targetClass, factoryMethod.getName()));
@@ -152,6 +152,18 @@ public interface ConfigurableListableBeanFactory
*/
boolean isConfigurationFrozen();
/**
* Mark current thread as main bootstrap thread for singleton instantiation,
* with lenient bootstrap locking applying for background threads.
* <p>Any such marker is to be removed at the end of the managed bootstrap in
* {@link #preInstantiateSingletons()}.
* @since 6.2.12
* @see #setBootstrapExecutor
* @see #preInstantiateSingletons()
*/
default void prepareSingletonBootstrap() {
}
/**
* Ensure that all non-lazy-init singletons are instantiated, also considering
* {@link org.springframework.beans.factory.FactoryBean FactoryBeans}.
@@ -159,6 +171,7 @@ public interface ConfigurableListableBeanFactory
* @throws BeansException if one of the singleton beans could not be created.
* Note: This may have left the factory with some beans already initialized!
* Call {@link #destroySingletons()} for full cleanup in this case.
* @see #prepareSingletonBootstrap()
* @see #destroySingletons()
*/
void preInstantiateSingletons() throws BeansException;
@@ -1102,6 +1102,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return null;
}
@Override
public void prepareSingletonBootstrap() {
this.mainThreadPrefix = getThreadNamePrefix();
}
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
@@ -1114,7 +1119,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
// Trigger initialization of all non-lazy singleton beans...
this.preInstantiationThread.set(PreInstantiation.MAIN);
this.mainThreadPrefix = getThreadNamePrefix();
if (this.mainThreadPrefix == null) {
this.mainThreadPrefix = getThreadNamePrefix();
}
try {
List<CompletableFuture<?>> futures = new ArrayList<>();
for (String beanName : beanNames) {
@@ -1474,7 +1481,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
super.registerSingleton(beanName, singletonObject);
updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
this.allBeanNamesByType.remove(Object.class);
this.singletonBeanNamesByType.remove(Object.class);
}
@Override
@@ -1643,8 +1653,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
@SuppressWarnings("NullAway") // Dataflow analysis limitation
@Nullable
@SuppressWarnings("NullAway")
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
@@ -1981,7 +1991,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
for (String candidate : candidateNames) {
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
(!multiple || matchesBeanName(candidate, descriptor.getDependencyName()) ||
getAutowireCandidateResolver().hasQualifier(descriptor))) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
@@ -2098,8 +2109,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
boolean candidateLocal = containsBeanDefinition(candidateBeanName);
boolean primaryLocal = containsBeanDefinition(primaryBeanName);
if (candidateLocal == primaryLocal) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"more than one 'primary' bean found among candidates: " + candidates.keySet());
String message = "more than one 'primary' bean found among candidates: " + candidates.keySet();
logger.trace(message);
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), message);
}
else if (candidateLocal) {
primaryBeanName = candidateBeanName;
@@ -2251,12 +2263,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
/**
* Determine whether the given candidate name matches the bean name or the aliases
* Determine whether the given dependency name matches the bean name or the aliases
* stored in this bean definition.
*/
protected boolean matchesBeanName(String beanName, @Nullable String candidateName) {
return (candidateName != null &&
(candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName)));
protected boolean matchesBeanName(String beanName, @Nullable String dependencyName) {
return (dependencyName != null &&
(dependencyName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), dependencyName)));
}
/**
@@ -128,43 +128,48 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
locked = (lockFlag && this.singletonLock.tryLock());
}
try {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
// Only post-process and store if not put there already during getObject() call above
// (for example, because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (shouldPostProcess) {
if (locked) {
if (isSingletonCurrentlyInCreation(beanName)) {
// Temporarily return non-post-processed object, not storing it yet
return object;
}
beforeSingletonCreation(beanName);
}
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
// Defensively synchronize against non-thread-safe FactoryBean.getObject() implementations,
// potentially to be called from a background thread while the main thread currently calls
// the same getObject() method within the singleton lock.
synchronized (factory) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
// Only post-process and store if not put there already during getObject() call above
// (for example, because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (shouldPostProcess) {
if (locked) {
afterSingletonCreation(beanName);
if (isSingletonCurrentlyInCreation(beanName)) {
// Temporarily return non-post-processed object, not storing it yet
return object;
}
beforeSingletonCreation(beanName);
}
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
if (locked) {
afterSingletonCreation(beanName);
}
}
}
}
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
return object;
}
return object;
}
finally {
if (locked) {
@@ -472,7 +472,7 @@ abstract class AbstractPropertyAccessorTests {
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
assertThat(name).as("name is OK").isEqualTo(target.getName());
accessor.setPropertyValues(new MutablePropertyValues());
// Check its unchanged
// Check it's unchanged
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
assertThat(name).as("name is OK").isEqualTo(target.getName());
}
@@ -87,6 +87,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
import static org.assertj.core.api.Assertions.assertThat;
@@ -1798,7 +1799,7 @@ class DefaultListableBeanFactoryTests {
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(TestBean.class))
.withMessageContaining("more than one 'primary'");
.withMessageEndingWith("more than one 'primary' bean found among candidates: [bd1, bd2]");
}
@Test
@@ -2122,7 +2123,7 @@ class DefaultListableBeanFactoryTests {
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(ConstructorDependency.class, 42))
.withMessageContaining("more than one 'primary'");
.withMessageEndingWith("more than one 'primary' bean found among candidates: [bd1, bd2]");
}
@Test
@@ -3223,6 +3224,10 @@ class DefaultListableBeanFactoryTests {
assertThat(lbf.getBeanNamesForType(DerivedTestBean.class)).containsExactly("bd1");
assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames);
assertThat(lbf.getBeanNamesForType(Object.class)).isSameAs(allBeanNames);
lbf.registerSingleton("bd3", new Object());
assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames);
assertThat(lbf.getBeanNamesForType(Object.class)).containsExactly(StringUtils.addStringToArray(allBeanNames, "bd3"));
}
@@ -1275,88 +1275,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean().get("testBean2")).isNull();
}
@Test
void fieldInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb1 = new TestBean("tb1");
TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
bf.registerSingleton("testBean2", tb2);
bf.registerAlias("testBean1", "testBean");
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
}
@Test
void methodInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndMultipleMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
.isThrownBy(() -> bf.getBean("annotatedBean"))
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
}
@Test
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
rbd2.setAutowireCandidate(false);
bf.registerBeanDefinition("testBean2", rbd2);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
TestBean tb = bf.getBean("testBean1", TestBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndNoMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isNull();
assertThat(bean.getTestBean()).isNull();
}
@Test
void constructorInjectionWithTypedMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
@@ -1409,6 +1327,19 @@ class AutowiredAnnotationBeanPostProcessorTests {
@Test
void constructorInjectionWithPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(NamedMapConstructorInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
NamedMapConstructorInjectionBean bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
}
@Test
void constructorInjectionWithQualifiedPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapConstructorInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
@@ -1497,6 +1428,114 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBeanSet()).contains(tb1, tb2);
}
@Test
void fieldInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb1 = new TestBean("tb1");
TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
bf.registerSingleton("testBean2", tb2);
bf.registerAlias("testBean1", "testBean");
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
}
@Test
void methodInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndMultipleMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
.isThrownBy(() -> bf.getBean("annotatedBean"))
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
}
@Test
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
rbd2.setAutowireCandidate(false);
bf.registerBeanDefinition("testBean2", rbd2);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
TestBean tb = bf.getBean("testBean1", TestBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndNoMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isNull();
assertThat(bean.getTestBean()).isNull();
}
@Test
void methodInjectionWithPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(NamedMapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
NamedMapMethodInjectionBean bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
}
@Test
void methodInjectionWithQualifiedPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("myTestBeanMap", new RootBeanDefinition(HashMap.class));
QualifiedMapMethodInjectionBean bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
}
@Test
void selfReference() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class));
@@ -3258,6 +3297,21 @@ class AutowiredAnnotationBeanPostProcessorTests {
}
public static class NamedMapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
public NamedMapConstructorInjectionBean(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
public static class QualifiedMapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap;
@@ -3357,6 +3411,37 @@ class AutowiredAnnotationBeanPostProcessorTests {
}
public static class NamedMapMethodInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
public static class QualifiedMapMethodInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
@Qualifier("myTestBeanMap")
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
@SuppressWarnings("serial")
public static class ObjectFactoryFieldInjectionBean implements Serializable {
@@ -16,6 +16,7 @@
package org.springframework.beans.factory.aot;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -34,6 +35,7 @@ import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -626,6 +628,22 @@ class BeanDefinitionMethodGeneratorTests {
-> assertThat(customPropertyValue.value()).isEqualTo("test")));
}
@Test
void generateBeanDefinitionMethodWhenHasAutowiredPropertyGeneratesMethod() {
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(CustomBean.class).addAutowiredProperty("innerBean")
.getBeanDefinition();
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) ->
assertThat(actual.getPropertyValues().get("innerBean"))
.isSameAs(AutowiredPropertyMarker.INSTANCE));
}
@Test
void generateBeanDefinitionMethodWhenHasAotContributionsAppliesContributions() {
RegisteredBean registeredBean = registerBean(
@@ -39,6 +39,7 @@ import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.ValueCodeGenerator;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -486,4 +487,16 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
}
@Nested
class AutowiredPropertyMarkerTests {
@Test
void generateWhenAutowiredPropertyMarker() {
compile(AutowiredPropertyMarker.INSTANCE, (instance, compiler) ->
assertThat(instance).isInstanceOf(AutowiredPropertyMarker.class)
.isSameAs(AutowiredPropertyMarker.INSTANCE));
}
}
}
@@ -418,6 +418,16 @@ class InstanceSupplierCodeGeneratorTests {
compileAndCheckWarnings(beanDefinition);
}
@Test
void generateWhenTargetFactoryMethodIsProtectedAndReturnTypeIsDeprecated() {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(DeprecatedBean.class)
.setFactoryMethodOnBean("deprecatedReturnTypeProtected", "config").getBeanDefinition();
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
.genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition());
compileAndCheckWarnings(beanDefinition);
}
private void compileAndCheckWarnings(BeanDefinition beanDefinition) {
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition,
((instanceSupplier, compiled) -> {})));
@@ -464,6 +474,26 @@ class InstanceSupplierCodeGeneratorTests {
compileAndCheckWarnings(beanDefinition);
}
@Test
void generateWhenTargetFactoryMethodReturnTypeIsDeprecatedForRemoval() {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(DeprecatedForRemovalBean.class)
.setFactoryMethodOnBean("deprecatedReturnType", "config").getBeanDefinition();
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
.genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition());
compileAndCheckWarnings(beanDefinition);
}
@Test
void generateWhenTargetFactoryMethodIsProtectedAndReturnTypeIsDeprecatedForRemoval() {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(DeprecatedForRemovalBean.class)
.setFactoryMethodOnBean("deprecatedReturnTypeProtected", "config").getBeanDefinition();
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
.genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition());
compileAndCheckWarnings(beanDefinition);
}
private void compileAndCheckWarnings(BeanDefinition beanDefinition) {
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition,
((instanceSupplier, compiled) -> {})));
@@ -28,6 +28,7 @@ import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.FieldRetrievingFactoryBean;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.parsing.ComponentDefinition;
@@ -46,7 +47,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Juergen Hoeller
* @author Mark Fisher
*/
@SuppressWarnings("rawtypes")
class UtilNamespaceHandlerTests {
private DefaultListableBeanFactory beanFactory;
@@ -55,7 +55,7 @@ class UtilNamespaceHandlerTests {
@BeforeEach
void setUp() {
void setup() {
this.beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory);
reader.setEventListener(this.listener);
@@ -109,17 +109,17 @@ class UtilNamespaceHandlerTests {
@Test
void testSimpleMap() {
Map<?, ?> map = (Map) this.beanFactory.getBean("simpleMap");
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
assertThat(map.get("foo")).isEqualTo("bar");
Map<?, ?> map2 = (Map) this.beanFactory.getBean("simpleMap");
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
assertThat(map).isSameAs(map2);
}
@Test
void testScopedMap() {
Map<?, ?> map = (Map) this.beanFactory.getBean("scopedMap");
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
assertThat(map.get("foo")).isEqualTo("bar");
Map<?, ?> map2 = (Map) this.beanFactory.getBean("scopedMap");
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
assertThat(map2.get("foo")).isEqualTo("bar");
assertThat(map).isNotSameAs(map2);
}
@@ -164,17 +164,23 @@ class UtilNamespaceHandlerTests {
}
@Test
void testMapWithRef() {
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithRef");
void testMapWithRef() throws Exception {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithRef");
assertThat(map).isInstanceOf(TreeMap.class);
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
assertThat(this.beanFactory.resolveDependency(
new DependencyDescriptor(getClass().getDeclaredField("mapWithRef"), true), null))
.isSameAs(map);
}
@Test
void testMapWithTypes() {
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithTypes");
void testMapWithTypes() throws Exception {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithTypes");
assertThat(map).isInstanceOf(LinkedCaseInsensitiveMap.class);
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
assertThat(this.beanFactory.resolveDependency(
new DependencyDescriptor(getClass().getDeclaredField("mapWithTypes"), true), null))
.isSameAs(map);
}
@Test
@@ -240,11 +246,11 @@ class UtilNamespaceHandlerTests {
void testCircularCollections() {
TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionsBean");
assertThat(bean.getSomeList()).singleElement().isEqualTo(bean);
assertThat(bean.getSomeSet()).singleElement().isEqualTo(bean);
assertThat(bean.getSomeList()).singleElement().isSameAs(bean);
assertThat(bean.getSomeSet()).singleElement().isSameAs(bean);
assertThat(bean.getSomeMap()).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isEqualTo(bean);
assertThat(value).isSameAs(bean);
});
}
@@ -255,17 +261,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isTrue();
assertThat(list).singleElement().isEqualTo(bean);
assertThat(list).singleElement().isSameAs(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
assertThat(set).singleElement().isEqualTo(bean);
assertThat(set).singleElement().isSameAs(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isEqualTo(bean);
assertThat(value).isSameAs(bean);
});
}
@@ -276,17 +282,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
assertThat(list).singleElement().isEqualTo(bean);
assertThat(list).singleElement().isSameAs(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isTrue();
assertThat(set).singleElement().isEqualTo(bean);
assertThat(set).singleElement().isSameAs(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isEqualTo(bean);
assertThat(value).isSameAs(bean);
});
}
@@ -297,17 +303,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
assertThat(list).singleElement().isEqualTo(bean);
assertThat(list).singleElement().isSameAs(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
assertThat(set).singleElement().isEqualTo(bean);
assertThat(set).singleElement().isSameAs(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isTrue();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isEqualTo(bean);
assertThat(value).isSameAs(bean);
});
}
@@ -372,4 +378,9 @@ class UtilNamespaceHandlerTests {
assertThat(props).as("Incorrect property value").containsEntry("foo2", "local2");
}
// For DependencyDescriptor resolution
private Map<String, TestBean> mapWithRef;
private Map<String, TestBean> mapWithTypes;
}
@@ -59,6 +59,8 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
private boolean jedi;
private String favoriteCafé;
private ITestBean spouse;
private String touchy;
@@ -209,6 +211,14 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
this.jedi = jedi;
}
public String getFavoriteCafé() {
return this.favoriteCafé;
}
public void setFavoriteCafé(String favoriteCafé) {
this.favoriteCafé = favoriteCafé;
}
@Override
public ITestBean getSpouse() {
return this.spouse;
@@ -33,4 +33,14 @@ public class DeprecatedForRemovalMemberConfiguration {
return bean.toString();
}
@SuppressWarnings("removal")
public DeprecatedForRemovalBean deprecatedReturnType() {
return new DeprecatedForRemovalBean();
}
@SuppressWarnings("removal")
DeprecatedForRemovalBean deprecatedReturnTypeProtected() {
return new DeprecatedForRemovalBean();
}
}
@@ -38,4 +38,9 @@ public class DeprecatedMemberConfiguration {
return new DeprecatedBean();
}
@SuppressWarnings("deprecation")
DeprecatedBean deprecatedReturnTypeProtected() {
return new DeprecatedBean();
}
}
@@ -37,12 +37,13 @@ import javax.lang.model.element.TypeElement;
/**
* Annotation {@link Processor} that writes a {@link CandidateComponentsMetadata}
* file for spring components.
* file for Spring components.
*
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 5.0
* @deprecated as of 6.1, in favor of the AOT engine.
* @deprecated as of 6.1, in favor of the AOT engine and the forthcoming
* support for an AOT-generated Spring components index
*/
@Deprecated(since = "6.1", forRemoval = true)
public class CandidateComponentsIndexer implements Processor {
@@ -25,8 +25,8 @@ import javax.lang.model.element.ElementKind;
/**
* A {@link StereotypesProvider} that extracts a stereotype for each
* {@code jakarta.*} or {@code javax.*} annotation <i>present</i> on a class or
* interface.
* {@code jakarta.*} or {@code javax.*} annotation <i>present</i>
* on a class or interface.
*
* @author Stephane Nicoll
* @since 5.0
@@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
@@ -97,6 +98,10 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
return null;
}
// The method may be on an interface, but we need metadata from the target class.
// If the target class is null, the method will be unchanged.
@@ -23,14 +23,14 @@ import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableCaching} that wish or need to specify
* explicitly how caches are resolved and how keys are generated for annotation-driven
* cache management.
* Interface to be implemented for explicitly specifying how caches are resolved
* and how keys are generated for annotation-driven cache management.
*
* <p>See @{@link EnableCaching} for general examples and context; see
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()}, and
* {@link #errorHandler()} for detailed instructions.
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableCaching}.
* See @{@link EnableCaching} for general examples and context; see
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()},
* and {@link #errorHandler()} for detailed instructions.
*
* <p><b>NOTE: A {@code CachingConfigurer} will get initialized early.</b>
* Do not inject common dependencies into autowired fields directly; instead, consider
@@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -139,6 +140,10 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
return null;
}
// The method may be on an interface, but we need metadata from the target class.
// If the target class is null, the method will be unchanged.
@@ -17,19 +17,18 @@
package org.springframework.context.annotation;
/**
* Enumeration used to determine whether JDK proxy-based or
* Enumeration used to determine whether JDK/CGLIB proxy-based or
* AspectJ weaving-based advice should be applied.
*
* @author Chris Beams
* @since 3.1
* @see org.springframework.scheduling.annotation.EnableAsync#mode()
* @see org.springframework.scheduling.annotation.AsyncConfigurationSelector#selectImports
* @see org.springframework.transaction.annotation.EnableTransactionManagement#mode()
* @see org.springframework.scheduling.annotation.EnableAsync#mode()
*/
public enum AdviceMode {
/**
* JDK proxy-based advice.
* JDK/CGLIB proxy-based advice.
*/
PROXY,
@@ -90,7 +90,6 @@ import org.springframework.util.ClassUtils;
* @see ScannedGenericBeanDefinition
* @see CandidateComponentsIndex
*/
@SuppressWarnings("removal") // components index
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
@@ -452,9 +451,9 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
String packageSearchPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPattern);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
@@ -537,13 +536,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
for (TypeFilter filter : this.excludeFilters) {
if (filter.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
for (TypeFilter filter : this.includeFilters) {
if (filter.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
@@ -149,8 +149,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private static final boolean jndiPresent = ClassUtils.isPresent(
"javax.naming.InitialContext", CommonAnnotationBeanPostProcessor.class.getClassLoader());
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
@Nullable
private static final Class<? extends Annotation> jakartaResourceType;
@@ -160,6 +158,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
@Nullable
private static final Class<? extends Annotation> ejbAnnotationType;
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
static {
jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
if (jakartaResourceType != null) {
@@ -424,7 +424,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
Class<?> targetClass = ClassUtils.getUserClass(clazz);
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
@@ -455,24 +455,23 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
if (method.isBridge()) {
return;
}
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (ejbAnnotationType != null && method.isAnnotationPresent(ejbAnnotationType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
if (method.getParameterCount() != 1) {
throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
}
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new EjbRefElement(method, method, pd));
}
}
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
else if (jakartaResourceType != null && method.isAnnotationPresent(jakartaResourceType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -481,13 +480,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new ResourceElement(method, bridgedMethod, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new ResourceElement(method, method, pd));
}
}
}
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
else if (javaxResourceType != null && method.isAnnotationPresent(javaxResourceType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -496,8 +495,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new LegacyResourceElement(method, method, pd));
}
}
}
@@ -157,9 +157,17 @@ class ConfigurationClassBeanDefinitionReader {
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
configBeanDef.setScope(scopeMetadata.getScopeName());
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
String configBeanName;
try {
configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException("Failed to generate bean name for imported class '" +
configClass.getMetadata().getClassName() + "'", ex);
}
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
@@ -63,13 +63,13 @@ public interface AotApplicationContextInitializer<C extends ConfigurableApplicat
private static <C extends ConfigurableApplicationContext> void initialize(
C applicationContext, String... initializerClassNames) {
Log logger = LogFactory.getLog(AotApplicationContextInitializer.class);
ClassLoader classLoader = applicationContext.getClassLoader();
logger.debug("Initializing ApplicationContext with AOT");
for (String initializerClassName : initializerClassNames) {
logger.trace(LogMessage.format("Applying %s", initializerClassName));
instantiateInitializer(initializerClassName, classLoader)
.initialize(applicationContext);
instantiateInitializer(initializerClassName, classLoader).initialize(applicationContext);
}
}
@@ -101,10 +101,9 @@ public @interface EventListener {
/**
* The event classes that this listener handles.
* <p>If this attribute is specified with a single value, the
* annotated method may optionally accept a single parameter.
* However, if this attribute is specified with multiple values,
* the annotated method must <em>not</em> declare any parameters.
* <p>The annotated method may optionally accept a single parameter
* of the given event class, or of a common base class or interface
* for all given event classes.
*/
@AliasFor("value")
Class<?>[] classes() default {};
@@ -45,9 +45,7 @@ import org.springframework.util.MultiValueMap;
*
* @author Stephane Nicoll
* @since 5.0
* @deprecated as of 6.1, in favor of the AOT engine.
*/
@Deprecated(since = "6.1", forRemoval = true)
public class CandidateComponentsIndex {
private static final AntPathMatcher pathMatcher = new AntPathMatcher(".");
@@ -83,7 +81,7 @@ public class CandidateComponentsIndex {
public Set<String> getCandidateTypes(String basePackage, String stereotype) {
List<Entry> candidates = this.index.get(stereotype);
if (candidates != null) {
return candidates.parallelStream()
return candidates.stream()
.filter(t -> t.match(basePackage))
.map(t -> t.type)
.collect(Collectors.toSet());
@@ -94,7 +92,7 @@ public class CandidateComponentsIndex {
private static class Entry {
private final String type;
final String type;
private final String packageName;
@@ -38,10 +38,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
*
* @author Stephane Nicoll
* @since 5.0
* @deprecated as of 6.1, in favor of the AOT engine.
*/
@Deprecated(since = "6.1", forRemoval = true)
@SuppressWarnings("removal")
public final class CandidateComponentsIndexLoader {
/**
@@ -936,6 +936,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
@SuppressWarnings("unchecked")
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Mark current thread for singleton instantiation with applied bootstrap locking.
beanFactory.prepareSingletonBootstrap();
// Initialize bootstrap executor for this context.
if (beanFactory.containsBean(BOOTSTRAP_EXECUTOR_BEAN_NAME) &&
beanFactory.isTypeMatch(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)) {
@@ -25,18 +25,18 @@ import java.lang.annotation.Target;
/**
* Declares that a field or method parameter should be formatted as a number.
*
* <p>Supports formatting by style or custom pattern string. Can be applied
* to any JDK {@code Number} type such as {@code Double} and {@code Long}.
* <p>Supports formatting by style or custom pattern string. Can be applied to
* any JDK {@code Number} types such as {@code Double} and {@code Long}.
*
* <p>For style-based formatting, set the {@link #style} attribute to be the
* desired {@link Style}. For custom formatting, set the {@link #pattern}
* attribute to be the number pattern, such as {@code #, ###.##}.
* <p>For style-based formatting, set the {@link #style} attribute to the desired
* {@link Style}. For custom formatting, set the {@link #pattern} attribute to the
* desired number pattern, such as {@code "#,###.##"}.
*
* <p>Each attribute is mutually exclusive, so only set one attribute per
* annotation instance (the one most convenient one for your formatting needs).
* When the {@link #pattern} attribute is specified, it takes precedence over
* the {@link #style} attribute. When no annotation attributes are specified,
* the default format applied is style-based for either number of currency,
* annotation (the one most convenient for your formatting needs). When the
* {@link #pattern} attribute is specified, it takes precedence over the
* {@link #style} attribute. When no annotation attributes are specified, the
* default format applied is style-based for either number or currency,
* depending on the annotated field or method parameter type.
*
* @author Keith Donald
@@ -50,19 +50,21 @@ import java.lang.annotation.Target;
public @interface NumberFormat {
/**
* The style pattern to use to format the field.
* The style pattern to use to format the field or method parameter.
* <p>Defaults to {@link Style#DEFAULT} for general-purpose number formatting
* for most annotated types, except for money types which default to currency
* formatting. Set this attribute when you wish to format your field in
* accordance with a common style other than the default style.
* formatting.
* <p>Set this attribute when you wish to format your field or method parameter
* in accordance with a common style other than the default style.
*/
Style style() default Style.DEFAULT;
/**
* The custom pattern to use to format the field.
* <p>Defaults to empty String, indicating no custom pattern String has been specified.
* Set this attribute when you wish to format your field in accordance with a
* custom number pattern not represented by a style.
* The custom pattern to use to format the field or method parameter.
* <p>Defaults to an empty String, indicating no custom pattern has been
* specified.
* <p>Set this attribute when you wish to format your field or method parameter
* in accordance with a custom number pattern not represented by a style.
*/
String pattern() default "";
@@ -87,7 +87,6 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
private Class<? extends Annotation> asyncAnnotationType;
public AsyncAnnotationBeanPostProcessor() {
setBeforeExistingAdvisors(true);
}
@@ -22,13 +22,18 @@ import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableAsync} that wish to customize the
* {@link Executor} instance used when processing async method invocations or the
* {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from
* async method with {@code void} return type.
* Interface to be implemented for customizing the {@link Executor} instance used when
* processing async method invocations or the {@link AsyncUncaughtExceptionHandler}
* instance used to process exceptions thrown from async methods with a {@code void}
* return type.
*
* <p>See @{@link EnableAsync} for usage examples.
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableAsync}.
* See the @{@link EnableAsync} javadoc for usage examples.
*
* <p><b>NOTE: An {@code AsyncConfigurer} will get initialized early.</b>
* Do not inject common dependencies into autowired fields directly; instead, consider
* declaring a lazy {@link org.springframework.beans.factory.ObjectProvider} for those.
*
* @author Chris Beams
* @author Stephane Nicoll
@@ -50,7 +50,9 @@ public class Task {
/**
* Return the underlying task.
* Return a {@link Runnable} that executes the underlying task.
* <p>Note, this does not necessarily return the {@link Task#Task(Runnable) original runnable}
* as it can be wrapped by the Framework for additional support.
*/
public Runnable getRunnable() {
return this.runnable;
@@ -16,6 +16,8 @@
package org.springframework.validation;
import java.util.HexFormat;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -126,8 +128,18 @@ public class FieldError extends ObjectError {
// We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but
// keep including the full nullSafeToString representation for backwards compatibility.
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
"': rejected value [" + formatRejectedValue() + "]; " +
resolvableToString();
}
private String formatRejectedValue() {
// Special handling of byte[], to be moved into ObjectUtils in 7.0
if (this.rejectedValue instanceof byte[] bytes && bytes.length != 0) {
return "{" + HexFormat.of().formatHex(bytes) + "}";
}
return ObjectUtils.nullSafeToString(this.rejectedValue);
}
}
@@ -69,8 +69,6 @@ import org.springframework.validation.method.ParameterValidationResult;
* at the type level of the containing target class, applying to all public service methods
* of that class. By default, JSR-303 will validate against its default group only.
*
* <p>This functionality requires a Bean Validation 1.1+ provider.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 3.1
@@ -60,8 +60,6 @@ import org.springframework.validation.method.MethodValidationResult;
* inline constraint annotations. Validation groups can be specified through {@code @Validated}
* as well. By default, JSR-303 will validate against its default group only.
*
* <p>This functionality requires a Bean Validation 1.1+ provider.
*
* @author Juergen Hoeller
* @since 3.1
* @see MethodValidationInterceptor
@@ -56,7 +56,6 @@ public class SpringConstraintValidatorFactory implements ConstraintValidatorFact
return this.beanFactory.createBean(key);
}
// Bean Validation 1.1 releaseInstance method
@Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
this.beanFactory.destroyBean(instance);
@@ -55,8 +55,6 @@ import org.springframework.validation.SmartValidator;
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean},
* and as the primary implementation of the {@link SmartValidator} interface.
*
* <p>This adapter is fully compatible with Bean Validation 1.1 as well as 2.0.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
@@ -31,6 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.core.SpringProperties;
import org.springframework.core.testfixture.EnabledForTestGroups;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -68,6 +69,16 @@ class BackgroundBootstrapTests {
ctx.close();
}
@Test
@Timeout(10)
@EnabledForTestGroups(LONG_RUNNING)
void bootstrapWithLoadTimeWeaverAware() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(LoadTimeWeaverAwareBeanConfig.class);
ctx.getBean("testBean1", TestBean.class);
ctx.getBean("testBean2", TestBean.class);
ctx.close();
}
@Test
@Timeout(10)
@EnabledForTestGroups(LONG_RUNNING)
@@ -266,6 +277,50 @@ class BackgroundBootstrapTests {
}
@Configuration(proxyBeanMethods = false)
static class LoadTimeWeaverAwareBeanConfig {
@Bean
LoadTimeWeaverAware loadTimeWeaverAware(ObjectProvider<TestBean> testBean1) {
Thread thread = new Thread(testBean1::getObject);
thread.start();
try {
thread.join();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
return (loadTimeWeaver -> {});
}
@Bean
public TestBean testBean1(TestBean testBean2) {
return new TestBean(testBean2);
}
@Bean @Lazy
public FactoryBean<TestBean> testBean2() {
try {
Thread.sleep(2000);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
TestBean testBean = new TestBean();
return new FactoryBean<>() {
@Override
public TestBean getObject() {
return testBean;
}
@Override
public Class<?> getObjectType() {
return testBean.getClass();
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class StrictLockingBeanConfig {
@@ -61,6 +61,7 @@ class ClassPathBeanDefinitionScannerTests {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(12);
assertThat(context.containsBean("serviceInvocationCounter")).isTrue();
assertThat(context.containsBean("fooServiceImpl")).isTrue();
@@ -73,8 +74,8 @@ class ClassPathBeanDefinitionScannerTests {
assertThat(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)).isTrue();
assertThat(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME)).isTrue();
assertThat(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME)).isTrue();
context.refresh();
context.refresh();
FooServiceImpl fooService = context.getBean("fooServiceImpl", FooServiceImpl.class);
assertThat(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent")).isTrue();
assertThat(fooService.foo(123)).isEqualTo("bar");
@@ -157,6 +158,7 @@ class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(12);
ClassPathBeanDefinitionScanner scanner2 = new ClassPathBeanDefinitionScanner(context) {
@@ -182,8 +184,8 @@ class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(7);
assertThat(beanCount).isGreaterThanOrEqualTo(7);
assertThat(context.containsBean("serviceInvocationCounter")).isTrue();
assertThat(context.containsBean("fooServiceImpl")).isTrue();
assertThat(context.containsBean("stubFooDao")).isTrue();
@@ -482,12 +484,14 @@ class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(12);
context.refresh();
assertThat(beanCount).isGreaterThanOrEqualTo(12);
context.refresh();
FooServiceImpl fooService = context.getBean("fooService", FooServiceImpl.class);
StaticListableBeanFactory myBf = (StaticListableBeanFactory) context.getBean("myBf");
MessageSource ms = (MessageSource) context.getBean("messageSource");
assertThat(fooService.isInitCalled()).isTrue();
assertThat(fooService.foo(123)).isEqualTo("bar");
assertThat(fooService.lookupFoo(123)).isEqualTo("bar");
@@ -509,9 +513,10 @@ class ClassPathBeanDefinitionScannerTests {
scanner.setIncludeAnnotationConfig(false);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(7);
context.refresh();
assertThat(beanCount).isGreaterThanOrEqualTo(7);
context.refresh();
try {
context.getBean("fooService");
}
@@ -545,9 +550,10 @@ class ClassPathBeanDefinitionScannerTests {
scanner.setAutowireCandidatePatterns("*NoSuchDao");
scanner.scan(BASE_PACKAGE);
context.refresh();
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
context.getBean("fooService"))
.satisfies(ex -> assertThat(ex.getMostSpecificCause()).isInstanceOf(NoSuchBeanDefinitionException.class));
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> context.getBean("fooService"))
.satisfies(ex ->
assertThat(ex.getMostSpecificCause()).isInstanceOf(NoSuchBeanDefinitionException.class));
}
@@ -409,11 +409,14 @@ class ConfigurationClassPostProcessorTests {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
beanFactory.setAllowBeanDefinitionOverriding(false);
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> pp.postProcessBeanFactory(beanFactory))
.withMessageContaining("bar")
.withMessageContaining("SingletonBeanConfig")
.withMessageContaining(TestBean.class.getName());
.withMessageContainingAll(
"bar",
"SingletonBeanConfig",
TestBean.class.getName()
);
}
@Test // gh-25430
@@ -422,10 +425,13 @@ class ConfigurationClassPostProcessorTests {
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
beanFactory.setAllowBeanDefinitionOverriding(false);
context.register(FirstConfiguration.class, SecondConfiguration.class);
assertThatIllegalStateException().isThrownBy(context::refresh)
.withMessageContaining("alias 'taskExecutor'")
.withMessageContaining("name 'applicationTaskExecutor'")
.withMessageContaining("bean definition 'taskExecutor'");
.withMessageContainingAll(
"alias 'taskExecutor'",
"name 'applicationTaskExecutor'",
"bean definition 'taskExecutor'"
);
context.close();
}
@@ -978,7 +984,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testSelfReferenceExclusionForFactoryMethodOnSameBean() {
void selfReferenceExclusionForFactoryMethodOnSameBean() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -992,7 +998,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testConfigWithDefaultMethods() {
void configWithDefaultMethods() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1006,7 +1012,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testConfigWithDefaultMethodsUsingAsm() {
void configWithDefaultMethodsUsingAsm() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1020,7 +1026,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testConfigWithFailingInit() { // gh-23343
void configWithFailingInit() { // gh-23343
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1034,7 +1040,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testCircularDependency() {
void circularDependency() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1048,42 +1054,42 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testCircularDependencyWithApplicationContext() {
void circularDependencyWithApplicationContext() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(A.class, AStrich.class))
.withMessageContaining("Circular reference");
}
@Test
void testPrototypeArgumentThroughBeanMethodCall() {
void prototypeArgumentThroughBeanMethodCall() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithPrototype.class);
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
ctx.close();
}
@Test
void testSingletonArgumentThroughBeanMethodCall() {
void singletonArgumentThroughBeanMethodCall() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithSingleton.class);
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
ctx.close();
}
@Test
void testNullArgumentThroughBeanMethodCall() {
void nullArgumentThroughBeanMethodCall() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithNull.class);
ctx.getBean("aFoo");
ctx.close();
}
@Test
void testInjectionPointMatchForNarrowTargetReturnType() {
void injectionPointMatchForNarrowTargetReturnType() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(FooBarConfiguration.class);
assertThat(ctx.getBean(FooImpl.class).bar).isSameAs(ctx.getBean(BarImpl.class));
ctx.close();
}
@Test
void testVarargOnBeanMethod() {
void varargOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class, TestBean.class);
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1093,7 +1099,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testEmptyVarargOnBeanMethod() {
void emptyVarargOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class);
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1102,7 +1108,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testCollectionArgumentOnBeanMethod() {
void collectionArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class, TestBean.class);
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class));
@@ -1110,7 +1116,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testEmptyCollectionArgumentOnBeanMethod() {
void emptyCollectionArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class);
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
assertThat(bean.testBeans).isEmpty();
@@ -1118,7 +1124,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testMapArgumentOnBeanMethod() {
void mapArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class, DummyRunnable.class);
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
assertThat(bean.testBeans).hasSize(1).containsValue(ctx.getBean(Runnable.class));
@@ -1126,7 +1132,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testEmptyMapArgumentOnBeanMethod() {
void emptyMapArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class);
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
assertThat(bean.testBeans).isEmpty();
@@ -1134,7 +1140,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testCollectionInjectionFromSameConfigurationClass() {
void collectionInjectionFromSameConfigurationClass() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class);
CollectionInjectionConfiguration bean = ctx.getBean(CollectionInjectionConfiguration.class);
assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class));
@@ -1142,7 +1148,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testMapInjectionFromSameConfigurationClass() {
void mapInjectionFromSameConfigurationClass() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapInjectionConfiguration.class);
MapInjectionConfiguration bean = ctx.getBean(MapInjectionConfiguration.class);
assertThat(bean.testBeans).containsOnly(Map.entry("testBean", ctx.getBean(Runnable.class)));
@@ -1150,20 +1156,20 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void testBeanLookupFromSameConfigurationClass() {
void beanLookupFromSameConfigurationClass() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanLookupConfiguration.class);
assertThat(ctx.getBean(BeanLookupConfiguration.class).getTestBean()).isSameAs(ctx.getBean(TestBean.class));
ctx.close();
}
@Test
void testNameClashBetweenConfigurationClassAndBean() {
void nameClashBetweenConfigurationClassAndBean() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class).getBean("myTestBean", TestBean.class));
.isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class));
}
@Test
void testBeanDefinitionRegistryPostProcessorConfig() {
void beanDefinitionRegistryPostProcessorConfig() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanDefinitionRegistryPostProcessorConfig.class);
assertThat(ctx.getBean("myTestBean")).isInstanceOf(TestBean.class);
ctx.close();
@@ -16,6 +16,7 @@
package org.springframework.context.event;
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -105,8 +106,9 @@ class AnnotationDrivenEventListenerTests {
this.eventCollector.assertTotalEventsCount(1);
this.eventCollector.clear();
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
TestEvent otherEvent = new TestEvent(this, Integer.valueOf(1));
this.context.publishEvent(otherEvent);
this.eventCollector.assertEvent(listener, otherEvent);
this.eventCollector.assertTotalEventsCount(1);
context.getBean(ApplicationEventMulticaster.class).removeApplicationListeners(l ->
@@ -742,6 +744,11 @@ class AnnotationDrivenEventListenerTests {
public void handleString(String content) {
collectEvent(content);
}
@EventListener({Boolean.class, Integer.class})
public void handleBooleanOrInteger(Serializable content) {
collectEvent(content);
}
}
@@ -1009,6 +1016,8 @@ class AnnotationDrivenEventListenerTests {
void handleString(String payload);
void handleBooleanOrInteger(Serializable content);
void handleTimestamp(Long timestamp);
void handleRatio(Double ratio);
@@ -1031,6 +1040,12 @@ class AnnotationDrivenEventListenerTests {
super.handleString(payload);
}
@EventListener({Boolean.class, Integer.class})
@Override
public void handleBooleanOrInteger(Serializable content) {
super.handleBooleanOrInteger(content);
}
@ConditionalEvent("#root.event.timestamp > #p0")
@Override
public void handleTimestamp(Long timestamp) {
@@ -18,11 +18,12 @@ package org.springframework.context.event.test;
/**
* @author Stephane Nicoll
* @author Juergen Hoeller
*/
@SuppressWarnings("serial")
public class TestEvent extends IdentifiableApplicationEvent {
public final String msg;
public final Object msg;
public TestEvent(Object source, String id, String msg) {
super(source, id);
@@ -34,6 +35,11 @@ public class TestEvent extends IdentifiableApplicationEvent {
this.msg = msg;
}
public TestEvent(Object source, Integer msg) {
super(source);
this.msg = msg;
}
public TestEvent(Object source) {
this(source, "test");
}
@@ -32,8 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*
* @author Stephane Nicoll
*/
@Deprecated
@SuppressWarnings("removal")
public class CandidateComponentsIndexLoaderTests {
@Test
@@ -30,8 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Stephane Nicoll
*/
@Deprecated
@SuppressWarnings("removal")
public class CandidateComponentsIndexTests {
@Test
@@ -503,10 +503,6 @@ class SpringValidatorAdapterTests {
private static final String ID = "id";
@Override
public void initialize(AnythingValid constraintAnnotation) {
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
List<Field> fieldsErrors = new ArrayList<>();
@@ -425,10 +425,6 @@ class ValidatorFactoryTests {
@Autowired
private Environment environment;
@Override
public void initialize(NameAddressValid constraintAnnotation) {
}
@Override
public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
if (value.expectsAutowiredValidator) {
@@ -495,10 +491,6 @@ class ValidatorFactoryTests {
public static class InnerValidator implements ConstraintValidator<InnerValid, InnerBean> {
@Override
public void initialize(InnerValid constraintAnnotation) {
}
@Override
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
@@ -542,10 +534,6 @@ class ValidatorFactoryTests {
public static class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {
@Override
public void initialize(NotXList constraintAnnotation) {
}
@Override
public boolean isValid(List<String> list, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
@@ -193,12 +193,16 @@ public class ClassReader {
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
this.classFileBuffer = classFileBuffer;
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V25) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
// SPRING PATCH: leniently try to parse newer class files as well
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
// throw new IllegalArgumentException(
// "Unsupported class file major version " + readShort(classFileOffset + 6));
// }
// END OF PATCH
// Create the constant pool arrays. The constant_pool_count field is after the magic,
// minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
@@ -290,6 +290,7 @@ public interface Opcodes {
int V23 = 0 << 16 | 67;
int V24 = 0 << 16 | 68;
int V25 = 0 << 16 | 69;
int V26 = 0 << 16 | 70;
/**
* Version flag indicating that the class is using 'preview' features.
@@ -1473,7 +1473,7 @@ final class SymbolTable {
/**
* Another entry (and so on recursively) having the same hash code (modulo the size of {@link
* SymbolTable#labelEntries}}) as this one.
* SymbolTable#labelEntries}) as this one.
*/
LabelEntry next;
File diff suppressed because it is too large Load Diff
@@ -110,7 +110,7 @@ class BridgeMethodResolver {
&& currentMethod != null) {
Signature target = new Signature(name, desc);
// If the target signature is the same as the current,
// we shouldn't change our bridge becaues invokespecial
// we shouldn't change our bridge because invokespecial
// is the only way to make progress (otherwise we'll
// get infinite recursion). This would typically
// only happen when a bridge method is created to widen
@@ -1278,29 +1278,32 @@ public class Enhancer extends AbstractClassGenerator {
Signature bridgeTarget = (Signature) bridgeToTarget.get(method.getSignature());
if (bridgeTarget != null) {
// checkcast each argument against the target's argument types
for (int i = 0; i < bridgeTarget.getArgumentTypes().length; i++) {
Type[] argTypes = method.getSignature().getArgumentTypes();
Type[] targetTypes = bridgeTarget.getArgumentTypes();
for (int i = 0; i < targetTypes.length; i++) {
e.load_arg(i);
Type target = bridgeTarget.getArgumentTypes()[i];
if (!target.equals(method.getSignature().getArgumentTypes()[i])) {
Type argType = argTypes[i];
Type target = targetTypes[i];
if (!target.equals(argType)) {
if (!TypeUtils.isPrimitive(target)) {
e.box(argType);
}
e.checkcast(target);
}
}
e.invoke_virtual_this(bridgeTarget);
// Not necessary to cast if the target & bridge have the same return type.
Type retType = method.getSignature().getReturnType();
// Not necessary to cast if the target & bridge have
// the same return type.
// (This conveniently includes void and primitive types,
// which would fail if casted. It's not possible to
// covariant from boxed to unbox (or vice versa), so no having
// to box/unbox for bridges).
// TODO: It also isn't necessary to checkcast if the return is
// assignable from the target. (This would happen if a subclass
// used covariant returns to narrow the return type within a bridge
// method.)
if (!retType.equals(bridgeTarget.getReturnType())) {
e.checkcast(retType);
Type target = bridgeTarget.getReturnType();
if (!target.equals(retType)) {
if (!TypeUtils.isPrimitive(target)) {
e.unbox(retType);
}
else {
e.checkcast(retType);
}
}
}
else {
@@ -100,15 +100,15 @@ public final class BridgeMethodResolver {
}
private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
Class<?> userClass = targetClass;
if (!bridgeMethod.isBridge() && localBridge) {
if (!bridgeMethod.isBridge()) {
userClass = ClassUtils.getUserClass(targetClass);
if (userClass == targetClass) {
return bridgeMethod;
}
}
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass));
Method bridgedMethod = cache.get(cacheKey);
if (bridgedMethod == null) {
@@ -118,7 +118,7 @@ public final class BridgeMethodResolver {
ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) {
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod));
searchCandidates(candidateMethods, bridgeMethod, targetClass));
}
if (bridgedMethod == null) {
// A bridge method was passed in but we couldn't find the bridged method.
@@ -149,14 +149,16 @@ public final class BridgeMethodResolver {
* @return the bridged method, or {@code null} if none found
*/
@Nullable
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
private static Method searchCandidates(
List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
if (candidateMethods.isEmpty()) {
return null;
}
Method previousMethod = null;
boolean sameSig = true;
for (Method candidateMethod : candidateMethods) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, targetClass)) {
return candidateMethod;
}
else if (previousMethod != null) {
@@ -172,12 +174,12 @@ public final class BridgeMethodResolver {
* Determines whether the bridge {@link Method} is the bridge for the
* supplied candidate {@link Method}.
*/
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> targetClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, targetClass)) {
return true;
}
Method method = findGenericDeclaration(bridgeMethod);
return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
return (method != null && isResolvedTypeMatch(method, candidateMethod, targetClass));
}
/**
@@ -186,14 +188,25 @@ public final class BridgeMethodResolver {
* are equal after resolving all types against the declaringType, otherwise
* returns {@code false}.
*/
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> targetClass) {
Type[] genericParameters = genericMethod.getGenericParameterTypes();
if (genericParameters.length != candidateMethod.getParameterCount()) {
return false;
}
Class<?> clazz = targetClass;
while (clazz != null && clazz != Object.class && clazz != genericMethod.getDeclaringClass()) {
if (checkResolvedTypeMatch(genericMethod, candidateMethod, clazz)) {
return true;
}
clazz = clazz.getSuperclass();
}
return false;
}
private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> clazz) {
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
for (int i = 0; i < candidateParameters.length; i++) {
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz);
Class<?> candidateParameter = candidateParameters[i];
if (candidateParameter.isArray()) {
// An array type: compare the component type.
@@ -273,7 +286,9 @@ public final class BridgeMethodResolver {
* introduced in Java 6 to fix <a href="https://bugs.openjdk.org/browse/JDK-6342411">
* JDK-6342411</a>.
* @return whether signatures match as described
* @deprecated as of 6.2.13: not necessary anymore due to {@link #getMostSpecificMethod}
*/
@Deprecated(since = "6.2.13", forRemoval = true)
public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
if (bridgeMethod == bridgedMethod) {
// Same method: for common purposes, return true to proceed as if it was a visibility bridge.
@@ -175,14 +175,18 @@ public abstract class AbstractCharSequenceDecoder<T extends CharSequence> extend
public final T decode(DataBuffer dataBuffer, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
Charset charset = getCharset(mimeType);
T value = decodeInternal(dataBuffer, charset);
DataBufferUtils.release(dataBuffer);
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(value, !traceOn);
return Hints.getLogPrefix(hints) + "Decoded " + formatted;
});
return value;
try {
Charset charset = getCharset(mimeType);
T value = decodeInternal(dataBuffer, charset);
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(value, !traceOn);
return Hints.getLogPrefix(hints) + "Decoded " + formatted;
});
return value;
}
finally {
DataBufferUtils.release(dataBuffer);
}
}
private Charset getCharset(@Nullable MimeType mimeType) {
@@ -50,7 +50,7 @@ public abstract class Hints {
/**
* Create a map wit a single hint via {@link Collections#singletonMap}.
* Create a map with a single hint via {@link Collections#singletonMap}.
* @param hintName the hint name
* @param value the hint value
* @return the created map
@@ -36,8 +36,9 @@ import org.springframework.util.Assert;
* <p>{@code DataBuffer}s has a separate {@linkplain #readPosition() read} and
* {@linkplain #writePosition() write} position, as opposed to {@code ByteBuffer}'s
* single {@linkplain ByteBuffer#position() position}. As such, the {@code DataBuffer}
* does not require a {@linkplain ByteBuffer#flip() flip} to read after writing. In general,
* the following invariant holds for the read and write positions, and the capacity:
* does not require a {@linkplain ByteBuffer#flip() flip} to read after writing.
* In general, the following invariant holds for the read and write positions,
* and the capacity:
*
* <blockquote>
* {@code 0} {@code <=}
@@ -46,12 +47,13 @@ import org.springframework.util.Assert;
* <i>capacity</i>
* </blockquote>
*
* <p>The {@linkplain #capacity() capacity} of a {@code DataBuffer} is expanded on demand,
* similar to {@code StringBuilder}.
* <p>The {@linkplain #capacity() capacity} of a {@code DataBuffer} is expanded on
* demand, similar to {@code StringBuilder}.
*
* <p>The main purpose of the {@code DataBuffer} abstraction is to provide a convenient wrapper
* around {@link ByteBuffer} which is similar to Netty's {@link io.netty.buffer.ByteBuf} but
* can also be used on non-Netty platforms (i.e. Servlet containers).
* <p>The main purpose of the {@code DataBuffer} abstraction is to provide a
* convenient wrapper around {@link ByteBuffer} which is similar to Netty's
* {@link io.netty.buffer.ByteBuf} but can also be used on non-Netty platforms
* (i.e. Servlet containers).
*
* @author Arjen Poutsma
* @author Brian Clozel
@@ -113,8 +115,8 @@ public interface DataBuffer {
* the current capacity, it will be expanded.
* @param capacity the new capacity
* @return this buffer
* @deprecated as of 6.0, in favor of {@link #ensureWritable(int)}, which
* has different semantics
* @deprecated as of 6.0, in favor of {@link #ensureWritable(int)}
* which has different semantics
*/
@Deprecated(since = "6.0")
DataBuffer capacity(int capacity);
@@ -186,6 +188,28 @@ public interface DataBuffer {
*/
byte getByte(int index);
/**
* Process a range of bytes from the current buffer using a
* {@link ByteProcessor}.
* @param index the index at which the processing will start
* @param length the maximum number of bytes to be processed
* @param processor the processor that consumes bytes
* @return the position that was reached when processing was stopped,
* or {@code -1} if the entire byte range was processed.
* @throws IndexOutOfBoundsException when {@code index} is out of bounds
* @since 6.2.12
*/
default int forEachByte(int index, int length, ByteProcessor processor) {
Assert.isTrue(length >= 0, "Length must be >= 0");
for (int position = index; position < index + length; position++) {
byte b = getByte(position);
if(!processor.process(b)) {
return position;
}
}
return -1;
}
/**
* Read a single byte from the current reading position from this data buffer.
* @return the byte at this buffer's current reading position
@@ -218,16 +242,16 @@ public interface DataBuffer {
DataBuffer write(byte b);
/**
* Write the given source into this buffer, starting at the current writing position
* of this buffer.
* Write the given source into this buffer, starting at the current writing
* position of this buffer.
* @param source the bytes to be written into this buffer
* @return this buffer
*/
DataBuffer write(byte[] source);
/**
* Write at most {@code length} bytes of the given source into this buffer, starting
* at the current writing position of this buffer.
* Write at most {@code length} bytes of the given source into this buffer,
* starting at the current writing position of this buffer.
* @param source the bytes to be written into this buffer
* @param offset the index within {@code source} to start writing from
* @param length the maximum number of bytes to be written from {@code source}
@@ -236,8 +260,8 @@ public interface DataBuffer {
DataBuffer write(byte[] source, int offset, int length);
/**
* Write one or more {@code DataBuffer}s to this buffer, starting at the current
* writing position. It is the responsibility of the caller to
* Write one or more {@code DataBuffer}s to this buffer, starting at the
* current writing position. It is the responsibility of the caller to
* {@linkplain DataBufferUtils#release(DataBuffer) release} the given data buffers.
* @param buffers the byte buffers to write into this buffer
* @return this buffer
@@ -245,8 +269,8 @@ public interface DataBuffer {
DataBuffer write(DataBuffer... buffers);
/**
* Write one or more {@link ByteBuffer} to this buffer, starting at the current
* writing position.
* Write one or more {@link ByteBuffer} to this buffer, starting at the
* current writing position.
* @param buffers the byte buffers to write into this buffer
* @return this buffer
*/
@@ -299,36 +323,37 @@ public interface DataBuffer {
}
/**
* Create a new {@code DataBuffer} whose contents is a shared subsequence of this
* data buffer's content. Data between this data buffer and the returned buffer is
* shared; though changes in the returned buffer's position will not be reflected
* in the reading nor writing position of this data buffer.
* Create a new {@code DataBuffer} whose contents is a shared subsequence
* of this data buffer's content. Data between this data buffer and the
* returned buffer is shared; though changes in the returned buffer's
* position will not be reflected in the reading nor writing position
* of this data buffer.
* <p><strong>Note</strong> that this method will <strong>not</strong> call
* {@link DataBufferUtils#retain(DataBuffer)} on the resulting slice: the reference
* count will not be increased.
* {@link DataBufferUtils#retain(DataBuffer)} on the resulting slice:
* the reference count will not be increased.
* @param index the index at which to start the slice
* @param length the length of the slice
* @return the specified slice of this data buffer
* @deprecated as of 6.0, in favor of {@link #split(int)}, which
* has different semantics
* @deprecated as of 6.0, in favor of {@link #split(int)}
* which has different semantics
*/
@Deprecated(since = "6.0")
DataBuffer slice(int index, int length);
/**
* Create a new {@code DataBuffer} whose contents is a shared, retained subsequence of this
* data buffer's content. Data between this data buffer and the returned buffer is
* shared; though changes in the returned buffer's position will not be reflected
* in the reading nor writing position of this data buffer.
* Create a new {@code DataBuffer} whose contents is a shared, retained subsequence
* of this data buffer's content. Data between this data buffer and the returned
* buffer is shared; though changes in the returned buffer's position will not be
* reflected in the reading nor writing position of this data buffer.
* <p><strong>Note</strong> that unlike {@link #slice(int, int)}, this method
* <strong>will</strong> call {@link DataBufferUtils#retain(DataBuffer)} (or equivalent) on the
* resulting slice.
* <strong>will</strong> call {@link DataBufferUtils#retain(DataBuffer)}
* (or equivalent) on the resulting slice.
* @param index the index at which to start the slice
* @param length the length of the slice
* @return the specified, retained slice of this data buffer
* @since 5.2
* @deprecated as of 6.0, in favor of {@link #split(int)}, which
* has different semantics
* @deprecated as of 6.0, in favor of {@link #split(int)}
* which has different semantics
*/
@Deprecated(since = "6.0")
default DataBuffer retainedSlice(int index, int length) {
@@ -337,12 +362,10 @@ public interface DataBuffer {
/**
* Splits this data buffer into two at the given index.
*
* <p>Data that precedes the {@code index} will be returned in a new buffer,
* while this buffer will contain data that follows after {@code index}.
* Memory between the two buffers is shared, but independent and cannot
* overlap (unlike {@link #slice(int, int) slice}).
*
* <p>The {@linkplain #readPosition() read} and
* {@linkplain #writePosition() write} position of the returned buffer are
* truncated to fit within the buffers {@linkplain #capacity() capacity} if
@@ -362,22 +385,22 @@ public interface DataBuffer {
* will not be reflected in the reading nor writing position of this data buffer.
* @return this data buffer as a byte buffer
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(ByteBuffer)},
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
* {@link #readableByteBuffers()} or {@link #writableByteBuffers()}
*/
@Deprecated(since = "6.0")
ByteBuffer asByteBuffer();
/**
* Expose a subsequence of this buffer's bytes as a {@link ByteBuffer}. Data between
* this {@code DataBuffer} and the returned {@code ByteBuffer} is shared; though
* changes in the returned buffer's {@linkplain ByteBuffer#position() position}
* Expose a subsequence of this buffer's bytes as a {@link ByteBuffer}. Data
* between this {@code DataBuffer} and the returned {@code ByteBuffer} is shared;
* though changes in the returned buffer's {@linkplain ByteBuffer#position() position}
* will not be reflected in the reading nor writing position of this data buffer.
* @param index the index at which to start the byte buffer
* @param length the length of the returned byte buffer
* @return this data buffer as a byte buffer
* @since 5.0.1
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(int, ByteBuffer, int, int)},
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
* {@link #readableByteBuffers()} or {@link #writableByteBuffers()}
*/
@Deprecated(since = "6.0")
ByteBuffer asByteBuffer(int index, int length);
@@ -531,4 +554,22 @@ public interface DataBuffer {
void close();
}
/**
* Process a range of bytes one by one.
* @since 6.2.12
*/
@FunctionalInterface
interface ByteProcessor {
/**
* Process the given {@code byte} and indicate whether processing
* should continue further.
* @param b a byte from the {@link DataBuffer}
* @return {@code true} if processing should continue,
* or {@code false} if processing should stop at this element
*/
boolean process(byte b);
}
}
@@ -31,8 +31,19 @@ package org.springframework.core.io.buffer;
public class DataBufferLimitException extends IllegalStateException {
/**
* Create an instance with the given message.
*/
public DataBufferLimitException(String message) {
super(message);
}
/**
* Create an instance with a message and a cause, e.g. {@link OutOfMemoryError}.
* @since 6.2.12
*/
public DataBufferLimitException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -832,13 +832,9 @@ public abstract class DataBufferUtils {
@Override
public int match(DataBuffer dataBuffer) {
for (int pos = dataBuffer.readPosition(); pos < dataBuffer.writePosition(); pos++) {
byte b = dataBuffer.getByte(pos);
if (match(b)) {
return pos;
}
}
return -1;
int start = dataBuffer.readPosition();
int end = dataBuffer.writePosition();
return dataBuffer.forEachByte(start, end - start, b -> !this.match(b));
}
@Override
@@ -881,14 +877,13 @@ public abstract class DataBufferUtils {
@Override
public int match(DataBuffer dataBuffer) {
for (int pos = dataBuffer.readPosition(); pos < dataBuffer.writePosition(); pos++) {
byte b = dataBuffer.getByte(pos);
if (match(b)) {
reset();
return pos;
}
int start = dataBuffer.readPosition();
int end = dataBuffer.writePosition();
int matchPosition = dataBuffer.forEachByte(start, end - start, b -> !this.match(b));
if (matchPosition != -1) {
reset();
}
return -1;
return matchPosition;
}
@Override
@@ -129,6 +129,11 @@ public class NettyDataBuffer implements PooledDataBuffer {
return this.byteBuf.getByte(index);
}
@Override
public int forEachByte(int index, int length, ByteProcessor processor) {
return this.byteBuf.forEachByte(index, length, processor::process);
}
@Override
public int capacity() {
return this.byteBuf.capacity();
@@ -374,7 +379,13 @@ public class NettyDataBuffer implements PooledDataBuffer {
@Override
public String toString() {
return this.byteBuf.toString();
try {
return this.byteBuf.toString();
}
catch (OutOfMemoryError ex) {
throw new DataBufferLimitException(
"Failed to convert data buffer to string: " + ex.getMessage(), ex);
}
}
@@ -41,7 +41,6 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NavigableSet;
@@ -395,7 +394,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
return new Resource[] {getResource(locationPattern)};
}
}
}
@@ -618,10 +617,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private Set<ClassPathManifestEntry> getClassPathManifestEntriesFromJar(File jar) throws IOException {
URL base = jar.toURI().toURL();
File parent = jar.getAbsoluteFile().getParentFile();
try (JarFile jarFile = new JarFile(jar)) {
Manifest manifest = jarFile.getManifest();
Attributes attributes = (manifest != null ? manifest.getMainAttributes() : null);
String classPath = (attributes != null ? attributes.getValue(Name.CLASS_PATH) : null);
Set<ClassPathManifestEntry> manifestEntries = new LinkedHashSet<>();
if (StringUtils.hasLength(classPath)) {
StringTokenizer tokenizer = new StringTokenizer(classPath);
@@ -631,8 +632,15 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// See jdk.internal.loader.URLClassPath.JarLoader.tryResolveFile(URL, String)
continue;
}
File candidate = new File(parent, path);
if (candidate.isFile() && candidate.getCanonicalPath().contains(parent.getCanonicalPath())) {
// Handle absolute paths correctly: do not apply parent to absolute paths.
File pathFile = new File(path);
File candidate = (pathFile.isAbsolute() ? pathFile : new File(parent, path));
// For relative paths, enforce security check: must be under parent.
// For absolute paths, just verify file exists (matching JVM behavior).
if (candidate.isFile() && (pathFile.isAbsolute() ||
candidate.getCanonicalPath().contains(parent.getCanonicalPath()))) {
manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches));
}
}
@@ -937,14 +945,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
Set<Resource> result = new LinkedHashSet<>(64);
NavigableSet<String> entriesCache = new TreeSet<>();
Iterator<String> entryIterator = jarFile.stream().map(JarEntry::getName).sorted().iterator();
while (entryIterator.hasNext()) {
String entryPath = entryIterator.next();
int entrySeparatorIndex = entryPath.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
if (entrySeparatorIndex >= 0) {
entryPath = entryPath.substring(entrySeparatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
}
entriesCache.add(entryPath);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
entriesCache.add(entries.nextElement().getName());
}
for (String entryPath : entriesCache) {
if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length());
if (getPathMatcher().match(subPattern, relativePath)) {
@@ -313,7 +313,15 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
this.concurrencyThrottle.beforeAccess();
doExecute(new TaskTrackingRunnable(taskToUse));
try {
doExecute(new TaskTrackingRunnable(taskToUse));
}
catch (Throwable ex) {
// Release concurrency permit if thread creation fails
this.concurrencyThrottle.afterAccess();
throw new TaskRejectedException(
"Failed to start execution thread for task: " + task, ex);
}
}
else if (this.activeThreads != null) {
doExecute(new TaskTrackingRunnable(taskToUse));
@@ -22,9 +22,7 @@ import org.springframework.util.Assert;
/**
* {@link TaskExecutor} implementation that executes each task <i>synchronously</i>
* in the calling thread.
*
* <p>Mainly intended for testing scenarios.
* in the calling thread. Mainly intended for testing scenarios.
*
* <p>Execution in the calling thread does have the advantage of participating
* in its thread context, for example the thread context class loader or the
@@ -40,13 +38,13 @@ import org.springframework.util.Assert;
public class SyncTaskExecutor implements TaskExecutor, Serializable {
/**
* Executes the given {@code task} synchronously, through direct
* invocation of it's {@link Runnable#run() run()} method.
* @throws IllegalArgumentException if the given {@code task} is {@code null}
* Execute the given {@code task} synchronously, through direct
* invocation of its {@link Runnable#run() run()} method.
* @throws RuntimeException if propagated from the given {@code Runnable}
*/
@Override
public void execute(Runnable task) {
Assert.notNull(task, "Runnable must not be null");
Assert.notNull(task, "Task must not be null");
task.run();
}
@@ -1483,15 +1483,16 @@ public abstract class ClassUtils {
}
/**
* Get the highest publicly accessible method in the supplied method's type hierarchy that
* Get the closest publicly accessible method in the supplied method's type hierarchy that
* has a method signature equivalent to the supplied method, if possible.
* <p>Otherwise, this method recursively searches the class hierarchy and implemented
* interfaces for an equivalent method that is {@code public} and declared in a
* {@code public} type.
* <p>If a publicly accessible equivalent method cannot be found, the supplied method
* will be returned, indicating that no such equivalent method exists. Consequently,
* callers of this method must manually validate the accessibility of the returned method
* if public access is a requirement.
* <p>This method recursively searches the class hierarchy and implemented interfaces for
* an equivalent method that is {@code public}, declared in a {@code public} type, and
* {@linkplain Module#isExported(String, Module) exported} to {@code spring-core}.
* <p>If the supplied method is not {@code public} or is {@code static}, or if a publicly
* accessible equivalent method cannot be found, the supplied method will be returned,
* indicating that no such equivalent method exists. Consequently, callers of this method
* must manually validate the accessibility of the returned method if public access is a
* requirement.
* <p>This is particularly useful for arriving at a public exported type on the Java
* Module System which allows the method to be invoked via reflection without an illegal
* access warning. This is also useful for invoking methods via a public API in bytecode
@@ -1507,18 +1508,22 @@ public abstract class ClassUtils {
* @see #getMostSpecificMethod(Method, Class)
*/
public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nullable Class<?> targetClass) {
// If the method is not public, we can abort the search immediately.
if (!Modifier.isPublic(method.getModifiers())) {
Class<?> declaringClass = method.getDeclaringClass();
// If the method is not public, or it's static, or its declaring class is public and exported
// already, we can abort the search immediately (avoiding reflection as well as cache access).
if (!Modifier.isPublic(method.getModifiers()) || Modifier.isStatic(method.getModifiers()) ||
(Modifier.isPublic(declaringClass.getModifiers()) &&
declaringClass.getModule().isExported(declaringClass.getPackageName(), ClassUtils.class.getModule()))) {
return method;
}
Method interfaceMethod = getInterfaceMethodIfPossible(method, targetClass, true);
// If we found a method in a public interface, return the interface method.
if (interfaceMethod != method) {
if (interfaceMethod != method && interfaceMethod.getDeclaringClass().getModule().isExported(
interfaceMethod.getDeclaringClass().getPackageName(), ClassUtils.class.getModule())) {
return interfaceMethod;
}
Class<?> declaringClass = method.getDeclaringClass();
// Bypass cache for java.lang.Object unless it is actually an overridable method declared there.
if (declaringClass.getSuperclass() == Object.class && !ReflectionUtils.isObjectMethod(method)) {
return method;
@@ -1533,19 +1538,20 @@ public abstract class ClassUtils {
private static Method findPubliclyAccessibleMethodIfPossible(
String methodName, Class<?>[] parameterTypes, Class<?> declaringClass) {
Method result = null;
Class<?> current = declaringClass.getSuperclass();
while (current != null) {
Method method = getMethodOrNull(current, methodName, parameterTypes);
if (method == null) {
break;
}
if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
result = method;
if (Modifier.isPublic(method.getDeclaringClass().getModifiers()) &&
method.getDeclaringClass().getModule().isExported(
method.getDeclaringClass().getPackageName(), ClassUtils.class.getModule())) {
return method;
}
current = method.getDeclaringClass().getSuperclass();
}
return result;
return null;
}
/**
@@ -33,11 +33,13 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.lang.Nullable;
/**
* A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
* A {@link ConcurrentHashMap} variant that uses {@link ReferenceType#SOFT soft} or
* {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}.
*
* <p>This class can be used as an alternative to
@@ -365,6 +367,118 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
});
}
@Override
@Nullable
public V computeIfAbsent(@Nullable K key, Function<? super K, ? extends V> mappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
if (entry != null) {
return entry.getValue();
}
V value = mappingFunction.apply(key);
// Add entry only if not null
if (value != null) {
Assert.state(entries != null, "No entries segment");
entries.add(value);
}
return value;
}
});
}
@Override
@Nullable
public V computeIfPresent(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
if (entry != null) {
V oldValue = entry.getValue();
V value = remappingFunction.apply(key, oldValue);
if (value != null) {
// Replace entry
entry.setValue(value);
return value;
}
else {
// Remove entry
if (ref != null) {
ref.release();
}
}
}
return null;
}
});
}
@Override
@Nullable
public V compute(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
V oldValue = null;
if (entry != null) {
oldValue = entry.getValue();
}
V value = remappingFunction.apply(key, oldValue);
if (value != null) {
if (entry != null) {
// Replace entry
entry.setValue(value);
}
else {
// Add entry
Assert.state(entries != null, "No entries segment");
entries.add(value);
}
return value;
}
else {
// Remove entry
if (ref != null) {
ref.release();
}
}
return null;
}
});
}
@Override
@Nullable
public V merge(@Nullable K key, @Nullable V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
if (entry != null) {
V oldValue = entry.getValue();
V newValue = remappingFunction.apply(oldValue, value);
if (newValue != null) {
// Replace entry
entry.setValue(newValue);
return newValue;
}
else {
// Remove entry
if (ref != null) {
ref.release();
}
return null;
}
}
else {
// Add entry
Assert.state(entries != null, "No entries segment");
entries.add(value);
return value;
}
}
});
}
@Override
public void clear() {
for (Segment segment : this.segments) {
@@ -514,7 +628,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
* @return the result of the operation
*/
@Nullable
public <T> T doTask(final int hash, @Nullable final Object key, final Task<T> task) {
private <T> T doTask(final int hash, @Nullable final Object key, final Task<T> task) {
boolean resize = task.hasOption(TaskOption.RESIZE);
if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
restructureIfNecessary(resize);
@@ -579,7 +693,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
private void restructure(boolean allowResize, @Nullable Reference<K, V> ref) {
boolean needsResize;
lock();
try {
int expectedCount = this.count.get();
@@ -595,7 +708,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
// Estimate new count, taking into account count inside lock and items that
// will be purged.
needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold);
boolean needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold);
boolean resizing = false;
int restructureSize = this.references.length;
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
@@ -636,8 +749,8 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
while (ref != null) {
if (!toPurge.contains(ref)) {
Entry<K, V> entry = ref.get();
// Also filter out null references that are now null
// they should be polled from the queue in a later restructure call.
// Also filter out null references that are now null:
// They should be polled from the queue in a later restructure call.
if (entry != null) {
purgedRef = this.referenceManager.createReference(
entry, ref.getHash(), purgedRef);
@@ -649,7 +762,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
this.references[i] = purgedRef;
}
}
this.count.set(Math.max(newCount, 0));
this.count.set(newCount);
}
finally {
unlock();
@@ -18,7 +18,6 @@ package org.springframework.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -36,10 +35,10 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
* that can be resolved using a {@link PlaceholderResolver PlaceholderResolver},
* <code>${</code> the prefix, and <code>}</code> the suffix.
*
* <p>A placeholder can also have a default value if its key does not represent a
* known property. The default value is separated from the key using a
* {@code separator}. For instance {@code ${name:John}} resolves to {@code John} if
* the placeholder resolver does not provide a value for the {@code name}
* <p>A placeholder can also have a default value if its key does not represent
* a known property. The default value is separated from the key using a
* {@code separator}. For instance {@code ${name:John}} resolves to {@code John}
* if the placeholder resolver does not provide a value for the {@code name}
* property.
*
* <p>Placeholders can also have a more complex structure, and the resolution of
@@ -50,13 +49,14 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
* must be rendered as is, the placeholder can be escaped using an {@code escape}
* character. For instance {@code \${name}} resolves as {@code ${name}}.
*
* <p>The prefix, suffix, separator, and escape characters are configurable. Only
* the prefix and suffix are mandatory, and the support for default values or
* escaping is conditional on providing non-null values for them.
* <p>The prefix, suffix, separator, and escape characters are configurable.
* Only the prefix and suffix are mandatory, and the support for default values
* or escaping is conditional on providing non-null values for them.
*
* <p>This parser makes sure to resolves placeholders as lazily as possible.
*
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 6.2
*/
final class PlaceholderParser {
@@ -120,51 +120,47 @@ final class PlaceholderParser {
* @return the supplied value with placeholders replaced inline
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
ParsedValue parsedValue = parse(value);
List<Part> parts = parse(value, false);
if (parts == null) {
return value;
}
ParsedValue parsedValue = new ParsedValue(value, parts);
PartResolutionContext resolutionContext = new PartResolutionContext(placeholderResolver,
this.prefix, this.suffix, this.ignoreUnresolvablePlaceholders,
candidate -> parse(candidate, false));
return parsedValue.resolve(resolutionContext);
}
/**
* Parse the specified value.
* @param value the value containing the placeholders to be replaced
* @return the different parts that have been identified
*/
ParsedValue parse(String value) {
List<Part> parts = parse(value, false);
return new ParsedValue(value, parts);
}
private List<Part> parse(String value, boolean inPlaceholder) {
LinkedList<Part> parts = new LinkedList<>();
private @Nullable List<Part> parse(String value, boolean inPlaceholder) {
int startIndex = nextStartPrefix(value, 0);
if (startIndex == -1) {
Part part = (inPlaceholder ? createSimplePlaceholderPart(value) : new TextPart(value));
parts.add(part);
return parts;
return null;
}
List<Part> parts = new ArrayList<>(4);
int position = 0;
while (startIndex != -1) {
int endIndex = nextValidEndPrefix(value, startIndex);
if (endIndex == -1) { // Not a valid placeholder, consume the prefix and continue
if (endIndex == -1) { // Not a valid placeholder, consume the prefix and continue
addText(value, position, startIndex + this.prefix.length(), parts);
position = startIndex + this.prefix.length();
startIndex = nextStartPrefix(value, position);
}
else if (isEscaped(value, startIndex)) { // Not a valid index, accumulate and skip the escape character
else if (isEscaped(value, startIndex)) { // Not a valid index, accumulate and skip the escape character
addText(value, position, startIndex - 1, parts);
addText(value, startIndex, startIndex + this.prefix.length(), parts);
position = startIndex + this.prefix.length();
startIndex = nextStartPrefix(value, position);
}
else { // Found valid placeholder, recursive parsing
else { // Found valid placeholder, recursive parsing
addText(value, position, startIndex, parts);
String placeholder = value.substring(startIndex + this.prefix.length(), endIndex);
List<Part> placeholderParts = parse(placeholder, true);
parts.addAll(placeholderParts);
if (placeholderParts == null) {
parts.add(createSimplePlaceholderPart(placeholder));
}
else {
parts.addAll(placeholderParts);
}
startIndex = nextStartPrefix(value, endIndex + this.suffix.length());
position = endIndex + this.suffix.length();
}
@@ -241,29 +237,6 @@ final class PlaceholderParser {
return new ParsedSection(buffer.toString(), null);
}
private static void addText(String value, int start, int end, LinkedList<Part> parts) {
if (start > end) {
return;
}
String text = value.substring(start, end);
if (!text.isEmpty()) {
if (!parts.isEmpty()) {
Part current = parts.removeLast();
if (current instanceof TextPart textPart) {
parts.add(new TextPart(textPart.text() + text));
}
else {
parts.add(current);
parts.add(new TextPart(text));
}
}
else {
parts.add(new TextPart(text));
}
}
}
private int nextStartPrefix(String value, int index) {
return value.indexOf(this.prefix, index);
}
@@ -296,15 +269,46 @@ final class PlaceholderParser {
return (this.escape != null && index > 0 && value.charAt(index - 1) == this.escape);
}
record ParsedSection(String key, @Nullable String fallback) {
private static void addText(String value, int start, int end, List<Part> parts) {
if (start >= end) {
return;
}
String text = value.substring(start, end);
if (!parts.isEmpty() && parts.get(parts.size() - 1) instanceof TextPart textPart) {
parts.set(parts.size() - 1, new TextPart(textPart.text() + text));
}
else {
parts.add(new TextPart(text));
}
}
/**
* A representation of the parsing of an input string.
* @param text the raw input string
* @param parts the parts that appear in the string, in order
*/
private record ParsedValue(String text, List<Part> parts) {
public String resolve(PartResolutionContext resolutionContext) {
try {
return Part.resolveAll(this.parts, resolutionContext);
}
catch (PlaceholderResolutionException ex) {
throw ex.withValue(this.text);
}
}
}
private record ParsedSection(String key, @Nullable String fallback) {
}
/**
* Provide the necessary context to handle and resolve underlying placeholders.
*/
static class PartResolutionContext implements PlaceholderResolver {
private static class PartResolutionContext implements PlaceholderResolver {
private final String prefix;
@@ -319,7 +323,6 @@ final class PlaceholderParser {
@Nullable
private Set<String> visitedPlaceholders;
PartResolutionContext(PlaceholderResolver resolver, String prefix, String suffix,
boolean ignoreUnresolvablePlaceholders, Function<String, List<Part>> parser) {
this.prefix = prefix;
@@ -352,7 +355,7 @@ final class PlaceholderParser {
return this.prefix + text + this.suffix;
}
public List<Part> parse(String text) {
public @Nullable List<Part> parse(String text) {
return this.parser.apply(text);
}
@@ -367,17 +370,17 @@ final class PlaceholderParser {
}
public void removePlaceholder(String placeholder) {
Assert.state(this.visitedPlaceholders != null, "Visited placeholders must not be null");
this.visitedPlaceholders.remove(placeholder);
if (this.visitedPlaceholders != null) {
this.visitedPlaceholders.remove(placeholder);
}
}
}
/**
* A part is a section of a String containing placeholders to replace.
*/
interface Part {
private interface Part {
/**
* Resolve this part using the specified {@link PartResolutionContext}.
@@ -408,30 +411,12 @@ final class PlaceholderParser {
}
/**
* A representation of the parsing of an input string.
* @param text the raw input string
* @param parts the parts that appear in the string, in order
*/
record ParsedValue(String text, List<Part> parts) {
public String resolve(PartResolutionContext resolutionContext) {
try {
return Part.resolveAll(this.parts, resolutionContext);
}
catch (PlaceholderResolutionException ex) {
throw ex.withValue(this.text);
}
}
}
/**
* A base {@link Part} implementation.
*/
abstract static class AbstractPart implements Part {
private abstract static class AbstractPart implements Part {
private final String text;
final String text;
protected AbstractPart(String text) {
this.text = text;
@@ -454,29 +439,19 @@ final class PlaceholderParser {
@Nullable
protected String resolveRecursively(PartResolutionContext resolutionContext, String key) {
String resolvedValue = resolutionContext.resolvePlaceholder(key);
if (resolvedValue != null) {
resolutionContext.flagPlaceholderAsVisited(key);
// Let's check if we need to recursively resolve that value
List<Part> nestedParts = resolutionContext.parse(resolvedValue);
String value = toText(nestedParts);
if (!isTextOnly(nestedParts)) {
value = new ParsedValue(resolvedValue, nestedParts).resolve(resolutionContext);
}
resolutionContext.removePlaceholder(key);
return value;
if (resolvedValue == null) {
// Not found
return null;
}
// Not found
return null;
}
private boolean isTextOnly(List<Part> parts) {
return parts.stream().allMatch(TextPart.class::isInstance);
}
private String toText(List<Part> parts) {
StringBuilder sb = new StringBuilder();
parts.forEach(part -> sb.append(part.text()));
return sb.toString();
// Let's check if we need to recursively resolve that value
List<Part> nestedParts = resolutionContext.parse(resolvedValue);
if (nestedParts == null) {
return resolvedValue;
}
resolutionContext.flagPlaceholderAsVisited(key);
String value = new ParsedValue(resolvedValue, nestedParts).resolve(resolutionContext);
resolutionContext.removePlaceholder(key);
return value;
}
}
@@ -484,7 +459,7 @@ final class PlaceholderParser {
/**
* A {@link Part} implementation that does not contain a valid placeholder.
*/
static class TextPart extends AbstractPart {
private static class TextPart extends AbstractPart {
/**
* Create a new instance.
@@ -496,7 +471,7 @@ final class PlaceholderParser {
@Override
public String resolve(PartResolutionContext resolutionContext) {
return text();
return this.text;
}
}
@@ -505,7 +480,7 @@ final class PlaceholderParser {
* A {@link Part} implementation that represents a single placeholder with
* a hard-coded fallback.
*/
static class SimplePlaceholderPart extends AbstractPart {
private static class SimplePlaceholderPart extends AbstractPart {
private final String key;
@@ -533,13 +508,13 @@ final class PlaceholderParser {
else if (this.fallback != null) {
return this.fallback;
}
return resolutionContext.handleUnresolvablePlaceholder(this.key, text());
return resolutionContext.handleUnresolvablePlaceholder(this.key, this.text);
}
@Nullable
private String resolveRecursively(PartResolutionContext resolutionContext) {
if (!this.text().equals(this.key)) {
String value = resolveRecursively(resolutionContext, this.text());
if (!this.text.equals(this.key)) {
String value = resolveRecursively(resolutionContext, this.text);
if (value != null) {
return value;
}
@@ -553,7 +528,7 @@ final class PlaceholderParser {
* A {@link Part} implementation that represents a single placeholder
* containing nested placeholders.
*/
static class NestedPlaceholderPart extends AbstractPart {
private static class NestedPlaceholderPart extends AbstractPart {
private final List<Part> keyParts;
@@ -582,7 +557,7 @@ final class PlaceholderParser {
else if (this.defaultParts != null) {
return Part.resolveAll(this.defaultParts, resolutionContext);
}
return resolutionContext.handleUnresolvablePlaceholder(resolvedKey, text());
return resolutionContext.handleUnresolvablePlaceholder(resolvedKey, this.text);
}
}
@@ -97,7 +97,7 @@ public class PropertyPlaceholderHelper {
* @param properties the {@code Properties} to use for replacement
* @return the supplied value with placeholders replaced inline
*/
public String replacePlaceholders(String value, final Properties properties) {
public String replacePlaceholders(String value, Properties properties) {
Assert.notNull(properties, "'properties' must not be null");
return replacePlaceholders(value, properties::getProperty);
}
@@ -111,9 +111,10 @@ public class PropertyPlaceholderHelper {
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver);
return this.parser.replacePlaceholders(value, placeholderResolver);
}
@Deprecated(since = "6.2.12", forRemoval = true)
protected String parseStringValue(String value, PlaceholderResolver placeholderResolver) {
return this.parser.replacePlaceholders(value, placeholderResolver);
}
@@ -30,6 +30,10 @@ final class VirtualThreadDelegate {
private final Thread.Builder threadBuilder = Thread.ofVirtual();
public VirtualThreadDelegate() {
// Matching constructor in dummy version, avoiding jar verification issues.
}
public ThreadFactory virtualThreadFactory() {
return this.threadBuilder.factory();
}
@@ -429,14 +429,14 @@ class BridgeMethodResolverTests {
}
public abstract static class AbstractDateAdder implements Adder<Date> {
public abstract static class AbstractAdder<T extends Serializable> implements Adder<T> {
@Override
public abstract void add(Date date);
public abstract void add(T item);
}
public static class DateAdder extends AbstractDateAdder {
public static class DateAdder extends AbstractAdder<Date> {
@Override
public void add(Date date) {
@@ -21,7 +21,9 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.testfixture.io.buffer.AbstractDataBufferAllocatingTests;
@@ -1045,4 +1047,35 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
release(buffer);
}
@ParameterizedDataBufferAllocatingTest
void forEachByteProcessAll(DataBufferFactory bufferFactory) {
super.bufferFactory = bufferFactory;
List<Byte> result = new ArrayList<>();
DataBuffer buffer = byteBuffer(new byte[]{'a', 'b', 'c', 'd'});
int index = buffer.forEachByte(0, 4, b -> {
result.add(b);
return true;
});
assertThat(index).isEqualTo(-1);
assertThat(result).containsExactly((byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd');
release(buffer);
}
@ParameterizedDataBufferAllocatingTest
void forEachByteProcessSome(DataBufferFactory bufferFactory) {
super.bufferFactory = bufferFactory;
List<Byte> result = new ArrayList<>();
DataBuffer buffer = byteBuffer(new byte[]{'a', 'b', 'c', 'd'});
int index = buffer.forEachByte(0, 4, b -> {
result.add(b);
return (b != 'c');
});
assertThat(index).isEqualTo(2);
assertThat(result).containsExactly((byte) 'a', (byte) 'b', (byte) 'c');
release(buffer);
}
}
@@ -337,6 +337,21 @@ class PathMatchingResourcePatternResolverTests {
assertThat(result.replace("\\", "/")).contains("!!!!").contains("/lib/asset.jar!/assets/file.txt");
}
@Test
void javaDashJarFindsAbsoluteClassPathManifestEntries() throws Exception {
Path assetJar = this.temp.resolve("dependency").resolve("asset.jar");
Files.createDirectories(assetJar.getParent());
writeAssetJar(assetJar);
writeApplicationJarWithAbsolutePath(this.temp.resolve("app.jar"), assetJar);
String java = ProcessHandle.current().info().command().get();
Process process = new ProcessBuilder(java, "-jar", "app.jar")
.directory(this.temp.toFile())
.start();
assertThat(process.waitFor()).isZero();
String result = StreamUtils.copyToString(process.getInputStream(), StandardCharsets.UTF_8);
assertThat(result.replace("\\", "/")).contains("!!!!").contains("asset.jar!/assets/file.txt");
}
private void writeAssetJar(Path path) throws Exception {
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()))) {
jar.putNextEntry(new ZipEntry("assets/"));
@@ -392,6 +407,35 @@ class PathMatchingResourcePatternResolverTests {
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
}
private void writeApplicationJarWithAbsolutePath(Path path, Path assetJar) throws Exception {
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.put(Name.CLASS_PATH, buildSpringClassPath() + assetJar.toAbsolutePath());
mainAttributes.put(Name.MAIN_CLASS, ClassPathManifestEntriesTestApplication.class.getName());
mainAttributes.put(Name.MANIFEST_VERSION, "1.0");
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()), manifest)) {
String appClassResource = ClassUtils.convertClassNameToResourcePath(
ClassPathManifestEntriesTestApplication.class.getName()) + ClassUtils.CLASS_FILE_SUFFIX;
String folder = "";
for (String name : appClassResource.split("/")) {
if (!name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) {
folder += name + "/";
jar.putNextEntry(new ZipEntry(folder));
jar.closeEntry();
}
else {
jar.putNextEntry(new ZipEntry(folder + name));
try (InputStream in = getClass().getResourceAsStream(name)) {
in.transferTo(jar);
}
jar.closeEntry();
}
}
}
assertThat(new FileSystemResource(path).exists()).isTrue();
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
}
private String buildSpringClassPath() throws Exception {
return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") +
copyClasses(LogFactory.class, "commons-logging");
@@ -16,6 +16,9 @@
package org.springframework.core.task;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.util.ConcurrencyThrottleSupport;
@@ -24,6 +27,12 @@ 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.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
/**
* @author Rick Evans
@@ -69,6 +78,59 @@ class SimpleAsyncTaskExecutorTests {
}
}
/**
* Verify that when thread creation fails in doExecute() while concurrency
* limiting is active, the concurrency permit is properly released to
* prevent permanent deadlock.
*
* <p>This test reproduces a critical bug where OutOfMemoryError from
* Thread.start() causes the executor to permanently deadlock:
* <ol>
* <li>beforeAccess() increments concurrencyCount
* <li>doExecute() throws Error before thread starts
* <li>TaskTrackingRunnable.run() never executes
* <li>afterAccess() in finally block never called
* <li>Subsequent tasks block forever in onLimitReached()
* </ol>
*
* <p>Test approach: The first execute() should fail with some exception
* (type doesn't matter - could be Error or TaskRejectedException).
* The second execute() is the real test: it should complete without
* deadlock if the permit was properly released.
*/
@Test
void executeFailsToStartThreadReleasesConcurrencyPermit() throws InterruptedException {
// Arrange
SimpleAsyncTaskExecutor executor = spy(new SimpleAsyncTaskExecutor());
executor.setConcurrencyLimit(1); // Enable concurrency limiting
Runnable task = () -> {};
Error failure = new OutOfMemoryError("TEST: Cannot start thread");
// Simulate thread creation failure
doThrow(failure).when(executor).doExecute(any(Runnable.class));
// Act - First execution fails
// Both "before fix" (throws Error) and "after fix" (throws TaskRejectedException)
// should throw some exception here - that's expected and correct
assertThatThrownBy(() -> executor.execute(task))
.isInstanceOf(Throwable.class);
// Arrange - Reset mock to allow second execution to succeed
willCallRealMethod().given(executor).doExecute(any(Runnable.class));
// Assert - Second execution should NOT deadlock
// This is the real test: if permit was leaked, this will timeout
CountDownLatch latch = new CountDownLatch(1);
executor.execute(() -> latch.countDown());
boolean completed = latch.await(1, TimeUnit.SECONDS);
assertThat(completed)
.withFailMessage("Executor should not deadlock if concurrency permit was properly released after first failure")
.isTrue();
}
@Test
void threadNameGetsSetCorrectly() {
String customPrefix = "chankPop#";
@@ -27,6 +27,7 @@ import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.URLConnection;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
@@ -687,13 +688,13 @@ class ClassUtilsTests {
}
@Test
void publicMethodInObjectClass() throws Exception {
void publicMethodInPublicClass() throws Exception {
Class<?> originalType = String.class;
Method originalMethod = originalType.getDeclaredMethod("hashCode");
Method originalMethod = originalType.getDeclaredMethod("toString");
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(Object.class);
assertThat(publiclyAccessibleMethod.getName()).isEqualTo("hashCode");
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@@ -703,9 +704,20 @@ class ClassUtilsTests {
Method originalMethod = originalType.getDeclaredMethod("size");
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
// Should find the interface method in List.
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(List.class);
assertThat(publiclyAccessibleMethod.getName()).isEqualTo("size");
// Should not find the interface method in List.
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test
void publicMethodInNonExportedClass() throws Exception {
Class<?> originalType = getClass().getClassLoader().loadClass("sun.net.www.protocol.http.HttpURLConnection");
Method originalMethod = originalType.getDeclaredMethod("getOutputStream");
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(URLConnection.class);
assertThat(publiclyAccessibleMethod.getName()).isSameAs(originalMethod.getName());
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@@ -856,6 +868,49 @@ class ClassUtilsTests {
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void staticMethodInPublicClass() throws Exception {
Method originalMethod = PublicSuperclass.class.getMethod("getCacheKey");
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void publicSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
Method originalMethod = PublicSubclass.class.getMethod("getCacheKey");
// Prerequisite: type must be public for this use case.
assertPublic(originalMethod.getDeclaringClass());
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void privateSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
Method originalMethod = PrivateSubclass.class.getMethod("getCacheKey");
// Prerequisite: type must not be public for this use case.
assertNotPublic(originalMethod.getDeclaringClass());
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertNotPubliclyAccessible(publiclyAccessibleMethod);
}
}
@@ -902,6 +957,10 @@ class ClassUtilsTests {
return Modifier.isPublic(member.getModifiers());
}
private static void assertStatic(Member member) {
assertThat(Modifier.isStatic(member.getModifiers())).as("%s must be static", member).isTrue();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@@ -1036,8 +1095,27 @@ class ClassUtilsTests {
String greet(String name);
}
public static class PublicSubclass extends PublicSuperclass {
/**
* This method intentionally has the exact same signature as
* {@link PublicSuperclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "child";
}
}
private static class PrivateSubclass extends PublicSuperclass implements PublicInterface, PrivateInterface {
/**
* This method intentionally has the exact same signature as
* {@link PublicSuperclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "child";
}
@Override
public int getNumber() {
return 2;
@@ -53,7 +53,7 @@ class ConcurrentReferenceHashMapTests {
@Test
void shouldCreateWithDefaults() {
void createWithDefaults() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>();
assertThat(map.getSegmentsSize()).isEqualTo(16);
assertThat(map.getSegment(0).getSize()).isEqualTo(1);
@@ -61,7 +61,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldCreateWithInitialCapacity() {
void createWithInitialCapacity() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32);
assertThat(map.getSegmentsSize()).isEqualTo(16);
assertThat(map.getSegment(0).getSize()).isEqualTo(2);
@@ -69,7 +69,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldCreateWithInitialCapacityAndLoadFactor() {
void createWithInitialCapacityAndLoadFactor() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32, 0.5f);
assertThat(map.getSegmentsSize()).isEqualTo(16);
assertThat(map.getSegment(0).getSize()).isEqualTo(2);
@@ -77,7 +77,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldCreateWithInitialCapacityAndConcurrentLevel() {
void createWithInitialCapacityAndConcurrentLevel() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(16, 2);
assertThat(map.getSegmentsSize()).isEqualTo(2);
assertThat(map.getSegment(0).getSize()).isEqualTo(8);
@@ -85,7 +85,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldCreateFullyCustom() {
void createFullyCustom() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(5, 0.5f, 3);
// concurrencyLevel of 3 ends up as 4 (nearest power of 2)
assertThat(map.getSegmentsSize()).isEqualTo(4);
@@ -95,28 +95,28 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldNeedNonNegativeInitialCapacity() {
void nonNegativeInitialCapacity() {
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 1));
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(-1, 1))
.withMessageContaining("Initial capacity must not be negative");
}
@Test
void shouldNeedPositiveLoadFactor() {
void positiveLoadFactor() {
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 0.1f, 1));
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 0.0f, 1))
.withMessageContaining("Load factor must be positive");
}
@Test
void shouldNeedPositiveConcurrencyLevel() {
void positiveConcurrencyLevel() {
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(1, 1));
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(1, 0))
.withMessageContaining("Concurrency level must be positive");
}
@Test
void shouldPutAndGet() {
void putAndGet() {
// NOTE we are using mock references so we don't need to worry about GC
assertThat(this.map).isEmpty();
this.map.put(123, "123");
@@ -129,14 +129,14 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldReplaceOnDoublePut() {
void replaceOnDoublePut() {
this.map.put(123, "321");
this.map.put(123, "123");
assertThat(this.map.get(123)).isEqualTo("123");
}
@Test
void shouldPutNullKey() {
void putNullKey() {
assertThat(this.map.get(null)).isNull();
assertThat(this.map.getOrDefault(null, "456")).isEqualTo("456");
this.map.put(null, "123");
@@ -145,7 +145,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldPutNullValue() {
void putNullValue() {
assertThat(this.map.get(123)).isNull();
assertThat(this.map.getOrDefault(123, "456")).isEqualTo("456");
this.map.put(123, "321");
@@ -157,12 +157,12 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldGetWithNoItems() {
void getWithNoItems() {
assertThat(this.map.get(123)).isNull();
}
@Test
void shouldApplySupplementalHash() {
void applySupplementalHash() {
Integer key = 123;
this.map.put(key, "123");
assertThat(this.map.getSupplementalHash()).isNotEqualTo(key.hashCode());
@@ -170,7 +170,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldGetFollowingNexts() {
void getFollowingNexts() {
// Use loadFactor to disable resize
this.map = new TestWeakConcurrentCache<>(1, 10.0f, 1);
this.map.put(1, "1");
@@ -184,7 +184,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldResize() {
void resize() {
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
this.map.put(1, "1");
assertThat(this.map.getSegment(0).getSize()).isEqualTo(1);
@@ -214,7 +214,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldPurgeOnGet() {
void purgeOnGet() {
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
for (int i = 1; i <= 5; i++) {
this.map.put(i, String.valueOf(i));
@@ -229,7 +229,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldPurgeOnPut() {
void purgeOnPut() {
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
for (int i = 1; i <= 5; i++) {
this.map.put(i, String.valueOf(i));
@@ -245,28 +245,28 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldPutIfAbsent() {
void putIfAbsent() {
assertThat(this.map.putIfAbsent(123, "123")).isNull();
assertThat(this.map.putIfAbsent(123, "123b")).isEqualTo("123");
assertThat(this.map.get(123)).isEqualTo("123");
}
@Test
void shouldPutIfAbsentWithNullValue() {
void putIfAbsentWithNullValue() {
assertThat(this.map.putIfAbsent(123, null)).isNull();
assertThat(this.map.putIfAbsent(123, "123")).isNull();
assertThat(this.map.get(123)).isNull();
}
@Test
void shouldPutIfAbsentWithNullKey() {
void putIfAbsentWithNullKey() {
assertThat(this.map.putIfAbsent(null, "123")).isNull();
assertThat(this.map.putIfAbsent(null, "123b")).isEqualTo("123");
assertThat(this.map.get(null)).isEqualTo("123");
}
@Test
void shouldRemoveKeyAndValue() {
void removeKeyAndValue() {
this.map.put(123, "123");
assertThat(this.map.remove(123, "456")).isFalse();
assertThat(this.map.get(123)).isEqualTo("123");
@@ -276,7 +276,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldRemoveKeyAndValueWithExistingNull() {
void removeKeyAndValueWithExistingNull() {
this.map.put(123, null);
assertThat(this.map.remove(123, "456")).isFalse();
assertThat(this.map.get(123)).isNull();
@@ -286,7 +286,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldReplaceOldValueWithNewValue() {
void replaceOldValueWithNewValue() {
this.map.put(123, "123");
assertThat(this.map.replace(123, "456", "789")).isFalse();
assertThat(this.map.get(123)).isEqualTo("123");
@@ -295,7 +295,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldReplaceOldNullValueWithNewValue() {
void replaceOldNullValueWithNewValue() {
this.map.put(123, null);
assertThat(this.map.replace(123, "456", "789")).isFalse();
assertThat(this.map.get(123)).isNull();
@@ -304,21 +304,61 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldReplaceValue() {
void replaceValue() {
this.map.put(123, "123");
assertThat(this.map.replace(123, "456")).isEqualTo("123");
assertThat(this.map.get(123)).isEqualTo("456");
}
@Test
void shouldReplaceNullValue() {
void replaceNullValue() {
this.map.put(123, null);
assertThat(this.map.replace(123, "456")).isNull();
assertThat(this.map.get(123)).isEqualTo("456");
}
@Test
void shouldGetSize() {
void computeIfAbsent() {
assertThat(this.map.computeIfAbsent(123, k -> "123")).isEqualTo("123");
assertThat(this.map.computeIfAbsent(123, k -> "123b")).isEqualTo("123");
assertThat(this.map.get(123)).isEqualTo("123");
this.map.remove(123);
assertThat(this.map.computeIfAbsent(123, k -> null)).isNull();
assertThat(this.map.containsKey(123)).isFalse();
}
@Test
void computeIfPresent() {
assertThat(this.map.computeIfPresent(123, (k, v) -> "123")).isNull();
this.map.put(123, "123");
assertThat(this.map.computeIfPresent(123, (k, v) -> v + "b")).isEqualTo("123b");
assertThat(this.map.get(123)).isEqualTo("123b");
assertThat(this.map.computeIfPresent(123, (k, v) -> null)).isNull();
assertThat(this.map.containsKey(123)).isFalse();
}
@Test
void compute() {
assertThat(this.map.compute(123, (k, v) -> "123" + v)).isEqualTo("123null");
assertThat(this.map.compute(123, (k, v) -> null)).isNull();
assertThat(this.map.compute(123, (k, v) -> null)).isNull();
assertThat(this.map.compute(123, (k, v) -> "123")).isEqualTo("123");
assertThat(this.map.compute(123, (k, v) -> v + "b")).isEqualTo("123b");
assertThat(this.map.get(123)).isEqualTo("123b");
}
@Test
void merge() {
assertThat(this.map.merge(123, "123", (v1, v2) -> v1 + v2)).isEqualTo("123");
assertThat(this.map.merge(123, null, (v1, v2) -> v1 + v2)).isEqualTo("123null");
assertThat(this.map.merge(123, null, (v1, v2) -> null)).isNull();
assertThat(this.map.merge(123, "123", (v1, v2) -> v1 + v2)).isEqualTo("123");
assertThat(this.map.merge(123, "b", (v1, v2) -> v1 + v2)).isEqualTo("123b");
assertThat(this.map.get(123)).isEqualTo("123b");
}
@Test
void size() {
assertThat(this.map).isEmpty();
this.map.put(123, "123");
this.map.put(123, null);
@@ -327,7 +367,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldSupportIsEmpty() {
void isEmpty() {
assertThat(this.map).isEmpty();
this.map.put(123, "123");
this.map.put(123, null);
@@ -336,7 +376,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldContainKey() {
void containsKey() {
assertThat(this.map.containsKey(123)).isFalse();
assertThat(this.map.containsKey(456)).isFalse();
this.map.put(123, "123");
@@ -346,7 +386,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldContainValue() {
void containsValue() {
assertThat(this.map.containsValue("123")).isFalse();
assertThat(this.map.containsValue(null)).isFalse();
this.map.put(123, "123");
@@ -356,7 +396,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldRemoveWhenKeyIsInMap() {
void removeWhenKeyIsInMap() {
this.map.put(123, null);
this.map.put(456, "456");
this.map.put(null, "789");
@@ -367,14 +407,14 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldRemoveWhenKeyIsNotInMap() {
void removeWhenKeyIsNotInMap() {
assertThat(this.map.remove(123)).isNull();
assertThat(this.map.remove(null)).isNull();
assertThat(this.map).isEmpty();
}
@Test
void shouldPutAll() {
void putAll() {
Map<Integer, String> m = new HashMap<>();
m.put(123, "123");
m.put(456, null);
@@ -387,7 +427,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldClear() {
void clear() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
@@ -399,7 +439,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldGetKeySet() {
void keySet() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
@@ -411,7 +451,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldGetValues() {
void valuesCollection() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
@@ -426,7 +466,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldGetEntrySet() {
void getEntrySet() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
@@ -438,7 +478,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldGetEntrySetFollowingNext() {
void getEntrySetFollowingNext() {
// Use loadFactor to disable resize
this.map = new TestWeakConcurrentCache<>(1, 10.0f, 1);
this.map.put(1, "1");
@@ -452,7 +492,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldRemoveViaEntrySet() {
void removeViaEntrySet() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
@@ -468,7 +508,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldSetViaEntrySet() {
void setViaEntrySet() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
@@ -502,7 +542,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void shouldSupportNullReference() {
void supportNullReference() {
// GC could happen during restructure so we must be able to create a reference for a null entry
map.createReferenceManager().createReference(null, 1234, null);
}
@@ -26,8 +26,6 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InOrder;
import org.springframework.util.PlaceholderParser.ParsedValue;
import org.springframework.util.PlaceholderParser.TextPart;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,10 +41,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
*
* @author Stephane Nicoll
* @author Sam Brannen
* @author Juergen Hoeller
*/
class PlaceholderParserTests {
@Nested // Tests with only the basic placeholder feature enabled
@Nested // Tests with only the basic placeholder feature enabled
class OnlyPlaceholderTests {
private final PlaceholderParser parser = new PlaceholderParser("${", "}", null, null, true);
@@ -82,7 +81,7 @@ class PlaceholderParserTests {
Map<String, String> properties = Map.of(
"p1", "v1",
"p2", "v2",
"p3", "${p1}:${p2}", // nested placeholders
"p3", "${p1}:${p2}", // nested placeholders
"p4", "${p3}", // deeply nested placeholders
"p5", "${p1}:${p2}:${bogus}"); // unresolvable placeholder
assertThat(this.parser.replacePlaceholders(text, properties::get)).isEqualTo(expected);
@@ -154,14 +153,13 @@ class PlaceholderParserTests {
@Test
void textWithInvalidPlaceholderSyntaxIsMerged() {
String text = "test${of${with${and${";
ParsedValue parsedValue = this.parser.parse(text);
assertThat(parsedValue.parts()).singleElement().isInstanceOfSatisfying(TextPart.class,
textPart -> assertThat(textPart.text()).isEqualTo(text));
assertThat(this.parser.replacePlaceholders(text,
placeholder -> {throw new UnsupportedOperationException();})).isEqualTo(text);
}
}
@Nested // Tests with the use of a separator
@Nested // Tests with the use of a separator
class DefaultValueTests {
private final PlaceholderParser parser = new PlaceholderParser("${", "}", ":", null, true);
@@ -195,7 +193,7 @@ class PlaceholderParserTests {
Map<String, String> properties = Map.of(
"p1", "v1",
"p2", "v2",
"p3", "${p1}:${p2}", // nested placeholders
"p3", "${p1}:${p2}", // nested placeholders
"p4", "${p3}", // deeply nested placeholders
"p5", "${p1}:${p2}:${bogus}", // unresolvable placeholder
"p6", "${p1}:${p2}:${bogus:def}"); // unresolvable w/ default
@@ -259,9 +257,9 @@ class PlaceholderParserTests {
assertThat(this.parser.replacePlaceholders("${invalid:${firstName}}", resolver)).isEqualTo("John");
verifyPlaceholderResolutions(resolver, "invalid", "firstName");
}
}
/**
* Tests that use the escape character.
*/
@@ -341,9 +339,9 @@ class PlaceholderParserTests {
Arguments.of("${service/host/${app.environment}/name:\\value}", "https://example.com/qa/name"),
Arguments.of("${service/host/${name\\:value}/}", "${service/host/${name:value}/}"));
}
}
@Nested
class ExceptionTests {
@@ -378,7 +376,6 @@ class PlaceholderParserTests {
.withMessage("Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\" <-- \"${p3}\"")
.withNoCause();
}
}

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