Compare commits

..

217 Commits

Author SHA1 Message Date
Brian Clozel 1f9c59b17b Release v6.1.20 2025-05-15 09:55:52 +02:00
rstoyanchev edfcc6ffb1 Make use of PatternMatchUtils ignoreCase option
Closes gh-34801
2025-05-14 15:49:06 +01:00
rstoyanchev f93132b11e Add missing @since tags in PatternMatchUtils
See: gh-34801
2025-05-14 15:17:56 +01:00
Juergen Hoeller 6ab4c84bd5 Upgrade to Reactor 2023.0.18
Closes gh-34899
2025-05-14 15:25:15 +02:00
Juergen Hoeller d5fca0d2c5 Upgrade to Jetty 12.0.21, Netty 4.1.121, Apache HttpClient 5.4.4, Checkstyle 10.23.1 2025-05-13 16:57:19 +02:00
Juergen Hoeller cbb94193fe Clarify CompositePropertySource behavior for EnumerablePropertySource contract
Closes gh-34886

(cherry picked from commit 6a9444473f)
2025-05-13 16:57:06 +02:00
Brian Clozel 5b5e2b6876 Fix HttpClient 5.3.x request config compatibility
As of gh-33806, the HttpComponents client request factory is forward
compatible with the 5.4+ versions of that library for configuring HTTP
request configuration.

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

Fixes gh-34854
2025-05-05 14:50:46 +02:00
Juergen Hoeller a5b0399a1d Polishing
(cherry picked from commit d0b186a1c7)
2025-04-28 16:20:29 +02:00
Juergen Hoeller 71f2725638 Try loadClass on LinkageError in case of same ClassLoader as well
Closes gh-34824

(cherry picked from commit 4172581f1b)
2025-04-28 16:20:24 +02:00
Sam Brannen daee9f1242 Reinstate the @⁠Inject Technology Compatibility Kit (TCK)
In commit 05ebca8677, the `public` modifier was removed from the
SpringAtInjectTckTests class, which prevents it from being run as a
JUnit 3 test class.

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

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

Closes gh-34800

(cherry picked from commit e384389790)
2025-04-25 12:37:58 +02:00
rstoyanchev 89ae20b9ff Add ignoreCase variants to PatternMatchUtils
See gh-34801
2025-04-23 12:13:49 +01:00
Brian Clozel a5a7d63dab Next development version (v6.1.20-SNAPSHOT) 2025-04-17 09:05:28 +02:00
Stéphane Nicoll d879112885 Upgrade to github-changelog-generator 0.0.12
Closes gh-34756
2025-04-15 10:06:17 +02:00
Juergen Hoeller 0a71df7aee Polishing 2025-04-10 18:55:34 +02:00
Juergen Hoeller 82b7fcb2cc Try loadClass on LinkageError in case of ClassLoader mismatch
See gh-34677

(cherry picked from commit 7f2c1f447f)
2025-04-10 18:49:31 +02:00
Juergen Hoeller 6717fca4ec Explicitly use original ClassLoader in case of package visibility
Closes gh-34684

(cherry picked from commit 6bb964e2d0)
2025-04-02 23:50:39 +02:00
Juergen Hoeller 86ea7003f7 Upgrade to Groovy 4.0.26, Jetty 12.0.18, Apache HttpClient 5.4.3, Checkstyle 10.22 2025-04-01 23:42:56 +02:00
Juergen Hoeller 0a157faec1 Polishing (backported from 6.2.x) 2025-04-01 22:36:40 +02:00
Juergen Hoeller 2548d44f3b Include cause in MethodInvocationException message
Closes gh-34691

(cherry picked from commit 203ca30a64)
2025-04-01 22:28:39 +02:00
Juergen Hoeller 6cc6ea1b2b Make jar caching configurable through setUseCaches
Closes gh-34694
2025-03-31 17:24:33 +02:00
Juergen Hoeller b73ca60811 Only attempt load for CGLIB classes in AOT mode
Closes gh-34677

(cherry picked from commit 743f32675d)
2025-03-31 17:23:46 +02:00
Sam Brannen d689629036 Support abstract @⁠Configuration classes without @⁠Bean methods again
Historically, @⁠Configuration classes that did not declare @⁠Bean
methods were allowed to be abstract. However, the changes made in
76a6b9ea79 introduced a regression that prevents such classes from
being abstract, resulting in a BeanInstantiationException. This change
in behavior is caused by the fact that such a @⁠Configuration class is
no longer replaced by a concrete subclass created dynamically by CGLIB.

This commit restores support for abstract @⁠Configuration classes
without @⁠Bean methods by modifying the "no enhancement required" check
in ConfigurationClassParser.

See gh-34486
Closes gh-34663

(cherry picked from commit 044258f085)
2025-03-31 14:33:02 +02:00
rstoyanchev 077721bdb4 Remove outdated notes on forwarded headers.
Closes gh-34625
2025-03-31 11:38:51 +01:00
Stéphane Nicoll b7654dd984 Make sure the generated values are available from a static context
This commit updates the tests of property values code generated to
invoke the generated code from a `static` context. This ensures that
the test fails if that's not the case.

This commit also updated LinkedHashMap handling that did suffer from
that problem.

Closes gh-34661
2025-03-27 12:09:29 +01:00
Juergen Hoeller 4645ce60c8 Polishing 2025-03-25 19:27:37 +01:00
Juergen Hoeller 564d3f6f5e Enable qualifier annotation tests including backported precedence tests
See gh-34644
2025-03-25 00:24:23 +01:00
Juergen Hoeller 7a1dfe79a0 Polishing (aligned with 6.2.x) 2025-03-21 16:08:02 +01:00
Juergen Hoeller 19257f4fdb Add javadoc notes on potential exception suppression in getBeansOfType
Closes gh-34629

(cherry picked from commit dc41ff569e)
2025-03-21 16:02:01 +01:00
Juergen Hoeller f06167ea01 Polishing 2025-03-21 11:06:10 +01:00
rstoyanchev 618e003b91 Fix dated Javadoc in MvcUriComponentsBuilder
related to forwarded headers

Closes gh-34615
2025-03-19 12:37:12 +00:00
Juergen Hoeller 8c494e5165 Defer triggerAfterCompletion invocation in doRollbackOnCommitException
Closes gh-34595

(cherry picked from commit cc986cd2e8)
2025-03-19 11:04:51 +01:00
Juergen Hoeller 62d7396769 Suggest compilation with -parameters in case of ambiguity
Closes gh-34609

(cherry picked from commit 86b2617c7f)
2025-03-19 11:04:39 +01:00
Brian Clozel 252058a65a Next development version (v6.1.19-SNAPSHOT) 2025-03-13 09:00:08 +01:00
Juergen Hoeller 9ea049ad6f Upgrade to Reactor 2023.0.16
Includes Netty 4.1.119 and Jetty 12.0.17

Closes gh-34579
2025-03-12 13:28:03 +01:00
Juergen Hoeller 1e31cd685e Avoid getTargetConnection call on transaction-aware Connection close
Closes gh-34484

(cherry picked from commit c64dae3623)
2025-02-28 14:21:55 +01:00
Juergen Hoeller 0d60f266ad Defensively call isShutdown method for executor description
Closes gh-34514

(cherry picked from commit 559ea6c480)
2025-02-28 14:21:41 +01:00
Juergen Hoeller a018ae6e02 Lenient fallback when cached WeakReference returns null
Closes gh-34423
2025-02-26 10:25:07 +01:00
Juergen Hoeller ea419d2dcd Avoid unnecessary CGLIB processing on configuration classes
Closes gh-34486

(cherry picked from commit aff9ac72ec)
2025-02-25 18:42:18 +01:00
Brian Clozel c02d07fdef Fix multiple Content-Language values in MockHttpServletResponse
Prior to this commit, `MockHttpServletResponse` would only support
adding a `Content-Language` once. Adding multiple header values would
always replace the content-language property in the response and the
entire header value.

This commit ensures that this behavior is supported.

Fixes gh-34491
2025-02-25 17:57:27 +01:00
Juergen Hoeller 7460be617b Consistent default ClassLoader fallback in hint classes
Closes gh-34470
2025-02-23 15:23:51 +01:00
Brian Clozel ccf4b028d2 Fix null value support in ContentCachingResponseWrapper
Prior to this commit, calling `setHeader` on the response wrapper would
have a separate code path for the "Content-Length" header. This did not
support calls with `null` values and would result in an exception.

This commit ensures that the cached content length value is reset in
this case and that the call is forwarded properly to the superclass.

Fixes gh-34465
2025-02-21 14:36:09 +01:00
Brian Clozel b5c89c91a9 Handle null values in MockHttpServletResponse#setHeader
Prior to this commit, `MockHttpServletResponse#setHeader` would not
remove the header entry when given a `null` value, as documented in the
Servlet API.
This commit ensures that this behavior is enforced.

Fixes gh-34466
2025-02-21 14:35:37 +01:00
Sébastien Deleuze 01ae0a7c26 Fix broken antora task
See https://github.com/spring-io/antora-extensions/pull/43

Closes gh-34455
2025-02-19 16:45:08 +01:00
Juergen Hoeller b48d72cbd0 Apply fallback in case of initial SmartClassLoader mismatch as well
See gh-34423

(cherry picked from commit 6786e1c3e5)
2025-02-18 20:53:21 +01:00
Juergen Hoeller 48628194dc Apply fallback in case of any exception coming out of createClass
Closes gh-34423

(cherry picked from commit 93134fd4d1)
2025-02-18 15:41:38 +01:00
Juergen Hoeller 3cf1fbf222 Mark XML-configured executor/scheduler as infrastructure bean
Closes gh-34015

(cherry picked from commit d0ceefedc6)
2025-02-18 15:41:17 +01:00
Stéphane Nicoll ecd943c7ec Next development version (v6.1.18-SNAPSHOT) 2025-02-13 13:23:32 +01:00
Juergen Hoeller 9d6d67188a Upgrade to Jetty 12.0.16, Netty 4.1.118, XStream 1.4.21, AssertJ 3.27.3, Checkstyle 10.21.2 2025-02-11 22:47:24 +01:00
Juergen Hoeller 24fd0940bd Align with SmartClassLoader handling for AOP proxy classes
Closes gh-34274

(cherry picked from commit f53da04717)
2025-02-11 22:19:43 +01:00
Sam Brannen 5b928f47a8 Finish incomplete sentences
(cherry picked from commit 9d3374b28d)
2025-02-11 12:32:27 +01:00
Stéphane Nicoll 5e52baee86 Upgrade to Reactor 2023.0.15
Closes gh-34406
2025-02-11 11:44:35 +01:00
Stéphane Nicoll c10596a1fd Upgrade to RSocket 1.1.5
Closes gh-34405
2025-02-11 11:43:04 +01:00
Sam Brannen a68b7289f0 Improve warning for unexpected use of value attribute as @⁠Component name
Prior to this commit, if a String 'value' attribute of an annotation
was annotated with @⁠AliasFor and explicitly configured to alias an
attribute other than @⁠Component.value, the value was still used as the
@⁠Component name, but the warning message that was logged stated that
the 'value' attribute should be annotated with
@⁠AliasFor(annotation=Component.class). However, it is not possible to
annotate an annotation attribute twice with @⁠AliasFor.

To address that, this commit revises the logic in
AnnotationBeanNameGenerator so that it issues a log message similar to
the following in such scenarios.

WARN o.s.c.a.AnnotationBeanNameGenerator - Although the 'value'
attribute in @⁠example.MyStereotype declares @⁠AliasFor for an
attribute other than @⁠Component's 'value' attribute, the value is
still used as the @⁠Component name based on convention. As of Spring
Framework 7.0, such a 'value' attribute will no longer be used as the
@⁠Component name.

See gh-34346
Closes gh-34317

(cherry picked from commit 17a94fb110)
2025-02-10 13:32:28 +01:00
Sam Brannen a5b9e667e3 Polishing
(cherry picked from commit 2fcae65853)
2025-02-10 13:32:20 +01:00
Branden Clark 78fe55f4d1 Check hasNext on sessionIds in UserDestinationResult
Closes gh-34333

Signed-off-by: Branden Clark <brandenrayclark@gmail.com>
2025-02-10 11:22:28 +00:00
Brian Clozel 65913c3655 Fix "Nth day of week" Quartz-style cron expressions
Prior to this commit, `CronExpression` would support Quartz-style
expressions with "Nth occurence of a  dayOfWeek" semantics by using the
`TemporalAdjusters.dayOfWeekInMonth` JDK support. This method will
return the Nth occurence starting with the month of the given temporal,
but in some cases will overflow to the next or previous month.
This behavior is not expected for our cron expression support.

This commit ensures that when an overflow happens (meaning, the
resulting date is not in the same month as the input temporal), we
should instead have another attempt at finding a valid month for this
expression.

Fixes gh-34377
2025-02-06 18:34:24 +01:00
Sam Brannen b7a996a64b Clarify component scanning of abstract classes with @⁠Lookup methods
Due to changes in gh-19118, classes that contain @⁠Lookup methods are
no longer required to be concrete classes for use with component
scanning; however, the reference documentation still states that such
classes must not be abstract.

This commit therefore removes the outdated reference documentation and
updates the corresponding Javadoc.

See gh-19118
Closes gh-34367

(cherry picked from commit 819a7c86c1)
2025-02-05 13:44:28 +01:00
Stéphane Nicoll 5b1a7c7f21 Handle arbitrary JoinPoint argument index
Closes gh-34369
2025-02-05 11:55:11 +01:00
Brian Clozel ebd80bdd6c Delete failing Freemarker test
This test was already ignored as of Java 21 because of a Java behavior
change, and now it started failing as of 17.0.14.
This commit removes the test entirely.
2025-02-04 10:03:50 +01:00
Juergen Hoeller a4fc68b8e8 Explicitly set custom ClassLoader on CGLIB Enhancer
Closes gh-34274

(cherry picked from commit 1b18928bf0)
2025-02-03 15:37:04 +01:00
Sam Brannen c333946b0b Restore property binding support for a Map that implements Iterable
The changes in commit c20a2e4763 introduced a regression with regard to
binding to a Map property when the Map also happens to implement
Iterable.

Although that is perhaps not a very common scenario, this commit
reorders the if-blocks in AbstractNestablePropertyAccessor's
getPropertyValue(PropertyTokenHolder) method so that a Map is
considered before an Iterable, thereby allowing an Iterable-Map to be
accessed as a Map.

See gh-907
Closes gh-34332

(cherry picked from commit b9e43d05bd)
2025-01-29 17:46:19 +01:00
rstoyanchev b0a8a3ec5f Enhance DisconnectedClientHelper exception type checks
We now look for the target exception types in cause chain as well,
but return false if we encounter a RestClient or WebClient
exception in the chain.

Closes gh-34264
2025-01-21 12:25:45 +00:00
rstoyanchev bb5be2119c Restore exception phrase in DisconnectedClientHelper
Effectively revert 203fa7, and add implementation comment and tests.

See gh-34264
2025-01-21 12:25:35 +00:00
Brian Clozel cf662368a5 Fix Wrong parentId tracking in JFR application startup
This commit fixes the tracking of the main event parentId for the Java
Flight Recorder implementation variant.

Fixes gh-34254
2025-01-13 20:22:07 +01:00
rstoyanchev e1b06ccfaa Improve logging in ReactiveTypeHandler
See gh-34188
2025-01-06 12:33:38 +00:00
rstoyanchev 9eefdb8041 Synchronize in WebAsyncManager onError/onTimeout
On connection loss, in a race between application thread and onError
callback trying to set the DeferredResult and dispatch, the onError
callback must not exit until dispatch completes. Currently, it may do
so because the DeferredResult has checks to bypasses locking or even
trying to dispatch if result is already set.

Closes gh-34192
2025-01-06 12:33:17 +00:00
rstoyanchev c50cb10964 Minor refactoring in WebAsyncManager
There is no need to set the DeferredResult from WebAsyncManager in an
onError notification because it is already done from the Lifecycle
interceptor in DeferredResult.

See gh-34192
2025-01-06 12:33:02 +00:00
rstoyanchev 245341231f Polishing in WebAsyncManager
See gh-34192
2025-01-06 12:32:40 +00:00
Sébastien Deleuze 875cc828cf Upgrade to Java 17.0.13 2024-12-30 12:14:52 +01:00
Stéphane Nicoll 898d3ec86a Backport tests for exact match resolution
See gh-34124
2024-12-23 12:15:44 +01:00
Tran Ngoc Nhan 8ccaabe778 Fix broken links in the web reference documentation
Backport of gh-34115.

Closes gh-34139
2024-12-23 11:22:17 +01:00
Sam Brannen 9c934b5019 Support varargs-only MethodHandle as SpEL function
Prior to this commit, if a MethodHandle was registered as a custom
function in the Spring Expression Language (SpEL) for a static method
that accepted only a variable argument list (for example,
`static String func(String... args)`), attempting to invoke the
registered function within a SpEL expression resulted in a
ClassCastException because the varargs array was unnecessarily wrapped
in an Object[].

This commit modifies the logic in FunctionReference's internal
executeFunctionViaMethodHandle() method to address that.

Closes gh-34109
2024-12-18 15:34:27 +01:00
Brian Clozel 4b45338ae6 Improve PathMatcher/PatternParser XML configuration
Prior to this commit, the MVC namespace for the XML Spring configuration
model would use the `PathMatcher` bean instance when provided like this:

```
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher"/>
<mvc:annotation-driven>
  <mvc:path-matching path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<mvc:resources mapping="/resources/**" location="classpath:/static/"/>
```

With this configuration, the handler mapping for annotated controller
would use the given `AntPathMatcher` instance but the handler mapping
for resources would still use the default, which is `PathPatternParser`
since 6.0.

This commit ensures that when a custom `path-matcher` is defined, it's
consistently used for all MVC handler mappings as an alias to the
well-known bean name. This allows to use `AntPathMatcher` consistently
while working on a migration path to `PathPatternParser`

This commit also adds a new XML attribute to the path matching
configuration that makes it possible to use a custom `PathPatternParser`
instance:

```
<bean id="patternParser" class="org.springframework.web.util.pattern.PathPatternParser"/>
<mvc:annotation-driven>
  <mvc:path-matching pattern-parser="patternParser"/>
</mvc:annotation-driven>
```

Closes gh-34102
See gh-34064
2024-12-17 10:30:18 +01:00
Juergen Hoeller 95003e3512 Avoid logger serialization behind shared EntityManager proxy
See gh-34084
2024-12-12 19:52:21 +01:00
Stéphane Nicoll 68368621f3 Prevent execution of Antora jobs on forks
Closes gh-34083
2024-12-12 17:39:59 +01:00
Brian Clozel 890a8f4311 Next development version (v6.1.17-SNAPSHOT) 2024-12-12 10:04:11 +01:00
Juergen Hoeller 0976d1a252 Upgrade to RxJava 3.1.10 and Checkstyle 10.20.2 2024-12-11 17:49:18 +01:00
Juergen Hoeller 2c0ce790d8 Avoid deprecated ListenableFuture name for internal class 2024-12-11 17:47:11 +01:00
Juergen Hoeller d512aafc36 Avoid javadoc references to deprecated types/methods
(cherry picked from commit 68997d8416)
2024-12-11 17:47:03 +01:00
Sam Brannen faa000f330 Log alias removal in DefaultListableBeanFactory
Prior to this commit, information was logged when a bean definition
overrode an existing bean definition, but nothing was logged when the
registration of a bean definition resulted in the removal of an alias.

With this commit, an INFO message is now logged whenever an alias is
removed in DefaultListableBeanFactory.

Closes gh-34070

(cherry picked from commit 41d9f21ab9)
2024-12-11 15:05:42 +01:00
Juergen Hoeller daf1b3d7a7 Remove unnecessary downcast to DefaultListableBeanFactory
See gh-33920
See gh-25952
2024-12-10 22:33:01 +01:00
Juergen Hoeller a48897a241 Log provider setup failure at info level without stacktrace
Closes gh-33979

(cherry picked from commit 3e3ca74020)
2024-12-10 22:28:38 +01:00
Stéphane Nicoll 7ffa12f90f Upgrade to Reactor 2023.0.13
Closes gh-34049
2024-12-10 14:09:57 +01:00
Stéphane Nicoll 0e74ffa144 Start building against Reactor 2023.0.13 snapshots
See gh-34049
2024-12-08 10:47:15 +01:00
Juergen Hoeller 357195289d Unit test for match against unresolvable wildcard
See gh-33982
2024-12-05 17:53:30 +01:00
Sébastien Deleuze 4fbca99f20 Remove unnecessary HandshakeHandlerRuntimeHints
Those hints are not needed anymore as of Spring Framework 6.1.
Backport of gh-34032.

Closes gh-34033
2024-12-05 15:46:31 +01:00
Juergen Hoeller c48ca5151f Upgrade to Gradle 8.11.1 2024-12-04 17:36:11 +01:00
Juergen Hoeller aaf2e8fbe6 Polishing
(cherry picked from commit edf7f3cd43)
2024-12-04 17:35:50 +01:00
Juergen Hoeller 3635ff0c17 Consistent fallback to NoUpgradeStrategyWebSocketService
Closes gh-33970

(cherry picked from commit 58c64cba2c)
2024-12-04 16:55:51 +01:00
Simon Baslé f20e76e227 Upgrade to Undertow 2.3.18.Final
Closes gh-33976
2024-11-27 14:31:46 +01:00
Simon Baslé dd32f95192 Dispatch in UndertowHttpHandlerAdapter
This ensures that the reactive handling of the request is dispatched
from the Undertow IO thread, marking the exchange as async rather than
ending it once the Undertow `handleRequest` method returns.

See gh-33885
Closes gh-33969
2024-11-26 16:58:21 +01:00
Tran Ngoc Nhan 75a920bc9f Fix a typo in the filters documentation
Backport of gh-33959
Closes gh-33971
2024-11-26 16:17:18 +01:00
Sam Brannen 92874adae9 Polish SpEL documentation
(cherry picked from commit 7196f3f554)
2024-11-18 11:43:14 +01:00
Sam Brannen 55167b7f50 Fix SpEL examples in reference guide
Closes gh-33907

(cherry picked from commit a12d40e10b)
2024-11-18 11:43:05 +01:00
Sam Brannen 3129afba19 Upgrade to Gradle 8.11
Closes gh-33895

(cherry picked from commit bb32df0a06)
2024-11-15 16:08:05 +01:00
Sam Brannen 1366d2926b Upgrade to antora-ui-spring version 0.4.18
Closes gh-33892

(cherry picked from commit fe8573c0ab)
2024-11-15 14:34:00 +01:00
Brian Clozel 5145cf476f Next development version (v6.1.16-SNAPSHOT) 2024-11-14 15:28:50 +01:00
Juergen Hoeller 6cb41dc5e3 Upgrade to Netty 4.1.115 2024-11-14 12:51:03 +01:00
Sam Brannen 1e95332f62 Fix link to "Resources" section in reference guide
Closes gh-33882
2024-11-14 10:15:34 +01:00
Juergen Hoeller fec6ba4dfe Polishing 2024-11-13 22:26:06 +01:00
Sam Brannen bfde33a514 Document options for Date/Time parsing & formatting issues with JDK 20+
This commit updates Javadoc and the reference guide to document options
for handling date/time parsing and formatting issues on JDK 20 and higher.

A new "Date and Time Formatting with JDK 20 and higher" page has also been
introduced in the wiki.

https://github.com/spring-projects/spring-framework/wiki/Date-and-Time-Formatting-with-JDK-20-and-higher

Closes gh-33151
2024-11-13 16:39:40 +01:00
Juergen Hoeller 14b9865de7 Remove ineffective JettyByteBufferIterator from WebSocket adapter
In JettyWebSocketHandlerAdapter, JettyByteBufferIterator does not actually add extra behavior (in contrast to JettyClientHttpConnector).
2024-11-13 15:10:32 +01:00
Juergen Hoeller 62eb21f938 Add note on declaring autowired fields as ObjectProvider
Closes gh-33834
2024-11-13 15:09:58 +01:00
Juergen Hoeller 01c85b1afb Add explicit note on blocking in case of concurrency limit
Closes gh-33873
2024-11-13 15:09:02 +01:00
Juergen Hoeller df376d9343 Upgrade to Micrometer 1.12.12 and Reactor 2023.0.12
Includes Netty 4.1.114, Jetty 12.0.15, Jetty Reactive HttpClient 4.0.8, RxJava 3.1.9, RSocket 1.1.4, Groovy 4.0.24, JRuby 9.4.9, Checkstyle 10.20.1

Closes gh-33877
Closes gh-33879
2024-11-13 15:08:40 +01:00
Sam Brannen 4d792d0e45 Remove mentions of Joda-Time support
Since Joda-Time support was removed in Spring Framework 6.0, this commit
removes obsolete mentions of Joda-Time in the reference guide and Javadoc.

See gh-27426
Closes gh-33881
2024-11-13 14:16:14 +01:00
Sam Brannen 0a5bd89129 Align JettyByteBufferIterator implementations
Both are now static nested classes.
2024-11-13 12:31:27 +01:00
Tran Ngoc Nhan 53b9a2cb78 Fix formatting issue in validation section of reference guide
Closes gh-33871
2024-11-12 20:49:56 +01:00
rstoyanchev e78179b96e Decode static resource path with UriUtils
Closes gh-33859
2024-11-12 10:16:05 +00:00
Brian Clozel 49a63e2c37 Add tests for gh-33867
Closes gh-33867
2024-11-12 10:02:48 +01:00
ZLATAN628 5666e363d1 Fix wrong uri tag for client observation convention
Prior to this commit, a client sending a request to
"https://example.org" would record the wrong URI tag as
"/https://example.org".

This commit ensures that the scheme+host part is matched correctly in
the default client observation conventions.

See gh-33867
2024-11-12 10:02:39 +01:00
Stéphane Nicoll f06853a339 Merge pull request #33865 from ngocnhan-tran1996
* pr/33865:
  Fix typo in reference documentation

Closes gh-33865
2024-11-10 14:45:49 +09:00
Tran Ngoc Nhan 8afd01ba2c Fix typo in reference documentation
See gh-33865
2024-11-10 14:45:19 +09:00
Sam Brannen 9724f9b9c8 Introduce tests for SpEL PropertyAccessor ordering
Closes gh-33861
2024-11-08 16:19:12 +01:00
Sam Brannen 0d9033592b Document that circular dependencies should be avoided in AOT mode
Closes gh-33786
2024-11-07 12:23:39 +01:00
Sam Brannen fc7b8ae966 Fix anchor name, consistently use title case, and polish wording
(cherry picked from commit 2e6c8daec6)
2024-11-07 12:22:25 +01:00
Sam Brannen c457131f1c Fix heading level for "Programmatic bean registration" in AOT chapter
(cherry picked from commit 9f0dbc4051)
2024-11-07 12:22:02 +01:00
Sam Brannen 39cfe136da Polishing 2024-11-07 11:49:22 +01:00
Sam Brannen 05a880e3b5 Fix XML bean reference example in reference manual
Closes gh-33855
2024-11-07 10:44:01 +01:00
Simon Baslé c93af1f76d Polishing: copyright header year
See gh-33823
2024-11-07 10:04:53 +01:00
Stéphane Nicoll 541866fd70 Merge pull request #33850 from wilkinsona
* pr/33850:
  Polish "Prefer modified resources over the originals in TestCompiler"
  Prefer modified resources over the originals in TestCompiler

Closes gh-33850
2024-11-06 23:11:58 +09:00
Stéphane Nicoll 0219ee656f Polish "Prefer modified resources over the originals in TestCompiler"
See gh-33850
2024-11-06 23:03:11 +09:00
Andy Wilkinson f6e1a5de09 Prefer modified resources over the originals in TestCompiler
Previously, when the test compiler had been seeded with a resource
file, any modifications to this resource performed during
compilation would be lost as this original content would always
be returned.

This commit updates the DynamicJavaFileManager to always store
the dynamic resource in the dynamicResourceFiles map, irrespective
of whether it's being created afresh or from some existing resource
content. This ensures that any modifications made to the resource
can be retrieved later on.

Similarly, DynamicClassLoader has been updated to prefer dynamic
resource files over any original resource files. This ensures that
the resource that it finds reflects any modifications that have
been made to it.

See gh-33850
2024-11-06 23:00:08 +09:00
Simon Baslé 9b3cb15389 Introduce HttpHeaders#headerSet to guarantee case-insensitive iteration
The `HttpHeaders#headerSet` method is intended as a drop-in replacement
for `entrySet` that guarantees a single casing for all header names
reported during the iteration, as the cost of some overhead but with
support for iterator removal and entry value-setting.

The `formatHeaders` static method is also altered to do a similar
deduplication of casing variants, but now additionally mentions
"with native header names [native name set]" if the native name set
contains casing variants.

Closes gh-33823
2024-11-06 11:11:39 +01:00
Hosam Aly 4ef2b429e0 Fix a typo in beanvalidation.adoc
ContraintViolation => ConstraintViolation

Closes gh-33846
2024-11-06 10:09:05 +01:00
Johnny Lim 0beb56a58c Fix indentation to use tabs in Kotlin source files
Closes gh-33840
2024-11-05 10:24:02 +01:00
Sam Brannen 6bd4687706 Update copyright headers
See gh-33839
2024-11-03 16:14:41 +01:00
Tran Ngoc Nhan 07b12666b4 Fix typos in Javadoc and variable names
Closes gh-33839
2024-11-03 16:13:50 +01:00
Stéphane Nicoll 438d6de3c1 Merge pull request #33768 from kunaljani1100
* pr/33768:
  Polish "Add test coverage for DomUtils"
  Add test coverage for DomUtils

Closes gh-33768
2024-11-03 18:16:17 +09:00
Stéphane Nicoll d43126705f Polish "Add test coverage for DomUtils"
See gh-33768
2024-11-03 18:15:34 +09:00
kunaljani1100 57bbf0ca0f Add test coverage for DomUtils
See gh-33768
2024-11-03 13:44:07 +09:00
Sam Brannen 27912b1ed1 Prevent accidental printing to System.err in the codebase
This also revises the Checkstyle rule so that invocations such as
System.out.printf() are also forbidden.
2024-11-01 10:42:31 +01:00
Brian Clozel 8ffbafd384 Prevent accidental Sysouts in the codebase 2024-10-30 10:36:30 +01:00
Juergen Hoeller 022fdcd67e Provide removeCache method on Caffeine/ConcurrentMapCacheManager
Closes gh-33813
2024-10-29 23:02:48 +01:00
Juergen Hoeller 11ebceee38 Call get/setRequestConfig for HttpClient 5.4 compatibility
Includes upgrade to HttpClient 5.4.1 while retaining a HttpClient 5.1 baseline.

Closes gh-33806
2024-10-29 23:01:57 +01:00
Juergen Hoeller fa21dffcf8 Restore traditional AspectJ behavior through "spring.aop.ajc.ignore=true"
Closes gh-33704
2024-10-29 23:01:16 +01:00
Juergen Hoeller 323de1208a Document limited support for lifecycle management
Closes gh-33780
2024-10-28 22:08:41 +01:00
Juergen Hoeller 94d46eba3c Exclusively mention CompletableFuture instead of ListenableFuture
Closes gh-33805
2024-10-28 22:05:10 +01:00
Sam Brannen e340e45f5a Rename aopAvailable constants in TransactionSynchronizationUtils
Closes gh-33796
2024-10-25 14:41:41 +02:00
Brian Clozel a06bbccf9e HttpHeaders.writeableHttpHeaders should unwrap many times
Prior to this commit, the `HttpHeaders.writeableHttpHeaders` would only
consider headers read-only instances that were wrapped once by
`HttpHeaders.readOnlyHttpHeaders`. This does not work when other
`HttpHeaders` wrappers are involved in the chain.

This commit ensures that `writeableHttpHeaders` unwraps all headers
instances down to the actual multivalue map and create a new headers
instance out of it.

Fixes gh-33789
2024-10-25 10:22:12 +02:00
rstoyanchev bbe362c0e6 Allow null from RestClient exchange methods
Closes gh-33779
2024-10-23 19:08:54 +01:00
Brian Clozel 4a81f2c904 Remove HTTP parts workaround for Resin
A workaround was added for the Resin Servlet container in gh-13937.
This avoids attempting to delete parts that are not named, because the
`part.delete()` call would fail for non-file entries. This can be
problematic for files that are unnamed as they might not be removed by
the Framework.

This commit removes this workaround as Resin is not supported anymore.

Fixes gh-33511
2024-10-23 09:24:30 +02:00
Sam Brannen fb0a108254 Improve Javadoc for core SpEL APIs 2024-10-22 13:04:29 +02:00
Sam Brannen c98f314665 Throw ParseException for unsupported character in SpEL expression
Prior to this commit, the SpEL Tokenizer threw an IllegalStateException
when an unsupported character was detected in a SpEL expression;
however, the Javadoc for ExpressionParser.parseExpression() states that
it throws a ParseException if an exception occurred during parsing.

This commit addresses that issue by throwing a SpelParseException for
an unsupported character in a SpEL expression, using a new
UNSUPPORTED_CHARACTER enum constant in SpelMessage.

Closes gh-33767
2024-10-22 13:03:08 +02:00
Sehwan Lim d22924c728 Fix incorrect regex rendering in MVC controller documentation
This commit fixes the issue where the regex pattern in the reference documentation
was not rendering correctly for the `/projects/{project:[a-z]+}/versions` mapping.

Closes gh-33766
2024-10-21 18:32:44 +02:00
Juergen Hoeller e235e661d8 Polishing 2024-10-21 18:13:42 +02:00
Juergen Hoeller 09fe0adb40 Load-time weaving support for WildFly 24+
Closes gh-33728
2024-10-21 18:13:34 +02:00
Brian Clozel 84e762b470 Fix double accounting of JMS process observations
Prior to this commit, the instrumentation of the processing of JMS
messages would happen a different levels of the hierarchy, accounting
for alli known implementations, `SimpleMessageListenerContainer` and
`DefaultMessageListenerContainer` as well as various use cases and
`MessageListener` variants.

Unfortunately, this instrumentation could lead to observing JMS
processing twice in some cases, and would not be consistent about the
scope of what's observed.

This commit moves the instrumentation basics into the
`AbstractMessageListenerContainer` but leaves the actual observation
calls to the public implementations.

Fixes gh-33758
2024-10-21 16:09:58 +02:00
Juergen Hoeller e90a2da05d Clarify defensive impact of allowEagerInit flag for type matching
Closes gh-33740
2024-10-21 15:38:25 +02:00
Sam Brannen d48f388c6a Polish Javadoc for @⁠DateTimeFormat 2024-10-21 12:19:35 +02:00
Sam Brannen bbbb7c396e Update Date/Time formatting tests for pre/post JDK 20
This commit updates our Date/Time formatting/printing tests to
demonstrate that the use of fallback patterns can help mitigate
locale-based parsing/formatting issues beginning with JDK 20.

The documentation within the tests is intentionally rather thorough for
two reasons:

1. We need to understand exactly what it is we are testing and why the
   tests are written that way.

2. We may re-use parts of the documentation and examples in forthcoming
   documentation that we will provide to users.

See gh-33151
2024-10-20 17:35:37 +02:00
Sam Brannen d72c8b32b7 Ignore duplicate @⁠Priority values when determining highest priority
Prior to this commit, DefaultListableBeanFactory's
determineHighestPriorityCandidate() method sometimes failed to
determine the highest priority candidate if duplicate priority
candidates were detected whose priority was not the highest priority in
the candidate set. In addition, the bean registration order affected
the outcome of the algorithm: if the highest priority was detected
before other duplicate priorities were detected, the algorithm
succeeded in determining the highest priority candidate.

This commit addresses those shortcomings by ignoring duplicate
@⁠Priority values unless the duplication is for the highest priority
encountered, in which case a NoUniqueBeanDefinitionException is still
thrown to signal that multiple beans were found with the same "highest
priority".

Closes gh-33733
2024-10-19 14:54:29 +02:00
Brian Clozel 67d78eb61c Avoid Servlet observations failures for invalid HTTP status
Prior to this commit, the `DefaultServerRequestObservationConvention`
for Servlet failed when the HTTP response status was invalid (for
example, set to "0").

This commit catches `IllegalArgumentException` thrown for such invalid
HTTP status and instead returns an unknown outcome for the observation.

Fixes gh-33725
2024-10-18 14:43:46 +02:00
Johnny Lim 73fd9133e9 Fix Javadoc in ReactorNetty2ResourceFactory
See gh-33338
Closes gh-33735
2024-10-18 12:36:15 +02:00
Sébastien Deleuze 5e28a25a30 Add a WebFlux integration test
This commit adds a WebFlux integration test with a request
body and a delayed response in order to test a use case
where we found a regression with Undertow 2.3.18.Final.

For now, Undertow 2.3.17.Final is still used.

Closes gh-33739
2024-10-18 11:13:09 +02:00
Sébastien Deleuze 4c44b91cf9 Speedup WebFlux tests by reducing the interval period
In WebFlux, we have various tests using a period of 50 or 100ms.
We should use a smaller value like 1ms to speedup tests.

Closes gh-33738
2024-10-18 11:12:08 +02:00
Brian Clozel 912c067e23 Fix buffer leak in Jackson2 decoders
Prior to this commit, the Jackson2 decoders (JSON, Smile, CBOR) could
leak buffers in case the decoding operation times out or is cancelled
and some buffers are still in flight.

This commit ensures that buffers are released on cancel signals.

Fixes gh-33731
2024-10-18 10:51:38 +02:00
Stéphane Nicoll a55701588e Next development version (v6.1.15-SNAPSHOT) 2024-10-17 10:28:22 +02:00
Juergen Hoeller fde7116ae4 Consistently skip processing of plain Java annotations
Closes gh-33580
2024-10-16 17:17:22 +02:00
Brian Clozel 0a645916cd Add checkstyle rule for toLowerCase/toUpperCase
This commit adds a checkstyle rule that prevents the re-introduction of
such calls.

See gh-33708
2024-10-16 16:25:03 +02:00
Habin Song 5302e7aa25 Update fallback.adoc
Fix a typo.

Closes gh-33721
2024-10-16 14:20:56 +02:00
github-actions[bot] cad02c1743 Update Antora Spring UI to v0.4.17 2024-10-16 14:15:55 +02:00
Juergen Hoeller c765d03a59 Use Locale.ROOT consistently for toLower/toUpperCase
Closes gh-33708
2024-10-16 13:44:59 +02:00
Juergen Hoeller 11d4272ff4 Use Locale.ROOT consistently for toLower/toUpperCase
Closes gh-33708
2024-10-16 13:36:23 +02:00
rstoyanchev 23656aebc6 Use Locale.ROOT consistently for toLower/toUpperCase
See gh-33708
2024-10-16 12:05:54 +01:00
Juergen Hoeller feb6a5f52d Polishing 2024-10-16 11:35:23 +02:00
Stéphane Nicoll a228eb8bd6 Upgrade to Reactor 2023.0.11
Closes gh-33637
2024-10-15 14:56:57 +02:00
Stéphane Nicoll c015814140 Upgrade to Micrometer 1.12.11
Closes gh-33647
2024-10-15 09:06:27 +02:00
rstoyanchev fb7890d739 Update processPath for double encoding
See gh-33689
2024-10-14 18:13:55 +01:00
Habin Song 7c2c4d7c9a Update scheduling.adoc
Change 'OutOfMemoryErrors' to 'OutOfMemoryError'.

Closes gh-33703
2024-10-14 19:01:33 +02:00
rstoyanchev 3bfbe30a78 Normalize static resource path early
Rather than leaving it to the Resource implementation, and
potentially normalizing twice, we apply it once as part of the
initial processPath checks.

Closes gh-33689
2024-10-14 15:52:15 +01:00
Brian Clozel 32962894a0 Add runtime hints for JMS connection factories proxies
Prior to this commit, the JMS connection factories would proxy various
interfaces. This typically requires runtime hints for GraalVM native
applications and spring-jms is missing those.

This commit adds a new `ConnectionFactoriesRuntimeHints` that
contributes such hints with type conditions.

Fixes gh-33590
2024-10-14 14:54:24 +02:00
Hosam Aly 97bce6d361 Fix link in testing/support-jdbc.adoc
Fix the link to "Testing Data Access Logic with an Embedded Database".

Closes gh-33686
2024-10-14 12:07:17 +02:00
Simon Baslé 8da31e1db7 Reject CORS request with 403 if Origin header is malformed
When assessing if a request is a CORS request, both mvc and reactive
`DefaultCorsProcessor` now catch `IllegalArgumentException` and turn
this into a 403 rejection rather than letting the exception propagate
into a 500 response.

Closes gh-33688
2024-10-14 11:50:19 +02:00
Stéphane Nicoll f991c19b30 Merge pull request #33617 from asibross
* pr/33617:
  Polish "Adapt Javadoc note about log level of BeanPostProcessorChecker"
  Adapt Javadoc note about log level of BeanPostProcessorChecker

Closes gh-33617
2024-10-09 14:44:32 +02:00
Stéphane Nicoll 3c80d4c978 Polish "Adapt Javadoc note about log level of BeanPostProcessorChecker"
See gh-33617
2024-10-09 14:42:36 +02:00
Asi Bross 6da32b4631 Adapt Javadoc note about log level of BeanPostProcessorChecker
See gh-33617
2024-10-09 14:40:49 +02:00
Stéphane Nicoll 91cadf7f24 Start building against Reactor 2023.0.11 snapshots
See gh-33637
2024-10-09 14:16:14 +02:00
Stéphane Nicoll 90637b6344 Start building against Micrometer 1.12.11 snapshots
See gh-33647
2024-10-09 14:15:22 +02:00
Juergen Hoeller 2622db1dbe Consistent references to annotation-based autowiring 2024-10-08 11:26:42 +02:00
Juergen Hoeller 3dac274d20 Avoid synchronization for delegate initialization
Closes gh-33656
2024-10-07 10:19:16 +02:00
Stéphane Nicoll 6b975592bf Harmonize GitHub Actions structure
See https://github.com/spring-projects/spring-boot/issues/42512
2024-10-07 07:57:16 +02:00
Sam Brannen c44e43d791 Upgrade to JUnit 5.10.5 2024-10-04 15:33:25 +02:00
Stéphane Nicoll c41f8ea3c8 Merge pull request #33634 from junhyeongkim2
* pr/33634:
  Use HttpMethod.GET for better readability

Closes gh-33634
2024-10-04 10:20:20 +02:00
jun a668580f77 Use HttpMethod.GET for better readability
See gh-33634
2024-10-04 10:10:58 +02:00
Simon Baslé b54420f8aa Include argument name in MethodArgumentTypeMismatchException message
Closes gh-33573
2024-10-03 16:32:29 +02:00
Sam Brannen 0b3b1391c1 Avoid usage of deprecated AssertJ feature 2024-09-30 12:41:35 +02:00
Johnny Lim 64daba34de Upgrade to Awaitility 4.2.2
See gh-33143
Closes gh-33604
2024-09-30 12:36:54 +02:00
Juergen Hoeller 87157d3200 Polishing 2024-09-30 11:13:50 +02:00
Juergen Hoeller 95f181352f Defensively check MethodParameter.getMethod() in KotlinDelegate
Closes gh-33609
2024-09-30 11:13:26 +02:00
Simon Baslé e32a2f339d Propagate method error in some cases of reactive findInCaches errors
In a Cacheable reactive method, if an exception is propagated from
both the method and the caching infrastructure, an NPE could previously
surface due to the `CacheAspectSupport` attempting to perform an
`onErrorResume` with a `null`. This change ensures that in such a case
the user-level exception from the method is propagated instead.

Closes gh-33492
2024-09-30 10:18:59 +02:00
Juergen Hoeller 9f4968ed05 Polishing 2024-09-27 19:16:12 +02:00
Juergen Hoeller b6cfa2db0b Refine warn log message with advisor and ROLE_INFRASTRUCTURE hints
Closes gh-33184
2024-09-27 19:16:05 +02:00
Juergen Hoeller 26054d1ab0 Fix TargetSource setup for valid proxy interface specification
See gh-31304
2024-09-26 15:26:31 +02:00
Juergen Hoeller f2b522211f Upgrade to Groovy 4.0.23 2024-09-25 14:23:23 +02:00
Juergen Hoeller 49da92b6a3 Avoid http URL String (making checkstyleNohttp happy)
See gh-33561
2024-09-25 13:53:34 +02:00
Juergen Hoeller daa109e2ec Preserve URLStreamHandler in toRelativeURL and convertClassLoaderURL
Closes gh-33561
See gh-33199
2024-09-25 12:45:34 +02:00
Juergen Hoeller ca0448260f Convert DateTimeException to expected IllegalArgumentException
Closes gh-33545
2024-09-25 12:45:20 +02:00
Sébastien Deleuze 2ab0101b8a Add coroutine context tests for WebClientExtensions
Closes gh-33548
2024-09-25 12:14:50 +02:00
Illia Sorokoumov 478aa250a0 Preserve coroutine context in WebClientExtensions
See gh-33548
2024-09-25 11:46:30 +02:00
Sam Brannen 5cc4d0a2ca Do not invoke AotTestExecutionListener for @⁠DisabledInAotMode tests
Closes gh-33589
2024-09-24 16:47:48 +02:00
Sam Brannen 602ac903ff Upgrade to JUnit 5.10.4 2024-09-24 15:39:53 +02:00
Sam Brannen af3a26ab06 Clean up warnings in Gradle build 2024-09-24 12:33:35 +02:00
Sam Brannen 1cecf52883 Upgrade to Apache HttpCore Reactive 5.3
Closes gh-33588
2024-09-24 12:21:50 +02:00
Sam Brannen 88905ea20d Upgrade to Apache HttpClient 5.4
This commit also updates the external Javadoc link to avoid the following
warning in the build.

URL https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/package-list was redirected to https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/package-list

Closes gh-33587
2024-09-24 12:20:44 +02:00
Simon Baslé 7df9db3bb7 Polishing test
Assert that BlockHound doesn't trigger, rather than assertions on a
condition that makes the test flakky because concurrency is involved.
2024-09-24 11:01:37 +02:00
Sam Brannen bd9c05a909 Upgrade to Gradle 8.10.2
Closes gh-33584
2024-09-24 10:46:58 +02:00
Andrey Popov 94c04821ff Use correct method to check encoded resource path
Closes: gh-33568
2024-09-20 09:32:24 +01:00
Brian Clozel 776811bdb8 Defer ExchangeFilterFunction execution in WebClient
Prior to this commit, the `DefaultWebClient` would execute the configured
`ExchangeFilterFunction` as the reactive pipeline is assembled during
subscription. This means that if imperative code is executed in a filter
function, it won't be aware of the current observation through the local
scope.

For example, when automatic context propagation is enabled for Reactor
operators, the logger MDC will not know about the current
traceId/spanId.

This commit ensures that client filter functions execution is deferred
during the actual client exchange.

Fixes gh-33559
2024-09-18 11:18:17 +02:00
Simon Baslé de4ff4b44b Polishing tests
This commit adds more test coverage of SpringCoreBlockHoundIntegration.

See gh-33450
2024-09-17 16:40:40 +02:00
Simon Baslé f6e96a24d4 Add BlockHound exception for ConcurrentReferenceHashMap$ReferenceManager
This commit adds a SpringCoreBlockHoundIntegration configuration to
allows `pollForPurge` method to block.

Closes gh-33450
2024-09-17 16:40:40 +02:00
Antoine Rey daea92397f Reference the spring-framework-petclinic repository
Closes gh-33539
2024-09-16 16:58:34 +02:00
Brian Clozel 5efb385e64 Read Expires cookie attribute in HttpComponents connector
Prior to this commit, the HttpComponents implementation for the
`WebClient` would only consider the max-age attribute of response
cookies when parsing the response. This is not aligned with other client
implementations that consider the max-age attribute first, and then the
expires if the former was not present. The expires date is then
translated into a max-age duration. This behavior is done naturally by
several implementations.

This commit updates the `HttpComponentsClientHttpResponse` to do the
same.

Fixes gh-33157
2024-09-13 15:49:06 +02:00
Sam Brannen b388ff60dd Fix parameterized test display names and polishing 2024-09-13 15:26:56 +02:00
Brian Clozel 412f5f677b Fix NPEs in MimeMessageHelper when adding files without names
Prior to this commit, `MimeMessageHelper` would accept `Resource`
instances as inline attachments in multipart MIME messages. If the
provided `Resource` implementation returns `null` for `getFileName()`,
the `addInLine` method would fail with a `NullPointerException` as
Jakarta activation fails to detect the content type for a null filename.

This commit falls back on "application/octet-stream" when the filename
is not known for the given resource instead of failing with an
exception.

Fixes gh-33527
2024-09-13 10:17:58 +02:00
Brian Clozel 6a20987933 Add missing URI template variables in RedirectViews
Prior to this commit, the URL handler mapping would expose the matching
pattern, the path within mapping and matching URI variables as request
attributes. This was the case when the mapping would use the
`AntPathMatcher` as matching infrastructure, but not when using the
`PathPattern` variant. In this case, the map of URI variables would be
`null`. This could throw `IllegalArgumentException` when `RedirectView`
instances were relying on the presence of specific variables.

This commit ensures that URI variables are also extracted when the
`PathPatternParser` is used.

Fixes gh-33422
2024-09-13 09:28:45 +02:00
Stéphane Nicoll 2ae62dec8c Next development version (v6.1.14-SNAPSHOT) 2024-09-12 14:11:01 +02:00
401 changed files with 6703 additions and 2433 deletions
@@ -1,8 +1,8 @@
name: Await HTTP Resource
description: Waits for an HTTP resource to be available (a HEAD request succeeds)
description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)'
inputs:
url:
description: 'The URL of the resource to await'
description: 'URL of the resource to await'
required: true
runs:
using: composite
+21 -16
View File
@@ -1,31 +1,35 @@
name: 'Build'
description: 'Builds the project, optionally publishing it to a local deployment repository'
inputs:
develocity-access-key:
description: 'Access key for authentication with ge.spring.io'
required: false
java-distribution:
description: 'Java distribution to use'
required: false
default: 'liberica'
java-early-access:
description: 'Whether the Java version is in early access'
required: false
default: 'false'
java-toolchain:
description: 'Whether a Java toolchain should be used'
required: false
default: 'false'
java-version:
description: 'Java version to compile and test with'
required: false
default: '17'
description: 'The Java version to compile and test with'
java-early-access:
required: false
default: 'false'
description: 'Whether the Java version is in early access'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
publish:
description: 'Whether to publish artifacts ready for deployment to Artifactory'
required: false
default: 'false'
description: 'Whether to publish artifacts ready for deployment to Artifactory'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
outputs:
build-scan-url:
description: 'The URL, if any, of the build scan produced by the build'
description: 'URL, if any, of the build scan produced by the build'
value: ${{ (inputs.publish == 'true' && steps.publish.outputs.build-scan-url) || steps.build.outputs.build-scan-url }}
version:
description: 'The version that was built'
description: 'Version that was built'
value: ${{ steps.read-version.outputs.version }}
runs:
using: composite
@@ -34,9 +38,10 @@ runs:
uses: ./.github/actions/prepare-gradle-build
with:
develocity-access-key: ${{ inputs.develocity-access-key }}
java-version: ${{ inputs.java-version }}
java-distribution: ${{ inputs.java-distribution }}
java-early-access: ${{ inputs.java-early-access }}
java-toolchain: ${{ inputs.java-toolchain }}
java-version: ${{ inputs.java-version }}
- name: Build
id: build
if: ${{ inputs.publish == 'false' }}
@@ -1,23 +1,23 @@
name: Create GitHub Release
description: Create the release on GitHub with a changelog
description: 'Create the release on GitHub with a changelog'
inputs:
milestone:
description: Name of the GitHub milestone for which a release will be created
description: 'Name of the GitHub milestone for which a release will be created'
required: true
token:
description: Token to use for authentication with GitHub
description: 'Token to use for authentication with GitHub'
required: true
runs:
using: composite
steps:
- name: Generate Changelog
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
with:
config-file: .github/actions/create-github-release/changelog-generator.yml
milestone: ${{ inputs.milestone }}
token: ${{ inputs.token }}
config-file: .github/actions/create-github-release/changelog-generator.yml
- name: Create GitHub Release
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
shell: bash
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
+19 -15
View File
@@ -1,33 +1,37 @@
name: 'Prepare Gradle Build'
name: Prepare Gradle Build
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
inputs:
develocity-access-key:
description: 'Access key for authentication with ge.spring.io'
required: false
java-distribution:
description: 'Java distribution to use'
required: false
default: 'liberica'
java-early-access:
description: 'Whether the Java version is in early access. When true, forces java-distribution to temurin'
required: false
default: 'false'
java-toolchain:
description: 'Whether a Java toolchain should be used'
required: false
default: 'false'
java-version:
description: 'Java version to use for the build'
required: false
default: '17'
description: 'The Java version to use for the build'
java-early-access:
required: false
default: 'false'
description: 'Whether the Java version is in early access'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
runs:
using: composite
steps:
- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || 'liberica' }}
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
java-version: |
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
@@ -0,0 +1,17 @@
name: Print JVM thread dumps
description: 'Prints a thread dump for all running JVMs'
runs:
using: composite
steps:
- if: ${{ runner.os == 'Linux' }}
shell: bash
run: |
for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do
jcmd $jvm_pid Thread.print
done
- if: ${{ runner.os == 'Windows' }}
shell: powershell
run: |
foreach ($jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem)) {
jcmd $jvm_pid Thread.print
}
+19 -13
View File
@@ -1,33 +1,39 @@
name: Send Notification
description: Sends a Google Chat message as a notification of the job's outcome
description: 'Sends a Google Chat message as a notification of the job''s outcome'
inputs:
webhook-url:
description: 'Google Chat Webhook URL'
required: true
build-scan-url:
description: 'URL of the build scan to include in the notification'
required: false
run-name:
description: 'Name of the run to include in the notification'
required: false
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
status:
description: 'Status of the job'
required: true
build-scan-url:
description: 'URL of the build scan to include in the notification'
run-name:
description: 'Name of the run to include in the notification'
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
webhook-url:
description: 'Google Chat Webhook URL'
required: true
runs:
using: composite
steps:
- shell: bash
- name: Prepare Variables
shell: bash
run: |
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
- shell: bash
- name: Success Notification
if: ${{ inputs.status == 'success' }}
shell: bash
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
- name: Failure Notification
if: ${{ inputs.status == 'failure' }}
shell: bash
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
- name: Cancel Notification
if: ${{ inputs.status == 'cancelled' }}
shell: bash
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
@@ -1,26 +1,26 @@
name: Sync to Maven Central
description: Syncs a release to Maven Central and waits for it to be available for use
description: 'Syncs a release to Maven Central and waits for it to be available for use'
inputs:
jfrog-cli-config-token:
description: 'Config token for the JFrog CLI'
required: true
spring-framework-version:
description: 'The version of Spring Framework that is being synced to Central'
required: true
ossrh-s01-token-username:
description: 'Username for authentication with s01.oss.sonatype.org'
ossrh-s01-staging-profile:
description: 'Staging profile to use when syncing to Central'
required: true
ossrh-s01-token-password:
description: 'Password for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-staging-profile:
description: 'Staging profile to use when syncing to Central'
ossrh-s01-token-username:
description: 'Username for authentication with s01.oss.sonatype.org'
required: true
spring-framework-version:
description: 'Version of Spring Framework that is being synced to Central'
required: true
runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
@@ -29,14 +29,14 @@ runs:
- name: Sync
uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1
with:
username: ${{ inputs.ossrh-s01-token-username }}
password: ${{ inputs.ossrh-s01-token-password }}
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
create: true
upload: true
close: true
release: true
create: true
generate-checksums: true
password: ${{ inputs.ossrh-s01-token-password }}
release: true
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
upload: true
username: ${{ inputs.ossrh-s01-token-username }}
- name: Await
uses: ./.github/actions/await-http-resource
with:
+1 -2
View File
@@ -1,5 +1,4 @@
name: Backport Bot
on:
issues:
types: [labeled]
@@ -29,6 +28,6 @@ jobs:
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT: ${{ toJSON(github.event) }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
+13 -13
View File
@@ -8,9 +8,9 @@ concurrency:
jobs:
build-and-deploy-snapshot:
name: Build and Deploy Snapshot
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
timeout-minutes: 60
if: ${{ github.repository == 'spring-projects/spring-framework' }}
steps:
- name: Check Out Code
uses: actions/checkout@v4
@@ -18,31 +18,31 @@ jobs:
id: build-and-publish
uses: ./.github/actions/build
with:
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: 'spring-framework-6.1.x'
repository: 'libs-snapshot-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
build-name: 'spring-framework-6.1.x'
folder: 'deployment-repository'
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-snapshot-local'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
- name: Send Notification
uses: ./.github/actions/send-notification
if: always()
uses: ./.github/actions/send-notification
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
status: ${{ job.status }}
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
+36
View File
@@ -0,0 +1,36 @@
name: Build Pull Request
on: pull_request
permissions:
contents: read
jobs:
build:
name: Build Pull Request
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Set Up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: '17'
- name: Check Out
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
- name: Print JVM Thread Dumps When Cancelled
if: cancelled()
uses: ./.github/actions/print-jvm-thread-dumps
- name: Upload Build Reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
+7 -7
View File
@@ -8,9 +8,9 @@ concurrency:
jobs:
ci:
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ${{ matrix.os.id }}
timeout-minutes: 60
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
os:
@@ -24,7 +24,6 @@ jobs:
- version: 22
toolchain: true
- version: 23
early-access: true
toolchain: true
exclude:
- os:
@@ -44,15 +43,16 @@ jobs:
id: build
uses: ./.github/actions/build
with:
java-version: ${{ matrix.java.version }}
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
java-early-access: ${{ matrix.java.early-access || 'false' }}
java-distribution: ${{ matrix.java.distribution }}
java-toolchain: ${{ matrix.java.toolchain }}
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
java-version: ${{ matrix.java.version }}
- name: Send Notification
uses: ./.github/actions/send-notification
if: always()
uses: ./.github/actions/send-notification
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
status: ${{ job.status }}
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
+3 -2
View File
@@ -14,14 +14,15 @@ permissions:
actions: write
jobs:
build:
name: Dispatch docs deployment
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Check out code
uses: actions/checkout@v4
with:
ref: docs-build
fetch-depth: 1
ref: docs-build
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
+14 -14
View File
@@ -7,8 +7,8 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-stage-release:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and Stage Release
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
steps:
- name: Check Out Code
@@ -17,37 +17,37 @@ jobs:
id: build-and-publish
uses: ./.github/actions/build
with:
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
repository: 'libs-staging-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
folder: 'deployment-repository'
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-staging-local'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-stage-release
uses: ./.github/workflows/verify.yml
with:
staging: true
version: ${{ needs.build-and-stage-release.outputs.version }}
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
with:
staging: true
version: ${{ needs.build-and-stage-release.outputs.version }}
sync-to-maven-central:
name: Sync to Maven Central
needs:
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
@@ -1,11 +0,0 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
permissions:
contents: read
jobs:
validation:
name: "Validate Gradle Wrapper"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
+25 -19
View File
@@ -2,21 +2,27 @@ name: Verify
on:
workflow_call:
inputs:
version:
required: true
type: string
staging:
description: 'Whether the release to verify is in the staging repository'
required: false
default: false
type: boolean
secrets:
repository-username:
required: false
repository-password:
required: false
google-chat-webhook-url:
version:
description: 'Version to verify'
required: true
type: string
secrets:
google-chat-webhook-url:
description: 'Google Chat Webhook URL'
required: true
repository-password:
description: 'Password for authentication with the repository'
required: false
repository-username:
description: 'Username for authentication with the repository'
required: false
token:
description: 'Token to use for authentication with GitHub'
required: true
jobs:
verify:
@@ -26,13 +32,13 @@ jobs:
- name: Check Out Release Verification Tests
uses: actions/checkout@v4
with:
repository: spring-projects/spring-framework-release-verification
ref: 'v0.0.2'
repository: spring-projects/spring-framework-release-verification
token: ${{ secrets.token }}
- name: Check Out Send Notification Action
uses: actions/checkout@v4
with:
path: spring-framework
path: send-notification
sparse-checkout: .github/actions/send-notification
- name: Set Up Java
uses: actions/setup-java@v4
@@ -40,7 +46,7 @@ jobs:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-read-only: false
- name: Configure Gradle Properties
@@ -50,22 +56,22 @@ jobs:
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
- name: Run Release Verification Tests
env:
RVT_VERSION: ${{ inputs.version }}
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
RVT_RELEASE_TYPE: oss
RVT_STAGING: ${{ inputs.staging }}
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
RVT_VERSION: ${{ inputs.version }}
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
uses: actions/upload-artifact@v4
if: failure()
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
- name: Send Notification
uses: ./spring-framework/.github/actions/send-notification
if: failure()
uses: ./send-notification/.github/actions/send-notification
with:
webhook-url: ${{ secrets.google-chat-webhook-url }}
status: ${{ job.status }}
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
status: ${{ job.status }}
webhook-url: ${{ secrets.google-chat-webhook-url }}
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.12-librca
java=17.0.13-librca
+2 -2
View File
@@ -95,13 +95,13 @@ configure([rootProject] + javaProjects) { project ->
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
"https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/",
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.10.2/api/",
// "https://junit.org/junit5/docs/5.10.4/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.18.1");
checkstyle.setToolVersion("10.23.1");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
List<String> buildFolders = List.of("bin", "build", "out");
project.allprojects(subproject -> {
Path rootPath = project.getRootDir().toPath();
+1 -1
View File
@@ -36,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
+1
View File
@@ -73,6 +73,7 @@ asciidoc:
kotlin-issues: 'https://youtrack.jetbrains.com/issue'
micrometer-docs: 'https://docs.micrometer.io/micrometer/reference'
micrometer-context-propagation-docs: 'https://docs.micrometer.io/context-propagation/reference'
petclinic-github-org: 'https://github.com/spring-petclinic'
reactive-streams-site: 'https://www.reactive-streams.org'
reactive-streams-spec: 'https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md#specification'
reactor-github-org: 'https://github.com/reactor'
@@ -23,6 +23,12 @@ The following table lists all currently supported Spring properties.
|===
| Name | Description
| `spring.aop.ajc.ignore`
| Instructs Spring to ignore ajc-compiled aspects for Spring AOP proxying, restoring traditional
Spring behavior for scenarios where both weaving and AspectJ auto-proxying are enabled. See
{spring-framework-api}++/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.html#IGNORE_AJC_PROPERTY_NAME++[`AbstractAspectJAdvisorFactory`]
for details.
| `spring.aot.enabled`
| Indicates the application should run with AOT generated artifacts. See
xref:core/aot.adoc[Ahead of Time Optimizations] and
@@ -32,7 +38,7 @@ for details.
| `spring.beaninfo.ignore`
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
JavaBeans `Introspector`. See
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`StandardBeanInfoFactory`]
for details.
| `spring.cache.reactivestreams.ignore`
@@ -49,15 +55,13 @@ for details.
| `spring.context.checkpoint`
| Property that specifies a common context checkpoint. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#CHECKPOINT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
| `spring.context.exit`
| Property for terminating the JVM when the context reaches a specific phase. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
@@ -420,7 +420,7 @@ who typically are in charge of the deployment configuration, such as the launch
Now that the sales pitch is over, let us first walk through a quick example of AspectJ
LTW that uses Spring, followed by detailed specifics about elements introduced in the
example. For a complete example, see the
{spring-github-org}/spring-petclinic[Petclinic sample application].
{petclinic-github-org}/spring-framework-petclinic[Petclinic sample application based on Spring Framework].
[[aop-aj-ltw-first-example]]
@@ -36,7 +36,7 @@ NOTE: At the moment, AOT is focused on allowing Spring applications to be deploy
We intend to support more JVM-based use cases in future generations.
[[aot.basics]]
== AOT engine overview
== AOT Engine Overview
The entry point of the AOT engine for processing an `ApplicationContext` is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
@@ -225,7 +225,7 @@ When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
[[aot.running]]
== Running with AOT optimizations
== Running with AOT Optimizations
AOT is a mandatory step to transform a Spring application to a native executable, so it
is automatically enabled when running in this mode. It is possible to use those optimizations
@@ -244,7 +244,7 @@ However, keep in mind that some optimizations are made at build time based on a
This section lists the best practices that make sure your application is ready for AOT.
[[aot.bestpractices.bean-registration]]
== Programmatic bean registration
=== Programmatic Bean Registration
The AOT engine takes care of the `@Configuration` model and any callback that might be
invoked as part of processing your configuration. If you need to register additional
@@ -266,7 +266,7 @@ notion of a classpath. For cases like this, it is crucial that the scanning happ
build time.
[[aot.bestpractices.bean-type]]
=== Expose The Most Precise Bean Type
=== Expose the Most Precise Bean Type
While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type.
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods.
@@ -326,21 +326,21 @@ However, this is not a best practice and flagging the preferred constructor with
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
[[aot.bestpractices.comlext-data-structure]]
=== Avoid Complex Data Structure for Constructor Parameters and Properties
[[aot.bestpractices.complex-data-structures]]
=== Avoid Complex Data Structures for Constructor Parameters and Properties
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
Rather than using such structure, decomposing to simple types or referring to a bean that is built as such is recommended.
Rather than using such structures, decomposing to simple types or referring to a bean that is built as such is recommended.
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
To use it, register its fully qualified name in `META-INF/spring/aot.factories` using the `Delegate` as the key.
[[aot.bestpractices.custom-arguments]]
=== Avoid Creating Bean with Custom Arguments
=== Avoid Creating Beans with Custom Arguments
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
@@ -352,6 +352,20 @@ For instance, autowiring on fields and methods will be skipped as they are handl
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
[[aot.bestpractices.circular-dependencies]]
=== Avoid Circular Dependencies
Certain use cases can result in circular dependencies between one or more beans. With the
regular runtime, it may be possible to wire those circular dependencies via `@Autowired`
on setter methods or fields. However, an AOT-optimized context will fail to start with
explicit circular dependencies.
In an AOT-optimized application, you should therefore strive to avoid circular
dependencies. If that is not possible, you can use `@Lazy` injection points or
`ObjectProvider` to lazily access or retrieve the necessary collaborating beans. See
xref:core/beans/classpath-scanning.adoc#beans-factorybeans-annotations-lazy-injection-points[this tip]
for further information.
[[aot.bestpractices.factory-bean]]
=== FactoryBean
@@ -317,8 +317,8 @@ exposed based on security policies in some environments -- for example, standalo
JDK 1.7.0_45 and higher (which requires 'Trusted-Library' setup in your manifests -- see
{stackoverflow-questions}/19394570/java-jre-7u45-breaks-classloader-getresources).
On JDK 9's module path (Jigsaw), Spring's classpath scanning generally works as expected.
However, make sure that your component classes are exported in your `module-info`
On the module path (Java Module System), Spring's classpath scanning generally works as
expected. However, make sure that your component classes are exported in your `module-info`
descriptors. If you expect Spring to invoke non-public members of your classes, make
sure that they are 'opened' (that is, that they use an `opens` declaration instead of an
`exports` declaration in your `module-info` descriptor).
@@ -480,11 +480,15 @@ factory method and other bean definition properties, such as a qualifier value t
the `@Qualifier` annotation. Other method-level annotations that can be specified are
`@Scope`, `@Lazy`, and custom qualifier annotations.
TIP: In addition to its role for component initialization, you can also place the `@Lazy`
[[beans-factorybeans-annotations-lazy-injection-points]]
[TIP]
====
In addition to its role for component initialization, you can also place the `@Lazy`
annotation on injection points marked with `@Autowired` or `@Inject`. In this context,
it leads to the injection of a lazy-resolution proxy. However, such a proxy approach
is rather limited. For sophisticated lazy interactions, in particular in combination
with optional dependencies, we recommend `ObjectProvider<MyTargetBean>` instead.
====
Autowired fields and methods are supported, as previously discussed, with additional
support for autowiring of `@Bean` methods. The following example shows how to do so:
@@ -751,7 +755,7 @@ and bean definition show.
TIP: If you run into naming conflicts due to multiple autodetected components having the
same non-qualified class name (i.e., classes with identical names but residing in
different packages), you may need to configure a `BeanNameGenerator` that defaults to the
fully qualified class name for the generated bean name. As of Spring Framework 5.2.3, the
fully qualified class name for the generated bean name. The
`FullyQualifiedAnnotationBeanNameGenerator` located in package
`org.springframework.context.annotation` can be used for such purposes.
@@ -920,7 +924,8 @@ Kotlin::
[[beans-scanning-qualifiers]]
== Providing Qualifier Metadata with Annotations
The `@Qualifier` annotation is discussed in xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
The `@Qualifier` annotation is discussed in
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
The examples in that section demonstrate the use of the `@Qualifier` annotation and
custom qualifier annotations to provide fine-grained control when you resolve autowire
candidates. Because those examples were based on XML bean definitions, the qualifier
@@ -883,11 +883,12 @@ e.g. for processing all events asynchronously and/or for handling listener excep
== Convenient Access to Low-level Resources
For optimal usage and understanding of application contexts, you should familiarize
yourself with Spring's `Resource` abstraction, as described in xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources].
yourself with Spring's `Resource` abstraction, as described in
xref:core/resources.adoc[Resources].
An application context is a `ResourceLoader`, which can be used to load `Resource` objects.
A `Resource` is essentially a more feature rich version of the JDK `java.net.URL` class.
In fact, the implementations of the `Resource` wrap an instance of `java.net.URL`, where
In fact, implementations of `Resource` wrap an instance of `java.net.URL`, where
appropriate. A `Resource` can obtain low-level resources from almost any location in a
transparent fashion, including from the classpath, a filesystem location, anywhere
describable with a standard URL, and some other variations. If the resource location
@@ -60,6 +60,7 @@ instance's values consist of all bean instances that match the expected type, an
`Map` instance's keys contain the corresponding bean names.
[[beans-autowired-exceptions]]
== Limitations and Disadvantages of Autowiring
@@ -101,10 +102,10 @@ In the latter scenario, you have several options:
== Excluding a Bean from Autowiring
On a per-bean basis, you can exclude a bean from autowiring. In Spring's XML format, set
the `autowire-candidate` attribute of the `<bean/>` element to `false`. The container
makes that specific bean definition unavailable to the autowiring infrastructure
(including annotation style configurations such as xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
).
the `autowire-candidate` attribute of the `<bean/>` element to `false`; with the `@Bean`
annotation, the attribute is named `autowireCandidate`. The container makes that specific
bean definition unavailable to the autowiring infrastructure, including annotation-based
injection points such as xref:core/beans/annotation-config/autowired.adoc[`@Autowired`].
NOTE: The `autowire-candidate` attribute is designed to only affect type-based autowiring.
It does not affect explicit references by name, which get resolved even if the
@@ -119,8 +120,8 @@ provide multiple patterns, define them in a comma-separated list. An explicit va
`true` or `false` for a bean definition's `autowire-candidate` attribute always takes
precedence. For such beans, the pattern matching rules do not apply.
These techniques are useful for beans that you never want to be injected into other
beans by autowiring. It does not mean that an excluded bean cannot itself be configured by
These techniques are useful for beans that you never want to be injected into other beans
by autowiring. It does not mean that an excluded bean cannot itself be configured by
using autowiring. Rather, the bean itself is not a candidate for autowiring other beans.
@@ -4,12 +4,13 @@
If a bean is a dependency of another bean, that usually means that one bean is set as a
property of another. Typically you accomplish this with the
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
in XML-based configuration metadata. However, sometimes dependencies between beans are
less direct. An example is when a static initializer in a class needs to be triggered,
such as for database driver registration. The `depends-on` attribute can explicitly force
one or more beans to be initialized before the bean using this element is initialized.
The following example uses the `depends-on` attribute to express a dependency on a single
bean:
in XML-based metadata or through xref:core/beans/dependencies/factory-autowire.adoc[autowiring].
However, sometimes dependencies between beans are less direct. An example is when a static
initializer in a class needs to be triggered, such as for database driver registration.
The `depends-on` attribute or `@DependsOn` annotation can explicitly force one or more beans
to be initialized before the bean using this element is initialized. The following example
uses the `depends-on` attribute to express a dependency on a single bean:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -32,10 +33,10 @@ delimiters):
----
NOTE: The `depends-on` attribute can specify both an initialization-time dependency and,
in the case of xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton] beans only, a corresponding
destruction-time dependency. Dependent beans that define a `depends-on` relationship
with a given bean are destroyed first, prior to the given bean itself being destroyed.
Thus, `depends-on` can also control shutdown order.
in the case of xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton]
beans only, a corresponding destruction-time dependency. Dependent beans that define a
`depends-on` relationship with a given bean are destroyed first, prior to the given bean
itself being destroyed. Thus, `depends-on` can also control shutdown order.
@@ -120,8 +120,6 @@ dynamically generate a subclass that overrides the method.
subclasses cannot be `final`, and the method to be overridden cannot be `final`, either.
* Unit-testing a class that has an `abstract` method requires you to subclass the class
yourself and to supply a stub implementation of the `abstract` method.
* Concrete methods are also necessary for component scanning, which requires concrete
classes to pick up.
* A further key limitation is that lookup methods do not work with factory methods and
in particular not with `@Bean` methods in configuration classes, since, in that case,
the container is not in charge of creating the instance and therefore cannot create
@@ -293,11 +291,6 @@ Kotlin::
----
======
Note that you should typically declare such annotated lookup methods with a concrete
stub implementation, in order for them to be compatible with Spring's component
scanning rules where abstract classes get ignored by default. This limitation does not
apply to explicitly registered or explicitly imported bean classes.
[TIP]
====
Another way of accessing differently scoped target beans is an `ObjectFactory`/
@@ -102,8 +102,8 @@ following snippet:
----
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
<bean id="theClientBean" class="...">
<property name="targetName" ref="theTargetBean"/>
</bean>
----
@@ -12,27 +12,27 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
Inventor einstein = parser.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
parser.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
.getValue(societyContext);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
val einstein = parser.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor::class.java)
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
parser.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
.getValue(societyContext)
----
======
@@ -110,8 +110,8 @@ potentially more efficient use cases if the `MethodHandle` target and parameters
been fully bound prior to registration; however, partially bound handles are also
supported.
Consider the `String#formatted(String, Object...)` instance method, which produces a
message according to a template and a variable number of arguments.
Consider the `String#formatted(Object...)` instance method, which produces a message
according to a template and a variable number of arguments.
You can register and use the `formatted` method as a `MethodHandle`, as the following
example shows:
@@ -151,10 +151,10 @@ Kotlin::
----
======
As hinted above, binding a `MethodHandle` and registering the bound `MethodHandle` is also
supported. This is likely to be more performant if both the target and all the arguments
are bound. In that case no arguments are necessary in the SpEL expression, as the
following example shows:
As mentioned above, binding a `MethodHandle` and registering the bound `MethodHandle` is
also supported. This is likely to be more performant if both the target and all the
arguments are bound. In that case no arguments are necessary in the SpEL expression, as
the following example shows:
[tabs]
======
@@ -168,9 +168,10 @@ Java::
String template = "This is a %s message with %s words: <%s>";
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
MethodType.methodType(String.class, Object[].class))
MethodType.methodType(String.class, Object[].class))
.bindTo(template)
.bindTo(varargs); //here we have to provide arguments in a single array binding
// Here we have to provide the arguments in a single array binding:
.bindTo(varargs);
context.setVariable("message", mh);
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
@@ -189,9 +190,10 @@ Kotlin::
val varargs = arrayOf("prerecorded", 3, "Oh Hello World!", "ignored")
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
MethodType.methodType(String::class.java, Array<Any>::class.java))
MethodType.methodType(String::class.java, Array<Any>::class.java))
.bindTo(template)
.bindTo(varargs) //here we have to provide arguments in a single array binding
// Here we have to provide the arguments in a single array binding:
.bindTo(varargs)
context.setVariable("message", mh)
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
@@ -196,7 +196,7 @@ Kotlin::
When used as `org.springframework.validation.Validator`, `LocalValidatorFactoryBean`
invokes the underlying `jakarta.validation.Validator`, and then adapts
``ContraintViolation``s to ``FieldError``s, and registers them with the `Errors` object
``ConstraintViolation``s to ``FieldError``s, and registers them with the `Errors` object
passed into the `validate` method.
@@ -280,10 +280,11 @@ Kotlin::
A portable format annotation API exists in the `org.springframework.format.annotation`
package. You can use `@NumberFormat` to format `Number` fields such as `Double` and
`Long`, and `@DateTimeFormat` to format `java.util.Date`, `java.util.Calendar`, `Long`
(for millisecond timestamps) as well as JSR-310 `java.time`.
`Long`, and `@DateTimeFormat` to format fields such as `java.util.Date`,
`java.util.Calendar`, and `Long` (for millisecond timestamps) as well as JSR-310
`java.time` types.
The following example uses `@DateTimeFormat` to format a `java.util.Date` as an ISO Date
The following example uses `@DateTimeFormat` to format a `java.util.Date` as an ISO date
(yyyy-MM-dd):
[tabs]
@@ -309,6 +310,28 @@ Kotlin::
----
======
For further details, see the javadoc for
{spring-framework-api}/format/annotation/DateTimeFormat.html[`@DateTimeFormat`] and
{spring-framework-api}/format/annotation/NumberFormat.html[`@NumberFormat`].
[WARNING]
====
Style-based formatting and parsing rely on locale-sensitive patterns which may change
depending on the Java runtime. Specifically, applications that rely on date, time, or
number parsing and formatting may encounter incompatible changes in behavior when running
on JDK 20 or higher.
Using an ISO standardized format or a concrete pattern that you control allows for
reliable system-independent and locale-independent parsing and formatting of date, time,
and number values.
For `@DateTimeFormat`, the use of fallback patterns can also help to address
compatibility issues.
For further details, see the
https://github.com/spring-projects/spring-framework/wiki/Date-and-Time-Formatting-with-JDK-20-and-higher[Date and Time Formatting with JDK 20 and higher]
page in the Spring Framework wiki.
====
[[format-FormatterRegistry-SPI]]
== The `FormatterRegistry` SPI
@@ -198,7 +198,7 @@ methods it offers can be found in the {spring-framework-api}/validation/Errors.h
Validators may also get locally invoked for the immediate validation of a given object,
not involving a binding process. As of 6.1, this has been simplified through a new
`Validator.validateObject(Object)` method which is available by default now, returning
a simple ´Errors` representation which can be inspected: typically calling `hasErrors()`
a simple `Errors` representation which can be inspected: typically calling `hasErrors()`
or the new `failOnError` method for turning the error summary message into an exception
(e.g. `validator.validateObject(myObject).failOnError(IllegalArgumentException::new)`).
@@ -733,7 +733,7 @@ reached, does the executor create a new thread beyond the core size. If the max
has also been reached, then the executor rejects the task.
By default, the queue is unbounded, but this is rarely the desired configuration,
because it can lead to `OutOfMemoryErrors` if enough tasks are added to that queue while
because it can lead to `OutOfMemoryError` if enough tasks are added to that queue while
all pool threads are busy. Furthermore, if the queue is unbounded, the max size has
no effect at all. Since the executor always tries the queue before creating a new
thread beyond the core size, a queue must have a finite capacity for the thread pool to
@@ -31,5 +31,5 @@ provide convenience methods that delegate to the aforementioned methods in
The `spring-jdbc` module provides support for configuring and launching an embedded
database, which you can use in integration tests that interact with a database.
For details, see xref:data-access/jdbc/embedded-database-support.adoc[Embedded Database Support]
and <<data-access.adoc#jdbc-embedded-database-dao-testing, Testing Data Access
Logic with an Embedded Database>>.
and xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-dao-testing[Testing Data Access
Logic with an Embedded Database].
@@ -193,7 +193,7 @@ Kotlin::
[[webtestclient-fn-config]]
=== Bind to Router Function
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
This setup allows you to test xref:web/webflux-functional.adoc[functional endpoints] via
mock request and response objects, without a running server.
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
@@ -1,5 +1,6 @@
[[webflux-cors]]
= CORS
[.small]#xref:web/webmvc-cors.adoc[See equivalent in the Servlet stack]#
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
@@ -364,7 +365,7 @@ Kotlin::
You can apply CORS support through the built-in
{spring-framework-api}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
good fit with <<webflux-fn, functional endpoints>>.
good fit with xref:web/webflux-functional.adoc[functional endpoints].
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for
@@ -1,5 +1,6 @@
[[webflux-fn]]
= Functional Endpoints
[.small]#xref:web/webmvc-functional.adoc[See equivalent in the Servlet stack]#
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
@@ -1,5 +1,6 @@
[[webflux-websocket]]
= WebSockets
[.small]#xref:web/websocket.adoc[See equivalent in the Servlet stack]#
This part of the reference documentation covers support for reactive-stack WebSocket
@@ -91,7 +91,7 @@ class WebConfig : WebFluxConfigurer {
[.small]#xref:web/webmvc/mvc-config/conversion.adoc[See equivalent in the Servlet stack]#
By default, formatters for various number and date types are installed, along with support
for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
for customization via `@NumberFormat` and `@DateTimeFormat` on fields and parameters.
To register custom formatters and converters in Java config, use the following:
@@ -356,7 +356,6 @@ which customizes Jackson's default properties with the following ones:
It also automatically registers the following well-known modules if they are detected on the classpath:
* {jackson-github-org}/jackson-datatype-joda[`jackson-datatype-joda`]: Support for Joda-Time types.
* {jackson-github-org}/jackson-datatype-jsr310[`jackson-datatype-jsr310`]: Support for Java 8 Date and Time API types.
* {jackson-github-org}/jackson-datatype-jdk8[`jackson-datatype-jdk8`]: Support for other Java 8 types, such as `Optional`.
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
@@ -101,7 +101,7 @@ On that foundation, Spring WebFlux provides a choice of two programming models:
from the `spring-web` module. Both Spring MVC and WebFlux controllers support reactive
(Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable
difference is that WebFlux also supports reactive `@RequestBody` arguments.
* <<webflux-fn>>: Lambda-based, lightweight, and functional programming model. You can think of
* xref:web/webflux-functional.adoc[Functional Endpoints]: Lambda-based, lightweight, and functional programming model. You can think of
this as a small library or a set of utilities that an application can use to route and
handle requests. The big difference with annotated controllers is that the application
is in charge of request handling from start to finish versus declaring intent through
@@ -1,5 +1,6 @@
[[mvc-cors]]
= CORS
[.small]#xref:web/webflux-cors.adoc[See equivalent in the Reactive stack]#
Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section
@@ -1,6 +1,7 @@
[[webmvc-fn]]
= Functional Endpoints
[.small]#<<web-reactive.adoc#webflux-fn, See equivalent in the Reactive stack>>#
[.small]#xref:web/webflux-functional.adoc[See equivalent in the Reactive stack]#
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions
are used to route and handle requests and contracts are designed for immutability.
@@ -26,7 +26,7 @@ available through the `ServletRequest.getParameter{asterisk}()` family of method
[[forwarded-headers]]
[[filters-forwarded-headers]]
== Forwarded Headers
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-forwarded-headers[See equivalent in the Reactive stack]#
@@ -4,7 +4,7 @@
[.small]#xref:web/webflux/config.adoc#webflux-config-conversion[See equivalent in the Reactive stack]#
By default, formatters for various number and date types are installed, along with support
for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
for customization via `@NumberFormat` and `@DateTimeFormat` on fields and parameters.
To register custom formatters and converters in Java config, use the following:
@@ -73,7 +73,6 @@ This builder customizes Jackson's default properties as follows:
It also automatically registers the following well-known modules if they are detected on the classpath:
* {jackson-github-org}/jackson-datatype-joda[jackson-datatype-joda]: Support for Joda-Time types.
* {jackson-github-org}/jackson-datatype-jsr310[jackson-datatype-jsr310]: Support for Java 8 Date and Time API types.
* {jackson-github-org}/jackson-datatype-jdk8[jackson-datatype-jdk8]: Support for other Java 8 types, such as `Optional`.
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
@@ -119,7 +119,7 @@ Some example patterns:
* `+"/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
* `++"/projects/{project:[a-z]+}/versions"++` - match and capture a variable with a regex
Captured URI variables can be accessed with `@PathVariable`. For example:
@@ -1,6 +1,7 @@
[[websocket]]
= WebSockets
:page-section-summary-toc: 1
[.small]#xref:web/webflux-websocket.adoc[See equivalent in the Reactive stack]#
This part of the reference documentation covers support for Servlet stack, WebSocket
@@ -238,7 +238,7 @@ heartbeats to be exchanged, the SockJS heartbeats are disabled.
The Spring SockJS support also lets you configure the `TaskScheduler` to
schedule heartbeats tasks. The task scheduler is backed by a thread pool,
with default settings based on the number of available processors. Your
with default settings based on the number of available processors. You
should consider customizing the settings according to your specific needs.
@@ -45,7 +45,7 @@ input is ignored. This is in contrast to property binding which by default binds
request parameter for which there is a matching property.
If neither a dedicated model object nor constructor binding is sufficient, and you must
use property binding, we strongy recommend registering `allowedFields` patterns (case
use property binding, we strongly recommend registering `allowedFields` patterns (case
sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set.
For example:
+1
View File
@@ -5,6 +5,7 @@
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.11.1",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.docs.integration.observability.config.conventions;
import java.util.Locale;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
@@ -34,7 +36,7 @@ public class CustomServerRequestObservationConvention implements ServerRequestOb
@Override
public String getContextualName(ServerRequestObservationContext context) {
// will be used for the trace name
return "http " + context.getCarrier().getMethod().toLowerCase();
return "http " + context.getCarrier().getMethod().toLowerCase(Locale.ROOT);
}
@Override
+19 -19
View File
@@ -8,19 +8,19 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
api(platform("io.micrometer:micrometer-bom:1.12.10"))
api(platform("io.netty:netty-bom:4.1.113.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.12"))
api(platform("io.netty:netty-bom:4.1.121.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.10"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.22"))
api(platform("io.projectreactor:reactor-bom:2023.0.18"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.26.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.13"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.13"))
api(platform("org.assertj:assertj-bom:3.27.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.21"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.21"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.10.3"))
api(platform("org.junit:junit-bom:5.10.5"))
api(platform("org.mockito:mockito-bom:5.12.0"))
constraints {
@@ -44,7 +44,7 @@ dependencies {
api("com.sun.xml.bind:jaxb-impl:3.0.2")
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.1.0")
api("com.thoughtworks.xstream:xstream:1.4.20")
api("com.thoughtworks.xstream:xstream:1.4.21")
api("commons-io:commons-io:2.15.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("io.micrometer:context-propagation:1.1.1")
@@ -54,11 +54,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.8")
api("io.reactivex.rxjava3:rxjava:3.1.10")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.17.Final")
api("io.undertow:undertow-servlet:2.3.17.Final")
api("io.undertow:undertow-websockets-jsr:2.3.17.Final")
api("io.undertow:undertow-core:2.3.18.Final")
api("io.undertow:undertow-servlet:2.3.18.Final")
api("io.undertow:undertow-websockets-jsr:2.3.18.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")
@@ -101,8 +101,8 @@ dependencies {
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.5")
api("org.apache.httpcomponents.client5:httpclient5:5.4.4")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
@@ -111,12 +111,12 @@ dependencies {
api("org.aspectj:aspectjrt:1.9.22.1")
api("org.aspectj:aspectjtools:1.9.22.1")
api("org.aspectj:aspectjweaver:1.9.22.1")
api("org.awaitility:awaitility:4.2.0")
api("org.awaitility:awaitility:4.2.2")
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.eclipse.jetty:jetty-reactive-httpclient:4.0.7")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
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")
@@ -131,7 +131,7 @@ dependencies {
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.2")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.8.0")
api("org.jruby:jruby:9.4.9.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.1.13
version=6.1.20
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -56,33 +56,33 @@ import kotlin.annotation.AnnotationTarget.TYPE
@SpringJUnitConfig(InterceptorConfig::class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class AspectJAutoProxyInterceptorKotlinIntegrationTests(
@Autowired val echo: Echo,
@Autowired val firstAdvisor: TestPointcutAdvisor,
@Autowired val secondAdvisor: TestPointcutAdvisor,
@Autowired val countingAspect: CountingAspect,
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
@Autowired val echo: Echo,
@Autowired val firstAdvisor: TestPointcutAdvisor,
@Autowired val secondAdvisor: TestPointcutAdvisor,
@Autowired val countingAspect: CountingAspect,
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
@Test
fun `Multiple interceptors with regular function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
assertThat(echo.echo(value)).isEqualTo(value)
@Test
fun `Multiple interceptors with regular function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
assertThat(echo.echo(value)).isEqualTo(value)
assertThat(firstAdvisor.interceptor.invocations).singleElement().matches { String::class.java.isAssignableFrom(it) }
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { String::class.java.isAssignableFrom(it) }
}
}
@Test
fun `Multiple interceptors with suspending function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
runBlocking {
assertThat(echo.suspendingEcho(value)).isEqualTo(value)
}
@Test
fun `Multiple interceptors with suspending function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
runBlocking {
assertThat(echo.suspendingEcho(value)).isEqualTo(value)
}
assertThat(firstAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
}
}
@Test // gh-33095
fun `Aspect and reactive transactional with suspending function`() {
@@ -113,17 +113,17 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
assertThat(countingAspect.counter).`as`("aspect applied once per key").isEqualTo(2)
}
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@EnableCaching
open class InterceptorConfig {
open class InterceptorConfig {
@Bean
open fun firstAdvisor() = TestPointcutAdvisor().apply { order = 0 }
@Bean
open fun firstAdvisor() = TestPointcutAdvisor().apply { order = 0 }
@Bean
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
@Bean
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
@Bean
open fun countingAspect() = CountingAspect()
@@ -138,34 +138,34 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
return ConcurrentMapCacheManager()
}
@Bean
open fun echo(): Echo {
return Echo()
}
}
@Bean
open fun echo(): Echo {
return Echo()
}
}
class TestMethodInterceptor: MethodInterceptor {
class TestMethodInterceptor: MethodInterceptor {
var invocations: MutableList<Class<*>> = mutableListOf()
var invocations: MutableList<Class<*>> = mutableListOf()
@Suppress("RedundantNullableReturnType")
override fun invoke(invocation: MethodInvocation): Any? {
val result = invocation.proceed()
invocations.add(result!!.javaClass)
return result
}
@Suppress("RedundantNullableReturnType")
override fun invoke(invocation: MethodInvocation): Any? {
val result = invocation.proceed()
invocations.add(result!!.javaClass)
return result
}
}
}
class TestPointcutAdvisor : StaticMethodMatcherPointcutAdvisor(TestMethodInterceptor()) {
class TestPointcutAdvisor : StaticMethodMatcherPointcutAdvisor(TestMethodInterceptor()) {
val interceptor: TestMethodInterceptor
get() = advice as TestMethodInterceptor
val interceptor: TestMethodInterceptor
get() = advice as TestMethodInterceptor
override fun matches(method: Method, targetClass: Class<*>): Boolean {
return targetClass == Echo::class.java && method.name.lowercase().endsWith("echo")
}
}
override fun matches(method: Method, targetClass: Class<*>): Boolean {
return targetClass == Echo::class.java && method.name.lowercase().endsWith("echo")
}
}
@Target(CLASS, FUNCTION, ANNOTATION_CLASS, TYPE)
@Retention(AnnotationRetention.RUNTIME)
@@ -185,16 +185,16 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
}
}
open class Echo {
open class Echo {
open fun echo(value: String): String {
return value
}
open fun echo(value: String): String {
return value
}
open suspend fun suspendingEcho(value: String): String {
delay(1)
return value
}
open suspend fun suspendingEcho(value: String): String {
delay(1)
return value
}
@Transactional
@Counting
@@ -212,6 +212,6 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
return "$value ${cacheCounter++}"
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -276,14 +276,18 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
}
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
// May need to add implicit join point arg name...
Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
if (firstArgType == JoinPoint.class ||
firstArgType == ProceedingJoinPoint.class ||
firstArgType == JoinPoint.StaticPart.class) {
String[] oldNames = this.argumentNames;
this.argumentNames = new String[oldNames.length + 1];
this.argumentNames[0] = "THIS_JOIN_POINT";
System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
for (int i = 0; i < this.aspectJAdviceMethod.getParameterCount(); i++) {
Class<?> argType = this.aspectJAdviceMethod.getParameterTypes()[i];
if (argType == JoinPoint.class ||
argType == ProceedingJoinPoint.class ||
argType == JoinPoint.StaticPart.class) {
String[] oldNames = this.argumentNames;
this.argumentNames = new String[oldNames.length + 1];
System.arraycopy(oldNames, 0, this.argumentNames, 0, i);
this.argumentNames[i] = "THIS_JOIN_POINT";
System.arraycopy(oldNames, i, this.argumentNames, i + 1, oldNames.length - i);
break;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -241,7 +241,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
try {
int algorithmicStep = STEP_JOIN_POINT_BINDING;
while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
while (this.numberOfRemainingUnboundArguments > 0 && algorithmicStep < STEP_FINISHED) {
switch (algorithmicStep++) {
case STEP_JOIN_POINT_BINDING -> {
if (!maybeBindThisJoinPoint()) {
@@ -373,7 +373,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
if (this.returningName != null) {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates. " +
"Consider compiling with -parameters in order to make declared parameter names available.");
}
// We're all set... find the unbound parameter, and bind it.
@@ -485,8 +486,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
*/
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
" unbound args at this()/target()/args() binding stage, with no way to determine between them");
}
List<String> varNames = new ArrayList<>();
@@ -535,8 +536,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
private void maybeBindReferencePointcutParameter() {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
+ " unbound args at reference pointcut binding stage, with no way to determine between them");
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
" unbound args at reference pointcut binding stage, with no way to determine between them");
}
List<String> varNames = new ArrayList<>();
@@ -741,7 +742,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
* Simple record to hold the extracted text from a pointcut body, together
* with the number of tokens consumed in extracting it.
*/
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
private record PointcutBody(int numTokensConsumed, @Nullable String text) {
}
/**
* Thrown in response to an ambiguous binding being detected when
@@ -18,6 +18,7 @@ package org.springframework.aop.aspectj.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.StringTokenizer;
@@ -37,6 +38,7 @@ import org.aspectj.lang.reflect.PerClauseKind;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.SpringProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
@@ -58,6 +60,23 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
private static final String AJC_MAGIC = "ajc$";
/**
* System property that instructs Spring to ignore ajc-compiled aspects
* for Spring AOP proxying, restoring traditional Spring behavior for
* scenarios where both weaving and AspectJ auto-proxying are enabled.
* <p>The default is "false". Consider switching this to "true" if you
* encounter double execution of your aspects in a given build setup.
* Note that we recommend restructuring your AspectJ configuration to
* avoid such double exposure of an AspectJ aspect to begin with.
* @since 6.1.15
*/
public static final String IGNORE_AJC_PROPERTY_NAME = "spring.aop.ajc.ignore";
private static final boolean shouldIgnoreAjcCompiledAspects =
SpringProperties.getFlag(IGNORE_AJC_PROPERTY_NAME);
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@@ -67,7 +86,8 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
@Override
public boolean isAspect(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null &&
(!shouldIgnoreAjcCompiledAspects || !compiledByAjc(clazz)));
}
@Override
@@ -114,6 +134,15 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
private static boolean compiledByAjc(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
/**
* Enum for AspectJ annotation types.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -56,9 +56,9 @@ public interface Advised extends TargetClassAware {
/**
* Determine whether the given interface is proxied.
* @param intf the interface to check
* @param ifc the interface to check
*/
boolean isInterfaceProxied(Class<?> intf);
boolean isInterfaceProxied(Class<?> ifc);
/**
* Change the {@code TargetSource} used by this {@code Advised} object.
@@ -222,15 +222,15 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
/**
* Add a new proxied interface.
* @param intf the additional interface to proxy
* @param ifc the additional interface to proxy
*/
public void addInterface(Class<?> intf) {
Assert.notNull(intf, "Interface must not be null");
if (!intf.isInterface()) {
throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface");
public void addInterface(Class<?> ifc) {
Assert.notNull(ifc, "Interface must not be null");
if (!ifc.isInterface()) {
throw new IllegalArgumentException("[" + ifc.getName() + "] is not an interface");
}
if (!this.interfaces.contains(intf)) {
this.interfaces.add(intf);
if (!this.interfaces.contains(ifc)) {
this.interfaces.add(ifc);
adviceChanged();
}
}
@@ -238,12 +238,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
/**
* Remove a proxied interface.
* <p>Does nothing if the given interface isn't proxied.
* @param intf the interface to remove from the proxy
* @param ifc the interface to remove from the proxy
* @return {@code true} if the interface was removed; {@code false}
* if the interface was not found and hence could not be removed
*/
public boolean removeInterface(Class<?> intf) {
return this.interfaces.remove(intf);
public boolean removeInterface(Class<?> ifc) {
return this.interfaces.remove(ifc);
}
@Override
@@ -252,9 +252,9 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
@Override
public boolean isInterfaceProxied(Class<?> intf) {
public boolean isInterfaceProxied(Class<?> ifc) {
for (Class<?> proxyIntf : this.interfaces) {
if (intf.isAssignableFrom(proxyIntf)) {
if (ifc.isAssignableFrom(proxyIntf)) {
return true;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ import org.springframework.aop.AopInvocationException;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
import org.springframework.aot.AotDetector;
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
import org.springframework.cglib.core.CodeGenerationException;
import org.springframework.cglib.core.SpringNamingPolicy;
@@ -201,7 +202,7 @@ class CglibAopProxy implements AopProxy, Serializable {
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
@@ -0,0 +1,135 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.aspectj;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Consumer;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AbstractAspectJAdvice}.
*
* @author Joshua Chen
* @author Stephane Nicoll
*/
class AbstractAspectJAdviceTests {
@Test
void setArgumentNamesFromStringArray_withoutJoinPointParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithNoJoinPoint");
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withJoinPointAsFirstParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsFirstParameter");
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withJoinPointAsLastParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsLastParameter");
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2", "THIS_JOIN_POINT"));
}
@Test
void setArgumentNamesFromStringArray_withJoinPointAsMiddleParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsMiddleParameter");
assertThat(advice).satisfies(hasArgumentNames("arg1", "THIS_JOIN_POINT", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withProceedingJoinPoint() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithProceedingJoinPoint");
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withStaticPart() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithStaticPart");
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
}
private Consumer<AbstractAspectJAdvice> hasArgumentNames(String... argumentNames) {
return advice -> assertThat(advice).extracting("argumentNames")
.asInstanceOf(InstanceOfAssertFactories.array(String[].class))
.containsExactly(argumentNames);
}
private AbstractAspectJAdvice getAspectJAdvice(final String methodName) {
AbstractAspectJAdvice advice = new TestAspectJAdvice(getMethod(methodName),
mock(AspectJExpressionPointcut.class), mock(AspectInstanceFactory.class));
advice.setArgumentNamesFromStringArray("arg1", "arg2");
return advice;
}
private Method getMethod(final String methodName) {
return Arrays.stream(Sample.class.getDeclaredMethods())
.filter(method -> method.getName().equals(methodName)).findFirst()
.orElseThrow();
}
@SuppressWarnings("serial")
public static class TestAspectJAdvice extends AbstractAspectJAdvice {
public TestAspectJAdvice(Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut,
AspectInstanceFactory aspectInstanceFactory) {
super(aspectJAdviceMethod, pointcut, aspectInstanceFactory);
}
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return false;
}
}
@SuppressWarnings("unused")
static class Sample {
void methodWithNoJoinPoint(String arg1, String arg2) {
}
void methodWithJoinPointAsFirstParameter(JoinPoint joinPoint, String arg1, String arg2) {
}
void methodWithJoinPointAsLastParameter(String arg1, String arg2, JoinPoint joinPoint) {
}
void methodWithJoinPointAsMiddleParameter(String arg1, JoinPoint joinPoint, String arg2) {
}
void methodWithProceedingJoinPoint(ProceedingJoinPoint joinPoint, String arg1, String arg2) {
}
void methodWithStaticPart(JoinPoint.StaticPart staticPart, String arg1, String arg2) {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -203,7 +203,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
itb.getSpouse();
assertThat(maaif.isMaterialized()).isTrue();
assertThat(imapa.getDeclaredPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getAge"), null)).isTrue();
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(0);
@@ -301,7 +300,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void bindingWithMultipleArgsDifferentlyOrdered() {
ManyValuedArgs target = new ManyValuedArgs();
ManyValuedArgs mva = createProxy(target, ManyValuedArgs.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
String a = "a";
int b = 12;
@@ -320,7 +319,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
NotLockable notLockableTarget = new NotLockable();
assertThat(notLockableTarget).isNotInstanceOf(Lockable.class);
NotLockable notLockable1 = createProxy(notLockableTarget, NotLockable.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
assertThat(notLockable1).isInstanceOf(Lockable.class);
Lockable lockable = (Lockable) notLockable1;
assertThat(lockable.locked()).isFalse();
@@ -329,7 +328,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
NotLockable notLockable2Target = new NotLockable();
NotLockable notLockable2 = createProxy(notLockable2Target, NotLockable.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
assertThat(notLockable2).isInstanceOf(Lockable.class);
Lockable lockable2 = (Lockable) notLockable2;
assertThat(lockable2.locked()).isFalse();
@@ -343,20 +342,19 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void introductionAdvisorExcludedFromTargetImplementingInterface() {
assertThat(AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(), "someBean")),
aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class)).isEmpty();
assertThat(AopUtils.findAdvisorsThatCanApply(getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
}
@Test
void introductionOnTargetImplementingInterface() {
CannotBeUnlocked target = new CannotBeUnlocked();
Lockable proxy = createProxy(target, CannotBeUnlocked.class,
// Ensure that we exclude
AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class));
assertThat(proxy).isInstanceOf(Lockable.class);
Lockable lockable = proxy;
assertThat(lockable.locked()).as("Already locked").isTrue();
@@ -370,8 +368,8 @@ abstract class AbstractAspectJAdvisorFactoryTests {
ArrayList<Object> target = new ArrayList<>();
List<?> proxy = createProxy(target, List.class,
AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
List.class));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
List.class));
assertThat(proxy).as("Type pattern must have excluded mixin").isNotInstanceOf(Lockable.class);
}
@@ -379,7 +377,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void introductionBasedOnAnnotationMatch() { // gh-9980
AnnotatedTarget target = new AnnotatedTargetImpl();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
Object proxy = createProxy(target, AnnotatedTarget.class, advisors);
assertThat(proxy).isInstanceOf(Lockable.class);
Lockable lockable = (Lockable) proxy;
@@ -393,9 +391,9 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
advisors.addAll(getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(), "someBean")));
aspectInstanceFactory(new MakeLockable(), "someBean")));
Modifiable modifiable = (Modifiable) createProxy(target, ITestBean.class, advisors);
assertThat(modifiable).isInstanceOf(Modifiable.class);
@@ -426,7 +424,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
UnsupportedOperationException expectedException = new UnsupportedOperationException();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
assertThat(advisors).as("One advice method was found").hasSize(1);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(itb::getAge);
@@ -439,12 +437,12 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
RemoteException expectedException = new RemoteException();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
assertThat(advisors).as("One advice method was found").hasSize(1);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
assertThatExceptionOfType(UndeclaredThrowableException.class)
.isThrownBy(itb::getAge)
.withCause(expectedException);
.isThrownBy(itb::getAge)
.withCause(expectedException);
}
@Test
@@ -452,7 +450,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
TwoAdviceAspect twoAdviceAspect = new TwoAdviceAspect();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(twoAdviceAspect, "someBean"));
aspectInstanceFactory(twoAdviceAspect, "someBean"));
assertThat(advisors).as("Two advice methods found").hasSize(2);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
itb.setName("");
@@ -466,7 +464,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void afterAdviceTypes() throws Exception {
InvocationTrackingAspect aspect = new InvocationTrackingAspect();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
Echo echo = createProxy(new Echo(), Echo.class, advisors);
assertThat(aspect.invocations).isEmpty();
@@ -475,7 +473,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
aspect.invocations.clear();
assertThatExceptionOfType(FileNotFoundException.class)
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
}
@@ -487,7 +485,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
assertThat(Modifier.isAbstract(aspect.getClass().getSuperclass().getModifiers())).isFalse();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspect, "incrementingAspect"));
ITestBean proxy = createProxy(new TestBean("Jane", 42), ITestBean.class, advisors);
assertThat(proxy.getAge()).isEqualTo(86); // (42 + 1) * 2
}
@@ -812,20 +809,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
invocations.add("before");
}
@AfterReturning("echo()")
void afterReturning() {
invocations.add("after returning");
}
@AfterThrowing("echo()")
void afterThrowing() {
invocations.add("after throwing");
}
@After("echo()")
void after() {
invocations.add("after");
}
@AfterReturning(pointcut = "this(target) && execution(* echo(*))", returning = "returnValue")
void afterReturning(JoinPoint joinPoint, Echo target, Object returnValue) {
invocations.add("after returning");
}
@AfterThrowing(pointcut = "this(target) && execution(* echo(*))", throwing = "exception")
void afterThrowing(JoinPoint joinPoint, Echo target, Throwable exception) {
invocations.add("after throwing");
}
}
@@ -967,7 +964,7 @@ abstract class AbstractMakeModifiable {
class MakeITestBeanModifiable extends AbstractMakeModifiable {
@DeclareParents(value = "org.springframework.beans.testfixture.beans.ITestBean+",
defaultImpl=ModifiableImpl.class)
defaultImpl = ModifiableImpl.class)
static MutableModifiable mixin;
}
@@ -31,17 +31,17 @@ import kotlin.coroutines.Continuation
*/
class AopUtilsKotlinTests {
@Test
fun `Invoking suspending function should return Mono`() {
val value = "foo"
val method = ReflectionUtils.findMethod(WithoutInterface::class.java, "handle",
@Test
fun `Invoking suspending function should return Mono`() {
val value = "foo"
val method = ReflectionUtils.findMethod(WithoutInterface::class.java, "handle",
String::class. java, Continuation::class.java)!!
val continuation = Continuation<Any>(CoroutineName("test")) { }
val continuation = Continuation<Any>(CoroutineName("test")) { }
val result = AopUtils.invokeJoinpointUsingReflection(WithoutInterface(), method, arrayOf(value, continuation))
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
assertThat(it.block()).isEqualTo(value)
}
}
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
assertThat(it.block()).isEqualTo(value)
}
}
@Test
fun `Invoking suspending function on bridged method should return Mono`() {
@@ -54,11 +54,11 @@ class AopUtilsKotlinTests {
}
}
@Suppress("unused")
suspend fun suspendingFunction(value: String): String {
delay(1)
return value
}
@Suppress("unused")
suspend fun suspendingFunction(value: String): String {
delay(1)
return value
}
class WithoutInterface {
suspend fun handle(value: String): String {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,10 +28,10 @@ import org.springframework.scheduling.annotation.Async;
* <p>This aspect routes methods marked with the {@link Async} annotation as well as methods
* in classes marked with the same. Any method expected to be routed asynchronously must
* return either {@code void}, {@link Future}, or a subtype of {@link Future} (in particular,
* Spring's {@link org.springframework.util.concurrent.ListenableFuture}). This aspect,
* therefore, will produce a compile-time error for methods that violate this constraint
* on the return type. If, however, a class marked with {@code @Async} contains a method
* that violates this constraint, it produces only a warning.
* {@link java.util.concurrent.CompletableFuture}). This aspect, therefore, will produce a
* compile-time error for methods that violate this constraint on the return type. If,
* however, a class marked with {@code @Async} contains a method that violates this
* constraint, it produces only a warning.
*
* <p>This aspect needs to be injected with an implementation of a task-oriented
* {@link java.util.concurrent.Executor} to activate it for a specific thread pool,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -291,7 +291,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
String lastKey = tokens.keys[tokens.keys.length - 1];
if (propValue.getClass().isArray()) {
Class<?> requiredType = propValue.getClass().componentType();
Class<?> componentType = propValue.getClass().componentType();
int arrayIndex = Integer.parseInt(lastKey);
Object oldValue = null;
try {
@@ -299,10 +299,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
oldValue = Array.get(propValue, arrayIndex);
}
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
requiredType, ph.nested(tokens.keys.length));
componentType, ph.nested(tokens.keys.length));
int length = Array.getLength(propValue);
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
Class<?> componentType = propValue.getClass().componentType();
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
System.arraycopy(propValue, 0, newArray, 0, length);
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
@@ -658,6 +657,14 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
value = list.get(index);
}
else if (value instanceof Map map) {
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
// IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
value = map.get(convertedMapKey);
}
else if (value instanceof Iterable iterable) {
// Apply index to Iterator in case of a Set/Collection/Iterable.
int index = Integer.parseInt(key);
@@ -685,14 +692,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
currIndex + ", accessed using property path '" + propertyName + "'");
}
}
else if (value instanceof Map map) {
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
// IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
value = map.get(convertedMapKey);
}
else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Property referenced in indexed property path '" + propertyName +
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.springframework.lang.Nullable;
* analogous to an InvocationTargetException.
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
@SuppressWarnings("serial")
public class MethodInvocationException extends PropertyAccessException {
@@ -41,7 +42,9 @@ public class MethodInvocationException extends PropertyAccessException {
* @param cause the Throwable raised by the invoked method
*/
public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, @Nullable Throwable cause) {
super(propertyChangeEvent, "Property '" + propertyChangeEvent.getPropertyName() + "' threw exception", cause);
super(propertyChangeEvent,
"Property '" + propertyChangeEvent.getPropertyName() + "' threw exception: " + cause,
cause);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,9 +29,11 @@ import org.springframework.util.ObjectUtils;
/**
* A simple descriptor for an injection point, pointing to a method/constructor
* parameter or a field. Exposed by {@link UnsatisfiedDependencyException}.
* Also available as an argument for factory methods, reacting to the
* requesting injection point for building a customized bean instance.
* parameter or a field.
*
* <p>Exposed by {@link UnsatisfiedDependencyException}. Also available as an
* argument for factory methods, reacting to the requesting injection point
* for building a customized bean instance.
*
* @author Juergen Hoeller
* @since 4.3
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,9 +92,13 @@ public interface ListableBeanFactory extends BeanFactory {
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
* of instances, including availability and uniqueness options.
* @param requiredType type the bean must match; can be an interface or superclass
* @param allowEagerInit whether stream-based access may initialize <i>lazy-init
* singletons</i> and <i>objects created by FactoryBeans</i> (or by factory methods
* with a "factory-bean" reference) for the type check
* @param allowEagerInit whether stream access may introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return a corresponding provider handle
* @since 5.3
* @see #getBeanProvider(ResolvableType, boolean)
@@ -112,9 +116,13 @@ public interface ListableBeanFactory extends BeanFactory {
* injection points. For programmatically retrieving a list of beans matching a
* specific type, specify the actual bean type as an argument here and subsequently
* use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options.
* @param allowEagerInit whether stream-based access may initialize <i>lazy-init
* singletons</i> and <i>objects created by FactoryBeans</i> (or by factory methods
* with a "factory-bean" reference) for the type check
* @param allowEagerInit whether stream access may introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return a corresponding provider handle
* @since 5.3
* @see #getBeanProvider(ResolvableType)
@@ -137,8 +145,6 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -168,18 +174,18 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>Bean names returned by this method should always return bean names <i>in the
* order of definition</i> in the backend configuration, as far as possible.
* @param type the generically typed class or interface to match
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
* <i>objects created by FactoryBeans</i> (or by factory methods with a
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
* @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references.
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return the names of beans (or objects created by FactoryBeans) matching
* the given object type (including subclasses), or an empty array if none
* @since 5.2
@@ -200,8 +206,6 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -229,18 +233,18 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>Bean names returned by this method should always return bean names <i>in the
* order of definition</i> in the backend configuration, as far as possible.
* @param type the class or interface to match, or {@code null} for all bean names
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
* <i>objects created by FactoryBeans</i> (or by factory methods with a
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
* @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references.
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return the names of beans (or objects created by FactoryBeans) matching
* the given object type (including subclasses), or an empty array if none
* @see FactoryBean#getObjectType
@@ -253,21 +257,24 @@ public interface ListableBeanFactory extends BeanFactory {
* subclasses), judging from either bean definitions or the value of
* {@code getObjectType} in the case of FactoryBeans.
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
* check nested beans which might match the specified type as well.
* check nested beans which might match the specified type as well. Also, it
* <b>suppresses exceptions for beans that are currently in creation in a circular
* reference scenario:</b> typically, references back to the caller of this method.
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
* will get initialized. If the object created by the FactoryBean doesn't match,
* the raw FactoryBean itself will be matched against the type.
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of getBeansOfType matches all kinds of beans, be it
* singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeansOfType(type, true, true)}.
* <p>The Map returned by this method should always return bean names and
* corresponding bean instances <i>in the order of definition</i> in the
* backend configuration, as far as possible.
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
* calls for specific bean names in preference to this Map-based retrieval method.</b>
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
* @param type the class or interface to match, or {@code null} for all concrete beans
* @return a Map with the matching beans, containing the bean names as
* keys and the corresponding bean instances as values
@@ -283,7 +290,9 @@ public interface ListableBeanFactory extends BeanFactory {
* subclasses), judging from either bean definitions or the value of
* {@code getObjectType} in the case of FactoryBeans.
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
* check nested beans which might match the specified type as well.
* check nested beans which might match the specified type as well. Also, it
* <b>suppresses exceptions for beans that are currently in creation in a circular
* reference scenario:</b> typically, references back to the caller of this method.
* <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
* which means that FactoryBeans will get initialized. If the object created by the
* FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
@@ -292,19 +301,22 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>The Map returned by this method should always return bean names and
* corresponding bean instances <i>in the order of definition</i> in the
* backend configuration, as far as possible.
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
* calls for specific bean names in preference to this Map-based retrieval method.</b>
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
* @param type the class or interface to match, or {@code null} for all concrete beans
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
* <i>objects created by FactoryBeans</i> (or by factory methods with a
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
* @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references.
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return a Map with the matching beans, containing the bean names as
* keys and the corresponding bean instances as values
* @throws BeansException if a bean could not be created
@@ -180,6 +180,9 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
for (Annotation annotation : annotationsToSearch) {
Class<? extends Annotation> type = annotation.annotationType();
if (isPlainJavaAnnotation(type)) {
continue;
}
boolean checkMeta = true;
boolean fallbackToMeta = false;
if (isQualifier(type)) {
@@ -194,6 +197,9 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
boolean foundMeta = false;
for (Annotation metaAnn : type.getAnnotations()) {
Class<? extends Annotation> metaType = metaAnn.annotationType();
if (isPlainJavaAnnotation(metaType)) {
continue;
}
if (isQualifier(metaType)) {
foundMeta = true;
// Only accept fallback match if @Qualifier annotation has a value...
@@ -213,7 +219,17 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
}
/**
* Checks whether the given annotation type is a recognized qualifier type.
* Check whether the given annotation type is a plain "java." annotation,
* typically from {@code java.lang.annotation}.
* <p>Aligned with
* {@code org.springframework.core.annotation.AnnotationsScanner#hasPlainJavaAnnotationsOnly}.
*/
private boolean isPlainJavaAnnotation(Class<? extends Annotation> annotationType) {
return annotationType.getName().startsWith("java.");
}
/**
* Check whether the given annotation type is a recognized qualifier type.
*/
protected boolean isQualifier(Class<? extends Annotation> annotationType) {
for (Class<? extends Annotation> qualifierType : this.qualifierTypes) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -156,6 +156,8 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
.builder(SuppressWarnings.class)
.addMember("value", "{\"rawtypes\", \"unchecked\"}")
.build());
method.addModifiers(javax.lang.model.element.Modifier.PRIVATE,
javax.lang.model.element.Modifier.STATIC);
method.returns(Map.class);
method.addStatement("$T map = new $T($L)", Map.class,
LinkedHashMap.class, map.size());
@@ -64,8 +64,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
private final Supplier<InstantiationDescriptor> instantiationDescriptor;
DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode,
RegisteredBean registeredBean,
DefaultBeanRegistrationCodeFragments(
BeanRegistrationsCode beanRegistrationsCode, RegisteredBean registeredBean,
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory) {
this.beanRegistrationsCode = beanRegistrationsCode;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -148,10 +148,10 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
this.parameterTypes = original.parameterTypes;
this.parameterIndex = original.parameterIndex;
this.fieldName = original.fieldName;
this.containingClass = original.containingClass;
this.required = original.required;
this.eager = original.eager;
this.nestingLevel = original.nestingLevel;
this.containingClass = original.containingClass;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.AotDetector;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
@@ -153,7 +154,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(beanDefinition.getBeanClass());
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setAttemptLoad(AotDetector.useGeneratedArtifacts());
if (this.owner instanceof ConfigurableBeanFactory cbf) {
ClassLoader cl = cbf.getBeanClassLoader();
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
@@ -265,7 +266,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
/**
* CGLIB MethodInterceptor to override methods, replacing them with a call
* to a generic MethodReplacer.
* to a generic {@link MethodReplacer}.
*/
private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
@@ -277,10 +278,10 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
}
@Override
@Nullable
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Assert.state(ro != null, "ReplaceOverride not found");
// TODO could cache if a singleton for minor performance optimization
MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
return mr.reimplement(obj, method, args);
}
@@ -488,8 +488,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return namedBean.getBeanInstance();
}
BeanFactory parent = getParentBeanFactory();
if (parent instanceof DefaultListableBeanFactory dlfb) {
return dlfb.resolveBean(requiredType, args, nonUniqueAsNull);
if (parent instanceof DefaultListableBeanFactory dlbf) {
return dlbf.resolveBean(requiredType, args, nonUniqueAsNull);
}
else if (parent != null) {
ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
@@ -1055,6 +1055,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
}
else {
if (logger.isInfoEnabled()) {
logger.info("Removing alias '" + beanName + "' for bean '" + aliasedName +
"' due to registration of bean definition for bean '" + beanName + "': [" +
beanDefinition + "]");
}
removeAlias(beanName);
}
}
@@ -1776,12 +1781,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* @param requiredType the target dependency type to match against
* @return the name of the candidate with the highest priority,
* or {@code null} if none found
* @throws NoUniqueBeanDefinitionException if multiple beans are detected with
* the same highest priority value
* @see #getPriority(Object)
*/
@Nullable
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {
String highestPriorityBeanName = null;
Integer highestPriority = null;
boolean highestPriorityConflictDetected = false;
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
@@ -1790,13 +1798,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (candidatePriority != null) {
if (highestPriority != null) {
if (candidatePriority.equals(highestPriority)) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"Multiple beans found with the same priority ('" + highestPriority +
"') among candidates: " + candidates.keySet());
highestPriorityConflictDetected = true;
}
else if (candidatePriority < highestPriority) {
highestPriorityBeanName = candidateBeanName;
highestPriority = candidatePriority;
highestPriorityConflictDetected = false;
}
}
else {
@@ -1806,6 +1813,13 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
}
}
if (highestPriorityConflictDetected) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"Multiple beans found with the same highest priority (" + highestPriority +
") among candidates: " + candidates.keySet());
}
return highestPriorityBeanName;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -178,13 +178,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// Quick check for existing instance without full singleton lock.
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
// Consistent creation of early reference within full singleton lock.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,16 +17,18 @@
package org.springframework.beans.propertyeditors;
import java.beans.PropertyEditorSupport;
import java.time.DateTimeException;
import java.time.ZoneId;
import org.springframework.util.StringUtils;
/**
* Editor for {@code java.time.ZoneId}, translating zone ID Strings into {@code ZoneId}
* objects. Exposes the {@code TimeZone} ID as a text representation.
* Editor for {@code java.time.ZoneId}, translating time zone Strings into {@code ZoneId}
* objects. Exposes the time zone as a text representation.
*
* @author Nicholas Williams
* @author Sam Brannen
* @author Juergen Hoeller
* @since 4.0
* @see java.time.ZoneId
* @see TimeZoneEditor
@@ -38,7 +40,12 @@ public class ZoneIdEditor extends PropertyEditorSupport {
if (StringUtils.hasText(text)) {
text = text.trim();
}
setValue(ZoneId.of(text));
try {
setValue(ZoneId.of(text));
}
catch (DateTimeException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.beans.support;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -77,8 +78,8 @@ public class PropertyComparator<T> implements Comparator<T> {
Object v1 = getPropertyValue(o1);
Object v2 = getPropertyValue(o2);
if (this.sortDefinition.isIgnoreCase() && (v1 instanceof String text1) && (v2 instanceof String text2)) {
v1 = text1.toLowerCase();
v2 = text2.toLowerCase();
v1 = text1.toLowerCase(Locale.ROOT);
v2 = text2.toLowerCase(Locale.ROOT);
}
int result;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -139,6 +139,7 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.isReadableProperty("list")).isTrue();
assertThat(accessor.isReadableProperty("set")).isTrue();
assertThat(accessor.isReadableProperty("map")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap")).isTrue();
assertThat(accessor.isReadableProperty("myTestBeans")).isTrue();
assertThat(accessor.isReadableProperty("xxx")).isFalse();
@@ -146,6 +147,7 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.isWritableProperty("list")).isTrue();
assertThat(accessor.isWritableProperty("set")).isTrue();
assertThat(accessor.isWritableProperty("map")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap")).isTrue();
assertThat(accessor.isWritableProperty("myTestBeans")).isTrue();
assertThat(accessor.isWritableProperty("xxx")).isFalse();
@@ -161,6 +163,14 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.isReadableProperty("map[key4][0].name")).isTrue();
assertThat(accessor.isReadableProperty("map[key4][1]")).isTrue();
assertThat(accessor.isReadableProperty("map[key4][1].name")).isTrue();
assertThat(accessor.isReadableProperty("map[key999]")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap[key1]")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap[key1].name")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap[key2][0]")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap[key2][0].name")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap[key2][1]")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap[key2][1].name")).isTrue();
assertThat(accessor.isReadableProperty("iterableMap[key999]")).isTrue();
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
assertThat(accessor.isReadableProperty("array[key1]")).isFalse();
@@ -177,6 +187,14 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.isWritableProperty("map[key4][0].name")).isTrue();
assertThat(accessor.isWritableProperty("map[key4][1]")).isTrue();
assertThat(accessor.isWritableProperty("map[key4][1].name")).isTrue();
assertThat(accessor.isWritableProperty("map[key999]")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap[key1]")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap[key1].name")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap[key2][0]")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap[key2][0].name")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap[key2][1]")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap[key2][1].name")).isTrue();
assertThat(accessor.isWritableProperty("iterableMap[key999]")).isTrue();
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
assertThat(accessor.isWritableProperty("array[key1]")).isFalse();
@@ -1394,6 +1412,9 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("nameC");
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("nameA");
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("nameB");
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZ");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1408,6 +1429,9 @@ abstract class AbstractPropertyAccessorTests {
pvs.add("map[key4][0].name", "nameA");
pvs.add("map[key4][1].name", "nameB");
pvs.add("map[key5[foo]].name", "name10");
pvs.add("iterableMap[key1].name", "newName1");
pvs.add("iterableMap[key2][0].name", "newName2A");
pvs.add("iterableMap[key2][1].name", "newName2B");
pvs.add("myTestBeans[0].name", "nameZZ");
accessor.setPropertyValues(pvs);
assertThat(tb0.getName()).isEqualTo("name5");
@@ -1427,6 +1451,9 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.getPropertyValue("map[key4][0].name")).isEqualTo("nameA");
assertThat(accessor.getPropertyValue("map[key4][1].name")).isEqualTo("nameB");
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name10");
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("newName1");
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("newName2A");
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("newName2B");
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZZ");
}
@@ -885,10 +885,15 @@ class DefaultListableBeanFactoryTests {
@Test
void beanDefinitionOverriding() {
lbf.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class));
// Override "test" bean definition.
lbf.registerBeanDefinition("test", new RootBeanDefinition(NestedTestBean.class));
// Temporary "test2" alias for nonexistent bean.
lbf.registerAlias("otherTest", "test2");
// Reassign "test2" alias to "test".
lbf.registerAlias("test", "test2");
// Assign "testX" alias to "test" as well.
lbf.registerAlias("test", "testX");
// Register new "testX" bean definition which also removes the "testX" alias for "test".
lbf.registerBeanDefinition("testX", new RootBeanDefinition(TestBean.class));
assertThat(lbf.getBean("test")).isInstanceOf(NestedTestBean.class);
@@ -1729,18 +1734,73 @@ class DefaultListableBeanFactoryTests {
assertThat(bean.getBeanName()).isEqualTo("bd1");
}
/**
* {@code determineHighestPriorityCandidate()} should reject duplicate
* priorities for the highest priority detected.
*
* @see #getBeanByTypeWithMultipleNonHighestPriorityCandidates()
*/
@Test
void getBeanByTypeWithMultiplePriority() {
void getBeanByTypeWithMultipleHighestPriorityCandidates() {
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
RootBeanDefinition bd1 = new RootBeanDefinition(HighPriorityTestBean.class);
RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class);
RootBeanDefinition bd2 = new RootBeanDefinition(LowPriorityTestBean.class);
RootBeanDefinition bd3 = new RootBeanDefinition(HighPriorityTestBean.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
lbf.registerBeanDefinition("bd3", bd3);
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(TestBean.class))
.withMessageContaining("Multiple beans found with the same priority")
.withMessageContaining("5"); // conflicting priority
.withMessageContaining("Multiple beans found with the same highest priority (5) among candidates: ");
}
/**
* {@code determineHighestPriorityCandidate()} should ignore duplicate
* priorities for any priority other than the highest, and the order in
* which beans is declared should not affect the outcome.
*
* @see #getBeanByTypeWithMultipleHighestPriorityCandidates()
*/
@Test // gh-33733
void getBeanByTypeWithMultipleNonHighestPriorityCandidates() {
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService1.class,
PriorityService2A.class,
PriorityService2B.class,
PriorityService3.class
);
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService3.class,
PriorityService2B.class,
PriorityService2A.class,
PriorityService1.class
);
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService2A.class,
PriorityService1.class,
PriorityService2B.class,
PriorityService3.class
);
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService2A.class,
PriorityService3.class,
PriorityService1.class,
PriorityService2B.class
);
}
private void getBeanByTypeWithMultipleNonHighestPriorityCandidates(Class<?>... classes) {
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
for (Class<?> clazz : classes) {
lbf.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz));
}
PriorityService bean = lbf.getBean(PriorityService.class);
assertThat(bean).isExactlyInstanceOf(PriorityService1.class);
}
@Test
@@ -3500,6 +3560,26 @@ class DefaultListableBeanFactoryTests {
}
interface PriorityService {
}
@Priority(1)
static class PriorityService1 implements PriorityService {
}
@Priority(2)
static class PriorityService2A implements PriorityService {
}
@Priority(2)
static class PriorityService2B implements PriorityService {
}
@Priority(3)
static class PriorityService3 implements PriorityService {
}
@Priority(5)
private static class HighPriorityTestBean extends TestBean {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.beans.factory.aot;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.temporal.ChronoUnit;
@@ -28,7 +29,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import javax.lang.model.element.Modifier;
@@ -54,7 +54,7 @@ import org.springframework.core.testfixture.aot.generate.value.ExampleClass;
import org.springframework.core.testfixture.aot.generate.value.ExampleClass$$GeneratedBy;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -83,14 +83,23 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
CodeBlock generatedCode = createValueCodeGenerator(generatedClass).generateCode(value);
typeBuilder.set(type -> {
type.addModifiers(Modifier.PUBLIC);
type.addSuperinterface(
ParameterizedTypeName.get(Supplier.class, Object.class));
type.addMethod(MethodSpec.methodBuilder("get").addModifiers(Modifier.PUBLIC)
type.addMethod(MethodSpec.methodBuilder("get").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Object.class).addStatement("return $L", generatedCode).build());
});
generationContext.writeGeneratedContent();
TestCompiler.forSystem().with(generationContext).compile(compiled ->
result.accept(compiled.getInstance(Supplier.class).get(), compiled));
result.accept(getGeneratedCodeReturnValue(compiled, generatedClass), compiled));
}
private static Object getGeneratedCodeReturnValue(Compiled compiled, GeneratedClass generatedClass) {
try {
Object instance = compiled.getInstance(Object.class, generatedClass.getName().reflectionName());
Method get = ReflectionUtils.findMethod(instance.getClass(), "get");
return get.invoke(null);
}
catch (Exception ex) {
throw new RuntimeException("Failed to invoke generated code '%s':".formatted(generatedClass.getName()), ex);
}
}
@Nested
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,12 +21,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.util.ClassUtils;
@@ -43,14 +44,17 @@ class QualifierAnnotationAutowireBeanFactoryTests {
private static final String MARK = "mark";
private final DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
@Test
void testAutowireCandidateDefaultWithIrrelevantDescriptor() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
RootBeanDefinition rbd = new RootBeanDefinition(Person.class, cavs, null);
lbf.registerBeanDefinition(JUERGEN, rbd);
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN,
new DependencyDescriptor(Person.class.getDeclaredField("name"), false))).isTrue();
@@ -60,12 +64,12 @@ class QualifierAnnotationAutowireBeanFactoryTests {
@Test
void testAutowireCandidateExplicitlyFalseWithIrrelevantDescriptor() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
RootBeanDefinition rbd = new RootBeanDefinition(Person.class, cavs, null);
rbd.setAutowireCandidate(false);
lbf.registerBeanDefinition(JUERGEN, rbd);
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isFalse();
assertThat(lbf.isAutowireCandidate(JUERGEN,
new DependencyDescriptor(Person.class.getDeclaredField("name"), false))).isFalse();
@@ -73,44 +77,46 @@ class QualifierAnnotationAutowireBeanFactoryTests {
new DependencyDescriptor(Person.class.getDeclaredField("name"), true))).isFalse();
}
@Disabled
@Test
void testAutowireCandidateWithFieldDescriptor() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
lbf.registerBeanDefinition(JUERGEN, person1);
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
cavs2.addGenericArgumentValue(MARK);
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
lbf.registerBeanDefinition(MARK, person2);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
QualifiedTestBean.class.getDeclaredField("qualified"), false);
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(
QualifiedTestBean.class.getDeclaredField("nonqualified"), false);
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, null)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, nonqualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isFalse();
}
@Test
void testAutowireCandidateExplicitlyFalseWithFieldDescriptor() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null);
person.setAutowireCandidate(false);
person.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
lbf.registerBeanDefinition(JUERGEN, person);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
QualifiedTestBean.class.getDeclaredField("qualified"), false);
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(
QualifiedTestBean.class.getDeclaredField("nonqualified"), false);
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isFalse();
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isFalse();
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isFalse();
@@ -118,56 +124,61 @@ class QualifierAnnotationAutowireBeanFactoryTests {
@Test
void testAutowireCandidateWithShortClassName() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addGenericArgumentValue(JUERGEN);
RootBeanDefinition person = new RootBeanDefinition(Person.class, cavs, null);
person.addQualifier(new AutowireCandidateQualifier(ClassUtils.getShortName(TestQualifier.class)));
lbf.registerBeanDefinition(JUERGEN, person);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
QualifiedTestBean.class.getDeclaredField("qualified"), false);
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(
QualifiedTestBean.class.getDeclaredField("nonqualified"), false);
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
}
@Disabled
@Test
void testAutowireCandidateWithConstructorDescriptor() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
lbf.registerBeanDefinition(JUERGEN, person1);
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
cavs2.addGenericArgumentValue(MARK);
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
lbf.registerBeanDefinition(MARK, person2);
MethodParameter param = new MethodParameter(QualifiedTestBean.class.getDeclaredConstructor(Person.class), 0);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(param, false);
param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
assertThat(param.getParameterName()).isEqualTo("tpb");
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isFalse();
}
@Disabled
@Test
void testAutowireCandidateWithMethodDescriptor() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
lbf.registerBeanDefinition(JUERGEN, person1);
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
cavs2.addGenericArgumentValue(MARK);
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
lbf.registerBeanDefinition(MARK, person2);
MethodParameter qualifiedParam =
new MethodParameter(QualifiedTestBean.class.getDeclaredMethod("autowireQualified", Person.class), 0);
MethodParameter nonqualifiedParam =
@@ -175,37 +186,70 @@ class QualifierAnnotationAutowireBeanFactoryTests {
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(qualifiedParam, false);
DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(nonqualifiedParam, false);
qualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
assertThat(qualifiedParam.getParameterName()).isEqualTo("tpb");
nonqualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
assertThat(qualifiedParam.getParameterName()).isEqualTo("tpb");
assertThat(nonqualifiedParam.getParameterName()).isEqualTo("tpb");
assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, null)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, nonqualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isFalse();
}
@Test
void testAutowireCandidateWithMultipleCandidatesDescriptor() throws Exception {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
cavs1.addGenericArgumentValue(JUERGEN);
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
lbf.registerBeanDefinition(JUERGEN, person1);
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
cavs2.addGenericArgumentValue(MARK);
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
person2.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
lbf.registerBeanDefinition(MARK, person2);
DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(
new MethodParameter(QualifiedTestBean.class.getDeclaredConstructor(Person.class), 0),
false);
assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue();
assertThat(lbf.isAutowireCandidate(MARK, qualifiedDescriptor)).isTrue();
}
@Test
void autowireBeanByTypeWithQualifierPrecedence() throws Exception {
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("testBean", bd);
lbf.registerBeanDefinition("spouse", bd2);
lbf.registerAlias("test", "testBean");
assertThat(lbf.resolveDependency(new DependencyDescriptor(getClass().getDeclaredField("testBean"), true), null))
.isSameAs(lbf.getBean("spouse"));
}
@Test
void autowireBeanByTypeWithQualifierPrecedenceInAncestor() throws Exception {
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
parent.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
parent.registerBeanDefinition("test", bd);
parent.registerBeanDefinition("spouse", bd2);
parent.registerAlias("test", "testBean");
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(parent);
lbf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
assertThat(lbf.resolveDependency(new DependencyDescriptor(getClass().getDeclaredField("testBean"), true), null))
.isSameAs(lbf.getBean("spouse"));
}
@SuppressWarnings("unused")
private static class QualifiedTestBean {
@@ -247,4 +291,8 @@ class QualifierAnnotationAutowireBeanFactoryTests {
private @interface TestQualifier {
}
@Qualifier("spouse")
private TestBean testBean;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,10 +23,12 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* @author Nicholas Williams
* @author Sam Brannen
* @author Juergen Hoeller
*/
class ZoneIdEditorTests {
@@ -69,4 +71,9 @@ class ZoneIdEditorTests {
assertThat(editor.getAsText()).as("The text version is not correct.").isEqualTo("America/New_York");
}
@Test
void correctExceptionForInvalid() {
assertThatIllegalArgumentException().isThrownBy(() -> editor.setAsText("INVALID")).withMessageContaining("INVALID");
}
}
@@ -45,99 +45,99 @@ import javax.lang.model.element.Modifier
*/
class InstanceSupplierCodeGeneratorKotlinTests {
private val generationContext = TestGenerationContext()
private val generationContext = TestGenerationContext()
@Test
fun generateWhenHasDefaultConstructor() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBean::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean = getBean<KotlinTestBean>(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBean::class.java)
Assertions.assertThat(compiled.sourceFile).contains("InstanceSupplier.using(KotlinTestBean::new)")
}
Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT))
}
@Test
fun generateWhenHasDefaultConstructor() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBean::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean = getBean<KotlinTestBean>(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBean::class.java)
Assertions.assertThat(compiled.sourceFile).contains("InstanceSupplier.using(KotlinTestBean::new)")
}
Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT))
}
@Test
fun generateWhenConstructorHasOptionalParameter() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBeanWithOptionalParameter::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean: KotlinTestBeanWithOptionalParameter = getBean(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBeanWithOptionalParameter::class.java)
Assertions.assertThat(compiled.sourceFile)
.contains("return BeanInstanceSupplier.<KotlinTestBeanWithOptionalParameter>forConstructor();")
}
Assertions.assertThat<TypeHint>(getReflectionHints().getTypeHint(KotlinTestBeanWithOptionalParameter::class.java))
.satisfies(hasMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
}
@Test
fun generateWhenConstructorHasOptionalParameter() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBeanWithOptionalParameter::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean: KotlinTestBeanWithOptionalParameter = getBean(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBeanWithOptionalParameter::class.java)
Assertions.assertThat(compiled.sourceFile)
.contains("return BeanInstanceSupplier.<KotlinTestBeanWithOptionalParameter>forConstructor();")
}
Assertions.assertThat<TypeHint>(getReflectionHints().getTypeHint(KotlinTestBeanWithOptionalParameter::class.java))
.satisfies(hasMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
}
private fun getReflectionHints(): ReflectionHints {
return generationContext.runtimeHints.reflection()
}
private fun getReflectionHints(): ReflectionHints {
return generationContext.runtimeHints.reflection()
}
private fun hasConstructorWithMode(mode: ExecutableMode): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.constructors()).anySatisfy(hasMode(mode))
}
}
private fun hasConstructorWithMode(mode: ExecutableMode): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.constructors()).anySatisfy(hasMode(mode))
}
}
private fun hasMemberCategory(category: MemberCategory): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.memberCategories).contains(category)
}
}
private fun hasMemberCategory(category: MemberCategory): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.memberCategories).contains(category)
}
}
private fun hasMode(mode: ExecutableMode): ThrowingConsumer<ExecutableHint> {
return ThrowingConsumer {
Assertions.assertThat(it.mode).isEqualTo(mode)
}
}
private fun hasMode(mode: ExecutableMode): ThrowingConsumer<ExecutableHint> {
return ThrowingConsumer {
Assertions.assertThat(it.mode).isEqualTo(mode)
}
}
@Suppress("UNCHECKED_CAST")
private fun <T> getBean(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
instanceSupplier: InstanceSupplier<*>): T {
(beanDefinition as RootBeanDefinition).instanceSupplier = instanceSupplier
beanFactory.registerBeanDefinition("testBean", beanDefinition)
return beanFactory.getBean("testBean") as T
}
@Suppress("UNCHECKED_CAST")
private fun <T> getBean(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
instanceSupplier: InstanceSupplier<*>): T {
(beanDefinition as RootBeanDefinition).instanceSupplier = instanceSupplier
beanFactory.registerBeanDefinition("testBean", beanDefinition)
return beanFactory.getBean("testBean") as T
}
private fun compile(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
result: BiConsumer<InstanceSupplier<*>, Compiled>) {
private fun compile(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
result: BiConsumer<InstanceSupplier<*>, Compiled>) {
val freshBeanFactory = DefaultListableBeanFactory(beanFactory)
freshBeanFactory.registerBeanDefinition("testBean", beanDefinition)
val registeredBean = RegisteredBean.of(freshBeanFactory, "testBean")
val typeBuilder = DeferredTypeBuilder()
val generateClass = generationContext.generatedClasses.addForFeature("TestCode", typeBuilder)
val generator = InstanceSupplierCodeGenerator(
generationContext, generateClass.name,
generateClass.methods, false
)
val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
Assertions.assertThat(instantiationDescriptor).isNotNull()
val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
typeBuilder.set { type: TypeSpec.Builder ->
type.addModifiers(Modifier.PUBLIC)
type.addSuperinterface(
ParameterizedTypeName.get(
Supplier::class.java,
InstanceSupplier::class.java
)
)
type.addMethod(
MethodSpec.methodBuilder("get")
.addModifiers(Modifier.PUBLIC)
.returns(InstanceSupplier::class.java)
.addStatement("return \$L", generatedCode).build()
)
}
generationContext.writeGeneratedContent()
TestCompiler.forSystem().with(generationContext).compile {
result.accept(it.getInstance(Supplier::class.java).get() as InstanceSupplier<*>, it)
}
}
val freshBeanFactory = DefaultListableBeanFactory(beanFactory)
freshBeanFactory.registerBeanDefinition("testBean", beanDefinition)
val registeredBean = RegisteredBean.of(freshBeanFactory, "testBean")
val typeBuilder = DeferredTypeBuilder()
val generateClass = generationContext.generatedClasses.addForFeature("TestCode", typeBuilder)
val generator = InstanceSupplierCodeGenerator(
generationContext, generateClass.name,
generateClass.methods, false
)
val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
Assertions.assertThat(instantiationDescriptor).isNotNull()
val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
typeBuilder.set { type: TypeSpec.Builder ->
type.addModifiers(Modifier.PUBLIC)
type.addSuperinterface(
ParameterizedTypeName.get(
Supplier::class.java,
InstanceSupplier::class.java
)
)
type.addMethod(
MethodSpec.methodBuilder("get")
.addModifiers(Modifier.PUBLIC)
.returns(InstanceSupplier::class.java)
.addStatement("return \$L", generatedCode).build()
)
}
generationContext.writeGeneratedContent()
TestCompiler.forSystem().with(generationContext).compile {
result.accept(it.getInstance(Supplier::class.java).get() as InstanceSupplier<*>, it)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -49,6 +50,8 @@ public class IndexedTestBean {
private SortedMap sortedMap;
private IterableMap iterableMap;
private MyTestBeans myTestBeans;
@@ -73,6 +76,9 @@ public class IndexedTestBean {
TestBean tb6 = new TestBean("name6", 0);
TestBean tb7 = new TestBean("name7", 0);
TestBean tb8 = new TestBean("name8", 0);
TestBean tbA = new TestBean("nameA", 0);
TestBean tbB = new TestBean("nameB", 0);
TestBean tbC = new TestBean("nameC", 0);
TestBean tbX = new TestBean("nameX", 0);
TestBean tbY = new TestBean("nameY", 0);
TestBean tbZ = new TestBean("nameZ", 0);
@@ -88,6 +94,12 @@ public class IndexedTestBean {
this.map.put("key2", tb5);
this.map.put("key.3", tb5);
List list = new ArrayList();
list.add(tbA);
list.add(tbB);
this.iterableMap = new IterableMap<>();
this.iterableMap.put("key1", tbC);
this.iterableMap.put("key2", list);
list = new ArrayList();
list.add(tbX);
list.add(tbY);
this.map.put("key4", list);
@@ -152,6 +164,14 @@ public class IndexedTestBean {
this.sortedMap = sortedMap;
}
public IterableMap getIterableMap() {
return this.iterableMap;
}
public void setIterableMap(IterableMap iterableMap) {
this.iterableMap = iterableMap;
}
public MyTestBeans getMyTestBeans() {
return myTestBeans;
}
@@ -161,6 +181,15 @@ public class IndexedTestBean {
}
@SuppressWarnings("serial")
public static class IterableMap<K,V> extends LinkedHashMap<K,V> implements Iterable<V> {
@Override
public Iterator<V> iterator() {
return values().iterator();
}
}
public static class MyTestBeans implements Iterable<TestBean> {
private final Collection<TestBean> testBeans;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -303,6 +303,17 @@ public class CaffeineCacheManager implements CacheManager {
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
}
/**
* Remove the specified cache from this cache manager, applying to
* custom caches as well as dynamically registered caches at runtime.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.customCacheNames.remove(name);
this.cacheMap.remove(name);
}
/**
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
* abstraction for the specified cache name.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@ import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
/**
* Helper class for populating a {@link jakarta.mail.internet.MimeMessage}.
@@ -960,7 +961,8 @@ public class MimeMessageHelper {
*/
public void addInline(String contentId, Resource resource) throws MessagingException {
Assert.notNull(resource, "Resource must not be null");
String contentType = getFileTypeMap().getContentType(resource.getFilename());
String contentType = (resource.getFilename() != null ?
getFileTypeMap().getContentType(resource.getFilename()) : MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE);
addInline(contentId, resource, contentType);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.scheduling.quartz;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Locale;
import javax.sql.DataSource;
@@ -155,7 +156,7 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource,
DatabaseMetaData::getDatabaseProductName);
productName = JdbcUtils.commonDatabaseName(productName);
if (productName != null && productName.toLowerCase().contains("hsql")) {
if (productName != null && productName.toLowerCase(Locale.ROOT).contains("hsql")) {
setUseDBLocks(false);
setLockHandler(new SimpleSemaphore());
}
@@ -39,6 +39,7 @@ import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.mail.MailParseException;
import org.springframework.mail.MailSendException;
import org.springframework.mail.SimpleMailMessage;
@@ -270,6 +271,25 @@ class JavaMailSenderTests {
assertThat(sender.transport.getSentMessages()).containsExactly(message.getMimeMessage());
}
@Test
void javaMailSenderWithMimeMessageHelperAndCustomResource() throws Exception {
sender.setHost("host");
sender.setUsername("username");
sender.setPassword("password");
MimeMessageHelper message = new MimeMessageHelper(sender.createMimeMessage(), true);
message.setTo("you@mail.org");
message.addInline("id", new ByteArrayResource(new byte[] {1, 2, 3}));
sender.send(message.getMimeMessage());
assertThat(sender.transport.getConnectedHost()).isEqualTo("host");
assertThat(sender.transport.getConnectedUsername()).isEqualTo("username");
assertThat(sender.transport.getConnectedPassword()).isEqualTo("password");
assertThat(sender.transport.isCloseCalled()).isTrue();
assertThat(sender.transport.getSentMessages()).containsExactly(message.getMimeMessage());
}
@Test
void javaMailSenderWithParseExceptionOnSimpleMessage() {
SimpleMailMessage simpleMessage = new SimpleMailMessage();
+7
View File
@@ -58,3 +58,10 @@ dependencies {
testRuntimeOnly("org.javamoney:moneta")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine") // for @Inject TCK
}
test {
description = "Runs JUnit Jupiter tests and the @Inject TCK via JUnit Vintage."
useJUnitPlatform {
includeEngines "junit-jupiter", "junit-vintage"
}
}
@@ -32,6 +32,10 @@ import org.springframework.lang.Nullable;
* {@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
* declaring a lazy {@link org.springframework.beans.factory.ObjectProvider} for those.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -175,6 +175,15 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
return cache;
}
/**
* Remove the specified cache from this cache manager.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.cacheMap.remove(name);
}
private void recreateCaches() {
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
entry.setValue(createConcurrentMapCache(entry.getKey()));
@@ -1141,7 +1141,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return evaluate(null, invoker, method, contexts);
Object e = evaluate(null, invoker, method, contexts);
return (e != null ? e : Flux.error((RuntimeException) ex));
}
catch (RuntimeException exception) {
return Flux.error(exception);
@@ -1155,7 +1156,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return evaluate(null, invoker, method, contexts);
Object e = evaluate(null, invoker, method, contexts);
return (e != null ? e : Mono.error((RuntimeException) ex));
}
catch (RuntimeException exception) {
return Mono.error(exception);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.context.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -33,6 +34,7 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotation.Adapt;
@@ -41,6 +43,7 @@ import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@@ -147,16 +150,26 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
Set<String> metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType,
key -> getMetaAnnotationTypes(mergedAnnotation));
if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) {
Object value = attributes.get("value");
Object value = attributes.get(MergedAnnotation.VALUE);
if (value instanceof String currentName && !currentName.isBlank()) {
if (conventionBasedStereotypeCheckCache.add(annotationType) &&
metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) && logger.isWarnEnabled()) {
logger.warn("""
Support for convention-based stereotype names is deprecated and will \
be removed in a future version of the framework. Please annotate the \
'value' attribute in @%s with @AliasFor(annotation=Component.class) \
to declare an explicit alias for @Component's 'value' attribute."""
.formatted(annotationType));
if (hasExplicitlyAliasedValueAttribute(mergedAnnotation.getType())) {
logger.warn("""
Although the 'value' attribute in @%s declares @AliasFor for an attribute \
other than @Component's 'value' attribute, the value is still used as the \
@Component name based on convention. As of Spring Framework 7.0, such a \
'value' attribute will no longer be used as the @Component name."""
.formatted(annotationType));
}
else {
logger.warn("""
Support for convention-based @Component names is deprecated and will \
be removed in a future version of the framework. Please annotate the \
'value' attribute in @%s with @AliasFor(annotation=Component.class) \
to declare an explicit alias for @Component's 'value' attribute."""
.formatted(annotationType));
}
}
if (beanName != null && !currentName.equals(beanName)) {
throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
@@ -224,7 +237,7 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
annotationType.equals("jakarta.inject.Named") ||
annotationType.equals("javax.inject.Named");
return (isStereotype && attributes.containsKey("value"));
return (isStereotype && attributes.containsKey(MergedAnnotation.VALUE));
}
/**
@@ -255,4 +268,14 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
return StringUtils.uncapitalizeAsProperty(shortClassName);
}
/**
* Determine if the supplied annotation type declares a {@code value()} attribute
* with an explicit alias configured via {@link AliasFor @AliasFor}.
* @since 6.2.3
*/
private static boolean hasExplicitlyAliasedValueAttribute(Class<? extends Annotation> annotationType) {
Method valueAttribute = ReflectionUtils.findMethod(annotationType, MergedAnnotation.VALUE);
return (valueAttribute != null && valueAttribute.isAnnotationPresent(AliasFor.class));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -565,9 +565,10 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
}
/**
* Determine whether the given bean definition qualifies as candidate.
* <p>The default implementation checks whether the class is not an interface
* and not dependent on an enclosing class.
* Determine whether the given bean definition qualifies as a candidate component.
* <p>The default implementation checks whether the class is not dependent on an
* enclosing class as well as whether the class is either concrete (and therefore
* not an interface) or has {@link Lookup @Lookup} methods.
* <p>Can be overridden in subclasses.
* @param beanDefinition the bean definition to check
* @return whether the bean definition qualifies as a candidate component
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -191,6 +191,15 @@ final class ConfigurationClass {
return this.beanMethods;
}
boolean hasNonStaticBeanMethods() {
for (BeanMethod beanMethod : this.beanMethods) {
if (!beanMethod.getMetadata().isStatic()) {
return true;
}
}
return false;
}
void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
this.importedResources.put(importedResource, readerClass);
}
@@ -212,7 +221,7 @@ final class ConfigurationClass {
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods")) {
if (this.metadata.isFinal()) {
if (hasNonStaticBeanMethods() && this.metadata.isFinal()) {
problemReporter.error(new FinalConfigurationProblem());
}
for (BeanMethod beanMethod : this.beanMethods) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,6 @@ import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
@@ -318,7 +317,7 @@ class ConfigurationClassBeanDefinitionReader {
// At this point, it's a top-level override (probably XML), just having been parsed
// before configuration class processing kicks in...
if (this.registry instanceof DefaultListableBeanFactory dlbf && !dlbf.isBeanDefinitionOverridable(beanName)) {
if (!this.registry.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
}

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