Compare commits

...

284 Commits

Author SHA1 Message Date
Brian Clozel eb3f034cd9 Release v6.2.4 2025-03-13 09:18:22 +01:00
Juergen Hoeller 387677eae8 Upgrade to JUnit 5.12
Closes gh-34416
2025-03-12 12:46:34 +01:00
Juergen Hoeller 4a314867d7 Upgrade to Reactor 2024.0.4 and Micrometer 1.14.5
Closes gh-34578
Closes gh-34580
2025-03-12 11:51:33 +01:00
Sam Brannen 020f556841 Support custom attribute with a value in MockCookie.parse()
Prior to this commit, MockCookie.parse() failed with an
IllegalArgumentException when attempting to parse a custom attribute
with a value, such as "Version=1". This is a regression that was
inadvertently introduced in 7fc4937199
when adding support for the "Partitioned" attribute which does not
support a value.

This commit addresses this regression by parsing both the name and the
value from an optional, custom attribute.

See gh-31454
Closes gh-34575
2025-03-12 11:19:38 +01:00
rstoyanchev 6ea3b5a0e8 Fix Javadoc failure
See gh-34549
2025-03-10 20:59:33 +00:00
rstoyanchev d764087dbf Correct since tag
See gh-34549
2025-03-10 18:17:47 +00:00
rstoyanchev 9ab43b138a Enhancement in HandlerMethodValidationException
Add dedicated method to Visitor for constraints directly on a
RequestBody method parameter (rather than nested).

Closes gh-34549
2025-03-10 18:01:55 +00:00
rstoyanchev 09ae080b99 isDisconnectedClientException protected for null
Closes gh-34533
2025-03-10 17:19:13 +00:00
Juergen Hoeller 70a1b2fae3 Upgrade to Checkstyle 10.21.4 2025-03-10 11:00:53 +01:00
Juergen Hoeller 90ddb40d7a Upgrade to Jetty 12.0.17 and Jetty Reactive HttpClient 4.0.9
Includes Apache HttpClient 5.4.1, Netty 4.1.119, Jackson 2.18.3, Gson 2.12.1, FreeMarker 2.3.34, Protobuf 4.30, Groovy 4.0.26, Jython 2.7.4, JRuby 9.4.12, Caffeine 3.2, QDox 2.2, Awaitility 4.3, EasyMock 5.5, HtmlUnit 4.10

Closes gh-34561
2025-03-10 11:00:35 +01:00
Juergen Hoeller 143985e862 Add tests for primary/fallback/defaultCandidate precedence
Closes gh-34449
2025-03-08 12:22:14 +01:00
Juergen Hoeller 4bd280b87e Explain availability and uniqueness (including primary/fallback/default) in javadoc
Closes gh-34447
2025-03-08 12:22:04 +01:00
Juergen Hoeller 5877a38fa1 Add explicit note on JSpecify support in Spring Framework 6.2 vs 7.0
Closes gh-34551
2025-03-08 12:21:46 +01:00
Vedran Pavic 94d29bac9f Fix typo in Spring MVC error responses documentation
Closes gh-34552

Signed-off-by: Vedran Pavic <vedran@vedranpavic.com>
2025-03-07 13:37:57 +01:00
Dmitry Sulman a6f3f18d89 Allow supertypes in ContentResultMatchersDsl matchers
Closes gh-34542
Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
2025-03-07 09:29:48 +01:00
Sam Brannen 8334cb1abb Revise tests to better express intent 2025-03-06 17:32:32 +01:00
Sam Brannen 2d88f18bf1 Update Javadoc 2025-03-06 16:26:40 +01:00
Sam Brannen 15a6641677 Clean up warnings in Gradle build 2025-03-06 16:26:32 +01:00
Juergen Hoeller 63c8e7cb5d Restore lenient matching of unresolved nested bound
Closes gh-34541
2025-03-06 15:44:12 +01:00
Sam Brannen c5ecc50bfe Document wrapping behavior for TestExecutionListener callbacks
Closes gh-34422
2025-03-06 13:31:23 +01:00
Sam Brannen 0fd94f1b9f Polishing 2025-03-06 13:30:20 +01:00
Juergen Hoeller 2472126475 Add fallback to pre-6.2.3 behavior for unresolvable generics
Closes gh-34535
2025-03-05 22:45:22 +01:00
Juergen Hoeller f3b50c9a58 Ignore null beans in SimpleAutowireCandidateResolver.resolveAutowireCandidates
Closes gh-34543
2025-03-05 22:44:09 +01:00
Sam Brannen 0a6e666857 Ensure GenericTypeResolverTests compiles with Eclipse compiler 2025-03-05 14:07:52 +01:00
Juergen Hoeller 403362fe3c Polishing 2025-03-04 18:44:56 +01:00
Juergen Hoeller 534f123fe8 Add explicit FileSystemResource path check for trailing slash
Closes gh-34509
2025-03-04 18:44:25 +01:00
Sam Brannen e421104cf3 Supply RuntimeHints to an AotContextLoader
This commit introduces a new loadContextForAotProcessing(...) variant
in AotContextLoader which accepts a RuntimeHints argument. This new
method is an interface default method which delegates to the existing
loadContextForAotProcessing(MergedContextConfiguration) variant for
backward compatibility.

In addition, the original loadContextForAotProcessing(...) variant is
now deprecated and has been converted to an interface default method
which throws an UnsupportedOperationException.

Note, however, that the framework now only invokes the new
loadContextForAotProcessing(...) variant within TestContextAotGenerator.

Closes gh-34513
2025-03-03 17:39:56 +01:00
Sam Brannen adfeba23e0 Polishing 2025-03-03 17:39:47 +01:00
Sam Brannen 7789e12575 Polishing 2025-03-03 15:04:42 +01:00
Sam Brannen 30d793cefe Revise deprecation of SqlQuery.rowsExpected
Closes gh-34526
2025-03-03 14:33:19 +01:00
Yanming Zhou 78cc5df748 Deprecate unused "rowsExpected" property of SqlQuery for removal
See gh-34526

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-03-03 14:33:14 +01:00
Juergen Hoeller 5ffaea7a43 Avoid JarURLConnection resource leak in AbstractFileResolvingResource.exists()
Closes gh-34528
2025-03-03 13:32:51 +01:00
Brian Clozel 1633ad24f3 Ignore nohttp check on buildSrc/build 2025-03-03 11:23:21 +01:00
Rhett CfZhuang 5faf2ed7f4 Fix missing Partitioned cookie support in reactive HTTP clients
This commit adds support for the Partitioned cookie attribute in the
reactive HTTP clients that support this: Reactor and HttpComponents.

Closes gh-34521

Signed-off-by: Rhett CfZhuang <dark.momo985@gmail.com>
[brian.clozel@broadcom.com: rework tests and support HttpComponents]
Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
2025-03-03 11:23:13 +01:00
Juergen Hoeller d2733cea36 Notify lenientCreationFinished condition after locked creation as well
Closes gh-34522
2025-03-01 22:20:23 +01:00
Johnny Lim 108caea385 Use ORDER constant in MockitoResetTestExecutionListener
See gh-34404
Closes gh-34445

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-03-01 13:35:46 +01:00
rstoyanchev 7e9ac120ac Deprecate UrlPathHelper in ServletWebSocketHandlerRegistry
Closes gh-34508
2025-02-28 14:39:29 +00:00
Juergen Hoeller c64dae3623 Avoid getTargetConnection call on transaction-aware Connection close
Closes gh-34484
2025-02-28 14:12:51 +01:00
Juergen Hoeller 559ea6c480 Defensively call isShutdown method for executor description
Closes gh-34514
2025-02-28 14:11:57 +01:00
Tran Ngoc Nhan 34315fc20f Fix web and webflux reference links
Closes gh-34517
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-02-28 12:06:11 +01:00
Juergen Hoeller 3bb4795d43 Resolve variable bounds at outermost recursion level only
Closes gh-34504
2025-02-27 22:51:41 +01:00
Ryan Prayogo aae2952a32 Replace DefaultKeyGenerator with SimpleKeyGenerator
DefaultKeyGenerator has been deprecated and replaced with SimpleKeyGenerator

Signed-off-by: Ryan Prayogo <57620+ryanprayogo@users.noreply.github.com>
2025-02-27 18:53:10 +01:00
rstoyanchev f62251aebd Avoid pathVar-requestParam name collision
Closes gh-34499
2025-02-27 14:34:26 +00:00
rstoyanchev f92f9c1d5b Fix handling of timeout in SseEmitter
Closes gh-34426
2025-02-27 14:34:26 +00:00
Sam Brannen 2b38c00656 Upgrade to Gradle 8.13
Closes gh-34495
2025-02-26 15:26:49 +01:00
Juergen Hoeller 06721ba6c0 Lenient fallback when cached WeakReference returns null
Closes gh-34423
2025-02-26 10:24:24 +01:00
Brian Clozel b6a5402d88 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-34488
2025-02-25 17:11:37 +01:00
Juergen Hoeller aff9ac72ec Avoid unnecessary CGLIB processing on configuration classes
Closes gh-34486
2025-02-25 16:20:12 +01:00
Brian Clozel f895d762cd Remove duplicate Content-Type header in error cases
Prior to this commit, the `DispatcherServlet` would try and reset the
response buffer in case of errors, if the response is not committed
already. This allows for more flexible error handling, even if the
response was being handled already when it errored.

Resetting the response buffer clears the body but leaves HTTP response
headers intact. This is done on purpose as to not clear headers
previously added by Servlet Filters. By leaving in place some headers
like "Content-Type", this does not take into account the fact that the
response body was cleared and that error handling will perform another
round of content negotiation. While this isn't a problem for some
Servlet containers which enforce a single "Content-Type" header value,
this can cause multiple/duplicate values for some others.

This commit ensures that the "Content-Type" response header is removed
at the same time as we clear the "producible media types" attribute:
another pass of content negotiation will be performed for error
handling.

Fixes gh-34366
2025-02-25 10:43:19 +01:00
Juergen Hoeller 634d1dd20b Consistent default ClassLoader fallback in hint classes
Closes gh-34470
2025-02-23 15:15:25 +01:00
Juergen Hoeller 725b02a66d Ignore entries cache if no matching root entry path found
Closes gh-34446
2025-02-23 14:02:57 +01:00
Brian Clozel 5a0bd9e5d4 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-34460
2025-02-21 14:33:03 +01:00
Brian Clozel 6dd73ae3e4 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-34464
2025-02-21 14:23:12 +01:00
Gang Cheng 4606337180 Prevent hung uploads in MultipartParser
This commit adds an onRequest() hook to request more data from
the source in order to avoid hung uploads in MultipartParser.

Closes gh-34388
Signed-off-by: Gang Cheng <chenggangpro@gmail.com>
2025-02-21 11:32:54 +01:00
Sébastien Deleuze f45a08d4f5 Remove BaseDefaultCodecs#synchronossMultipartPresent
Closes gh-34459
2025-02-20 09:07:26 +01:00
Sébastien Deleuze d62ce2969a Fix broken antora task
See https://github.com/spring-io/antora-extensions/pull/43

Closes gh-34454
2025-02-19 16:40:50 +01:00
Johnny Lim ed3fd12210 Add missing @since tags in MockHttpServletRequestDsl
This commit adds missing `@since` tags for formField() and formFields in
MockHttpServletRequestDsl. See gh-34412 related issue.

Closes gh-34448
Signed-off-by: Johnny Lim <izeye@naver.com>
2025-02-19 13:58:31 +01:00
Vincent Potucek 6f596e2e84 Add a project icon for IntelliJ IDEA
The icon comes from https://spring.io/img/projects/spring-framework.svg.

Closes gh-34444
Signed-off-by: Vincent Potucek <vincent.potucek@sap.com>
2025-02-19 13:45:16 +01:00
Juergen Hoeller 6786e1c3e5 Apply fallback in case of initial SmartClassLoader mismatch as well
See gh-34423
2025-02-18 20:40:13 +01:00
Juergen Hoeller 93134fd4d1 Apply fallback in case of any exception coming out of createClass
Closes gh-34423
2025-02-18 15:16:25 +01:00
Juergen Hoeller d0ceefedc6 Mark XML-configured executor/scheduler as infrastructure bean
Closes gh-34015
2025-02-18 13:13:34 +01:00
Juergen Hoeller e230ea537c Consistently resolve unique default candidate bean
Closes gh-34432
2025-02-18 13:11:36 +01:00
Juergen Hoeller 94eb6006e8 Leniently accept same singleton instance if implicitly appeared
Closes gh-34427
2025-02-18 13:01:08 +01:00
Sébastien Deleuze 2576702cda Use public interface for HTTP Interface documentation
Closes gh-34443
2025-02-18 11:57:04 +01:00
Sébastien Deleuze 2099e046d3 Polishing
See gh-34439
2025-02-18 11:40:16 +01:00
Sébastien Deleuze 2ee7a8e77a Add missing converters to DefaultRestClientBuilder
With this commit, DefaultRestClientBuilder configures the same
default converters than RestTemplate.

Closes gh-34439
2025-02-18 11:19:07 +01:00
Johnny Lim 3c40e5e501 Add Javadoc since for HandlerMethod(HandlerMethod, Object, boolean)
See 56c4d2d

Closes gh-34431
Signed-off-by: Johnny Lim <izeye@naver.com>
2025-02-16 12:10:01 +01:00
Sam Brannen 9e45178202 Stop referring to "Java 8" in AnnotationUtils 2025-02-13 15:59:20 +01:00
Sam Brannen d82e70e345 Cross reference annotation search APIs in Javadoc
Closes gh-34421
2025-02-13 15:59:08 +01:00
Stéphane Nicoll 1109892134 Next development version (v6.2.4-SNAPSHOT) 2025-02-13 14:19:02 +01:00
Sam Brannen a8be11ebaf Upgrade to TestNG 7.11 2025-02-13 13:08:54 +01:00
Juergen Hoeller dfc10c1a8d Wait for lenient bean creation in non-locked threads as well
Closes gh-34349
2025-02-12 23:06:22 +01:00
Juergen Hoeller bbb593db48 Consistently ignore non-jar files in classpath
Closes gh-34417
2025-02-12 19:18:30 +01:00
Sam Brannen b2134ee71f Avoid Gradle build warnings about @⁠SuppressFBWarnings from FindBugs
In order to avoid Gradle build warnings about @⁠SuppressFBWarnings, this
commit introduces a testCompileOnly dependency on `findbugs` in the
spring-webmvc module so that the class file for @⁠SuppressFBWarnings is
available to the compileTestJava task.

Closes gh-34418
2025-02-12 17:49:23 +01:00
Sam Brannen 440a259b71 Clean up warnings in Gradle build 2025-02-12 16:46:41 +01:00
Sam Brannen 6174055910 Link to @⁠MockitoBean, @⁠MockitoSpyBean, & @⁠TestBean Javadoc from ref docs 2025-02-12 16:31:33 +01:00
Sam Brannen e31ce359a1 Support @⁠MockitoSpyBean at the type level on test classes
Prior to this commit, @⁠MockitoSpyBean could only be declared on fields
within test classes, which prevented developers from being able to
easily reuse spy configuration across a test suite.

With this commit, @⁠MockitoSpyBean is now supported at the type level
on test classes, their superclasses, and interfaces implemented by
those classes. @⁠MockitoSpyBean is also supported on enclosing classes
for @⁠Nested test classes, their superclasses, and interfaces
implemented by those classes, while honoring @⁠NestedTestConfiguration
semantics.

In addition, @⁠MockitoSpyBean:

- has a new `types` attribute that can be used to declare the type or
  types to spy when @⁠MockitoSpyBean is declared at the type level

- can be declared as a repeatable annotation at the type level

- can be declared as a meta-annotation on a custom composed annotation
  which can be reused across a test suite (see the @⁠SharedSpies
  example in the reference manual)

To support these new features, this commit also includes the following
changes.

- MockitoSpyBeanOverrideProcessor has been revised to support
  @⁠MockitoSpyBean at the type level.

- The "Bean Overriding in Tests" and "@⁠MockitoBean and
  @⁠MockitoSpyBean" sections of the reference manual have been fully
  revised.

See gh-34408
Closes gh-33925
2025-02-12 15:54:54 +01:00
Juergen Hoeller b336bbe539 Wait for lenient bean creation in locked thread when necessary
Closes gh-34349
2025-02-12 12:17:02 +01:00
Sébastien Deleuze 056757b493 Refine tests in MockMvcExtensionsTests
Closes gh-34412
2025-02-12 11:33:42 +01:00
Kevin Houtz 79c5fec1be Add form fields support to MockMvc Kotlin DSL
See gh-34412

Signed-off-by: Kevin Houtz <kevin@khoutz.com>
2025-02-12 11:33:29 +01:00
Brian Clozel 689782cbad Make ProblemDetail Serializable
This commit makes the `ProblemDetail` type implement `Serializable` in
order to be serialized and shared in distributed systems.

Closes gh-34409
2025-02-11 22:33:05 +01:00
Juergen Hoeller 2df90e32c0 Upgrade to Netty 4.1.118 and Checkstyle 10.21.2 2025-02-11 22:10:15 +01:00
Juergen Hoeller f53da04717 Align with SmartClassLoader handling for AOP proxy classes
Closes gh-34274
2025-02-11 22:10:02 +01:00
Sam Brannen 124b38450b Remove unused code and improve assertion
See gh-34363
2025-02-11 16:30:52 +01:00
Sam Brannen b07217ab67 Use ConversionService to convert POJO to array for SpEL varargs invocations
Prior to this commit, if an appropriate Converter was registered with
the ConversionService that converts from a POJO to an array and that
ConversionService was registered with the Spring Expression Language
(SpEL) TypeConverter, an attempt to invoke a varargs method in a SpEL
expression with such a POJO would fail because the ConversionService
was not used to convert the POJO to an array suitable for the varargs
method invocation.

This commit revises the implementations of convertArguments(...) and
convertAllMethodHandleArguments(...) in ReflectionHelper to support
such use cases.

Closes gh-34371
2025-02-11 16:11:13 +01:00
Sam Brannen aa7e84c89f Polishing 2025-02-11 16:11:13 +01:00
Stéphane Nicoll bd1c7b379f Merge pull request #34400 from canattofilipe
* pr/34400:
  Polish "Use correct method to retrieve DefaultListableBeanFactory"
  Use correct method to retrieve DefaultListableBeanFactory

Closes gh-34400
2025-02-11 16:01:04 +01:00
Stéphane Nicoll 6af19244a5 Polish "Use correct method to retrieve DefaultListableBeanFactory"
See gh-34400
2025-02-11 16:00:34 +01:00
canattofilipe 328dd71f6e Use correct method to retrieve DefaultListableBeanFactory
See gh-34400

Signed-off-by: canattofilipe <canattofilipe@gmail.com>
2025-02-11 15:57:01 +01:00
rstoyanchev d04883f839 HTTP Interface client handles query param with ":"
Closes gh-34364
2025-02-11 11:11:05 +00:00
rstoyanchev 9f55296049 Nested list/map/array with constructor binding
Closes gh-34305
2025-02-11 11:11:05 +00:00
rstoyanchev 4591a67641 Handle [] leniently in constructor binding
See gh-34305
2025-02-11 11:11:05 +00:00
Sam Brannen 9797bc0acd Expose order values of TestExecutionListener implementations as constants
Prior to this commit, the order values of TestExecutionListener
implementations were hard-coded in their getOrder() methods.

To benefit users and integrators, this commit exposes those order values
as an ORDER constant in each TestExecutionListener.

See gh-34225
Closes gh-34404
2025-02-11 11:53:33 +01:00
Sam Brannen 9d3374b28d Finish incomplete sentences 2025-02-11 11:53:33 +01:00
Sam Brannen 09086fc648 Revise TestExecutionListener order values documentation
See gh-34265
2025-02-11 11:53:33 +01:00
Mengqi Xu 128e90064e Document order values for TestExecutionListener implementations
Closes gh-34265

Signed-off-by: Mengqi Xu <2663479778@qq.com>
2025-02-11 11:53:33 +01:00
Stéphane Nicoll 16e3973118 Upgrade to Reactor 2024.0.3
Closes gh-34403
2025-02-11 11:36:47 +01:00
Stéphane Nicoll 5c37d07ad3 Upgrade to RSocket 1.1.5
Closes gh-34402
2025-02-11 10:39:21 +01:00
Stéphane Nicoll 12891d1975 Upgrade to Micrometer 1.14.4
Closes gh-34401
2025-02-11 10:34:33 +01:00
Juergen Hoeller 1ca941ba9a Consistently resolve renamed type variables
Closes gh-34386
2025-02-10 15:51:39 +01:00
Sam Brannen 17a94fb110 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
2025-02-10 13:29:40 +01:00
Sam Brannen 2fcae65853 Polishing 2025-02-10 13:29:33 +01:00
rstoyanchev 7a0fe7d14f WebAsyncManager wraps disconnected client errors
If the Servlet container delegates a disconnected client error via
AsyncListener#onError, wrap it as AsyncRequestNotUsableException
for more targeted and consistent handling of such errors.

Closes gh-34363
2025-02-10 11:14:21 +00:00
rstoyanchev ccdaed594e Polishing contribution
Closes gh-34333
2025-02-10 11:14:21 +00:00
Branden Clark c41b0140cd Check hasNext on sessionIds in UserDestinationResult
See gh-34333

Signed-off-by: Branden Clark <brandenrayclark@gmail.com>
2025-02-10 11:14:21 +00:00
rstoyanchev ceffda7874 Polishing contribution
Closes gh-34362
2025-02-10 11:14:21 +00:00
Jared Wiltshire 49f9b40fba Support RFC 8441 upgrades over HTTP/2 CONNECT
See gh-34362

Signed-off-by: Jared Wiltshire <jazdw@users.noreply.github.com>
2025-02-10 11:14:21 +00:00
Sam Brannen d59991fcc9 Revise contribution
See gh-34006
2025-02-10 11:42:24 +01:00
Yanming Zhou cf46f391d7 Improve diagnostics when a Bean Override cannot be selected by type
See gh-34004
Closes gh-34006

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-02-10 11:06:10 +01:00
Andras Dobrosi 2b4c7d09b0 Match ContentDisposition attributes case-insensitively
This commit ensures that `ContentDisposition` parses attributes like
"filename" and "filename*" in a case insensitive fashion, per RFC 6266.

Closes gh-34383

Signed-off-by: Andras Dobrosi <dobrosi@gmail.com>
[brian.clozel@broadcom.com: apply code conventions]
Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
2025-02-10 09:21:02 +01:00
Johnny Lim 61138698c6 Fix copyright end year in ServerResponseResultHandler
See https://github.com/spring-projects/spring-framework/commit/1cea1fe962cc95544fce7be8fd2f56419e89aaaf#diff-fa20069f0305b5aee07bf90a7f8d0502a6ee139892639b7dfe470b54c3a8574aL2-R2

See gh-34066
Closes gh-34391

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-02-09 11:38:24 +01:00
Daeho Kwon 3548e872b9 Fix deprecation warnings in RuntimeHintsAgentPlugin
Closes gh-34390

Signed-off-by: Daeho Kwon <trewq231@naver.com>
2025-02-08 16:19:26 +01:00
Sam Brannen 305686dbf7 Ensure Bean Overrides are discovered once in @⁠Nested hierarchies
Changes made to the Bean Override search algorithms in commit
9181cce65f resulted in a regression that caused tests to start failing
due to duplicate BeanOverrideHandlers under the following circumstances.

- An enclosing class (typically a top-level test class) declares a
  @⁠BeanOverride such as @⁠MockitoBean.

- An inner class is declared in that enclosing class.

- A @⁠Nested test class which extends that inner class is declared in
  the same enclosing class.

The reason for the duplicate detection is that the current search
algorithm visits the common enclosing class twice.

To address that, this commit revises the search algorithm in
BeanOverrideHandler so that enclosing classes are only visited once.

See gh-33925
Closes gh-34324
2025-02-08 13:30:22 +01:00
Juergen Hoeller ace2f0a3e5 Expose resolveAutowireCandidates method with basic candidate filtering
DependencyObjectProvider is aligned to check basic autowire-candidate status even in case of custom filtering.

Closes gh-34203
2025-02-07 19:00:25 +01:00
Juergen Hoeller 1a573d6e3c Lazily retrieve TypeDescriptor annotations on demand
Closes gh-33948
2025-02-07 18:55:24 +01:00
Sam Brannen bb7a8006c5 Fix @⁠Nullable declaration in DataBinder on 6.2.x 2025-02-07 18:33:03 +01:00
Sam Brannen ba56c1a8f4 Fix copy-n-paste error 2025-02-07 18:26:26 +01:00
Sam Brannen 9107f7b592 Honor @⁠Primary before fallback qualifier for Bean Overrides
Prior to this commit, test bean overrides (for example, @⁠MockitoBean,
@⁠TestBean, etc.) eagerly honored the name of the annotated field as a
fallback qualifier, effectively ignoring @⁠Primary and @⁠Fallback
semantics for certain use cases.

This led to situations where a bean override for a test would select a
different bean than the core container would for the same autowiring
metadata.

To address that, this commit revises the implementation of
BeanOverrideBeanFactoryPostProcessor so that @⁠Primary and @⁠Fallback
semantics are consistently honored before attempting to use the
annotated field's name as a fallback qualifier.

Closes gh-34374
2025-02-07 18:06:52 +01:00
rstoyanchev 1d7cb4fc13 Defer initialization of HandlerMethod validation flags
Re-create the HandlerMethod only after the original is used as a key
in the CORS lookup map.

Closes gh-34379
2025-02-07 13:20:16 +00:00
rstoyanchev 84992536c5 Defer initialization of HandlerMethod validation flags
Re-create the HandlerMethod only after the original is used as a key
in the CORS lookup map.

Closes gh-34375
2025-02-07 13:10:55 +00:00
rstoyanchev ff49b0b683 Align AnnotatedMethod#equals and #hashcode
See gh-34375
2025-02-07 13:10:55 +00:00
rstoyanchev 56c4d2d4ca Improve HandlerMethod#resolvedFromHandlerMethod initialization
Ensure the original instance is always the one returned no matter how
many times the HandlerMethod is re-created.

Make the constructor protected to allow subclasses to re-create the
HandlerMethod as the concrete subclass.

See gh-34375
2025-02-07 13:10:55 +00:00
Brian Clozel 174d0e4576 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-34360
2025-02-06 18:27:07 +01:00
Sébastien Deleuze dba4881318 Refine the CGLIB error message with native
See gh-34370
2025-02-06 09:18:51 +01:00
Sébastien Deleuze 16ec08aec8 Provide a more actionable CGLIB error message with native
Closes gh-34370
2025-02-05 17:47:25 +01:00
rstoyanchev 462c2bd538 Enhance constructor binding for List/Map/Array
Support List/Map/Array of simple values, or values supported
by type conversion.

Closes gh-34305
2025-02-05 14:26:12 +00:00
rstoyanchev 7f29f0e663 Revert commit 3505c4bcad
The fix did not address the issue. It only made the constructor not
fail with tests succeeding due to setter binding instead.

See gh-34043
2025-02-05 14:26:12 +00:00
rstoyanchev 3898482d3f Revert commit 0f38c28e91
The fix is not how the issue needs to be addressed.

See gh-34121
2025-02-05 14:26:12 +00:00
rstoyanchev bb7ce21076 Expose Locale to method validation in WebFlux
Closes gh-33810
2025-02-05 14:26:12 +00:00
Stéphane Nicoll fd4dee7ce3 Merge pull request #34183 from remeio
* pr/34183:
  Polish "Add support for multidimensional arrays"
  Add support for multidimensional arrays

Closes gh-34183
2025-02-05 14:42:35 +01:00
Stéphane Nicoll ec037b1ec0 Polish "Add support for multidimensional arrays"
See gh-34183
2025-02-05 14:35:33 +01:00
xumengqi 68c1e2ac92 Add support for multidimensional arrays
See gh-34183
2025-02-05 14:23:09 +01:00
Sam Brannen 819a7c86c1 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
2025-02-05 13:39:29 +01:00
Stéphane Nicoll 9c1346d240 Merge pull request #34316 from JoshuaChen
* pr/34316:
  Polish "Handle arbitrary JoinPoint argument index"
  Handle arbitrary JoinPoint argument index

Closes gh-34316
2025-02-05 11:50:35 +01:00
Stéphane Nicoll fb6e86551a Polish "Handle arbitrary JoinPoint argument index"
See gh-34316
2025-02-05 11:49:56 +01:00
Joshua Chen 13ba770428 Handle arbitrary JoinPoint argument index
See gh-34316

Signed-off-by: Joshua Chen <27291761@qq.com>
2025-02-05 11:44:51 +01:00
Stéphane Nicoll a4d99d6201 Merge pull request #33957 from anaconda875
* pr/33957:
  Polish "Use proper return type in AsyncExecutionInterceptor"
  Use proper return type in AsyncExecutionInterceptor

Closes gh-33957
2025-02-04 16:01:38 +01:00
Stéphane Nicoll 3923150dad Polish "Use proper return type in AsyncExecutionInterceptor"
See gh-33957
2025-02-04 15:59:35 +01:00
Bao.Ngo 8eb24458f8 Use proper return type in AsyncExecutionInterceptor
See gh-33957
2025-02-04 15:58:53 +01:00
Juergen Hoeller cda7e98643 Lazily override default editors when actually needed
Closes gh-34361
2025-02-04 13:26:16 +01:00
Stéphane Nicoll e92809d470 Update CI actions 2025-02-04 09:29:51 +01:00
rstoyanchev f477c1653d Allow WebSocket over HTTP CONNECT
Closes gh-34044
2025-02-03 15:28:27 +00:00
rstoyanchev 1cea1fe962 Polishing contribution
Closes gh-34066
2025-02-03 15:15:21 +00:00
Tarek Mues 8b07f93620 Add getters ServerResponseResultHandler
See gh-34066
2025-02-03 15:11:47 +00:00
Pierre Rossato a9ecf90524 Minor update in WebSocket STOMP documentation
Closes gh-34353

Signed-off-by: Pierre Rossato <pierre.rossato@gmail.com>
2025-02-03 15:10:32 +00:00
Daeho Kwon 7536777a18 Replace hardcoded "Sec-WebSocket-Version" with constant
Closes gh-34319

Signed-off-by: Daeho Kwon <trewq231@naver.com>
2025-02-03 15:06:28 +00:00
Juergen Hoeller 1b18928bf0 Explicitly set custom ClassLoader on CGLIB Enhancer
Closes gh-34274
2025-02-03 15:23:51 +01:00
Juergen Hoeller 8c2b44bd6c Support filtered/unfiltered stream access on ObjectProvider
Closes gh-34318
Closes gh-34203
2025-02-03 15:04:27 +01:00
Juergen Hoeller 2df8ea94e0 Clean root entry path to match jar entries format in cache
Closes gh-34348
2025-01-31 22:26:14 +01:00
Juergen Hoeller 323e52b5a9 Continue with pre-instantiation when current bean is in creation already
Closes gh-34349
2025-01-31 15:16:52 +01:00
Juergen Hoeller ed994dcd97 Resolve bounds for type variable before emptiness check
Closes gh-34328
2025-01-30 15:35:14 +01:00
Juergen Hoeller 4c3b435d23 Enforce exact match for bounds of nested type variable
Closes gh-34300
2025-01-30 15:34:55 +01:00
Brian Clozel cfe2db0581 Improve reference docs on RestClient.retrieve()
As of Spring Framework 6.2, the `RestClient.retrieve()` method is a
no-op and developers must invoke a terminal operation on the returned
`ResponseSpec` to have any side effect.

This has been documented in the Javadoc and the wiki release notes, but
this commit highlights this as well in the reference documentation.

Closes gh-34334
2025-01-29 18:26:12 +01:00
Sam Brannen 53afe27109 Clean up warnings in Gradle build
See gh-34332
2025-01-29 17:52:27 +01:00
Sam Brannen b9e43d05bd 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
2025-01-29 17:34:47 +01:00
Brian Clozel d80de043ce Fix filtered HTTP headers in data binding
Prior to this commit, several common HTTP headers were ignored from the
data binding process when collecting property values, in gh-34039 and
gh-34182.

This commit completes the initial enhancement by ensuring that the
default header predicate is also considering cases where constructor
binding is applied and the Java type has a lowercase variant of the HTTP
header name to filter.

Fixes gh-34292
2025-01-29 16:06:19 +01:00
Brian Clozel 7c5b6f1e1c 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-01-29 15:55:58 +01:00
rstoyanchev a6b6d19545 Update localAddress Javadoc in WebSocketSession
Closes gh-34304
2025-01-28 15:36:26 +00:00
rstoyanchev 9b58df8857 Update HandlerMethod#createWithResolvedBean
Avoid re-creating the instance unless it is a bean name that
needs to be resolved through the BeanFactory.

Closes gh-34277
2025-01-28 15:36:26 +00:00
rstoyanchev 1cc767e90b Polishing in ExtendedServletRequestDataBinder 2025-01-28 15:36:19 +00:00
Sam Brannen 34d6dd9b62 Polishing 2025-01-27 15:19:00 +01:00
Sébastien Deleuze 5499878de0 Support properties in kotlinx.serialization converters
This commit adds support for Kotlin properties in Spring WebMVC
controllers, supported for reasons explained in gh-31856, with
kotlinx.serialization converters.

Closes gh-34284
2025-01-27 15:06:57 +01:00
Sébastien Deleuze a970fc16aa Support properties in kotlinx.serialization codecs
This commit adds support for Kotlin properties in Spring WebFlux
controllers, supported for reasons explained in gh-31856, with
kotlinx.serialization codecs.

See gh-34284
2025-01-27 15:06:50 +01:00
Sam Brannen 683733a682 Link to AspectJ "runtime-api" Javadoc site
The AspectJ team has confirmed that the following Javadoc site hosts
the latest version (currently 1.9.22.1) of the runtime API for AspectJ,
generated with a modern version of the javadoc tool which generates the
element-list file required by the Spring Framework build.

https://eclipse.dev/aspectj/doc/latest/runtime-api/

See https://github.com/eclipse-aspectj/aspectj/issues/321
See gh-34293
2025-01-24 15:33:24 +01:00
Sam Brannen 11fe2987d0 Use appropriate link for Testing chapter
This commit effectively removes a reference to a nonexistent "#testing"
fragment.
2025-01-23 10:58:48 +01:00
Juergen Hoeller 90423a98b0 Enforce match for resolved part of unresolvable target type
Closes gh-34298
2025-01-21 19:15:20 +01:00
Brian Clozel e9dc6be51c Fix Javadoc for field reflection hints
Closes gh-34297
2025-01-21 17:25:15 +01:00
Sam Brannen 6863b904b5 Polish contribution
See gh-34295
2025-01-21 17:22:49 +01:00
Mengqi Xu 016a4c4260 Improve Javadoc for SpringProperties.getFlag()
getFlag() returns true when the property is equal, ignoring case, to the
string "true", not just "true"; "TrUe" also means true.

Closes gh-34295

Signed-off-by: Mengqi Xu <2663479778@qq.com>
2025-01-21 17:19:17 +01:00
rstoyanchev 233f755416 Update Javadoc that links to RestTemplate
Ensure RestClient is mentioned where appropriate.

Closes gh-34270
2025-01-21 12:21:22 +00:00
rstoyanchev b026680e1c 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:21:22 +00:00
rstoyanchev e47cc19964 Restore exception phrase in DisconnectedClientHelper
Effectively revert 203fa7, and add implementation comment and tests.

See gh-34264
2025-01-21 12:21:22 +00:00
Sam Brannen 15b1310470 Link to AspectJ runtime 1.9.22.1 Javadoc site
Closes gh-34293
2025-01-21 11:31:14 +01:00
Juergen Hoeller ee60eb7207 Fall back to HTTP GET in case of 405 from HTTP HEAD
Closes gh-34217
2025-01-20 18:17:49 +01:00
Sam Brannen cecebd0ef1 Polish HttpHeadersAssertTests 2025-01-20 16:48:10 +01:00
Johnny Lim aac4dbf420 Polish HttpHeadersAssert
See gh-34286

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-01-20 16:46:16 +01:00
Stéphane Nicoll 82bc4ff71d Handle TextPart with escaped separator
This commit harmonizes how a candidate value is parsed to extract its
key and default, if any. Rather than returning {@code null} if no
default is available, `splitKeyAndValue` now consistently returns a
non-null array.

This prevents an escaped separator character to be mistakenly identified
as a placeholder in certain cases.

Closes gh-34289
2025-01-20 15:04:06 +01:00
Brian Clozel 356d5c2cf2 Polishing 2025-01-20 08:02:57 +01:00
Brian Clozel 46c13adfa8 Document custom HttpServiceArgumentResolver usage
This commit adds a reference documentation section explaining how
applications can use custom `HttpServiceArgumentResolver`
implementations to support complex application types as method
parameters in HTTP interfaces.

Closes gh-34227
2025-01-20 07:57:56 +01:00
Sam Brannen 886ca7f2db Polish contribution and SimpleCommandLineArgs-related code
Closes gh-34282
2025-01-18 17:03:04 +01:00
puppy4c c463b937b8 Update Javadoc for SimpleCommandLinePropertySource
For ComandLineArgs, supplying the same option multiple times with
different values is valid, and the values will be stored in a List.

This commit also updates the Javadoc for SimpleCommandLineArgsParser.

See gh-34282

Signed-off-by: puppy4c <puppy4c@foxmail.com>
2025-01-18 16:26:14 +01:00
Sam Brannen 4783c321d9 Polish formatting for consistency across implementations 2025-01-18 15:04:25 +01:00
Sam Brannen 0dfcb44821 Remove unnecessary @⁠Nullable declaration 2025-01-18 15:03:47 +01:00
Sam Brannen 8c3d5621db Upgrade to AssertJ 3.27.3 2025-01-18 14:02:07 +01:00
Sam Brannen 12a6a84829 Polishing 2025-01-18 14:02:07 +01:00
Sam Brannen 6662ecc8cc Upgrade to Gradle Develocity plugin version 3.19
In light of the fact that the Develocity service on ge.spring.io has
been upgraded to 2024.3.1.
2025-01-17 11:47:47 +01:00
Stéphane Nicoll 4b0446fbef Next development version (v6.2.3-SNAPSHOT) 2025-01-16 10:06:12 +01:00
rstoyanchev 384d2749c6 Polishing in HttpServiceMethod 2025-01-15 19:03:00 +00:00
rstoyanchev 5150a9a6ad Polishing contribution
Closes gh-34230
2025-01-15 18:31:50 +00:00
Yanming Zhou a8c5885aff Fix wrong document about RequestHeaderArgumentResolver
Also test is added to verify that.

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>

See gh-34230
2025-01-15 18:31:49 +00:00
Juergen Hoeller 7d844274dc Upgrade to Jetty 12.0.16, Netty 4.1.117, Protobuf 4.29.3, Checkstyle 10.21.1 2025-01-15 18:27:49 +01:00
Juergen Hoeller 70ceb3abdf Refer to local Consumer type instead of Kotlin test type 2025-01-15 17:41:22 +01:00
Sam Brannen 9181cce65f Support @⁠MockitoBean at the type level on test classes
Prior to this commit, @⁠MockitoBean could only be declared on fields
within test classes, which prevented developers from being able to
easily reuse mock configuration across a test suite.

With this commit, @⁠MockitoBean is now supported at the type level on
test classes, their superclasses, and interfaces implemented by those
classes. @⁠MockitoBean is also supported on enclosing classes for
@⁠Nested test classes, their superclasses, and interfaces implemented
by those classes, while honoring @⁠NestedTestConfiguration semantics.

In addition, @⁠MockitoBean:

- has a new `types` attribute that can be used to declare the type or
  types to mock when @⁠MockitoBean is declared at the type level

- can be declared as a repeatable annotation at the type level

- can be declared as a meta-annotation on a custom composed annotation
  which can be reused across a test suite (see the @⁠SharedMocks
  example in the reference manual)

To support these new features, this commit also includes the following
changes.

- The `field` property in BeanOverrideHandler is now @⁠Nullable.

- BeanOverrideProcessor has a new `default` createHandlers() method
  which is invoked when a @⁠BeanOverride annotation is found at the
  type level.

- MockitoBeanOverrideProcessor implements the new createHandlers()
  method.

- The internal findHandlers() method in BeanOverrideHandler has been
  completely overhauled.

- The @⁠MockitoBean and @⁠MockitoSpyBean section of the reference
  manual has been completely overhauled.

Closes gh-33925
2025-01-15 17:13:35 +01:00
Sam Brannen 8b6523a35b Cross reference @⁠NestedTestConfiguration for Bean Overrides 2025-01-15 17:13:35 +01:00
Stéphane Nicoll e7f349328d Merge pull request #34086 from rPraml
* pr/34086:
  Polish "Resolve base type in parameterized type if necessary"
  Resolve base type in parameterized type if necessary

Closes gh-34086
2025-01-15 17:07:58 +01:00
Stéphane Nicoll fe5f5d53b2 Polish "Resolve base type in parameterized type if necessary"
See gh-34086
2025-01-15 17:06:05 +01:00
Roland Praml 033d277bcf Resolve base type in parameterized type if necessary
See gh-34086
2025-01-15 17:03:20 +01:00
Juergen Hoeller 227385083d Restrict lenient nested matching to immediate type variable
Includes fix for matching multiple wildcard bounds properly.

Closes gh-34119
Closes gh-34234
2025-01-15 15:02:46 +01:00
Stéphane Nicoll d280358e98 Fix typo in HttpHeadersAssert#doesNotContainHeaders
This commit deprecates the existing doesNotContainsHeaders in favor of a
newly introduced method that does not have the typo.

Closes gh-34263
2025-01-15 11:19:00 +01:00
Stéphane Nicoll e08a7ece61 Merge pull request #34223 from remeio
* pr/34223:
  Polish contribution
  Polish HttpEntityMethodProcessor

Closes gh-34223
2025-01-14 17:36:45 +01:00
Stéphane Nicoll 24fa9ea394 Polish contribution
See gh-34223
2025-01-14 17:30:39 +01:00
Mengqi Xu 4acd9ee69b Polish HttpEntityMethodProcessor
Signed-off-by: Mengqi Xu <2663479778@qq.com>

See gh-34223
2025-01-14 17:28:00 +01:00
Stéphane Nicoll cd1aefa5e0 Upgrade to Reactor 2024.0.2
Closes gh-34252
2025-01-14 16:36:43 +01:00
Sam Brannen 3c3b8c7aac Track only qualifier annotations in BeanOverrideHandler
Closes gh-34260
2025-01-14 11:35:00 +01:00
Sam Brannen 5a68d1f029 Remove @⁠FunctionalInterface declaration from BeanOverrideProcessor
Closes gh-34259
2025-01-14 11:29:45 +01:00
Sam Brannen ff9b7678a7 Polish Bean Override internals and tests 2025-01-14 11:25:37 +01:00
Brian Clozel 799566573a 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-34128
2025-01-13 19:03:43 +01:00
Juergen Hoeller 467d5f3ca3 Try late locking for waiting on specific bean to be finished
Closes gh-34186
2025-01-13 17:48:13 +01:00
Stéphane Nicoll 172c25fb0e Upgrade to Micrometer 1.14.3
Closes gh-34251
2025-01-13 13:54:47 +01:00
Juergen Hoeller 8771b9ea21 Defensively acquire singleton lock for FactoryBean type check
Closes gh-34247
2025-01-13 13:04:42 +01:00
Juergen Hoeller ff72652890 Defensively check expected type for qualified bean
Closes gh-34187
2025-01-13 13:03:25 +01:00
Stéphane Nicoll a1503a59ee Start building against Reactor 2024.0.2 snapshots
See gh-34252
2025-01-13 09:09:19 +01:00
Stéphane Nicoll 5f29df3ac3 Start building against Micrometer 1.14.3 snapshots
See gh-34251
2025-01-13 09:08:06 +01:00
Juergen Hoeller 25287205ba Use ReentrantLock for compilation without synchronization
Closes gh-34133
2025-01-12 18:11:47 +01:00
Juergen Hoeller 2bb4df79c3 Ignore SQLServerException with "not supported" message
Closes gh-34233
2025-01-12 18:08:54 +01:00
Juergen Hoeller 36fd82f32f Defensively resolve JarFile from JarURLConnection
Closes gh-34216
2025-01-12 18:07:09 +01:00
Juergen Hoeller 0f26f42da7 Defensively check for jar separator in jar entry names
Closes gh-34126
2025-01-12 18:05:49 +01:00
Juergen Hoeller c48fec885c Avoid caching invalid root directories
Closes gh-34111
2025-01-12 18:00:10 +01:00
Stéphane Nicoll 7c4351ab0a Polish
See gh-34231
2025-01-11 12:35:42 +01:00
rstoyanchev 07455b10f3 Use response decorator to check if error handled
Closes gh-34231
2025-01-10 10:51:39 +01:00
rstoyanchev a72855b2b3 DefaultResponseErrorHandler updates
Deprecate handleError(response), and ensure it continues to be invoked
if overridden.

See gh-34231
2025-01-10 10:51:11 +01:00
rstoyanchev cdddf09c20 Polishing in DefaultResponseErrorHandler
See gh-34231
2025-01-10 10:50:41 +01:00
xumengqi 53b3b934fc BindException declared but not handled by ExceptionHandler
Closes gh-34214

Signed-off-by: Mengqi Xu <2663479778@qq.com>
2025-01-08 16:29:12 +01:00
Brian Clozel 9173e13f74 Document that http.client.requests measure the entire HTTP exchange
Closes gh-34201
2025-01-08 14:39:50 +01:00
Brian Clozel 84be0d85b4 Refine location checks for PathResource
This commit ensures that checks for PathResource locations are skipped
because this resource implementation will always resolve under the
current location.

Closes gh-34167
2025-01-08 10:55:22 +01:00
Brian Clozel c971276f34 Refine default filtered headers for web data binding
Prior to this commit, HTTP request data binding had been improved to
filter out by default the "Priority" header in #34039.

This commit extends the set of filtered header names with:
"Accept", "Authorization", "Connection", "Cookie", "From", "Host",
"Origin", "Priority", "Range", "Referer", "Upgrade".

If an application wishes to let those header be bound, it will need to
configure the binder and replace the default header predicate by calling
`setHeaderPredicate`.

Closes gh-34182
2025-01-07 22:11:41 +01:00
Sam Brannen cd2fbb1ec5 Properly resolve @⁠TestBean factory method within class hierarchy
Prior to this commit, the search algorithm used to locate a @⁠TestBean
factory method within a test class hierarchy incorrectly found factory
methods declared in subclasses or nested test classes "below" the class
in which the @⁠TestBean field was declared. This resulted in "duplicate
bean override" failures for @⁠TestBean overrides which are clearly not
duplicates but rather "overrides of an override".

This commit ensures that @⁠TestBean factory method resolution is
consistent in type hierarchies as well as in enclosing class
hierarchies (for @⁠Nested test classes) by beginning the search for a
factory method in the class which declares the @⁠TestBean field.

Closes gh-34204
2025-01-07 17:20:52 +02:00
Sam Brannen e8745522fc Polishing 2025-01-07 11:52:33 +02:00
Brian Clozel 2bfb283c91 Update CONTRIBUTING guidelines for DCO
The CLA process has been replaced with a DCO, this commit updates the
CONTRIBUTING guidelines accordingly.
See https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring
2025-01-07 09:55:36 +01:00
Mattias-Sehlstedt 50b1fb0b15 Change the description for the uri client request observation
This commit describes what parts that are removed from the URI template
keyvalue.

Closes: gh-34116
Signed-off-by: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com>
2025-01-07 09:02:25 +01:00
rstoyanchev a985b73939 Improve logging in ReactiveTypeHandler
See gh-34188
2025-01-06 12:13:29 +00:00
rstoyanchev 6ec7dcf2c1 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:06:43 +00:00
rstoyanchev d94e04d97a 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:06:43 +00:00
rstoyanchev 5a44897c55 Polishing in WebAsyncManager
See gh-34192
2025-01-06 12:06:43 +00:00
Sam Brannen 181db1db75 Update copyright headers to 2025 2025-01-05 17:00:47 +02:00
Sam Brannen 138bdc597d Upgrade to AssertJ 3.27.2 2025-01-05 12:28:23 +02:00
Sam Brannen ef4f1f0a71 Ensure @⁠BeanOverride in subclass takes precedence over superclass
Prior to this commit, a @⁠BeanOverride (such as @⁠TestBean) for a
specific target bean which was declared in a superclass always took
precedence over a bean override for the same target bean in a subclass,
thereby rendering the bean override configuration in the subclass
useless. In other words, there was no way for a test class to override
a bean override declared in a superclass.

To address that, this commit switches from direct use of
ReflectionUtils.doWithFields() to a custom search algorithm that
traverses the class hierarchy using tail recursion for processing
@⁠BeanOverride fields (delegating now to
ReflectionUtils.doWithLocalFields() in order to continue to benefit
from the caching of declared fields in ReflectionUtils).

Closes gh-34194
2025-01-04 18:27:05 +02:00
Sam Brannen 51b89743e1 Polishing 2025-01-04 18:12:13 +02:00
Sam Brannen 019f76468b Revise TestBeanForInheritanceIntegrationTests
Amongst other refinements, this commit ensures that the "anotherBean"
use case is actually tested.
2025-01-04 14:16:59 +02:00
Stéphane Nicoll f802c0cf24 Restore use of MethodSource 2025-01-03 16:53:54 +01:00
Sam Brannen 0da4ae96b4 Rename internal MockitoBeans class to MockBeans
This is a prerequisite for gh-33925 which will introduce a public
@MockitoBeans container annotation in the same package.
2025-01-03 17:26:45 +02:00
Sam Brannen 2ba0022704 Polishing 2025-01-03 17:26:45 +02:00
Stéphane Nicoll c59ca087b4 Backport tests for exact match resolution
See gh-34124

(cherry picked from commit 898d3ec86a)
2025-01-03 17:10:17 +02:00
Sam Brannen 9de2b947cb Upgrade to AssertJ 3.27.1 2025-01-03 16:42:07 +02:00
Sam Brannen e73e7564d0 Upgrade to Mockito 5.15.2 2025-01-03 16:36:39 +02:00
Brian Clozel d927d64c40 Improve query params in uri KeyValue with HTTP interface client
Prior to this commit, the HTTP interface client would create URI
templates and name query params like so:
"?{queryParam0}={queryParam0[0]}".
While technically correct, the URI template is further used in
observations as a KeyValue. This means that several service methods
could result in having the exact same URI template even if they produce
a different set of query params.

This commit improves the naming of query params in the generated URI
templates for better observability integration.

Closes gh-34176
2025-01-02 20:00:38 +01:00
Stéphane Nicoll 8544435833 Merge pull request #34180 from v-perfilev
* pr/34180:
  Polish "Fix broken link to Code of Conduct in CONTRIBUTING.md"
  Fix broken link to Code of Conduct in CONTRIBUTING.md

Closes gh-34180
2025-01-01 10:08:39 +01:00
Stéphane Nicoll 80ea1f62b6 Polish "Fix broken link to Code of Conduct in CONTRIBUTING.md"
See gh-34180
2025-01-01 10:08:18 +01:00
Vladimir Perfilev bc287918f2 Fix broken link to Code of Conduct in CONTRIBUTING.md
See gh-34180
2025-01-01 10:07:29 +01:00
Stéphane Nicoll c9436992c9 Update actions in CI build 2024-12-31 09:49:43 +01:00
rstoyanchev 4350fc21b3 List constructor arg initialized correctly
DataBinder now uses the calculated List size rather than
the number of indexes to initialize the list.

Closes gh-34145
2024-12-30 14:47:26 +00:00
rstoyanchev 59ed4686c5 Create ParameterErrors for type level constraint
Closes gh-34105
2024-12-30 14:47:26 +00:00
Sébastien Deleuze d5bebd5ced Upgrade to Java 17.0.13 2024-12-30 12:15:24 +01:00
Johnny Lim 6d86b23fbe Apply Checkstyle MethodParamPadCheck module
This commit also fixes its violations.

Closes gh-34173
2024-12-30 09:02:06 +01:00
Simon Baslé 0f38c28e91 Fix ServletRequestDataBinder ctor binding with []-indexed query params
This change ensures that a request containing query parameters in the
array format `someArray[]=value` can be bound into a simple array in
constructors, even for cases where the array values don't have nested
properties.

The value resolver is directly called in the constructor case, before
any mutable properties are considered or even cleared (see
`WebDataBinder#adaptEmptyArrayIndices` method). As a result, we need to
accommodate the possibility that the request stores array elements under
the `name[]` key rather than `name`. This change attempts a secondary
lookup with the `[]` suffix if the type is a list or array, and the key
doesn't include an index.

Closes gh-34121
2024-12-27 10:59:15 +01:00
Simon Baslé 3505c4bcad Ensure DataBinder can bind constructor with a Map with simple values
This change ensures that DataBinder can bind constructor with a Map
parameter that has no nested properties, but just simple values like
a String: `someMap[0]=exampleString`.

Integration tests have been added to cover similar cases that use the
ServletRequestDataBinder.

Closes gh-34043
2024-12-27 10:57:30 +01:00
Stéphane Nicoll 58670db46e Merge pull request #34165 from brendenehlers
* pr/34165:
  Polish contribution
  Fix grammatical structure in "Basic Concepts"

Closes gh-34165
2024-12-27 09:31:47 +01:00
Stéphane Nicoll 0cdb8a1657 Polish contribution
Using commas render better than em dashes.

See gh-34165
2024-12-27 09:31:34 +01:00
Brenden Ehlers fe8c70dddb Fix grammatical structure in "Basic Concepts"
See gh-34165
2024-12-27 09:30:43 +01:00
Sébastien Deleuze 1ed74efa6b Polishing
Closes gh-34148
2024-12-26 13:16:09 +01:00
Brenden Ehlers eabd574038 Fix XML code snippet
See gh-34148
2024-12-26 13:15:51 +01:00
Stéphane Nicoll ea386c2398 Merge pull request #34149 from brendenehlers
* pr/34149:
  Fix typo in link

Closes gh-34149
2024-12-25 09:29:52 +01:00
Brenden Ehlers 6c6e8ad831 Fix typo in link
See gh-34149
2024-12-25 09:29:29 +01:00
Sébastien Deleuze 69b74d7645 Fix NullPointerException in HttpComponentsClientHttpResponse
Closes gh-34132
2024-12-23 15:34:04 +01:00
Stéphane Nicoll 5ce5647d09 Restore support of exact match property
This commit fixes a regression in property placeholder resolution where
the original key was no longer considered for an exact match before
processing the placeholder itself.

By default, property resolution uses ':' as the separator between the
key and the fallback value.

Consider a request to resolve ${prefix://service}. Previously,
placeholder resolution would first attempt to resolve the raw text, that
is 'prefix://service', before attempting to resolve the 'prefix' key and
then use '//service' if the key did not resolve.

This commit restores that behaviour purely for backward compatible
reason.

Closes gh-34124
2024-12-23 12:19:45 +01:00
Tran Ngoc Nhan ebae02a92b Fix broken links in the web reference documentation
Closes gh-34115
2024-12-23 11:14:40 +01:00
Sam Brannen 99cb6ba0a7 Upgrade to Gradle 8.12 2024-12-21 16:02:15 +01:00
Sam Brannen 2baf70768d Upgrade to AssertJ 3.27 2024-12-21 15:49:31 +01:00
Sam Brannen 724700d708 Polishing 2024-12-21 15:31:01 +01:00
Andreas Kruck 83ba7e69ec Fix minor typo in reference documentation for Kotlin annotations
Closes gh-34134
2024-12-21 15:28:33 +01:00
Sam Brannen c1236a3340 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 17:59:14 +01:00
luozongle01 a942362221 Fix a typo
Closes gh-34101
2024-12-17 11:40:47 +01:00
Brian Clozel 1a34b0dd87 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-34064
2024-12-17 10:14:18 +01:00
Sam Brannen b0fcde9aa3 Upgrade to JUnit 5.11.4
Closes gh-34095
2024-12-16 14:42:30 +01:00
Sam Brannen a89db89fc0 Polishing 2024-12-14 17:10:41 +01:00
Sam Brannen 3d0fffa8e4 Polish contribution
See gh-33945
2024-12-14 16:40:49 +01:00
Mico Piira 71f872e8bb Fix implicit variable resolution in JSP EvalTag
Prior to this commit, the order of parameters passed to
ELResolver#getValue was incorrect.

The `name` should correspond to the `property` parameter of the
`getValue` method instead the `base` parameter.

See gh-32383
See gh-33942
Closes gh-33945
2024-12-14 16:40:31 +01:00
Juergen Hoeller 54a90b20ed Avoid logger serialization behind shared EntityManager proxy
See gh-34084
2024-12-12 19:48:21 +01:00
Stéphane Nicoll d7a3971403 Merge pull request #34077 from scordio
* pr/34077:
  Polish "Prevent execution of Antora jobs on forks"
  Prevent execution of Antora jobs on forks

Closes gh-34077
2024-12-12 17:35:51 +01:00
Stéphane Nicoll cfa3463cec Polish "Prevent execution of Antora jobs on forks"
See gh-34077
2024-12-12 17:35:38 +01:00
Stefano Cordio d2264b221a Prevent execution of Antora jobs on forks
See gh-34077
2024-12-12 17:34:15 +01:00
Stéphane Nicoll 2c32601553 Add note about using @EventListener on lazy beans
Closes gh-34057
2024-12-12 17:32:18 +01:00
Sam Brannen ea8b18fbc7 Polish Javadoc for BeanOverrideHandler 2024-12-12 14:41:23 +01:00
Brian Clozel 3ab4ee2bba Next development version (v6.2.2-SNAPSHOT) 2024-12-12 10:53:14 +01:00
364 changed files with 9097 additions and 2121 deletions
@@ -31,10 +31,11 @@ runs:
${{ 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@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
develocity-token-expiry: 4
- name: Configure Gradle Properties
shell: bash
run: |
@@ -20,7 +20,7 @@ runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
@@ -21,7 +21,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
+3 -14
View File
@@ -9,22 +9,11 @@ jobs:
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
- name: Check Out Code
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
id: build
uses: ./.github/actions/build
- name: Print JVM Thread Dumps When Cancelled
if: cancelled()
uses: ./.github/actions/print-jvm-thread-dumps
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
@@ -12,8 +12,9 @@ permissions:
jobs:
update-antora-ui-spring:
runs-on: ubuntu-latest
name: Update on Supported Branches
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
strategy:
matrix:
branch: [ '6.1.x' ]
@@ -25,8 +26,9 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'framework-docs/antora-playbook.yml'
update-antora-ui-spring-docs-build:
runs-on: ubuntu-latest
name: Update on docs-build
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
name: Update
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
cache-read-only: false
- name: Configure Gradle Properties
+2 -1
View File
@@ -42,7 +42,8 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
*.iml
*.ipr
*.iws
.idea
.idea/*
!.idea/icon.svg
out
test-output
atlassian-ide-plugin.xml
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 510 510" style="enable-background:new 0 0 510 510;" xml:space="preserve">
<style type="text/css">
.st0{fill:#6DB33F;}
</style>
<title>icon-framework</title>
<g id="Layer_2_1_">
<g id="Layer_1-2">
<path class="st0" d="M479.2,162.5c-5.6-34.3-20.5-64.4-43.9-87.8S382,36.5,347.6,30.9C320.1,11.2,288.5,0,255,0
s-65.1,11.2-92.6,30.9C128,36.5,98,51.3,74.7,74.7C50.9,98.4,36.5,128.7,31,162.2C11.2,189.8,0,221.4,0,255s11.2,65.2,31,92.8
c5.5,33.5,19.9,63.8,43.7,87.5c23.3,23.3,53.3,38.2,87.7,43.8C190,498.8,221.5,510,255,510s65-11.2,92.6-30.9
c34.4-5.6,64.4-20.5,87.7-43.8c23.8-23.8,38.1-54.1,43.7-87.5c19.8-27.6,31-59.2,31-92.8S498.8,190,479.2,162.5z M473,224.2
c-0.8,6-1.9,11.9-3.2,17.9c-7.2-22.4-17.1-43.8-29.4-63.8c-5.5-22.8-13.6-44.9-24.4-65.8c22.9,14.8,42.2,32.6,56.7,52.6
C475.7,184.6,475.8,204.6,473,224.2z M444.7,199.6c9.3,17.7,16.6,36.3,21.9,55.6c-5.3,19.3-12.7,37.9-22,55.6
C450.9,274,451,236.4,444.7,199.6L444.7,199.6z M175.4,341.1c6.5-4.5,13.7-7.8,21.3-9.8c32.2-7.5,46.7-9.9,66.3-18.4
c36.8-15.9,74.8-53.3,84.6-94c-16.1,38.8-59.5,70.2-97.9,81.6c-26.2,7.8-79.8,13.6-80.1,13.6l-1.4-1.2
c-30.6-17.4-27-85.8,31.8-104.1c25.7-8,55.9-0.7,83.5-5.4c29.4-5,64.4-24.5,79.9-52c12.3,46.4,26.1,118.5-9.7,160.2
C309.4,363,211.8,318,175.4,341.1z M310.8,444.6c-17.7,9.4-36.5,16.8-55.8,22.1c-19.4-5.3-38.1-12.7-55.8-22.1
C236.1,450.9,273.9,450.9,310.8,444.6z M117.6,392.4c13.1,13.1,27.3,25.1,42.3,35.8c-19-5.9-37.2-13.8-54.5-23.7
c-9.9-17.3-17.8-35.7-23.7-54.7C92.4,365.1,104.5,379.3,117.6,392.4z M81.7,160.1c5.9-19,13.9-37.4,23.7-54.7
c17.3-9.8,35.5-17.8,54.5-23.7c-15.1,10.8-29.2,22.7-42.3,35.8C104.5,130.7,92.4,144.9,81.7,160.1z M149.1,346.4
c-8.5,0.2-15.5-6.5-15.7-15c-0.2-8.5,6.5-15.5,15-15.7c8.5-0.2,15.5,6.5,15.7,15c0,0,0,0.1,0,0.1
C164.2,339.3,157.5,346.2,149.1,346.4L149.1,346.4z M199.2,65.4c17.7-9.4,36.5-16.8,55.8-22.1c19.4,5.3,38.1,12.7,55.8,22.1
C273.9,59.1,236.1,59.1,199.2,65.4L199.2,65.4z M392.4,392.4c13.1-13.1,25.1-27.3,35.9-42.4c-5.9,19-13.9,37.3-23.7,54.5
c-17.3,9.8-35.5,17.7-54.5,23.7C365.2,417.5,379.3,405.5,392.4,392.4L392.4,392.4z M392.4,117.6c-13.1-13.1-27.2-25.1-42.3-35.8
c19,5.9,37.2,13.9,54.5,23.7c9.8,17.3,17.8,35.6,23.7,54.7C417.6,144.9,405.5,130.7,392.4,117.6L392.4,117.6z M430.5,79.5
c19.4,19.3,32.6,43.6,39.3,71.1c-16.2-19.2-36.6-36.2-60.4-50c-13.8-23.8-30.8-44.2-50-60.4C387.1,46.9,411.2,60.1,430.5,79.5
L430.5,79.5z M397.6,94c-20.8-10.7-42.8-18.8-65.6-24.2c-20-12.4-41.5-22.4-63.9-29.6c15.5-3.4,31.3-5.1,47.2-5.2
c10,0,19.9,0.8,29.7,2.3C365.1,51.8,382.8,71.1,397.6,94z M255,6.8c27.6,0,53.8,8,77.4,22.2c-5.6-0.5-11.3-0.7-17.1-0.7
c-20.4,0.1-40.6,2.8-60.3,8.1c-19.7-5.3-39.9-8-60.3-8.1c-5.8,0-11.5,0.2-17.1,0.7C201.2,14.7,227.4,6.8,255,6.8z M165,37.3
c9.8-1.5,19.8-2.3,29.7-2.3c15.9,0.1,31.7,1.8,47.2,5.2C219.5,47.4,198,57.3,178,69.8c-22.7,5.4-44.8,13.6-65.6,24.2
C127.2,71.1,144.9,51.8,165,37.3z M79.5,79.5c19.3-19.3,43.5-32.6,71.1-39.3c-19.2,16.2-36.2,36.6-50,60.4
c-23.7,13.8-44,30.6-60.2,49.8C47,123.4,59.8,99.1,79.5,79.5z M37.4,164.9c14.6-20,33.8-37.7,56.6-52.4
c-10.7,20.9-18.8,43-24.3,65.8c-12.3,20-22.2,41.4-29.4,63.8C34.6,215.4,33.5,189.3,37.4,164.9z M65.4,310.4
c-9.3-17.6-16.6-36.2-21.9-55.4c5.3-19.2,12.6-37.8,21.9-55.4C59.1,236.3,59.1,273.7,65.4,310.4L65.4,310.4z M29.2,332.8
c-14.4-23.7-22.4-50-22.4-77.8s8.1-54.1,22.4-77.8c-2.1,25,0.4,51.3,7.4,77.8C29.5,281.5,27,307.8,29.2,332.8z M40.3,268
c7.2,22.3,17.1,43.7,29.4,63.7c5.5,22.8,13.6,44.9,24.3,65.8c-22.8-14.7-42-32.5-56.6-52.4C33.5,320.7,34.6,294.6,40.3,268z
M79.5,430.5c-19.6-19.6-32.5-43.9-39.1-70.9c16.1,19.1,36.5,36,60.2,49.8c13.8,23.7,30.8,44.2,50,60.4
C122.9,463.1,98.8,449.9,79.5,430.5z M112.5,416c20.8,10.7,42.8,18.8,65.5,24.2c20,12.4,41.5,22.4,63.9,29.6
c-15.5,3.4-31.3,5.1-47.2,5.1c-10,0-19.9-0.8-29.7-2.3C144.9,458.1,127.2,438.9,112.5,416L112.5,416z M255,503.2
c-27.6,0-53.8-8-77.4-22.2c5.6,0.5,11.3,0.7,17.1,0.7c20.4-0.1,40.6-2.8,60.3-8.1c19.7,5.3,39.9,8,60.3,8.1
c5.8,0,11.5-0.2,17.1-0.7C308.8,495.3,282.6,503.2,255,503.2z M345,472.7c-9.8,1.5-19.8,2.3-29.7,2.3c-15.9-0.1-31.7-1.8-47.2-5.1
c22.4-7.2,43.9-17.2,63.9-29.6c22.7-5.4,44.8-13.6,65.6-24.2C382.8,438.9,365.1,458.1,345,472.7z M430.5,430.5
c-19.3,19.3-43.5,32.6-71.1,39.3c19.2-16.2,36.2-36.6,50-60.4c23.7-13.8,44-30.6,60.2-49.8C463.1,386.6,450.1,410.9,430.5,430.5
L430.5,430.5z M472.6,345.1c-14.6,20-33.8,37.7-56.6,52.4c10.7-20.8,18.8-42.8,24.3-65.6c12.3-20,22.2-41.3,29.5-63.7
C475.4,294.7,476.5,320.8,472.6,345.1L472.6,345.1z M473.5,255.3c2.6-9.9,4.7-20,6.1-30.1c2.2-15.7,2.7-31.7,1.4-47.5
c14.2,23.6,22.2,49.8,22.2,77.4s-8.1,54.1-22.4,77.8C482.9,307.9,480.5,281.6,473.5,255.3L473.5,255.3z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

+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
+6 -5
View File
@@ -16,7 +16,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
### Code of Conduct
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework#coc-ov-file).
By participating you are expected to uphold this code.
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
@@ -65,10 +65,6 @@ follow-up reports will need to be created as new issues with a fresh description
#### Submit a Pull Request
1. If you have not previously done so, please sign the
[Contributor License Agreement](https://cla.spring.io/sign/spring). You will be reminded
automatically when you submit the PR.
1. Should you create an issue first? No, just create the pull request and use the
description to provide context and motivation, as you would for an issue. If you want
to start a discussion first or have already created an issue, once a pull request is
@@ -85,6 +81,11 @@ multiple edits or corrections of the same logical change. See
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
for an overview of streamlining the commit history.
1. All commits must include a _Signed-off-by_ trailer at the end of each commit message
to indicate that the contributor agrees to the Developer Certificate of Origin.
For additional details, please refer to the blog post
[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring).
1. Format commit messages using 55 characters for the subject line, 72 characters per line
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
+6 -6
View File
@@ -9,7 +9,7 @@ plugins {
id 'io.github.goooler.shadow' version '8.1.8' apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'me.champeau.mrjar' version '0.1.1'
id "net.ltgt.errorprone" version "3.1.0" apply false
id "net.ltgt.errorprone" version "4.1.0" apply false
}
ext {
@@ -25,17 +25,17 @@ configure(allprojects) { project ->
repositories {
mavenCentral()
maven {
url "https://repo.spring.io/milestone"
url = "https://repo.spring.io/milestone"
content {
// Netty 5 optional support
includeGroup 'io.projectreactor.netty'
}
}
if (version.contains('-')) {
maven { url "https://repo.spring.io/milestone" }
maven { url = "https://repo.spring.io/milestone" }
}
if (version.endsWith('-SNAPSHOT')) {
maven { url "https://repo.spring.io/snapshot" }
maven { url = "https://repo.spring.io/snapshot" }
}
}
configurations.all {
@@ -91,7 +91,7 @@ configure([rootProject] + javaProjects) { project ->
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.11.3/api/",
// "https://junit.org/junit5/docs/5.12.0/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.20.2");
checkstyle.setToolVersion("10.21.4");
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();
@@ -27,6 +27,7 @@ import org.gradle.api.attributes.Usage;
import org.gradle.api.attributes.java.TargetJvmVersion;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.jvm.JvmTestSuite;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.testing.Test;
import org.gradle.testing.base.TestingExtension;
@@ -52,7 +53,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
TaskProvider<Test> agentTest = project.getTasks().register(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
test.useJUnitPlatform(options -> {
options.includeTags("RuntimeHintsTests");
});
@@ -63,7 +64,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
});
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
project.getTasks().named("check", task -> task.dependsOn(agentTest));
project.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
});
}
+2 -2
View File
@@ -9,7 +9,7 @@ apply from: "${rootDir}/gradle/publications.gradle"
repositories {
maven {
url "https://repo.spring.io/release"
url = "https://repo.spring.io/release"
}
}
@@ -87,7 +87,7 @@ tasks.register('schemaZip', Zip) {
archiveClassifier.set("schema")
description = "Builds -${archiveClassifier} archive containing all " +
"XSDs for deployment at https://springframework.org/schema."
duplicatesStrategy DuplicatesStrategy.EXCLUDE
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
moduleProjects.each { module ->
def Properties schemas = new Properties();
+1 -1
View File
@@ -37,7 +37,7 @@ javadoc {
repositories {
maven {
url "https://repo.spring.io/release"
url = "https://repo.spring.io/release"
}
}
@@ -444,9 +444,9 @@ through Java 8's `java.util.Optional`, as the following example shows:
}
----
You can also use a `@Nullable` annotation (of any kind in any package -- for example,
`javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety
support:
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
null-safety support:
[tabs]
======
@@ -477,6 +477,13 @@ Kotlin::
----
======
[NOTE]
====
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
detects type-level annotations and consistently declares JSpecify in its own codebase.
====
You can also use `@Autowired` for interfaces that are well-known resolvable
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
@@ -578,6 +578,8 @@ Kotlin::
----
======
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
The method signature once again declares the event type to which it listens,
but, this time, with a flexible name and without implementing a specific listener interface.
The event type can also be narrowed through generics as long as the actual event type
@@ -57,9 +57,9 @@ The following table describes these properties:
In addition to bean definitions that contain information on how to create a specific
bean, the `ApplicationContext` implementations also permit the registration of existing
objects that are created outside the container (by users). This is done by accessing the
ApplicationContext's `BeanFactory` through the `getBeanFactory()` method, which returns
the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory` supports
this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
ApplicationContext's `BeanFactory` through the `getAutowireCapableBeanFactory()` method,
which returns the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory`
supports this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
methods. However, typical applications work solely with beans defined through regular
bean definition metadata.
@@ -3,7 +3,7 @@
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>]
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element]
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
@@ -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`/
@@ -168,8 +168,8 @@ listings shows how to use the `parent` attribute:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
<!-- in the child (descendant) context, bean name is the same as the parent bean -->
<bean id="accountService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
@@ -64,7 +64,7 @@ This prevents the same `@Bean` method from accidentally being invoked through a
Java method call, which helps to reduce subtle bugs that can be hard to track down.
When `@Bean` methods are declared within classes that are not annotated with
`@Configuration` - or when `@Configuration(proxyBeanMethods=false)` is declared -,
`@Configuration`, or when `@Configuration(proxyBeanMethods=false)` is declared,
they are referred to as being processed in a "lite" mode. In such scenarios,
`@Bean` methods are effectively a general-purpose factory method mechanism without
special runtime processing (that is, without generating a CGLIB subclass for it).
+1 -1
View File
@@ -7,7 +7,7 @@ xref:overview.adoc[Overview] :: History, Design Philosophy, Feedback,
Getting Started.
xref:core.adoc[Core] :: IoC Container, Events, Resources, i18n,
Validation, Data Binding, Type Conversion, SpEL, AOP, AOT.
<<testing.adoc#testing, Testing>> :: Mock Objects, TestContext Framework,
xref:testing.adoc[Testing] :: Mock Objects, TestContext Framework,
Spring MVC Test, WebTestClient.
xref:data-access.adoc[Data Access] :: Transactions, DAO Support,
JDBC, R2DBC, O/R Mapping, XML Marshalling.
@@ -270,6 +270,7 @@ By default, the following `KeyValues` are created:
== HTTP Client Instrumentation
HTTP client exchange observations are created with the name `"http.client.requests"` for blocking and reactive clients.
This observation measures the entire HTTP request/response exchange, from connection establishment up to body deserialization.
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
[[observability.http-client.resttemplate]]
@@ -285,7 +286,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -313,7 +314,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if the request could not be created.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -342,7 +343,7 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -115,11 +115,15 @@ Finally, the body can be set to a callback function that writes to an `OutputStr
==== Retrieving the response
Once the request has been set up, the HTTP response is accessed by invoking `retrieve()`.
The response body can be accessed by using `body(Class)` or `body(ParameterizedTypeReference)` for parameterized types like lists.
Once the request has been set up, it can be sent by chaining method calls after `retrieve()`.
For example, the response body can be accessed by using `retrieve().body(Class)` or `retrieve().body(ParameterizedTypeReference)` for parameterized types like lists.
The `body` method converts the response contents into various types for instance, bytes can be converted into a `String`, JSON can be converted into objects using Jackson, and so on (see <<rest-message-conversion>>).
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body.
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body, with `retrieve().toEntity(Class)`
NOTE: Calling `retrieve()` by itself is a no-op and returns a `ResponseSpec`.
Applications must invoke a terminal operation on the `ResponseSpec` to have any side effect.
If consuming the response has no interest for your use case, you can use `retrieve().toBodilessEntity()`.
This sample shows how `RestClient` can be used to perform a simple `GET` request.
@@ -853,7 +857,7 @@ Start by creating the interface with `@HttpExchange` methods:
[source,java,indent=0,subs="verbatim,quotes"]
----
interface RepositoryService {
public interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@@ -904,7 +908,7 @@ For `RestTemplate`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
public interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@@ -938,10 +942,10 @@ method parameters:
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
| `@RequestHeader`
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
individual value. Type conversion is supported for non-String values. This overrides
the annotation's `headers` attribute.
| Add a request header or multiple headers. The argument may be a single value,
a `Collection<?>` of values, `Map<String, ?>`,`MultiValueMap<String, ?>`.
Type conversion is supported for non-String values. Header values are added and
do not override already added header values.
| `@PathVariable`
| Add a variable for expand a placeholder in the request URL. The argument may be a
@@ -988,6 +992,27 @@ parameter annotation) is set to `false`, or the parameter is marked optional as
[[rest-http-interface.custom-resolver]]
=== Custom argument resolver
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters.
This would take over the entire HTTP request and not improve the semantics of the interface.
Instead of adding many method parameters, developers can combine them into a custom type
and configure a dedicated `HttpServiceArgumentResolver` implementation.
In the following HTTP interface, we are using a custom `Search` type as a parameter:
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type
and writes its data in the outgoing HTTP request.
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0]
Finally, we can use this argument resolver during the setup and use our HTTP interface.
include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0]
[[rest-http-interface-return-values]]
=== Return Values
@@ -14,16 +14,13 @@ For example, `@Autowired lateinit var thing: Thing` implies that a bean
of type `Thing` must be registered in the application context, while `@Autowired lateinit var thing: Thing?`
does not raise an error if such a bean does not exist.
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, Car)` implies
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, car)` implies
that a bean of type `Toy` must be registered in the application context, while a bean of
type `Car` may or may not exist. The same behavior applies to autowired constructor parameters.
NOTE: If you use bean validation on classes with properties or a primary constructor
parameters, you may need to use
with parameters, you may need to use
{kotlin-docs}/annotations.html#annotation-use-site-targets[annotation use-site targets],
such as `@field:NotNull` or `@get:Size(min=5, max=15)`, as described in
{stackoverflow-site}/a/35853200/1092077[this Stack Overflow response].
@@ -1,42 +1,62 @@
[[spring-testing-annotation-beanoverriding-mockitobean]]
= `@MockitoBean` and `@MockitoSpyBean`
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
respectively. In the latter case, an early instance of the original bean is captured and
wrapped by the spy.
{spring-framework-api}/test/context/bean/override/mockito/MockitoBean.html[`@MockitoBean`] and
{spring-framework-api}/test/context/bean/override/mockito/MockitoSpyBean.html[`@MockitoSpyBean`]
can be used in test classes to override a bean in the test's `ApplicationContext` with a
Mockito _mock_ or _spy_, respectively. In the latter case, an early instance of the
original bean is captured and wrapped by the spy.
By default, the annotated field's type is used to search for candidate beans to override.
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to
override. Alternatively, a candidate whose bean name matches the name of the field will
match.
The annotations can be applied in the following ways.
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
exist. However, if you would like for the test to fail when a corresponding bean does not
exist, you can set the `enforceOverride` attribute to `true` for example,
`@MockitoBean(enforceOverride = true)`.
* On a non-static field in a test class or any of its superclasses.
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
* At the type level on a test class or any superclass or implemented interface in the
type hierarchy above the test class.
* At the type level on an enclosing class for a `@Nested` test class or on any class or
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
class.
To use a by-name override rather than a by-type override, specify the `name` attribute
of the annotation.
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
is inferred from the type of the annotated field. If multiple candidates exist in the
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation
for example, `@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple
candidates exist in the `ApplicationContext`, you can explicitly specify a bean name to
mock or spy by setting the `name` attribute. Note, however, that the `types` attribute
must contain a single type if an explicit bean `name` is configured for example,
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
To support reuse of mock configuration, `@MockitoBean` and `@MockitoSpyBean` may be used
as meta-annotations to create custom _composed annotations_ for example, to define
common mock or spy configuration in a single annotation that can be reused across a test
suite. `@MockitoBean` and `@MockitoSpyBean` can also be used as repeatable annotations at
the type level — for example, to mock or spy several beans by name.
[WARNING]
====
Qualifiers, including the name of the field, are used to determine if a separate
Qualifiers, including the name of a field, are used to determine if a separate
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
the same bean in several tests, make sure to name the field consistently to avoid
the same bean in several test classes, make sure to name the fields consistently to avoid
creating unnecessary contexts.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
By default, the `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
If no existing bean matches, a new bean is created on the fly. As mentioned previously,
you can switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to
`true`.
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy for bean overrides].
If a corresponding bean does not exist, a new bean will be created. However, you can
switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`
for example, `@MockitoBean(enforceOverride = true)`.
The `@MockitoSpyBean` annotation uses the `WRAP`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy],
and the original instance is wrapped in a Mockito spy. This strategy requires that
exactly one candidate bean exists.
@@ -61,7 +81,11 @@ Such fields can therefore be `public`, `protected`, package-private (default vis
or `private` depending on the needs or coding practices of the project.
====
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
== `@MockitoBean` Examples
The following example shows how to use the default behavior of the `@MockitoBean`
annotation.
[tabs]
======
@@ -69,23 +93,26 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoBean // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
<1> Replace the bean with type `CustomService` with a Mockito mock.
======
In the example above, we are creating a mock for `CustomService`. If more than one bean
of that type exists, the bean named `customService` is considered. Otherwise, the test
will fail, and you will need to provide a qualifier of some sort to identify which of the
`CustomService` beans you want to override. If no such bean exists, a bean definition
will be created with an auto-generated bean name.
`CustomService` beans you want to override. If no such bean exists, a bean will be
created with an auto-generated bean name.
The following example uses a by-name lookup, rather than a by-type lookup:
The following example uses a by-name lookup, rather than a by-type lookup. If no bean
named `service` exists, one is created.
[tabs]
======
@@ -93,20 +120,20 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoBean("service") // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Replace the bean named `service` with a Mockito `mock`.
<1> Replace the bean named `service` with a Mockito mock.
======
If no bean definition named `service` exists, one is created.
The following example shows how to use the default behavior of the `@MockitoSpyBean` annotation:
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
[tabs]
======
@@ -114,14 +141,73 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = {OrderService.class, UserService.class}) // <1>
@MockitoBean(name = "ps1", types = PrintingService.class) // <2>
public @interface SharedMocks {
}
----
<1> Register `OrderService` and `UserService` mocks by-type.
<2> Register `PrintingService` mock by-name.
======
The following demonstrates how `@SharedMocks` can be used on a test class.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@SharedMocks // <1>
class BeanOverrideTests {
@Autowired OrderService orderService; // <2>
@Autowired UserService userService; // <2>
@Autowired PrintingService ps1; // <2>
// Inject other components that rely on the mocks.
@Test
void testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common mocks via the custom `@SharedMocks` annotation.
<2> Optionally inject mocks to _stub_ or _verify_ them.
======
TIP: The mocks can also be injected into `@Configuration` classes or other test-related
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
APIs.
[[spring-testing-annotation-beanoverriding-mockitospybean-examples]]
== `@MockitoSpyBean` Examples
The following example shows how to use the default behavior of the `@MockitoSpyBean`
annotation.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoSpyBean // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
<1> Wrap the bean with type `CustomService` with a Mockito spy.
======
In the example above, we are wrapping the bean with type `CustomService`. If more than
@@ -129,7 +215,7 @@ one bean of that type exists, the bean named `customService` is considered. Othe
the test will fail, and you will need to provide a qualifier of some sort to identify
which of the `CustomService` beans you want to spy.
The following example uses a by-name lookup, rather than a by-type lookup:
The following example uses a by-name lookup, rather than a by-type lookup.
[tabs]
======
@@ -137,13 +223,67 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoSpyBean("service") // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito `spy`.
<1> Wrap the bean named `service` with a Mockito spy.
======
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoSpyBean(types = {OrderService.class, UserService.class}) // <1>
@MockitoSpyBean(name = "ps1", types = PrintingService.class) // <2>
public @interface SharedSpies {
}
----
<1> Register `OrderService` and `UserService` spies by-type.
<2> Register `PrintingService` spy by-name.
======
The following demonstrates how `@SharedSpies` can be used on a test class.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@SharedSpies // <1>
class BeanOverrideTests {
@Autowired OrderService orderService; // <2>
@Autowired UserService userService; // <2>
@Autowired PrintingService ps1; // <2>
// Inject other components that rely on the spies.
@Test
void testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common spies via the custom `@SharedSpies` annotation.
<2> Optionally inject spies to _stub_ or _verify_ them.
======
TIP: The spies can also be injected into `@Configuration` classes or other test-related
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
APIs.
@@ -1,8 +1,9 @@
[[spring-testing-annotation-beanoverriding-testbean]]
= `@TestBean`
`@TestBean` is used on a non-static field in a test class to override a specific bean in
the test's `ApplicationContext` with an instance provided by a factory method.
{spring-framework-api}/test/context/bean/override/convention/TestBean.html[`@TestBean`]
is used on a non-static field in a test class to override a specific bean in the test's
`ApplicationContext` with an instance provided by a factory method.
The associated factory method name is derived from the annotated field's name, or the
bean name if specified. The factory method must be `static`, accept no arguments, and
@@ -92,8 +93,10 @@ Java::
[TIP]
====
Spring searches for the factory method to invoke in the test class, in the test class
hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
To locate the factory method to invoke, Spring searches in the class in which the
`@TestBean` field is declared, in one of its superclasses, or in any implemented
interfaces. If the `@TestBean` field is declared in a `@Nested` test class, the enclosing
class hierarchy will also be searched.
Alternatively, a factory method in an external class can be referenced via its
fully-qualified method name following the syntax `<fully-qualified class name>#<method name>`
@@ -2,8 +2,8 @@
= Bean Overriding in Tests
Bean overriding in tests refers to the ability to override specific beans in the
`ApplicationContext` for a test class, by annotating one or more non-static fields in the
test class.
`ApplicationContext` for a test class, by annotating the test class or one or more
non-static fields in the test class.
NOTE: This feature is intended as a less risky alternative to the practice of registering
a bean via `@Bean` with the `DefaultListableBeanFactory`
@@ -42,15 +42,16 @@ The `spring-test` module registers implementations of the latter two
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
properties file].
The bean overriding infrastructure searches in test classes for any non-static field that
is meta-annotated with `@BeanOverride` and instantiates the corresponding
`BeanOverrideProcessor` which is responsible for creating an appropriate
`BeanOverrideHandler`.
The bean overriding infrastructure searches for annotations on test classes as well as
annotations on non-static fields in test classes that are meta-annotated with
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
responsible for creating an appropriate `BeanOverrideHandler`.
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
defined by the corresponding `BeanOverrideStrategy`:
[[testcontext-bean-overriding-strategy]]
`REPLACE`::
Replaces the bean. Throws an exception if a corresponding bean does not exist.
`REPLACE_OR_CREATE`::
@@ -7,16 +7,17 @@ by default, exactly in the following order:
* `ServletTestExecutionListener`: Configures Servlet API mocks for a
`WebApplicationContext`.
* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext`
annotation for "`before`" modes.
annotation for "before" modes.
* `ApplicationEventsTestExecutionListener`: Provides support for
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
* `BeanOverrideTestExecutionListener`: Provides support for xref:testing/testcontext-framework/bean-overriding.adoc[] .
* `BeanOverrideTestExecutionListener`: Provides support for
xref:testing/testcontext-framework/bean-overriding.adoc[].
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
instance.
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
Micrometer's `ObservationRegistry`.
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
"`after`" modes.
"after" modes.
* `CommonCachesTestExecutionListener`: Clears resource caches in the test's
`ApplicationContext` if necessary.
* `TransactionalTestExecutionListener`: Provides transactional test execution with
@@ -161,15 +162,16 @@ change from release to release -- for example, `SqlScriptsTestExecutionListener`
introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener`
was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
Boot and Spring Security register their own default `TestExecutionListener`
implementations by using the aforementioned xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism]
.
implementations by using the aforementioned
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism].
To avoid having to be aware of and re-declare all default listeners, you can set the
`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`.
`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the
default listeners. The merging algorithm ensures that duplicates are removed from the
list and that the resulting set of merged listeners is sorted according to the semantics
of `AnnotationAwareOrderComparator`, as described in xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
of `AnnotationAwareOrderComparator`, as described in
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
If a listener implements `Ordered` or is annotated with `@Order`, it can influence the
position in which it is merged with the defaults. Otherwise, locally declared listeners
are appended to the list of default listeners when merged.
@@ -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-view]]
= View Technologies
[.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]#
The rendering of views in Spring WebFlux is pluggable. Whether you decide to
@@ -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
@@ -605,9 +605,4 @@ For method parameters and returns values, generally, `@HttpExchange` supports a
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
server-side specific parameter types. For details, see the list for
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
pairs like in `@RequestMapping(headers={})` on the client side. On the server side,
this extends to the full syntax that
xref:#webflux-ann-requestmapping-params-and-headers[`@RequestMapping`] supports.
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
@@ -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.
@@ -1,6 +1,7 @@
[[mvc-view]]
= View Technologies
:page-section-summary-toc: 1
[.small]#xref:web/webflux-view.adoc[See equivalent in the Reactive stack]#
The rendering of views in Spring MVC is pluggable. Whether you decide to use
@@ -130,6 +130,7 @@ the `Configuration` object.
[[mvc-view-freemarker-forms]]
== Form Handling
[.small]#xref:web/webflux-view.adoc#webflux-view-freemarker-forms[See equivalent in the Reactive stack]#
Spring provides a tag library for use in JSPs that contains, among others, a
`<spring:bind/>` element. This element primarily lets forms display values from
@@ -47,7 +47,7 @@ through https://www.webjars.org/[WebJars].
[[mvc-view-script-integrate]]
== Script Templates
[.small]#xref:web/webflux-view.adoc#webflux-view-script[See equivalent in the Reactive stack]#
[.small]#xref:web/webflux-view.adoc#webflux-view-script-integrate[See equivalent in the Reactive stack]#
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
the script files to load, what function to call to render templates, and so on.
@@ -37,7 +37,7 @@ already set.
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
To enable RFC 9457 responses for Spring MVC exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
@@ -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
@@ -19,6 +19,6 @@ from where they are handled according to their destination prefix. As the channe
a `ThreadPoolExecutor`, messages are processed in different threads, and the resulting sequence
of handling may not match the exact order in which they were received.
To enable ordered publishing, set the `setPreserveReceiveOrder` flag as follows:
To enable ordered receiving, set the `setPreserveReceiveOrder` flag as follows:
include-code::./ReceiveOrderWebSocketConfiguration[tag=snippet,indent=0]
+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.14.2",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
@@ -0,0 +1,105 @@
/*
* 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.docs.integration.resthttpinterface.customresolver;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.invoker.HttpRequestValues;
import org.springframework.web.service.invoker.HttpServiceArgumentResolver;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
public class CustomHttpServiceArgumentResolver {
// tag::httpinterface[]
public interface RepositoryService {
@GetExchange("/repos/search")
List<Repository> searchRepository(Search search);
}
// end::httpinterface[]
class Sample {
void sample() {
// tag::usage[]
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(new SearchQueryArgumentResolver())
.build();
RepositoryService repositoryService = factory.createClient(RepositoryService.class);
Search search = Search.create()
.owner("spring-projects")
.language("java")
.query("rest")
.build();
List<Repository> repositories = repositoryService.searchRepository(search);
// end::usage[]
}
}
// tag::argumentresolver[]
static class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
@Override
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (parameter.getParameterType().equals(Search.class)) {
Search search = (Search) argument;
requestValues.addRequestParameter("owner", search.owner());
requestValues.addRequestParameter("language", search.language());
requestValues.addRequestParameter("query", search.query());
return true;
}
return false;
}
}
// end::argumentresolver[]
record Search (String query, String owner, String language) {
static Builder create() {
return new Builder();
}
static class Builder {
Builder query(String query) { return this;}
Builder owner(String owner) { return this;}
Builder language(String language) { return this;}
Search build() {
return new Search(null, null, null);
}
}
}
record Repository(String name) {
}
}
@@ -0,0 +1,78 @@
/*
* 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.docs.integration.resthttpinterface.customresolver
import org.springframework.core.MethodParameter
import org.springframework.web.client.RestClient
import org.springframework.web.client.support.RestClientAdapter
import org.springframework.web.service.annotation.GetExchange
import org.springframework.web.service.invoker.HttpRequestValues
import org.springframework.web.service.invoker.HttpServiceArgumentResolver
import org.springframework.web.service.invoker.HttpServiceProxyFactory
class CustomHttpServiceArgumentResolver {
// tag::httpinterface[]
interface RepositoryService {
@GetExchange("/repos/search")
fun searchRepository(search: Search): List<Repository>
}
// end::httpinterface[]
class Sample {
fun sample() {
// tag::usage[]
val restClient = RestClient.builder().baseUrl("https://api.github.com/").build()
val adapter = RestClientAdapter.create(restClient)
val factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(SearchQueryArgumentResolver())
.build()
val repositoryService = factory.createClient<RepositoryService>(RepositoryService::class.java)
val search = Search(owner = "spring-projects", language = "java", query = "rest")
val repositories = repositoryService.searchRepository(search)
// end::usage[]
repositories.size
}
}
// tag::argumentresolver[]
class SearchQueryArgumentResolver : HttpServiceArgumentResolver {
override fun resolve(
argument: Any?,
parameter: MethodParameter,
requestValues: HttpRequestValues.Builder
): Boolean {
if (parameter.getParameterType() == Search::class.java) {
val search = argument as Search
requestValues.addRequestParameter("owner", search.owner)
.addRequestParameter("language", search.language)
.addRequestParameter("query", search.query)
return true
}
return false
}
}
// end::argumentresolver[]
data class Search(val query: String, val owner: String, val language: String)
data class Repository(val name: String)
}
+31 -31
View File
@@ -7,31 +7,31 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
api(platform("io.micrometer:micrometer-bom:1.14.2"))
api(platform("io.netty:netty-bom:4.1.115.Final"))
api(platform("com.fasterxml.jackson:jackson-bom:2.18.3"))
api(platform("io.micrometer:micrometer-bom:1.14.5"))
api(platform("io.netty:netty-bom:4.1.119.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.1"))
api(platform("io.rsocket:rsocket-bom:1.1.4"))
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
api(platform("io.projectreactor:reactor-bom:2024.0.4"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.26.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.15"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.15"))
api(platform("org.assertj:assertj-bom:3.27.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.17"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.17"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.11.3"))
api(platform("org.mockito:mockito-bom:5.14.2"))
api(platform("org.junit:junit-bom:5.12.0"))
api(platform("org.mockito:mockito-bom:5.16.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
api("com.github.ben-manes.caffeine:caffeine:3.2.0")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.11.0")
api("com.google.protobuf:protobuf-java-util:4.28.3")
api("com.google.code.gson:gson:2.12.1")
api("com.google.protobuf:protobuf-java-util:4.30.0")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
@@ -43,7 +43,7 @@ dependencies {
api("com.sun.xml.bind:jaxb-core:3.0.2")
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.qdox:qdox:2.2.0")
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")
@@ -100,8 +100,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.4.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
api("org.apache.httpcomponents.client5:httpclient5:5.4.2")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.3")
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")
@@ -110,18 +110,18 @@ 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.2")
api("org.awaitility:awaitility:4.3.0")
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
api("org.dom4j:dom4j:2.1.4")
api("org.easymock:easymock:5.4.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
api("org.easymock:easymock:5.5.0")
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")
api("org.ehcache:jcache:1.0.1")
api("org.freemarker:freemarker:2.3.33")
api("org.freemarker:freemarker:2.3.34")
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
api("org.glassfish:jakarta.el:4.0.2")
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
@@ -130,24 +130,24 @@ dependencies {
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.6.0")
api("org.htmlunit:htmlunit:4.10.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.9.0")
api("org.jruby:jruby:9.4.12.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.3")
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.26.0")
api("org.seleniumhq.selenium:selenium-java:4.26.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.29.0")
api("org.seleniumhq.selenium:selenium-java:4.29.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.16")
api("org.testng:testng:7.10.2")
api("org.slf4j:slf4j-api:2.0.17")
api("org.testng:testng:7.11.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.55")
api("org.webjars:webjars-locator-lite:1.0.0")
api("org.webjars:webjars-locator-core:0.59")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.0")
api("org.xmlunit:xmlunit-matchers:2.10.0")
api("org.yaml:snakeyaml:2.3")
api("org.yaml:snakeyaml:2.4")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.1-SNAPSHOT
version=6.2.4
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.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -3
View File
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -206,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id "com.gradle.develocity" version "3.17.2"
id "com.gradle.develocity" version "3.19"
id "io.spring.ge.conventions" version "0.0.17"
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
}
@@ -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-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.
@@ -125,7 +125,7 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple
return null;
};
return doSubmit(task, executor, invocation.getMethod().getReturnType());
return doSubmit(task, executor, userMethod.getReturnType());
}
/**
@@ -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.
@@ -99,7 +99,7 @@ abstract class AbstractProxyExceptionHandlingTests {
assertThat(throwableSeenByInterceptor).isSameAs(undeclaredCheckedException);
assertThat(throwableSeenByCaller)
.isInstanceOf(UndeclaredThrowableException.class)
.hasCauseReference(undeclaredCheckedException);
.cause().isSameAs(undeclaredCheckedException);
}
@Test
@@ -147,7 +147,7 @@ abstract class AbstractProxyExceptionHandlingTests {
invokeProxy();
assertThat(throwableSeenByCaller)
.isInstanceOf(UndeclaredThrowableException.class)
.hasCauseReference(undeclaredCheckedException);
.cause().isSameAs(undeclaredCheckedException);
}
@Test
@@ -0,0 +1,73 @@
/*
* 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.interceptor;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.core.task.AsyncTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AsyncExecutionInterceptor}.
*
* @author Bao Ngo
* @since 7.0
*/
class AsyncExecutionInterceptorTests {
@Test
@SuppressWarnings("unchecked")
void invokeOnInterfaceWithGeneric() throws Throwable {
AsyncExecutionInterceptor interceptor = spy(new AsyncExecutionInterceptor(null));
FutureRunner impl = new FutureRunner();
MethodInvocation mi = mock();
given(mi.getThis()).willReturn(impl);
given(mi.getMethod()).willReturn(GenericRunner.class.getMethod("run"));
interceptor.invoke(mi);
ArgumentCaptor<Class<?>> classArgumentCaptor = ArgumentCaptor.forClass(Class.class);
verify(interceptor).doSubmit(any(Callable.class), any(AsyncTaskExecutor.class), classArgumentCaptor.capture());
assertThat(classArgumentCaptor.getValue()).isEqualTo(Future.class);
}
interface GenericRunner<O> {
O run();
}
static class FutureRunner implements GenericRunner<Future<Void>> {
@Override
public Future<Void> run() {
return CompletableFuture.runAsync(() -> {
});
}
}
}
@@ -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.
@@ -658,6 +658,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 +693,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 +
@@ -904,16 +904,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
private Object newValue(Class<?> type, @Nullable TypeDescriptor desc, String name) {
try {
if (type.isArray()) {
Class<?> componentType = type.componentType();
// TODO - only handles 2-dimensional arrays
if (componentType.isArray()) {
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, Array.newInstance(componentType.componentType(), 0));
return array;
}
else {
return Array.newInstance(componentType, 0);
}
return createArray(type);
}
else if (Collection.class.isAssignableFrom(type)) {
TypeDescriptor elementDesc = (desc != null ? desc.getElementTypeDescriptor() : null);
@@ -937,6 +928,24 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
}
/**
* Create the array for the given array type.
* @param arrayType the desired type of the target array
* @return a new array instance
*/
private static Object createArray(Class<?> arrayType) {
Assert.notNull(arrayType, "Array type must not be null");
Class<?> componentType = arrayType.componentType();
if (componentType.isArray()) {
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, createArray(componentType));
return array;
}
else {
return Array.newInstance(componentType, 0);
}
}
/**
* Parse the given property name into the corresponding property name tokens.
* @param propertyName the property name to parse
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,4 +45,18 @@ public interface PropertyEditorRegistrar {
*/
void registerCustomEditors(PropertyEditorRegistry registry);
/**
* Indicate whether this registrar exclusively overrides default editors
* rather than registering custom editors, intended to be applied lazily.
* <p>This has an impact on registrar handling in a bean factory: see
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#addPropertyEditorRegistrar}.
* @since 6.2.3
* @see PropertyEditorRegistry#registerCustomEditor
* @see PropertyEditorRegistrySupport#overrideDefaultEditor
* @see PropertyEditorRegistrySupport#setDefaultEditorRegistrar
*/
default boolean overridesDefaultEditors() {
return false;
}
}
@@ -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.
@@ -99,6 +99,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
private boolean configValueEditorsActive = false;
@Nullable
private PropertyEditorRegistrar defaultEditorRegistrar;
@Nullable
private Map<Class<?>, PropertyEditor> defaultEditors;
@@ -155,6 +158,19 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
this.configValueEditorsActive = true;
}
/**
* Set a registrar for default editors, as a lazy way of overriding default editors.
* <p>This is expected to be a collaborator with {@link PropertyEditorRegistrySupport},
* downcasting the given {@link PropertyEditorRegistry} accordingly and calling
* {@link #overrideDefaultEditor} for registering additional default editors on it.
* @param registrar the registrar to call when default editors are actually needed
* @since 6.2.3
* @see #overrideDefaultEditor
*/
public void setDefaultEditorRegistrar(PropertyEditorRegistrar registrar) {
this.defaultEditorRegistrar = registrar;
}
/**
* Override the default editor for the specified type with the given property editor.
* <p>Note that this is different from registering a custom editor in that the editor
@@ -184,6 +200,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
if (!this.defaultEditorsActive) {
return null;
}
if (this.overriddenDefaultEditors == null && this.defaultEditorRegistrar != null) {
this.defaultEditorRegistrar.registerCustomEditors(this);
}
if (this.overriddenDefaultEditors != null) {
PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
if (editor != null) {
@@ -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.
@@ -39,10 +39,14 @@ import org.springframework.util.StringUtils;
* (which the methods defined on the ListableBeanFactory interface don't,
* in contrast to the methods defined on the BeanFactory interface).
*
* <p><b>NOTE:</b> It is generally preferable to use {@link ObjectProvider#stream()}
* via {@link BeanFactory#getBeanProvider} instead of this utility class.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 04.07.2003
* @see BeanFactory#getBeanProvider
*/
public abstract class BeanFactoryUtils {
@@ -308,7 +312,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @return the Map of matching bean instances, or an empty Map if none
* @throws BeansException if a bean could not be created
* @see ListableBeanFactory#getBeansOfType(Class)
@@ -347,7 +351,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean 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
@@ -395,7 +399,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @return the matching bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -425,7 +429,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean 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
@@ -457,7 +461,7 @@ public abstract class BeanFactoryUtils {
* <p>This version of {@code beanOfType} automatically includes
* prototypes and FactoryBeans.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @return the matching bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -481,7 +485,7 @@ public abstract class BeanFactoryUtils {
* only raw FactoryBeans will be checked (which doesn't require initialization
* of each FactoryBean).
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean 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
@@ -529,7 +533,7 @@ public abstract class BeanFactoryUtils {
/**
* Extract a unique bean for the given type from the given Map of matching beans.
* @param type type of bean to match
* @param type the type of bean to match
* @param matchingBeans all matching beans found
* @return the unique bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.beans.factory;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -45,6 +46,21 @@ import org.springframework.lang.Nullable;
* Alternatively, you may implement the specific methods that your callers expect,
* for example, just {@link #getObject()} or {@link #getIfAvailable()}.
*
* <p>Note that {@link #getObject()} never returns {@code null} - it will throw a
* {@link NoSuchBeanDefinitionException} instead -, whereas {@link #getIfAvailable()}
* will return {@code null} if no matching bean is present at all. However, both
* methods will throw a {@link NoUniqueBeanDefinitionException} if more than one
* matching bean is found without a clear unique winner (see below). Last but not
* least, {@link #getIfUnique()} will return {@code null} both when no matching bean
* is found and when more than one matching bean is found without a unique winner.
*
* <p>Uniqueness is generally up to the container's candidate resolution algorithm
* but always honors the "primary" flag (with only one of the candidate beans marked
* as primary) and the "fallback" flag (with only one of the candidate beans not
* marked as fallback). The default-candidate flag is consistently taken into
* account as well, even for non-annotation-based injection points, with a single
* default candidate winning in case of no clear primary/fallback indication.
*
* @author Juergen Hoeller
* @since 4.3
* @param <T> the object type
@@ -53,6 +69,18 @@ import org.springframework.lang.Nullable;
*/
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
/**
* A predicate for unfiltered type matches, including non-default candidates
* but still excluding non-autowire candidates when used on injection points.
* @since 6.2.3
* @see #stream(Predicate)
* @see #orderedStream(Predicate)
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#isDefaultCandidate()
*/
Predicate<Class<?>> UNFILTERED = (clazz -> true);
@Override
default T getObject() throws BeansException {
Iterator<T> it = iterator();
@@ -175,7 +203,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* if unique (not called otherwise)
* @throws BeansException in case of creation errors
* @since 5.0
* @see #getIfAvailable()
* @see #getIfUnique()
*/
default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
T dependency = getIfUnique();
@@ -198,6 +226,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
/**
* Return a sequential {@link Stream} over all matching object instances,
* without specific ordering guarantees (but typically in registration order).
* <p>Note: The result may be filtered by default according to qualifiers on the
* injection point versus target beans and the general autowire candidate status
* of matching beans. For custom filtering against type-matching candidates, use
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
* @since 5.1
* @see #iterator()
* @see #orderedStream()
@@ -219,6 +251,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* {@link #stream()} method. You may override this to apply an
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
* if necessary.
* <p>Note: The result may be filtered by default according to qualifiers on the
* injection point versus target beans and the general autowire candidate status
* of matching beans. For custom filtering against type-matching candidates, use
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
* @since 5.1
* @see #stream()
* @see org.springframework.core.OrderComparator
@@ -227,4 +263,32 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
return stream().sorted(OrderComparator.INSTANCE);
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* without specific ordering guarantees (but typically in registration order).
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @since 6.2.3
* @see #stream()
* @see #orderedStream(Predicate)
*/
default Stream<T> stream(Predicate<Class<?>> customFilter) {
return stream().filter(obj -> customFilter.test(obj.getClass()));
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* pre-ordered according to the factory's common order comparator.
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @since 6.2.3
* @see #orderedStream()
* @see #stream(Predicate)
*/
default Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
return orderedStream().filter(obj -> customFilter.test(obj.getClass()));
}
}
@@ -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.
@@ -95,7 +95,7 @@ public abstract class BeanFactoryAnnotationUtils {
// Full qualifier matching supported.
return qualifiedBeanOfType(lbf, beanType, qualifier);
}
else if (beanFactory.containsBean(qualifier)) {
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
// Fallback: target bean at least found by bean name.
return beanFactory.getBean(qualifier, beanType);
}
@@ -110,16 +110,16 @@ public abstract class BeanFactoryAnnotationUtils {
/**
* Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier
* (for example, {@code <qualifier>} or {@code @Qualifier}) matching the given qualifier).
* @param bf the factory to get the target bean from
* @param beanFactory the factory to get the target bean from
* @param beanType the type of bean to retrieve
* @param qualifier the qualifier for selecting between multiple bean matches
* @return the matching bean of type {@code T} (never {@code null})
*/
private static <T> T qualifiedBeanOfType(ListableBeanFactory bf, Class<T> beanType, String qualifier) {
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType);
private static <T> T qualifiedBeanOfType(ListableBeanFactory beanFactory, Class<T> beanType, String qualifier) {
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, beanType);
String matchingBean = null;
for (String beanName : candidateBeans) {
if (isQualifierMatch(qualifier::equals, beanName, bf)) {
if (isQualifierMatch(qualifier::equals, beanName, beanFactory)) {
if (matchingBean != null) {
throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName);
}
@@ -127,11 +127,11 @@ public abstract class BeanFactoryAnnotationUtils {
}
}
if (matchingBean != null) {
return bf.getBean(matchingBean, beanType);
return beanFactory.getBean(matchingBean, beanType);
}
else if (bf.containsBean(qualifier)) {
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
// Fallback: target bean at least found by bean name - probably a manually registered singleton.
return bf.getBean(qualifier, beanType);
return beanFactory.getBean(qualifier, beanType);
}
else {
throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
@@ -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.
@@ -443,6 +443,7 @@ public class InstanceSupplierCodeGenerator {
}
}
record ConstructorDescriptor(String beanName, Constructor<?> constructor, Class<?> publicType) {
Class<?> actualType() {
@@ -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.
@@ -183,7 +183,11 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
* on the given registry, fresh for each bean creation attempt. This avoids
* the need for synchronization on custom editors; hence, it is generally
* preferable to use this method instead of {@link #registerCustomEditor}.
* <p>If the given registrar implements
* {@link PropertyEditorRegistrar#overridesDefaultEditors()} to return {@code true},
* it will be applied lazily (only when default editors are actually needed).
* @param registrar the PropertyEditorRegistrar to register
* @see PropertyEditorRegistrar#overridesDefaultEditors()
*/
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
@@ -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.
@@ -991,7 +991,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
*/
@Nullable
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
this.singletonLock.lock();
boolean locked = this.singletonLock.tryLock();
if (!locked) {
return null;
}
try {
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
if (bw != null) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -137,6 +137,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Nullable
private ConversionService conversionService;
/** Default PropertyEditorRegistrars to apply to the beans of this factory. */
private final Set<PropertyEditorRegistrar> defaultEditorRegistrars = new LinkedHashSet<>(4);
/** Custom PropertyEditorRegistrars to apply to the beans of this factory. */
private final Set<PropertyEditorRegistrar> propertyEditorRegistrars = new LinkedHashSet<>(4);
@@ -883,7 +886,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Override
public void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar) {
Assert.notNull(registrar, "PropertyEditorRegistrar must not be null");
this.propertyEditorRegistrars.add(registrar);
if (registrar.overridesDefaultEditors()) {
this.defaultEditorRegistrars.add(registrar);
}
else {
this.propertyEditorRegistrars.add(registrar);
}
}
/**
@@ -1114,6 +1122,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
setBeanExpressionResolver(otherFactory.getBeanExpressionResolver());
setConversionService(otherFactory.getConversionService());
if (otherFactory instanceof AbstractBeanFactory otherAbstractFactory) {
this.defaultEditorRegistrars.addAll(otherAbstractFactory.defaultEditorRegistrars);
this.propertyEditorRegistrars.addAll(otherAbstractFactory.propertyEditorRegistrars);
this.customEditors.putAll(otherAbstractFactory.customEditors);
this.typeConverter = otherAbstractFactory.typeConverter;
@@ -1313,36 +1322,48 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
protected void registerCustomEditors(PropertyEditorRegistry registry) {
if (registry instanceof PropertyEditorRegistrySupport registrySupport) {
registrySupport.useConfigValueEditors();
}
if (!this.propertyEditorRegistrars.isEmpty()) {
for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
try {
registrar.registerCustomEditors(registry);
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
"] failed because it tried to obtain currently created bean '" +
ex.getBeanName() + "': " + ex.getMessage());
}
onSuppressedException(ex);
continue;
}
}
throw ex;
}
if (!this.defaultEditorRegistrars.isEmpty()) {
// Optimization: lazy overriding of default editors only when needed
registrySupport.setDefaultEditorRegistrar(new BeanFactoryDefaultEditorRegistrar());
}
}
else if (!this.defaultEditorRegistrars.isEmpty()) {
// Fallback: proactive overriding of default editors
applyEditorRegistrars(registry, this.defaultEditorRegistrars);
}
if (!this.propertyEditorRegistrars.isEmpty()) {
applyEditorRegistrars(registry, this.propertyEditorRegistrars);
}
if (!this.customEditors.isEmpty()) {
this.customEditors.forEach((requiredType, editorClass) ->
registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
}
}
private void applyEditorRegistrars(PropertyEditorRegistry registry, Set<PropertyEditorRegistrar> registrars) {
for (PropertyEditorRegistrar registrar : registrars) {
try {
registrar.registerCustomEditors(registry);
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
"] failed because it tried to obtain currently created bean '" +
ex.getBeanName() + "': " + ex.getMessage());
}
onSuppressedException(ex);
return;
}
}
throw ex;
}
}
}
/**
* Return a merged RootBeanDefinition, traversing the parent bean definition
@@ -2095,4 +2116,20 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
final List<MergedBeanDefinitionPostProcessor> mergedDefinition = new ArrayList<>();
}
/**
* {@link PropertyEditorRegistrar} that delegates to the bean factory's
* default registrars, adding exception handling for circular reference
* scenarios where an editor tries to refer back to the currently created bean.
*
* @since 6.2.3
*/
class BeanFactoryDefaultEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
applyEditorRegistrars(registry, defaultEditorRegistrars);
}
}
}
@@ -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.
@@ -33,7 +33,10 @@ import java.util.Comparator;
import java.util.Set;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -259,6 +262,43 @@ abstract class AutowireUtils {
return method.getReturnType();
}
/**
* Check the autowire-candidate status for the specified bean.
* @param beanFactory the bean factory
* @param beanName the name of the bean to check
* @return whether the specified bean qualifies as an autowire candidate
* @since 6.2.3
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
*/
public static boolean isAutowireCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
try {
return beanFactory.getMergedBeanDefinition(beanName).isAutowireCandidate();
}
catch (NoSuchBeanDefinitionException ex) {
// A manually registered singleton instance not backed by a BeanDefinition.
return true;
}
}
/**
* Check the default-candidate status for the specified bean.
* @param beanFactory the bean factory
* @param beanName the name of the bean to check
* @return whether the specified bean qualifies as a default candidate
* @since 6.2.4
* @see AbstractBeanDefinition#isDefaultCandidate()
*/
public static boolean isDefaultCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
try {
BeanDefinition mbd = beanFactory.getMergedBeanDefinition(beanName);
return (!(mbd instanceof AbstractBeanDefinition abd) || abd.isDefaultCandidate());
}
catch (NoSuchBeanDefinitionException ex) {
// A manually registered singleton instance not backed by a BeanDefinition.
return true;
}
}
/**
* Reflective {@link InvocationHandler} for lazy access to the current target object.
@@ -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.
@@ -177,7 +177,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
// Set of bean definition names with a primary marker. */
/** Set of bean definition names with a primary marker. */
private final Set<String> primaryBeanNames = ConcurrentHashMap.newKeySet(16);
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
@@ -508,6 +508,32 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
Stream<T> stream = matchingBeans.values().stream();
return stream.sorted(adaptOrderComparator(matchingBeans));
}
@SuppressWarnings("unchecked")
@Override
public Stream<T> stream(Predicate<Class<?>> customFilter) {
return Arrays.stream(getBeanNamesForTypedStream(requiredType, allowEagerInit))
.filter(name -> customFilter.test(getType(name)))
.map(name -> (T) getBean(name))
.filter(bean -> !(bean instanceof NullBean));
}
@SuppressWarnings("unchecked")
@Override
public Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
String[] beanNames = getBeanNamesForTypedStream(requiredType, allowEagerInit);
if (beanNames.length == 0) {
return Stream.empty();
}
Map<String, T> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
for (String beanName : beanNames) {
if (customFilter.test(getType(beanName))) {
Object beanInstance = getBean(beanName);
if (!(beanInstance instanceof NullBean)) {
matchingBeans.put(beanName, (T) beanInstance);
}
}
}
return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans));
}
};
}
@@ -1089,8 +1115,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
"without bootstrap executor configured - falling back to mainline initialization");
}
}
if (!mbd.isLazyInit()) {
instantiateSingleton(beanName);
try {
instantiateSingleton(beanName);
}
catch (BeanCurrentlyInCreationException ex) {
logger.info("Bean '" + beanName + "' marked for pre-instantiation (not lazy-init) " +
"but currently initialized by other thread - skipping it in mainline thread");
}
}
return null;
}
@@ -1464,6 +1497,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (candidateName == null) {
candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
}
if (candidateName == null) {
candidateName = determineDefaultCandidate(candidates);
}
if (candidateName != null) {
Object beanInstance = candidates.get(candidateName);
if (beanInstance == null) {
@@ -1885,8 +1921,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
candidates.put(candidateName, beanInstance);
}
}
else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor streamDescriptor &&
streamDescriptor.isOrdered())) {
else if (containsSingleton(candidateName) ||
(descriptor instanceof StreamDependencyDescriptor streamDescriptor && streamDescriptor.isOrdered())) {
Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance));
}
@@ -1934,7 +1970,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (priorityCandidate != null) {
return priorityCandidate;
}
// Step 4: pick directly registered dependency
// Step 4: pick unique default-candidate
String defaultCandidate = determineDefaultCandidate(candidates);
if (defaultCandidate != null) {
return defaultCandidate;
}
// Step 5: pick directly registered dependency
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey();
Object beanInstance = entry.getValue();
@@ -2095,6 +2136,28 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return null;
}
/**
* Return a unique "default-candidate" among remaining non-default candidates.
* @param candidates a Map of candidate names and candidate instances
* (or candidate classes if not created yet) that match the required type
* @return the name of the default candidate, or {@code null} if none found
* @since 6.2.4
* @see AbstractBeanDefinition#isDefaultCandidate()
*/
@Nullable
private String determineDefaultCandidate(Map<String, Object> candidates) {
String defaultBeanName = null;
for (String candidateBeanName : candidates.keySet()) {
if (AutowireUtils.isDefaultCandidate(this, candidateBeanName)) {
if (defaultBeanName != null) {
return null;
}
defaultBeanName = candidateBeanName;
}
}
return defaultBeanName;
}
/**
* Determine whether the given candidate name matches the bean name or the aliases
* stored in this bean definition.
@@ -2479,6 +2542,34 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
return (result instanceof Stream stream ? stream : Stream.of(result));
}
@Override
public Stream<Object> stream(Predicate<Class<?>> customFilter) {
return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true))
.filter(name -> AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, name))
.filter(name -> customFilter.test(getType(name)))
.map(name -> getBean(name))
.filter(bean -> !(bean instanceof NullBean));
}
@Override
public Stream<Object> orderedStream(Predicate<Class<?>> customFilter) {
String[] beanNames = getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true);
if (beanNames.length == 0) {
return Stream.empty();
}
Map<String, Object> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
for (String beanName : beanNames) {
if (AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, beanName) &&
customFilter.test(getType(beanName))) {
Object beanInstance = getBean(beanName);
if (!(beanInstance instanceof NullBean)) {
matchingBeans.put(beanName, beanInstance);
}
}
}
return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@@ -100,6 +101,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
/** Names of beans currently excluded from in creation checks. */
private final Set<String> inCreationCheckExclusions = ConcurrentHashMap.newKeySet(16);
/** Specific lock for lenient creation tracking. */
private final Lock lenientCreationLock = new ReentrantLock();
/** Specific lock condition for lenient creation tracking. */
private final Condition lenientCreationFinished = this.lenientCreationLock.newCondition();
/** Names of beans that are currently in lenient creation. */
private final Set<String> singletonsInLenientCreation = new HashSet<>();
/** Flag that indicates whether we're currently within destroySingletons. */
private volatile boolean singletonsCurrentlyInDestruction = false;
@@ -257,6 +267,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
Thread.currentThread().getName() + "\" while other thread holds " +
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
}
this.lenientCreationLock.lock();
try {
this.singletonsInLenientCreation.add(beanName);
}
finally {
this.lenientCreationLock.unlock();
}
}
else {
// No specific locking indication (outside a coordinated bootstrap) and
@@ -279,15 +296,57 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
beforeSingletonCreation(beanName);
try {
beforeSingletonCreation(beanName);
}
catch (BeanCurrentlyInCreationException ex) {
this.lenientCreationLock.lock();
try {
while ((singletonObject = this.singletonObjects.get(beanName)) == null) {
if (!this.singletonsInLenientCreation.contains(beanName)) {
break;
}
try {
this.lenientCreationFinished.await();
}
catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
finally {
this.lenientCreationLock.unlock();
}
if (singletonObject != null) {
return singletonObject;
}
if (locked) {
throw ex;
}
// Try late locking for waiting on specific bean to be finished.
this.singletonLock.lock();
locked = true;
// Lock-created singleton object should have appeared in the meantime.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
beforeSingletonCreation(beanName);
}
boolean newSingleton = false;
boolean recordSuppressedExceptions = (locked && this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
// Leniently created singleton object could have appeared in the meantime.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
@@ -311,8 +370,18 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
try {
addSingleton(beanName, singletonObject);
}
catch (IllegalStateException ex) {
// Leniently accept same instance if implicitly appeared.
Object object = this.singletonObjects.get(beanName);
if (singletonObject != object) {
throw ex;
}
}
}
}
return singletonObject;
@@ -321,6 +390,14 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
if (locked) {
this.singletonLock.unlock();
}
this.lenientCreationLock.lock();
try {
this.singletonsInLenientCreation.remove(beanName);
this.lenientCreationFinished.signalAll();
}
finally {
this.lenientCreationLock.unlock();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,13 @@
package org.springframework.beans.factory.support;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.lang.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* {@link AutowireCandidateResolver} implementation to use when no annotation
@@ -36,46 +40,6 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
*/
public static final SimpleAutowireCandidateResolver INSTANCE = new SimpleAutowireCandidateResolver();
@Override
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
return bdHolder.getBeanDefinition().isAutowireCandidate();
}
@Override
public boolean isRequired(DependencyDescriptor descriptor) {
return descriptor.isRequired();
}
@Override
public boolean hasQualifier(DependencyDescriptor descriptor) {
return false;
}
@Override
@Nullable
public String getSuggestedName(DependencyDescriptor descriptor) {
return null;
}
@Override
@Nullable
public Object getSuggestedValue(DependencyDescriptor descriptor) {
return null;
}
@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
return null;
}
@Override
@Nullable
public Class<?> getLazyResolutionProxyClass(DependencyDescriptor descriptor, @Nullable String beanName) {
return null;
}
/**
* This implementation returns {@code this} as-is.
* @see #INSTANCE
@@ -85,4 +49,35 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
return this;
}
/**
* Resolve a map of all beans of the given type, also picking up beans defined in
* ancestor bean factories, with the specific condition that each bean actually
* has autowire candidate status. This matches simple injection point resolution
* as implemented by this {@link AutowireCandidateResolver} strategy, including
* beans which are not marked as default candidates but excluding beans which
* are not even marked as autowire candidates.
* @param lbf the bean factory
* @param type the type of bean to match
* @return the Map of matching bean instances, or an empty Map if none
* @throws BeansException if a bean could not be created
* @since 6.2.3
* @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class)
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
* @see AbstractBeanDefinition#isDefaultCandidate()
*/
@SuppressWarnings("unchecked")
public static <T> Map<String, T> resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class<T> type) {
Map<String, T> candidates = new LinkedHashMap<>();
for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type)) {
if (AutowireUtils.isAutowireCandidate(lbf, beanName)) {
Object beanInstance = lbf.getBean(beanName);
if (!(beanInstance instanceof NullBean)) {
candidates.put(beanName, (T) beanInstance);
}
}
}
return candidates;
}
}
@@ -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.
@@ -135,4 +135,12 @@ public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
}
}
/**
* Indicate the use of {@link PropertyEditorRegistrySupport#overrideDefaultEditor} above.
*/
@Override
public boolean overridesDefaultEditors() {
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.
@@ -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();
@@ -1395,6 +1413,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();
@@ -1409,6 +1430,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");
@@ -1428,6 +1452,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");
}
@@ -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.
@@ -103,6 +103,27 @@ class BeanWrapperAutoGrowingTests {
assertThat(bean.getThreeDimensionalArray()[1][2][3]).isInstanceOf(Bean.class);
}
@Test
void getPropertyValueAutoGrow3dArrayList() {
assertThat(wrapper.getPropertyValue("threeDimensionalArrayList[1][2][3][4]")).isNotNull();
assertThat(bean.getThreeDimensionalArrayList()).hasSize(2);
assertThat(bean.getThreeDimensionalArrayList().get(1)).hasNumberOfRows(3);
assertThat(bean.getThreeDimensionalArrayList().get(1)[2]).hasNumberOfRows(4);
assertThat(bean.getThreeDimensionalArrayList().get(1)[2][3]).hasSize(5);
assertThat(bean.getThreeDimensionalArrayList().get(1)[2][3][4]).isInstanceOf(Bean.class);
}
@Test
void getPropertyValueAutoGrow3dArrayListForDefault3dArray() {
assertThat(wrapper.getPropertyValue("threeDimensionalArrayList[0]")).isNotNull();
assertThat(bean.getThreeDimensionalArrayList()).hasSize(1);
// Default 3-dimensional array should be [[[]]]
assertThat(bean.getThreeDimensionalArrayList().get(0)).hasNumberOfRows(1);
assertThat(bean.getThreeDimensionalArrayList().get(0)[0]).hasNumberOfRows(1);
assertThat(bean.getThreeDimensionalArrayList().get(0)[0][0]).isEmpty();
}
@Test
void setPropertyValueAutoGrow2dArray() {
Bean newBean = new Bean();
@@ -123,6 +144,16 @@ class BeanWrapperAutoGrowingTests {
.extracting(Bean::getProp).isEqualTo("enigma");
}
@Test
void setPropertyValueAutoGrow3dArrayList() {
Bean newBean = new Bean();
newBean.setProp("enigma");
wrapper.setPropertyValue("threeDimensionalArrayList[0][1][2][3]", newBean);
assertThat(bean.getThreeDimensionalArrayList().get(0)[1][2][3])
.isInstanceOf(Bean.class)
.extracting(Bean::getProp).isEqualTo("enigma");
}
@Test
void getPropertyValueAutoGrowList() {
assertThat(wrapper.getPropertyValue("list[0]")).isNotNull();
@@ -215,6 +246,8 @@ class BeanWrapperAutoGrowingTests {
private Bean[][][] threeDimensionalArray;
private List<Bean[][][]> threeDimensionalArrayList;
private List<Bean> list;
private List<List<Bean>> nestedList;
@@ -269,6 +302,14 @@ class BeanWrapperAutoGrowingTests {
this.threeDimensionalArray = threeDimensionalArray;
}
public List<Bean[][][]> getThreeDimensionalArrayList() {
return threeDimensionalArrayList;
}
public void setThreeDimensionalArrayList(List<Bean[][][]> threeDimensionalArrayList) {
this.threeDimensionalArrayList = threeDimensionalArrayList;
}
public List<Bean> getList() {
return list;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Juergen Hoeller
@@ -56,9 +55,6 @@ class BeanFactoryLockingTests {
@Override
public void afterPropertiesSet() throws Exception {
Thread thread = new Thread(() -> {
// Fail for circular reference from other thread
assertThatExceptionOfType(BeanCurrentlyInCreationException.class).isThrownBy(() ->
beanFactory.getBean(ThreadDuringInitialization.class));
// Leniently create unrelated other bean outside of singleton lock
assertThat(beanFactory.getBean(TestBean.class).getName()).isEqualTo("tb");
// Creation attempt in other thread was successful
@@ -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.
@@ -113,6 +113,7 @@ import static org.mockito.Mockito.verify;
* @author Chris Beams
* @author Phillip Webb
* @author Stephane Nicoll
* @author Yanming Zhou
*/
class DefaultListableBeanFactoryTests {
@@ -1515,12 +1516,16 @@ class DefaultListableBeanFactoryTests {
bd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.LOWEST_PRECEDENCE);
lbf.registerBeanDefinition("bean1", bd1);
GenericBeanDefinition bd2 = new GenericBeanDefinition();
bd2.setBeanClass(TestBean.class);
bd2.setBeanClass(DerivedTestBean.class);
bd2.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "highest"))));
bd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE);
lbf.registerBeanDefinition("bean2", bd2);
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName))
.containsExactly("highest", "lowest");
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName))
.containsExactly("highest", "lowest");
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz))
.map(TestBean::getName)).containsExactly("lowest");
}
@Test
@@ -1540,6 +1545,8 @@ class DefaultListableBeanFactoryTests {
lbf.registerBeanDefinition("bean2", bd2);
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName))
.containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean");
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName))
.containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean");
}
@Test
@@ -1652,12 +1659,94 @@ class DefaultListableBeanFactoryTests {
bd2.setPrimary(true);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
lbf.registerSingleton("bd3", new TestBean());
TestBean bean = lbf.getBean(TestBean.class);
assertThat(bean.getBeanName()).isEqualTo("bd2");
assertThat(lbf.containsSingleton("bd1")).isFalse();
}
@Test
void getBeanByTypeWithUniqueNonFallbackDefinition() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
bd1.setLazyInit(true);
bd1.setFallback(true);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
bd2.setFallback(true);
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
lbf.registerBeanDefinition("bd3", bd3);
TestBean bean = lbf.getBean(TestBean.class);
assertThat(bean.getBeanName()).isEqualTo("bd3");
assertThat(lbf.containsSingleton("bd1")).isFalse();
}
@Test
void getBeanByTypeWithPrimaryAndUniqueNonFallbackDefinition() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
bd1.setLazyInit(true);
bd1.setFallback(true);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
bd2.setPrimary(true);
bd2.setFallback(true);
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
lbf.registerBeanDefinition("bd3", bd3);
TestBean bean = lbf.getBean(TestBean.class);
assertThat(bean.getBeanName()).isEqualTo("bd2");
assertThat(lbf.containsSingleton("bd1")).isFalse();
}
@Test
void getBeanByTypeWithUniqueNonFallbackAndUniqueNonDefaultDefinition() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
bd1.setLazyInit(true);
bd1.setFallback(true);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
bd2.setFallback(true);
bd2.setDefaultCandidate(false);
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
bd3.setDefaultCandidate(false);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
lbf.registerBeanDefinition("bd3", bd3);
TestBean bean = lbf.getBean(TestBean.class);
assertThat(bean.getBeanName()).isEqualTo("bd3");
assertThat(lbf.containsSingleton("bd1")).isFalse();
}
@Test
void getBeanByTypeWithUniqueNonDefaultDefinition() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
bd1.setDefaultCandidate(false);
bd1.setLazyInit(true);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
TestBean bean = lbf.getBean(TestBean.class);
assertThat(bean.getBeanName()).isEqualTo("bd2");
assertThat(lbf.containsSingleton("bd1")).isFalse();
}
@Test
void getBeanByTypeWithUniqueNonDefaultSingleton() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
bd1.setDefaultCandidate(false);
bd1.setLazyInit(true);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerSingleton("bd2", new TestBean());
TestBean bean = lbf.getBean(TestBean.class);
assertThat(bean.getBeanName()).isNull();
assertThat(lbf.containsSingleton("bd1")).isFalse();
}
@Test
@SuppressWarnings("rawtypes")
void getFactoryBeanByTypeWithPrimary() {
@@ -1934,6 +2023,11 @@ class DefaultListableBeanFactoryTests {
assertThat(resolved).hasSize(2);
assertThat(resolved).contains(lbf.getBean("bd1"));
assertThat(resolved).contains(lbf.getBean("bd2"));
resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet());
assertThat(resolved).hasSize(2);
assertThat(resolved).contains(lbf.getBean("bd1"));
assertThat(resolved).contains(lbf.getBean("bd2"));
}
@Test
@@ -1983,6 +2077,11 @@ class DefaultListableBeanFactoryTests {
assertThat(resolved).hasSize(2);
assertThat(resolved).contains(lbf.getBean("bd1"));
assertThat(resolved).contains(lbf.getBean("bd2"));
resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet());
assertThat(resolved).hasSize(2);
assertThat(resolved).contains(lbf.getBean("bd1"));
assertThat(resolved).contains(lbf.getBean("bd2"));
}
@Test
@@ -2378,11 +2477,20 @@ class DefaultListableBeanFactoryTests {
parentBf.registerBeanDefinition("highPriorityTestBean", bd2);
ObjectProvider<TestBean> testBeanProvider = lbf.getBeanProvider(ResolvableType.forClass(TestBean.class));
List<TestBean> resolved = testBeanProvider.orderedStream().toList();
assertThat(resolved).containsExactly(
assertThat(testBeanProvider.orderedStream()).containsExactly(
lbf.getBean("highPriorityTestBean", TestBean.class),
lbf.getBean("lowPriorityTestBean", TestBean.class),
lbf.getBean("plainTestBean", TestBean.class));
assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly(
lbf.getBean("highPriorityTestBean", TestBean.class),
lbf.getBean("lowPriorityTestBean", TestBean.class));
assertThat(testBeanProvider.stream()).containsExactly(
lbf.getBean("plainTestBean", TestBean.class),
lbf.getBean("lowPriorityTestBean", TestBean.class),
lbf.getBean("highPriorityTestBean", TestBean.class));
assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly(
lbf.getBean("lowPriorityTestBean", TestBean.class),
lbf.getBean("highPriorityTestBean", TestBean.class));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
@@ -64,6 +65,8 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.beans.testfixture.beans.DerivedTestBean;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.IndexedTestBean;
import org.springframework.beans.testfixture.beans.NestedTestBean;
@@ -1605,7 +1608,11 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
testBeans = bean.streamTestBeans();
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
testBeans = bean.sortedTestBeans();
testBeans = bean.streamTestBeansInOrder();
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
testBeans = bean.allTestBeans();
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
testBeans = bean.allTestBeansInOrder();
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
}
@@ -1632,7 +1639,13 @@ class AutowiredAnnotationBeanPostProcessorTests {
testBeans = bean.streamTestBeans();
assertThat(testBeans).hasSize(1);
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
testBeans = bean.sortedTestBeans();
testBeans = bean.streamTestBeansInOrder();
assertThat(testBeans).hasSize(1);
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
testBeans = bean.allTestBeans();
assertThat(testBeans).hasSize(1);
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
testBeans = bean.allTestBeansInOrder();
assertThat(testBeans).hasSize(1);
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
}
@@ -1656,7 +1669,11 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(testBeans).isEmpty();
testBeans = bean.streamTestBeans();
assertThat(testBeans).isEmpty();
testBeans = bean.sortedTestBeans();
testBeans = bean.streamTestBeansInOrder();
assertThat(testBeans).isEmpty();
testBeans = bean.allTestBeans();
assertThat(testBeans).isEmpty();
testBeans = bean.allTestBeansInOrder();
assertThat(testBeans).isEmpty();
}
@@ -1678,7 +1695,9 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.iterateTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.forEachTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.streamTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.sortedTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.streamTestBeansInOrder()).containsExactly(testBean1, testBean2);
assertThat(bean.allTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.allTestBeansInOrder()).containsExactly(testBean1, testBean2);
}
@Test
@@ -1706,7 +1725,9 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.iterateTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.forEachTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.streamTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.sortedTestBeans()).containsExactly(testBean2, testBean1);
assertThat(bean.streamTestBeansInOrder()).containsExactly(testBean2, testBean1);
assertThat(bean.allTestBeans()).containsExactly(testBean1, testBean2);
assertThat(bean.allTestBeansInOrder()).containsExactly(testBean2, testBean1);
}
@Test
@@ -1722,8 +1743,57 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("testBean2", tb2);
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
assertThat(bean.sortedTestBeans()).containsExactly(bf.getBean("testBean2", TestBean.class),
assertThat(bean.streamTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
bf.getBean("testBean1", TestBean.class));
assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
bf.getBean("testBean1", TestBean.class));
}
@Test
void objectProviderInjectionWithNonCandidatesInStream() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
RootBeanDefinition tb1 = new RootBeanDefinition(TestBeanFactory.class);
tb1.setFactoryMethodName("newTestBean1");
bf.registerBeanDefinition("testBean1", tb1);
RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class);
tb2.setFactoryMethodName("newTestBean2");
bf.registerBeanDefinition("testBean2", tb2);
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
RootBeanDefinition tb3 = new RootBeanDefinition(TestBean.class);
tb3.setAutowireCandidate(false);
tb3.setLazyInit(true);
parent.registerBeanDefinition("testBean3", tb3);
RootBeanDefinition tb4 = new RootBeanDefinition(DerivedTestBean.class);
tb4.setDefaultCandidate(false);
tb4.setLazyInit(true);
parent.registerBeanDefinition("testBean4", tb4);
bf.setParentBeanFactory(parent);
RootBeanDefinition tb5 = new RootBeanDefinition(NullFactoryMethods.class);
tb5.setFactoryMethodName("createTestBean");
bf.registerBeanDefinition("testBean5", tb5);
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
bf.getBean("testBean2", TestBean.class));
assertThat(bean.streamTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
bf.getBean("testBean1", TestBean.class));
assertThat(bf.containsSingleton("testBean3")).isFalse();
assertThat(bean.plainTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
bf.getBean("testBean2", TestBean.class));
assertThat(bean.plainTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
bf.getBean("testBean1", TestBean.class));
assertThat(bf.containsSingleton("testBean4")).isFalse();
assertThat(bean.allTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
bf.getBean("testBean2", TestBean.class), bf.getBean("testBean4", TestBean.class));
assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
bf.getBean("testBean1", TestBean.class), bf.getBean("testBean4", TestBean.class));
Map<String, TestBean> typeMatches = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, TestBean.class);
assertThat(typeMatches.remove("testBean3")).isNotNull();
Map<String, TestBean> candidates = SimpleAutowireCandidateResolver.resolveAutowireCandidates(bf, TestBean.class);
assertThat(candidates).containsExactlyEntriesOf(typeMatches);
}
@Test
@@ -3304,9 +3374,25 @@ class AutowiredAnnotationBeanPostProcessorTests {
return this.testBean.stream().toList();
}
public List<TestBean> sortedTestBeans() {
public List<TestBean> streamTestBeansInOrder() {
return this.testBean.orderedStream().toList();
}
public List<TestBean> plainTestBeans() {
return this.testBean.stream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)).toList();
}
public List<TestBean> plainTestBeansInOrder() {
return this.testBean.orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)).toList();
}
public List<TestBean> allTestBeans() {
return this.testBean.stream(ObjectProvider.UNFILTERED).toList();
}
public List<TestBean> allTestBeansInOrder() {
return this.testBean.orderedStream(ObjectProvider.UNFILTERED).toList();
}
}
@@ -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,10 +50,15 @@ class DefaultSingletonBeanRegistryTests {
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
assertThat(tb2Flag.get()).isTrue();
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
assertThat(beanRegistry.getSingletonCount()).isEqualTo(2);
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb", "tb2");
TestBean tb3 = (TestBean) beanRegistry.getSingleton("tb3", () -> {
TestBean newTb = new TestBean();
beanRegistry.registerSingleton("tb3", newTb);
return newTb;
});
assertThat(beanRegistry.getSingleton("tb3")).isSameAs(tb3);
assertThat(beanRegistry.getSingletonCount()).isEqualTo(3);
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb", "tb2", "tb3");
beanRegistry.destroySingletons();
assertThat(beanRegistry.getSingletonCount()).isZero();
@@ -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-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.
@@ -203,6 +203,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);
}
@@ -223,8 +232,9 @@ final class ConfigurationClass {
void validate(ProblemReporter problemReporter) {
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods") && this.metadata.isFinal()) {
// A configuration class may not be final (CGLIB limitation) unless it does not have to proxy bean methods
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods") && hasNonStaticBeanMethods() &&
this.metadata.isFinal()) {
problemReporter.error(new FinalConfigurationProblem());
}
@@ -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.
@@ -109,8 +109,15 @@ class ConfigurationClassEnhancer {
}
return configClass;
}
try {
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
// Use original ClassLoader if config class not locally loaded in overriding class loader
boolean classLoaderMismatch = (classLoader != null && classLoader != configClass.getClassLoader());
if (classLoaderMismatch && classLoader instanceof SmartClassLoader smartClassLoader) {
classLoader = smartClassLoader.getOriginalClassLoader();
}
Enhancer enhancer = newEnhancer(configClass, classLoader);
Class<?> enhancedClass = createClass(enhancer, classLoaderMismatch);
if (logger.isTraceEnabled()) {
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
configClass.getName(), enhancedClass.getName()));
@@ -129,6 +136,9 @@ class ConfigurationClassEnhancer {
*/
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
}
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
@@ -152,8 +162,21 @@ class ConfigurationClassEnhancer {
* Uses enhancer to generate a subclass of superclass,
* ensuring that callbacks are registered for the new subclass.
*/
private Class<?> createClass(Enhancer enhancer) {
Class<?> subclass = enhancer.createClass();
private Class<?> createClass(Enhancer enhancer, boolean fallback) {
Class<?> subclass;
try {
subclass = enhancer.createClass();
}
catch (Throwable ex) {
if (!fallback) {
throw (ex instanceof CodeGenerationException cgex ? cgex : new CodeGenerationException(ex));
}
// Possibly a package-visible @Bean method declaration not accessible
// in the given ClassLoader -> retry with original ClassLoader
enhancer.setClassLoader(null);
subclass = enhancer.createClass();
}
// Registering callbacks statically (as opposed to thread-local)
// is critical for usage in an OSGi environment (SPR-5932)...
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
@@ -164,8 +187,7 @@ class ConfigurationClassEnhancer {
/**
* Marker interface to be implemented by all @Configuration CGLIB subclasses.
* Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance}
* through checking to see if candidate classes are already assignable to it, for example,
* have already been enhanced.
* through checking to see if candidate classes are already assignable to it.
* <p>Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration}
* classes require access to the {@link BeanFactory} that created them.
* <p>Note that this interface is intended for framework-internal use only, however
@@ -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.
@@ -167,14 +167,22 @@ class ConfigurationClassParser {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
ConfigurationClass configClass;
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
parse(annotatedBeanDef, holder.getBeanName());
configClass = parse(annotatedBeanDef, holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
configClass = parse(bd.getBeanClassName(), holder.getBeanName());
}
// Downgrade to lite (no enhancement) in case of no instance-level @Bean methods.
if (!configClass.hasNonStaticBeanMethods() && ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(
bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) {
bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE,
ConfigurationClassUtils.CONFIGURATION_CLASS_LITE);
}
}
catch (BeanDefinitionStoreException ex) {
@@ -189,20 +197,25 @@ class ConfigurationClassParser {
this.deferredImportSelectorHandler.process();
}
private void parse(AnnotatedBeanDefinition beanDef, String beanName) {
processConfigurationClass(
new ConfigurationClass(beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition)),
DEFAULT_EXCLUSION_FILTER);
private ConfigurationClass parse(AnnotatedBeanDefinition beanDef, String beanName) {
ConfigurationClass configClass = new ConfigurationClass(
beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition));
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
return configClass;
}
private void parse(Class<?> clazz, String beanName) {
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
private ConfigurationClass parse(Class<?> clazz, String beanName) {
ConfigurationClass configClass = new ConfigurationClass(clazz, beanName);
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
return configClass;
}
final void parse(@Nullable String className, String beanName) throws IOException {
final ConfigurationClass parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
ConfigurationClass configClass = new ConfigurationClass(reader, beanName);
processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
return configClass;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.scheduling.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
@@ -53,6 +54,7 @@ public class ExecutorBeanDefinitionParser extends AbstractSingleBeanDefinitionPa
if (StringUtils.hasText(poolSize)) {
builder.addPropertyValue("poolSize", poolSize);
}
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}
private void configureRejectionPolicy(Element element, BeanDefinitionBuilder builder) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.scheduling.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
@@ -41,6 +42,7 @@ public class SchedulerBeanDefinitionParser extends AbstractSingleBeanDefinitionP
if (StringUtils.hasText(poolSize)) {
builder.addPropertyValue("poolSize", poolSize);
}
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}
}
@@ -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.
@@ -317,8 +317,16 @@ final class QuartzCronField extends CronField {
private static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) {
TemporalAdjuster adjuster = TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek);
return temporal -> {
Temporal result = adjuster.adjustInto(temporal);
return rollbackToMidnight(temporal, result);
// TemporalAdjusters can overflow to a different month
// in this case, attempt the same adjustment with the next/previous month
for (int i = 0; i < 12; i++) {
Temporal result = adjuster.adjustInto(temporal);
if (result.get(ChronoField.MONTH_OF_YEAR) == temporal.get(ChronoField.MONTH_OF_YEAR)) {
return rollbackToMidnight(temporal, result);
}
temporal = result;
}
return null;
};
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -142,6 +142,10 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
*/
protected static final Log logger = LogFactory.getLog(DataBinder.class);
/** Internal constant for constructor binding via "[]". */
private static final int NO_INDEX = -1;
@Nullable
private Object target;
@@ -964,7 +968,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
value = createMap(paramPath, paramType, resolvableType, valueResolver);
}
else if (paramType.isArray()) {
value = createArray(paramPath, resolvableType, valueResolver);
value = createArray(paramPath, paramType, resolvableType, valueResolver);
}
}
@@ -981,11 +985,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
}
catch (TypeMismatchException ex) {
ex.initPropertyName(paramPath);
args[i] = null;
failedParamNames.add(paramPath);
getBindingResult().recordFieldValue(paramPath, paramType, value);
getBindingErrorProcessor().processPropertyAccessException(ex, getBindingResult());
handleTypeMismatchException(ex, paramPath, paramType, value);
}
}
}
@@ -1048,9 +1050,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return false;
}
@SuppressWarnings("unchecked")
@Nullable
private <V> List<V> createList(
private List<?> createList(
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
ResolvableType elementType = type.getNested(2);
@@ -1058,16 +1059,23 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
if (indexes == null) {
return null;
}
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0);
List<V> list = (List<V>) CollectionFactory.createCollection(paramType, size);
indexes.forEach(i -> list.add(null));
for (int index : indexes) {
list.set(index, (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver));
int lastIndex = Math.max(indexes.last(), 0);
int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1 : 0);
List<?> list = (List<?>) CollectionFactory.createCollection(paramType, size);
for (int i = 0; i < size; i++) {
list.add(null);
}
for (int index : indexes) {
String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]";
list.set(Math.max(index, 0),
createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
}
return list;
}
@SuppressWarnings("unchecked")
@Nullable
private <V> Map<String, V> createMap(
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
@@ -1078,34 +1086,44 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
if (!name.startsWith(paramPath + "[")) {
continue;
}
int startIdx = paramPath.length() + 1;
int endIdx = name.indexOf(']', startIdx);
String nestedPath = name.substring(0, endIdx + 2);
boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\'');
String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx));
if (map == null) {
map = CollectionFactory.createMap(paramType, 16);
}
if (!map.containsKey(key)) {
map.put(key, (V) createObject(elementType, nestedPath, valueResolver));
}
String indexedPath = name.substring(0, endIdx + 1);
map.put(key, createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
}
return map;
}
@SuppressWarnings("unchecked")
@Nullable
private <V> V[] createArray(String paramPath, ResolvableType type, ValueResolver valueResolver) {
private <V> V[] createArray(
String paramPath, Class<?> paramType, ResolvableType type, ValueResolver valueResolver) {
ResolvableType elementType = type.getNested(2);
SortedSet<Integer> indexes = getIndexes(paramPath, valueResolver);
if (indexes == null) {
return null;
}
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1: 0);
int lastIndex = Math.max(indexes.last(), 0);
int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1: 0);
V[] array = (V[]) Array.newInstance(elementType.resolve(), size);
for (int index : indexes) {
array[index] = (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver);
String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]";
array[Math.max(index, 0)] =
createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver);
}
return array;
}
@@ -1114,9 +1132,18 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
SortedSet<Integer> indexes = null;
for (String name : valueResolver.getNames()) {
if (name.startsWith(paramPath + "[")) {
int endIndex = name.indexOf(']', paramPath.length() + 2);
String rawIndex = name.substring(paramPath.length() + 1, endIndex);
int index = Integer.parseInt(rawIndex);
int index;
if (paramPath.length() + 2 == name.length()) {
if (!name.endsWith("[]")) {
continue;
}
index = NO_INDEX;
}
else {
int endIndex = name.indexOf(']', paramPath.length() + 2);
String indexValue = name.substring(paramPath.length() + 1, endIndex);
index = Integer.parseInt(indexValue);
}
indexes = (indexes != null ? indexes : new TreeSet<>());
indexes.add(index);
}
@@ -1124,6 +1151,50 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return indexes;
}
@SuppressWarnings("unchecked")
@Nullable
private <V> V createIndexedValue(
String paramPath, Class<?> containerType, ResolvableType elementType,
String indexedPath, ValueResolver valueResolver) {
Object value = null;
Class<?> elementClass = elementType.resolve(Object.class);
if (List.class.isAssignableFrom(elementClass)) {
value = createList(indexedPath, elementClass, elementType, valueResolver);
}
else if (Map.class.isAssignableFrom(elementClass)) {
value = createMap(indexedPath, elementClass, elementType, valueResolver);
}
else if (elementClass.isArray()) {
value = createArray(indexedPath, elementClass, elementType, valueResolver);
}
else {
Object rawValue = valueResolver.resolveValue(indexedPath, elementClass);
if (rawValue != null) {
try {
value = convertIfNecessary(rawValue, elementClass);
}
catch (TypeMismatchException ex) {
handleTypeMismatchException(ex, paramPath, containerType, rawValue);
}
}
else {
value = createObject(elementType, indexedPath + ".", valueResolver);
}
}
return (V) value;
}
private void handleTypeMismatchException(
TypeMismatchException ex, String paramPath, Class<?> paramType, @Nullable Object value) {
ex.initPropertyName(paramPath);
getBindingResult().recordFieldValue(paramPath, paramType, value);
getBindingErrorProcessor().processPropertyAccessException(ex, getBindingResult());
}
private void validateConstructorArgument(
Class<?> constructorClass, String nestedPath, String name, @Nullable Object value) {
@@ -1410,6 +1481,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
/**
* Return the names of all property values.
* <p>Useful for proactive checks whether there are property values nested
* further below the path for a constructor arg. If not then the
* constructor arg can be considered missing and not to be instantiated.
* @since 6.1.2
*/
Set<String> getNames();
@@ -367,7 +367,7 @@ public class MethodValidationAdapter implements MethodValidator {
container = null;
}
if (node.getKind().equals(ElementKind.PROPERTY)) {
if (node.getKind().equals(ElementKind.PROPERTY) || node.getKind().equals(ElementKind.BEAN)) {
nestedViolations
.computeIfAbsent(parameterNode, k ->
new ParamErrorsBuilder(parameter, value, container, index, key))
@@ -75,7 +75,7 @@
The bean name of the KeyGenerator that is to be used to retrieve the backing caches.
This attribute is not required, and only needs to be specified
explicitly if the default strategy (DefaultKeyGenerator) is not sufficient.
explicitly if the default strategy (SimpleKeyGenerator) is not sufficient.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
@@ -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.
@@ -422,6 +422,7 @@ class CglibProxyTests extends AbstractAopProxyTests {
}
@Test // SPR-13328
@SuppressWarnings("unchecked")
void varargsWithEnumArray() {
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
MyBean proxy = (MyBean) proxyFactory.getProxy();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -141,6 +141,7 @@ class JdkDynamicProxyTests extends AbstractAopProxyTests {
}
@Test // SPR-13328
@SuppressWarnings("unchecked")
void varargsWithEnumArray() {
ProxyFactory proxyFactory = new ProxyFactory(new VarargTestBean());
VarargTestInterface proxy = (VarargTestInterface) proxyFactory.getProxy();
@@ -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.
@@ -168,6 +168,25 @@ class AnnotationBeanNameGeneratorTests {
assertGeneratedName(RestControllerAdviceClass.class, "myRestControllerAdvice");
}
@Test // gh-34317
void generateBeanNameFromStereotypeAnnotationWithStringValueAsExplicitAliasForMetaAnnotationOtherThanComponent() {
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithoutExplicitName".
assertGeneratedName(StereotypeWithoutExplicitName.class, "enigma");
}
@Test // gh-34317
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentNameWithBlankName() {
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithGeneratedName".
assertGeneratedName(StereotypeWithGeneratedName.class, "enigma");
}
@Test // gh-34317
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentName() {
assertGeneratedName(StereotypeWithExplicitName.class, "explicitName");
}
private void assertGeneratedName(Class<?> clazz, String expectedName) {
BeanDefinition bd = annotatedBeanDef(clazz);
@@ -210,7 +229,7 @@ class AnnotationBeanNameGeneratorTests {
@Retention(RetentionPolicy.RUNTIME)
@Component
@interface ConventionBasedComponent1 {
// This intentionally convention-based. Please do not add @AliasFor.
// This is intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default "";
}
@@ -218,7 +237,7 @@ class AnnotationBeanNameGeneratorTests {
@Retention(RetentionPolicy.RUNTIME)
@Component
@interface ConventionBasedComponent2 {
// This intentionally convention-based. Please do not add @AliasFor.
// This is intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default "";
}
@@ -260,7 +279,7 @@ class AnnotationBeanNameGeneratorTests {
@Target(ElementType.TYPE)
@Controller
@interface TestRestController {
// This intentionally convention-based. Please do not add @AliasFor.
// This is intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default "";
}
@@ -319,7 +338,6 @@ class AnnotationBeanNameGeneratorTests {
String[] basePackages() default {};
}
@TestControllerAdvice(basePackages = "com.example", name = "myControllerAdvice")
static class ControllerAdviceClass {
}
@@ -328,4 +346,56 @@ class AnnotationBeanNameGeneratorTests {
static class RestControllerAdviceClass {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@interface MetaAnnotationWithStringAttribute {
String attribute() default "";
}
/**
* Custom stereotype annotation which has a {@code String value} attribute that
* is explicitly declared as an alias for an attribute in a meta-annotation
* other than {@link Component @Component}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
@MetaAnnotationWithStringAttribute
@interface MyStereotype {
@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
String value() default "";
}
@MyStereotype("enigma")
static class StereotypeWithoutExplicitName {
}
/**
* Custom stereotype annotation which is identical to {@link MyStereotype @MyStereotype}
* except that it has a {@link #name} attribute that is an explicit alias for
* {@link Component#value}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
@MetaAnnotationWithStringAttribute
@interface MyNamedStereotype {
@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
String value() default "";
@AliasFor(annotation = Component.class, attribute = "value")
String name() default "";
}
@MyNamedStereotype(value = "enigma", name ="explicitName")
static class StereotypeWithExplicitName {
}
@MyNamedStereotype(value = "enigma")
static class StereotypeWithGeneratedName {
}
}
@@ -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.
@@ -19,6 +19,7 @@ package org.springframework.context.annotation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.testfixture.EnabledForTestGroups;
@@ -33,6 +34,38 @@ import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
*/
class BackgroundBootstrapTests {
@Test
@Timeout(5)
@EnabledForTestGroups(LONG_RUNNING)
void bootstrapWithUnmanagedThread() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadBeanConfig.class);
ctx.getBean("testBean1", TestBean.class);
ctx.getBean("testBean2", TestBean.class);
ctx.close();
}
@Test
@Timeout(5)
@EnabledForTestGroups(LONG_RUNNING)
void bootstrapWithUnmanagedThreads() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadsBeanConfig.class);
ctx.getBean("testBean1", TestBean.class);
ctx.getBean("testBean2", TestBean.class);
ctx.getBean("testBean3", TestBean.class);
ctx.getBean("testBean4", TestBean.class);
ctx.close();
}
@Test
@Timeout(5)
@EnabledForTestGroups(LONG_RUNNING)
void bootstrapWithCircularReference() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CircularReferenceBeanConfig.class);
ctx.getBean("testBean1", TestBean.class);
ctx.getBean("testBean2", TestBean.class);
ctx.close();
}
@Test
@Timeout(5)
@EnabledForTestGroups(LONG_RUNNING)
@@ -41,11 +74,109 @@ class BackgroundBootstrapTests {
ctx.getBean("testBean1", TestBean.class);
ctx.getBean("testBean2", TestBean.class);
ctx.getBean("testBean3", TestBean.class);
ctx.getBean("testBean4", TestBean.class);
ctx.close();
}
@Configuration
@Configuration(proxyBeanMethods = false)
static class UnmanagedThreadBeanConfig {
@Bean
public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
new Thread(testBean2::getObject).start();
try {
Thread.sleep(1000);
}
catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
return new TestBean();
}
@Bean
public TestBean testBean2() {
try {
Thread.sleep(2000);
}
catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
return new TestBean();
}
}
@Configuration(proxyBeanMethods = false)
static class UnmanagedThreadsBeanConfig {
@Bean
public TestBean testBean1(ObjectProvider<TestBean> testBean3, ObjectProvider<TestBean> testBean4) {
new Thread(testBean3::getObject).start();
new Thread(testBean4::getObject).start();
new Thread(testBean3::getObject).start();
new Thread(testBean4::getObject).start();
try {
Thread.sleep(1000);
}
catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
return new TestBean();
}
@Bean
public TestBean testBean2(TestBean testBean4) {
return new TestBean(testBean4);
}
@Bean
public TestBean testBean3(TestBean testBean4) {
return new TestBean(testBean4);
}
@Bean
public TestBean testBean4() {
try {
Thread.sleep(2000);
}
catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
return new TestBean();
}
}
@Configuration(proxyBeanMethods = false)
static class CircularReferenceBeanConfig {
@Bean
public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
new Thread(testBean2::getObject).start();
try {
Thread.sleep(1000);
}
catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
return new TestBean();
}
@Bean
public TestBean testBean2(TestBean testBean1) {
try {
Thread.sleep(2000);
}
catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
return new TestBean();
}
}
@Configuration(proxyBeanMethods = false)
static class CustomExecutorBeanConfig {
@Bean
@@ -58,7 +189,7 @@ class BackgroundBootstrapTests {
}
@Bean(bootstrap = BACKGROUND) @DependsOn("testBean3")
public TestBean testBean1(TestBean testBean3) throws InterruptedException{
public TestBean testBean1(TestBean testBean3) throws InterruptedException {
Thread.sleep(3000);
return new TestBean();
}
@@ -75,8 +206,8 @@ class BackgroundBootstrapTests {
}
@Bean
public String dependent(@Lazy TestBean testBean1, @Lazy TestBean testBean2, @Lazy TestBean testBean3) {
return "";
public TestBean testBean4(@Lazy TestBean testBean1, @Lazy TestBean testBean2, @Lazy TestBean testBean3) {
return new TestBean();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,16 @@ package org.springframework.context.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import org.junit.jupiter.api.Test;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,19 +41,108 @@ class ConfigurationClassEnhancerTests {
@Test
void enhanceReloadedClass() throws Exception {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
ClassLoader parentClassLoader = getClass().getClassLoader();
CustomClassLoader classLoader = new CustomClassLoader(parentClassLoader);
ClassLoader classLoader = new CustomSmartClassLoader(parentClassLoader);
Class<?> myClass = parentClassLoader.loadClass(MyConfig.class.getName());
configurationClassEnhancer.enhance(myClass, parentClassLoader);
Class<?> myReloadedClass = classLoader.loadClass(MyConfig.class.getName());
Class<?> enhancedReloadedClass = configurationClassEnhancer.enhance(myReloadedClass, classLoader);
assertThat(enhancedReloadedClass.getClassLoader()).isEqualTo(classLoader);
Class<?> enhancedClass = configurationClassEnhancer.enhance(myClass, parentClassLoader);
assertThat(myClass).isAssignableFrom(enhancedClass);
myClass = classLoader.loadClass(MyConfig.class.getName());
enhancedClass = configurationClassEnhancer.enhance(myClass, classLoader);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader);
assertThat(myClass).isAssignableFrom(enhancedClass);
}
@Test
void withPublicClass() {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader);
classLoader = new OverridingClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader);
assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
}
@Test
void withNonPublicClass() {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new OverridingClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicClass.class, classLoader);
assertThat(MyConfigWithNonPublicClass.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
}
@Test
void withNonPublicMethod() {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader);
classLoader = new OverridingClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader);
assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass);
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
}
@Configuration
static class MyConfig {
@Bean
String myBean() {
return "bean";
}
}
@Configuration
public static class MyConfigWithPublicClass {
@Bean
public String myBean() {
return "bean";
@@ -56,9 +150,29 @@ class ConfigurationClassEnhancerTests {
}
static class CustomClassLoader extends SecureClassLoader implements SmartClassLoader {
@Configuration
static class MyConfigWithNonPublicClass {
CustomClassLoader(ClassLoader parent) {
@Bean
public String myBean() {
return "bean";
}
}
@Configuration
public static class MyConfigWithNonPublicMethod {
@Bean
String myBean() {
return "bean";
}
}
static class CustomSmartClassLoader extends SecureClassLoader implements SmartClassLoader {
CustomSmartClassLoader(ClassLoader parent) {
super(parent);
}
@@ -82,6 +196,29 @@ class ConfigurationClassEnhancerTests {
public boolean isClassReloadable(Class<?> clazz) {
return clazz.getName().contains("MyConfig");
}
@Override
public ClassLoader getOriginalClassLoader() {
return getParent();
}
@Override
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
return defineClass(name, b, 0, b.length, protectionDomain);
}
}
static class BasicSmartClassLoader extends SecureClassLoader implements SmartClassLoader {
BasicSmartClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
return defineClass(name, b, 0, b.length, protectionDomain);
}
}
}
@@ -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.
@@ -67,6 +67,7 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -104,6 +105,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
@@ -118,6 +120,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
@@ -132,6 +135,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isNotSameAs(foo);
@@ -143,6 +147,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isNotSameAs(foo);
@@ -154,6 +159,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
Foo foo = beanFactory.getBean("foo", Foo.class);
@@ -167,6 +173,7 @@ class ConfigurationClassPostProcessorTests {
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass()).isTrue();
Foo foo = beanFactory.getBean("foo", Foo.class);
@@ -174,6 +181,15 @@ class ConfigurationClassPostProcessorTests {
assertThat(bar.foo).isNotSameAs(foo);
}
@Test
void enhancementIsNotPresentWithEmptyConfig() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(EmptyConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass()).isTrue();
assertThat(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).getBeanClass().getName()).doesNotContain(ClassUtils.CGLIB_CLASS_SEPARATOR);
}
@Test
void configurationIntrospectionOfInnerClassesWorksWithDotNameSyntax() {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(getClass().getName() + ".SingletonBeanConfig"));
@@ -1166,7 +1182,7 @@ class ConfigurationClassPostProcessorTests {
}
@Configuration
static class StaticSingletonBeanConfig {
static final class StaticSingletonBeanConfig {
@Bean
public static Foo foo() {
@@ -1179,6 +1195,10 @@ class ConfigurationClassPostProcessorTests {
}
}
@Configuration
static final class EmptyConfig {
}
@Configuration
@Order(2)
static class OverridingSingletonBeanConfig {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,16 +37,18 @@ class InvalidConfigurationClassDefinitionTests {
@Test
void configurationClassesMayNotBeFinal() {
@Configuration
final class Config { }
final class Config {
@Bean String dummy() { return "dummy"; }
}
BeanDefinition configBeanDef = rootBeanDefinition(Config.class).getBeanDefinition();
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("config", configBeanDef);
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() ->
pp.postProcessBeanFactory(beanFactory))
.withMessageContaining("Remove the final modifier");
assertThatExceptionOfType(BeanDefinitionParsingException.class)
.isThrownBy(() -> pp.postProcessBeanFactory(beanFactory))
.withMessageContaining("Remove the final modifier");
}
}

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