Compare commits

...
213 Commits
Author SHA1 Message Date
Brian Clozel 5024bb7227 Release v6.2.0 2024-11-14 16:43:48 +01:00
Simon Baslé 35b452b458 Upgrade to Undertow 2.3.18.Final, dispatch in UndertowHttpHandlerAdapter
This ensures that the reactive handling of the request is dispatched
from the Undertow IO thread, marking the exchange as async rather than
ending it once the Undertow `handleRequest` method returns.

Closes gh-33885
2024-11-14 16:14:57 +01:00
Brian Clozel 56525da43a Merge branch '6.1.x' 2024-11-14 15:29:49 +01:00
Brian Clozel 5145cf476f Next development version (v6.1.16-SNAPSHOT) 2024-11-14 15:28:50 +01:00
Juergen Hoeller d4f8b5d820 Merge branch '6.1.x'
# Conflicts:
#	framework-platform/framework-platform.gradle
2024-11-14 12:52:10 +01:00
Juergen Hoeller 6cb41dc5e3 Upgrade to Netty 4.1.115 2024-11-14 12:51:03 +01:00
Sam Brannen cca245020d Improve documentation for SpelCompilerMode
Closes gh-33223
2024-11-14 11:36:46 +01:00
rstoyanchev de8a94f30d Do not set up CookieStore in Apache connector
Closes gh-33822
2024-11-14 09:30:36 +00:00
Sam Brannen 973d815763 Merge branch '6.1.x' 2024-11-14 10:16:02 +01:00
Sam Brannen 1e95332f62 Fix link to "Resources" section in reference guide
Closes gh-33882
2024-11-14 10:15:34 +01:00
Juergen Hoeller 1c2dbec971 Merge branch '6.1.x' 2024-11-13 22:26:54 +01:00
Juergen Hoeller fec6ba4dfe Polishing 2024-11-13 22:26:06 +01:00
Juergen Hoeller 37b110a181 Polishing 2024-11-13 19:00:08 +01:00
Juergen Hoeller 18a3b52d5e Restore enum-style exception message expected by test 2024-11-13 18:56:19 +01:00
Sam Brannen b523f3caff Polish documentation and exception message for @⁠DurationFormat 2024-11-13 17:19:53 +01:00
Sam Brannen 7f9901d35d Merge branch '6.1.x' 2024-11-13 16:46:11 +01:00
Sam Brannen bfde33a514 Document options for Date/Time parsing & formatting issues with JDK 20+
This commit updates Javadoc and the reference guide to document options
for handling date/time parsing and formatting issues on JDK 20 and higher.

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

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

Closes gh-33151
2024-11-13 16:39:40 +01:00
Juergen Hoeller 25e2c1b7ce Merge branch '6.1.x'
# Conflicts:
#	framework-platform/framework-platform.gradle
#	spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java
2024-11-13 15:12:55 +01:00
Juergen Hoeller 14b9865de7 Remove ineffective JettyByteBufferIterator from WebSocket adapter
In JettyWebSocketHandlerAdapter, JettyByteBufferIterator does not actually add extra behavior (in contrast to JettyClientHttpConnector).
2024-11-13 15:10:32 +01:00
Juergen Hoeller 62eb21f938 Add note on declaring autowired fields as ObjectProvider
Closes gh-33834
2024-11-13 15:09:58 +01:00
Juergen Hoeller 01c85b1afb Add explicit note on blocking in case of concurrency limit
Closes gh-33873
2024-11-13 15:09:02 +01:00
Juergen Hoeller df376d9343 Upgrade to Micrometer 1.12.12 and Reactor 2023.0.12
Includes Netty 4.1.114, Jetty 12.0.15, Jetty Reactive HttpClient 4.0.8, RxJava 3.1.9, RSocket 1.1.4, Groovy 4.0.24, JRuby 9.4.9, Checkstyle 10.20.1

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

See gh-27426
Closes gh-33881
2024-11-13 14:16:14 +01:00
Juergen Hoeller 4ca596b7bc Upgrade to Micrometer 1.14.0 and Reactor 2024.0.0
Includes Netty 4.1.114, Jetty 12.0.15, Jetty Reactive HttpClient 4.0.8, RxJava 3.1.9, RSocket 1.1.4, HSQLDB 2.7.4, H2 2.3.232, SnakeYAML 2.3, Groovy 4.0.24, JRuby 9.4.9, Jackson 2.18.1, Gson 2.11, Protobuf 4.28.3, XStream 1.4.21, Woodstox 6.7, TestNG 7.10.2, Selenium 4.26, HtmlUnit 4.6, XMLUnit 2.10, Checkstyle 10.20.1

Closes gh-33876
Closes gh-33878
2024-11-13 13:34:43 +01:00
Juergen Hoeller 90ef7ac514 Cache singleton results for @Lazy injection points
Includes consistent use of unmodifiable collections.

Closes gh-33841
2024-11-13 13:29:20 +01:00
Sam Brannen c3991392df Merge branch '6.1.x' 2024-11-13 12:32:48 +01:00
Sam Brannen 0a5bd89129 Align JettyByteBufferIterator implementations
Both are now static nested classes.
2024-11-13 12:31:27 +01:00
Sébastien Deleuze 357dbc0354 Add AOT support for container element constraints
This commit introduces support for bean validation container
element constraints, including transitive ones.

Transitive constraints in the parameterized types of a container
are not discoverable via the BeanDescriptor, so a complementary
type discovery is done on Spring side to cover the related use
case.

Closes gh-33842
2024-11-13 11:03:48 +01:00
Sam Brannen 525407e4b1 Merge branch '6.1.x' 2024-11-12 20:50:46 +01:00
Tran Ngoc Nhan 53b9a2cb78 Fix formatting issue in validation section of reference guide
Closes gh-33871
2024-11-12 20:49:56 +01:00
rstoyanchev cbe2f36106 Decode static resource path with UriUtils
See gh-33859
2024-11-12 11:39:22 +00:00
Sam Brannen 9dabfdf0bf Remove obsolete AstUtilsTests
AstUtilsTests got accidentally reintroduced in 5b5a072351 after
AstUtils was renamed to AccessorUtils in e3ba957431.
2024-11-12 12:15:11 +01:00
rstoyanchev 942d2c5541 Merge branch '6.1.x' 2024-11-12 10:30:39 +00:00
rstoyanchev e78179b96e Decode static resource path with UriUtils
Closes gh-33859
2024-11-12 10:16:05 +00:00
Brian Clozel 5b5a072351 Merge branch '6.1.x' 2024-11-12 10:05:24 +01:00
Tran Ngoc Nhan 85cf563741 Fix typo in reference documentation
See gh-33865
2024-11-12 10:04:06 +01:00
Sam Brannen e3ba957431 Polish SpEL internals 2024-11-12 10:04:06 +01:00
Sam Brannen caec8f4f36 Revise Javadoc for PropertyAccessor & IndexAccessor regarding ordering
Closes gh-33862
2024-11-12 10:04:06 +01:00
Sam Brannen ae16a7fc08 Update tests for 6.2
See gh-33861
2024-11-12 10:04:06 +01:00
Sam Brannen 43e7921bcc Introduce tests for SpEL PropertyAccessor ordering
Closes gh-33861
2024-11-12 10:04:06 +01:00
Brian Clozel 49a63e2c37 Add tests for gh-33867
Closes gh-33867
2024-11-12 10:02:48 +01:00
ZLATAN628 5666e363d1 Fix wrong uri tag for client observation convention
Prior to this commit, a client sending a request to
"https://example.org" would record the wrong URI tag as
"/https://example.org".

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

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

Closes gh-33865
2024-11-10 14:45:49 +09:00
Tran Ngoc Nhan 8afd01ba2c Fix typo in reference documentation
See gh-33865
2024-11-10 14:45:19 +09:00
Sam Brannen 9724f9b9c8 Introduce tests for SpEL PropertyAccessor ordering
Closes gh-33861
2024-11-08 16:19:12 +01:00
Sam Brannen 8202282975 Polishing 2024-11-08 15:34:54 +01:00
rstoyanchev d4719493bd Empty string check after conversion in AbstractNamedValueArgumentResolver
Closes gh-33794
2024-11-08 09:21:08 +00:00
rstoyanchev 59ec871e76 Append trailing slash to static location
Closes gh-33815
2024-11-08 07:11:55 +00:00
rstoyanchev 37243f44e8 Refactor cookie support for Apache HttpClient
Closes gh-33822
2024-11-08 06:30:51 +00:00
longtanfei e67f892e44 Use optimistic locking where possible in ResponseBodyEmitter
Closes gh-33831
2024-11-07 16:33:05 +01:00
Sam Brannen 1ced8c3856 Merge branch '6.1.x' 2024-11-07 12:26:21 +01:00
Sam Brannen 0d9033592b Document that circular dependencies should be avoided in AOT mode
Closes gh-33786
2024-11-07 12:23:39 +01:00
Sam Brannen fc7b8ae966 Fix anchor name, consistently use title case, and polish wording
(cherry picked from commit 2e6c8daec6)
2024-11-07 12:22:25 +01:00
Sam Brannen c457131f1c Fix heading level for "Programmatic bean registration" in AOT chapter
(cherry picked from commit 9f0dbc4051)
2024-11-07 12:22:02 +01:00
Sam Brannen 39cfe136da Polishing 2024-11-07 11:49:22 +01:00
Sam Brannen fd676ed932 Merge branch '6.1.x' 2024-11-07 10:44:47 +01:00
Sam Brannen 05a880e3b5 Fix XML bean reference example in reference manual
Closes gh-33855
2024-11-07 10:44:01 +01:00
Simon Baslé 715e38c44f Merge branch '6.1.x' 2024-11-07 10:05:15 +01:00
Simon Baslé c93af1f76d Polishing: copyright header year
See gh-33823
2024-11-07 10:04:53 +01:00
Sam Brannen 2e6c8daec6 Fix anchor name, consistently use title case, and polish wording 2024-11-06 17:46:06 +01:00
Stéphane Nicoll c5f8dcc245 Merge branch '6.1.x' 2024-11-06 23:12:06 +09:00
Stéphane Nicoll 541866fd70 Merge pull request #33850 from wilkinsona
* pr/33850:
  Polish "Prefer modified resources over the originals in TestCompiler"
  Prefer modified resources over the originals in TestCompiler

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

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

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

See gh-33850
2024-11-06 23:00:08 +09:00
Sébastien Deleuze 72e7f1552b Refine @Contract Javadoc
This commit refines `@Contract` Javadoc to mention
this and new return values.

Closes gh-33849
2024-11-06 12:03:19 +01:00
Simon Baslé bbe90a57ba Merge branch '6.1.x' 2024-11-06 11:40:46 +01:00
Simon Baslé 9b3cb15389 Introduce HttpHeaders#headerSet to guarantee case-insensitive iteration
The `HttpHeaders#headerSet` method is intended as a drop-in replacement
for `entrySet` that guarantees a single casing for all header names
reported during the iteration, as the cost of some overhead but with
support for iterator removal and entry value-setting.

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

Closes gh-33823
2024-11-06 11:11:39 +01:00
Hejow 4697ae10ee Relax the visibility of MockMVC DSL constructors
Closes gh-33778
2024-11-06 10:28:15 +01:00
Sébastien Deleuze fb98d74a47 Merge branch '6.1.x' 2024-11-06 10:09:13 +01:00
Hosam Aly 4ef2b429e0 Fix a typo in beanvalidation.adoc
ContraintViolation => ConstraintViolation

Closes gh-33846
2024-11-06 10:09:05 +01:00
Sébastien Deleuze 7e1d6fe934 Merge branch '6.1.x' 2024-11-05 10:27:22 +01:00
Johnny Lim 0beb56a58c Fix indentation to use tabs in Kotlin source files
Closes gh-33840
2024-11-05 10:24:02 +01:00
Brian Clozel c6126b2033 Remove Java 22 from CI test matrix
Java 23 is now GA.
2024-11-04 09:48:48 +01:00
Brian Clozel f1e6482f92 Upgrade to Mockito 5.14.2
This version brings bytebuddy 1.15.4 which supports JDK 24.
2024-11-04 09:21:21 +01:00
Sam Brannen e3301dd1c4 Merge branch '6.1.x' 2024-11-03 16:17:43 +01:00
Sam Brannen 6bd4687706 Update copyright headers
See gh-33839
2024-11-03 16:14:41 +01:00
Tran Ngoc Nhan 07b12666b4 Fix typos in Javadoc and variable names
Closes gh-33839
2024-11-03 16:13:50 +01:00
Stéphane Nicoll 2b601f9e51 Merge branch '6.1.x' 2024-11-03 18:16:31 +09:00
Stéphane Nicoll 438d6de3c1 Merge pull request #33768 from kunaljani1100
* pr/33768:
  Polish "Add test coverage for DomUtils"
  Add test coverage for DomUtils

Closes gh-33768
2024-11-03 18:16:17 +09:00
Stéphane Nicoll d43126705f Polish "Add test coverage for DomUtils"
See gh-33768
2024-11-03 18:15:34 +09:00
kunaljani1100 57bbf0ca0f Add test coverage for DomUtils
See gh-33768
2024-11-03 13:44:07 +09:00
Sam Brannen a3b979c5ec Register runtime hints for @⁠TestBean fully-qualified method names
This commit introduces a TestBeanReflectiveProcessor that registers
GraalVM native image reflection hints for a fully-qualified method name
configured via @⁠TestBean.

Closes gh-33836
2024-11-01 16:48:06 +01:00
Sam Brannen a8f5848a5d Add missing runtime hint assertions for Bean Overrides 2024-11-01 16:47:36 +01:00
Sam Brannen 97f23dca22 Remove extra "Bean" from TestBeanBeanOverrideHandler class name
The extra "Bean" was accidentally added in commit ebdf6dc2fc.

See gh-33702
2024-11-01 14:57:30 +01:00
Sam Brannen 86784b61cc Introduce support for a custom reason in @⁠DisabledInAotMode
Closes gh-33833
2024-11-01 12:24:53 +01:00
Sam Brannen fdb763eeaf Merge branch '6.1.x' 2024-11-01 10:42:51 +01:00
Sam Brannen 27912b1ed1 Prevent accidental printing to System.err in the codebase
This also revises the Checkstyle rule so that invocations such as
System.out.printf() are also forbidden.
2024-11-01 10:42:31 +01:00
Sam Brannen ba692aa3ef Honor MockReset without @⁠MockitoBean or @⁠MockitoSpyBean fields
Prior to this commit, the static factory methods in MockReset (such as
MockReset.before() and MockReset.after()) could only be applied to
beans within the ApplicationContext if the test class declared at least
one field annotated with either @⁠MockitoBean or @⁠MockitoSpyBean.

However, the Javadoc states that it should be possible to apply
MockReset directly to any mock in the ApplicationContext using the
static methods in MockReset.

To address that, this commit reworks the "enabled" logic in
MockitoResetTestExecutionListener as follows.

- We no longer check for the presence of annotations from the
  org.springframework.test.context.bean.override.mockito package to
  determine if MockReset is enabled.

- Instead, we now rely on a new isEnabled() method to determine if
  MockReset is enabled.

The logic in the isEnabled() method still relies on the mockitoPresent
flag as an initial check; however, mockitoPresent only determines if
Mockito is present in the classpath. It does not determine if Mockito
can actually be used. For example, it does not detect if the necessary
reachability metadata has been registered to use Mockito within a
GraalVM native image.

To address that last point, the isEnabled() method performs an
additional check to determine if Mockito can be used in the current
environment. Specifically, it invokes Mockito.mockingDetails().isMock()
which in turn initializes core Mockito classes without actually
attempting to create a mock. If that fails, that means that Mockito
cannot actually be used in the current environment, which typically
indicates that GraalVM reachability metadata has not been registered
for the org.mockito.plugins.MockMaker in use (such as the
ProxyMockMaker).

In addition, isEnabled() lazily determines if Mockito can be
initialized, since attempting to detect that during static
initialization results in a GraalVM native image error stating that
Mockito internals were "unintentionally initialized at build time".

If Mockito cannot be initialized, MockitoResetTestExecutionListener
logs a DEBUG level message providing access to the corresponding stack
trace, and MockReset support is disabled.

Closes gh-33829
2024-10-31 15:56:54 +01:00
Sam Brannen 0846706688 Adapt copyright headers copied from Spring Boot 2024-10-31 15:33:50 +01:00
rstoyanchev bc65f93467 Improve cancel in SubscriberInputStreamTests 2024-10-31 10:05:45 +00:00
rstoyanchev 38c88e769d Polishing in SubscriberInputStreamTests 2024-10-31 10:05:45 +00:00
Sam Brannen 4a0edc59cc Verify support for MockReset for beans within the ApplicationContext
This commit verifies that MockReset.before() and MockReset.after() are
supported for beans within the ApplicationContext.

However, the test class must declare a field annotated with either
@⁠MockitoBean or @⁠MockitoSpyBean in order for the MockReset feature to
be triggered.

See gh-33742
2024-10-30 17:31:15 +01:00
Juergen Hoeller c979eddab1 Consistent deprecation markers for 6.2 2024-10-30 16:45:00 +01:00
Juergen Hoeller 864cb25eec Upgrade to ASM 9.7.1
Closes gh-33821
2024-10-30 16:44:47 +01:00
Sam Brannen e23c8bfbb6 Polishing 2024-10-30 15:56:19 +01:00
Sam Brannen 08e0baac94 Honor @⁠Primary for test Bean Overrides such as @⁠MockitoBean
Spring Boot has honored @⁠Primary for selecting which candidate bean
@⁠MockBean and @⁠SpyBean should mock or spy since Spring Boot 1.4.3;
however, the support for @⁠Primary was not ported from Spring Boot to
Spring Framework's new Bean Overrides feature in the TestContext
framework.

To address that, this commit introduces support for @⁠Primary for
selecting bean overrides -- for example, for annotations such as
@⁠TestBean, @⁠MockitoBean, and @⁠MockitoSpyBean.

See https://github.com/spring-projects/spring-boot/issues/7621
Closes gh-33819
2024-10-30 15:49:16 +01:00
Sébastien Deleuze 9166688b6f Remove the pure attribute from @Contract
Closes gh-33820
2024-10-30 15:11:44 +01:00
Sébastien Deleuze da8925131e Polish @Contract Javadoc 2024-10-30 15:11:28 +01:00
Sébastien Deleuze 09d87fac57 Annotate RestClient retrieve() with @CheckReturnValue
This annotation triggers a warning in IDEs supporting
`@CheckReturnValue` when retrieve() return value is not
used.

Closes gh-33818
2024-10-30 14:45:35 +01:00
Sébastien Deleuze a005c325f7 Introduce @CheckReturnValue annotation
This commit introduces a `@CheckReturnValue` annotation,
inspired from org.jetbrains.annotations.CheckReturnValue,
that specifies that the method return value must be used.

See gh-33818
2024-10-30 14:45:35 +01:00
Sébastien Deleuze eaafdea1fb Fix ListenableFutureReturnValueHandler Javadoc
See gh-33808
2024-10-30 14:44:25 +01:00
Sébastien Deleuze f4b3520ab3 Suppress Kotlin code sample warnings 2024-10-30 11:54:14 +01:00
Sam Brannen f427ac383d (Re)suppress deprecation warnings
See gh-33780
2024-10-30 10:52:01 +01:00
Brian Clozel 79cf554850 Merge branch '6.1.x' 2024-10-30 10:37:17 +01:00
Brian Clozel 8ffbafd384 Prevent accidental Sysouts in the codebase 2024-10-30 10:36:30 +01:00
Sam Brannen c0c41ddda5 Disable unsupported tests in AOT mode as well 2024-10-30 10:29:02 +01:00
Sam Brannen 7148b28b2b Integration test Bean Override support for multiple candidate beans
This commit introduces integration tests which verify that Bean
Overrides (for example, @⁠MockitoBean and @⁠MockitoSpyBean) can select
a single candidate bean to override when there are multiple candidates
that match the required type.

To "select" the desired bean, these tests rely on one of the following.

- explicit bean name in the bean override annotation

- explicit @⁠Qualifier on the bean override field

- explicit @⁠Primary on one of the candidate beans

However, the @⁠Primary tests are currently @⁠Disabled until @⁠Primary
is honored in the Spring TestContext Framework.

See gh-33742
2024-10-30 10:22:19 +01:00
Juergen Hoeller 1255bd1fc9 Add remaining @SuppressWarnings("removal") for ListenableFuture
See gh-33780
2024-10-29 23:15:53 +01:00
Juergen Hoeller a1f6098158 Merge branch '6.1.x' 2024-10-29 23:04:05 +01:00
Juergen Hoeller 022fdcd67e Provide removeCache method on Caffeine/ConcurrentMapCacheManager
Closes gh-33813
2024-10-29 23:02:48 +01:00
Juergen Hoeller 11ebceee38 Call get/setRequestConfig for HttpClient 5.4 compatibility
Includes upgrade to HttpClient 5.4.1 while retaining a HttpClient 5.1 baseline.

Closes gh-33806
2024-10-29 23:01:57 +01:00
Juergen Hoeller fa21dffcf8 Restore traditional AspectJ behavior through "spring.aop.ajc.ignore=true"
Closes gh-33704
2024-10-29 23:01:16 +01:00
Juergen Hoeller 9e3371ef07 Mark ListenableFuture as deprecated for removal
Closes gh-33808
2024-10-29 18:36:40 +01:00
Sam BrannenandAndy Wilkinson c2c6bb25c6 Use BeanFactory to get type produced by a FactoryBean for Bean Overrides
Previously, we only looked at the OBJECT_TYPE_ATTRIBUTE on a
FactoryBean's bean definition; however this does not work for
situations where the information is provided by the definition's target
type rather than the attribute.

Rather than manually considering the target type in addition to the
existing consideration of the attribute, we now ask the BeanFactory for
the type that will be produced by the FactoryBean instead.

See https://github.com/spring-projects/spring-boot/issues/40234
Closes gh-33811

Co-authored-by: Andy Wilkinson <andy.wilkinson@broadcom.com>
2024-10-29 14:21:18 +01:00
Sam Brannen 40960fa85a Verify @⁠MockitoSpyBean can spy bean from FactoryBean with generics
This commit introduces a test which verifies that @⁠MockitoSpyBean on a
field with generics can be used to replace an existing bean with
matching generics that's produced by a FactoryBean that's
programmatically registered via an ImportBeanDefinitionRegistrar.

However, the test is currently @⁠Disabled until the fix for
https://github.com/spring-projects/spring-boot/issues/40234 has been
ported to Spring Framework.

See gh-33742
2024-10-29 13:55:22 +01:00
Sam Brannen 578928de39 Introduce test for Bean Override for "broken" FactoryBean
See gh-33800
2024-10-29 10:56:23 +01:00
Juergen Hoeller 015beb0058 Provide first-class virtual thread option for common executors
Closes gh-33807
2024-10-28 22:14:41 +01:00
Juergen Hoeller 3732c71168 Merge branch '6.1.x' 2024-10-28 22:12:12 +01:00
Juergen Hoeller 323de1208a Document limited support for lifecycle management
Closes gh-33780
2024-10-28 22:08:41 +01:00
Juergen Hoeller 94d46eba3c Exclusively mention CompletableFuture instead of ListenableFuture
Closes gh-33805
2024-10-28 22:05:10 +01:00
rstoyanchev 4749d810db Refactor ReactorClientHttpRequestFactory timeouts
Closes gh-33782
2024-10-28 12:45:53 +00:00
rstoyanchev 044da794f4 Polishing ReactorClientHttpRequestFactory 2024-10-28 12:45:53 +00:00
rstoyanchev 89d56b1fa6 Streaming ReactorClientHttpResponse
Closes gh-33781
2024-10-28 12:45:53 +00:00
rstoyanchev f1cfe7a3d4 InputStreamSubscriber instantiation and Javadoc
See gh-31677
2024-10-28 12:45:53 +00:00
rstoyanchev dfaf7a0c6a Rename InputStreamSubscriber to SubscriberInputStream
It is both, but InputStream is what's exposed for public use,
in effect an InputStream backed by a Subscriber source.

See gh-31677
2024-10-28 12:45:53 +00:00
rstoyanchev 86a42db002 InputStreamSubscriber/Tests conform to style
See gh-31677
2024-10-28 12:45:53 +00:00
rstoyanchev d4b31fd4b2 InputStreamSubscriber compiler issues
See gh-31677
2024-10-28 12:45:53 +00:00
rstoyanchev a366ea0e15 Align InputStreamSubscriber copies
There are legitimate differences, but also some are fixes that
should be on both sides.

See gh-31677
2024-10-28 12:45:53 +00:00
OlegDokuka 37622a7f90 Support Publisher to InputStream conversion
See gh-31677
2024-10-28 12:45:53 +00:00
Sam Brannen de2c10abcd Sync Javadoc for @⁠TestBean & @⁠MockitoBean with reference manual
See gh-33701
2024-10-28 12:58:33 +01:00
Sam Brannen 81d89f478a Relax singleton enforcement for Bean Overrides in the TestContext framework
In gh-33602, we introduced strict singleton enforcement for bean
overrides -- for example, for @⁠MockitoBean, @⁠TestBean, etc. However,
the use of BeanFactory#isSingleton(beanName) can result in a
BeanCreationException for certain beans, such as a Spring Data JPA
FactoryBean for a JpaRepository.

In light of that, this commit relaxes the singleton enforcement in
BeanOverrideBeanFactoryPostProcessor by only checking the result of
BeanDefinition#isSingleton() for existing bean definitions.

This commit also updates the Javadoc and reference documentation to
reflect the status quo.

See gh-33602
Closes gh-33800
2024-10-28 12:45:05 +01:00
Sam Brannen 52e813d0ad Improve structure and naming of ProxyExceptionHandlingTests
Closes gh-33797
2024-10-25 16:14:42 +02:00
Sam Brannen f19a1b50e5 Polishing 2024-10-25 15:22:56 +02:00
Sam Brannen 5532574f56 Remove unused casts and variables 2024-10-25 15:16:05 +02:00
Sam Brannen 1b3e96b9bf Merge branch '6.1.x' 2024-10-25 14:49:46 +02:00
Sam Brannen e340e45f5a Rename aopAvailable constants in TransactionSynchronizationUtils
Closes gh-33796
2024-10-25 14:41:41 +02:00
Brian Clozel 9df4fcdc9e Merge branch '6.1.x' 2024-10-25 10:55:00 +02:00
Brian Clozel a06bbccf9e HttpHeaders.writeableHttpHeaders should unwrap many times
Prior to this commit, the `HttpHeaders.writeableHttpHeaders` would only
consider headers read-only instances that were wrapped once by
`HttpHeaders.readOnlyHttpHeaders`. This does not work when other
`HttpHeaders` wrappers are involved in the chain.

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

Fixes gh-33789
2024-10-25 10:22:12 +02:00
Sam Brannen d1d6ff8737 Verify @⁠MockitoSpyBean can be used with circular dependencies
See gh-33742
2024-10-24 16:53:26 +02:00
Sam Brannen 9f0dbc4051 Fix heading level for "Programmatic bean registration" in AOT chapter 2024-10-24 16:43:17 +02:00
Brian Clozel acccbbec3f Document UrlHandler Servlet and reactive filters
Closes gh-33784
2024-10-24 16:14:18 +02:00
Sam Brannen 3cc76ef87c Polishing 2024-10-24 15:47:16 +02:00
Sam Brannen 0221471265 Verify @⁠Mockito[Spy]Bean can mock/spy parameterized types
See gh-33742
2024-10-24 15:45:28 +02:00
Sam Brannen 3b82733e1f Support @⁠Mockito[Spy]Bean & @⁠TestBean w/ @⁠DirtiesContext "before method" modes
Closes gh-33783
2024-10-24 15:06:03 +02:00
Sam Brannen ba8024d077 Verify @⁠MockitoBean can be used with generics and parameterized types
See gh-33742
2024-10-24 12:48:24 +02:00
Sam Brannen efda3f0f99 Reorganize Mockito integration tests 2024-10-24 12:21:58 +02:00
Sam Brannen 761850816c Verify @⁠MockitoBean can be used with JUnit 4 and SpringMethodRule
See gh-33742
2024-10-24 12:21:58 +02:00
Sam Brannen 2d028c39b4 Align with naming in ReflectionTestUtils 2024-10-24 10:25:18 +02:00
Juergen Hoeller fe6d9145e5 Avoid hard dependency on Spring AOP for mock resolution
Closes gh-33774
2024-10-23 21:26:06 +02:00
rstoyanchev c160e89d9d Merge branch '6.1.x' 2024-10-23 19:09:53 +01:00
rstoyanchev bbe362c0e6 Allow null from RestClient exchange methods
Closes gh-33779
2024-10-23 19:08:54 +01:00
rstoyanchev bff76d756b Refactor implementation of retrieve in RestClient
Closes gh-33777
2024-10-23 18:59:07 +01:00
Sam Brannen 8fa99dcbdd Polishing 2024-10-23 17:16:41 +02:00
Sam Brannen 5bf179b851 Remove proxyTargetAware attribute from @⁠MockitoSpyBean
This commit removes the proxyTargetAware attribute from @⁠MockitoSpyBean
while keeping the underlying feature in tact (i.e., transparent
verification for spies created via @⁠MockitoSpyBean).

Closes gh-33775
2024-10-23 16:39:01 +02:00
Brian Clozel 20d21a8251 Rework links to Spring Boot in reference docs
Closes gh-33776
2024-10-23 16:10:37 +02:00
Sam Brannen d8a6423c0c Support transparent verification for @⁠MockitoSpyBean
Prior to this commit, SpringAopBypassingVerificationStartedListener
provided partial support for transparent verification for Mockito spies
created via @⁠MockitoSpyBean when the spy is wrapped in a Spring AOP
proxy. However, attempting to actually verify invocations for a spy
resulted in an exception from Mockito since MockUtil.isMock() returned
false in such scenarios.

This commit addresses that by introducing a SpringMockResolver that
resolves mocks by walking the Spring AOP proxy chain until the target
or a non-static proxy is found.

SpringMockResolver is automatically registered whenever the spring-test
JAR is on the classpath, allowing Mockito to transparently resolve mocks
wrapped in Spring AOP proxies.

Closes gh-33774
2024-10-23 15:51:35 +02:00
Sam Brannen c85689b308 Polishing 2024-10-23 14:01:52 +02:00
Sam Brannen 982f7f8f58 Verify @⁠MockitoBean & @⁠MockitoSpyBean can be used Spring AOP proxies
Use cases not yet supported with @⁠MockitoSpyBean are currently @⁠Disabled.

See gh-33742
2024-10-23 13:54:11 +02:00
rstoyanchev d05f880d74 Fix test causing failure in RestClientObservationTests
See gh-33697
2024-10-23 12:24:28 +01:00
rstoyanchev 2b113e3fd0 Minor refactoring
Closes gh-33697
2024-10-23 11:38:19 +01:00
rstoyanchev 73e5aa38ec Polishing contribution
See gh-33697
2024-10-23 11:38:19 +01:00
Nicklas Wiegandt a0af708c03 Add cookie support to RestClient
See gh-33697
2024-10-23 11:38:18 +01:00
Brian Clozel d8c153a9d1 Remove support for Resin Servlet container
This commit removes all references to the Resin Servlet container, as it
is not supported as of Spring Framework 6.0 because we require a
JakartaEE baseline.

Closes gh-33772
2024-10-23 10:10:29 +02:00
Brian Clozel 1ba773170d Merge branch '6.1.x' 2024-10-23 09:26:40 +02:00
Brian Clozel 4a81f2c904 Remove HTTP parts workaround for Resin
A workaround was added for the Resin Servlet container in gh-13937.
This avoids attempting to delete parts that are not named, because the
`part.delete()` call would fail for non-file entries. This can be
problematic for files that are unnamed as they might not be removed by
the Framework.

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

Fixes gh-33511
2024-10-23 09:24:30 +02:00
Juergen Hoeller 081d0b33d4 Consistently sort jar entries in doFindPathMatchingJarResources
Includes consistent clearCache() behavior for manifest entries.

Closes gh-33771
See gh-33705
2024-10-22 21:29:22 +02:00
Phillip Webb 1c69a3c521 Fix PathMatchingResourcePatternResolver manifest classpath discovery
Update `PathMatchingResourcePatternResolver` so that in addition to
searching the `java.class.path` system property for classpath enties,
it also searches the `MANIFEST.MF` files from within those jars.

Prior to this commit, the `addClassPathManifestEntries()` method
expected that the JVM had added `Class-Path` manifest entries to the
`java.class.path` system property, however, this did not always happen.

The updated code now performs a deep search by loading `MANIFEST.MF`
files from jars discovered from the system property. To deal with
potential performance issue, loaded results are also now cached.

The updated code has been tested with Spring Boot 3.3 jars extracted
using `java -Djarmode=tools`.

See gh-33705
2024-10-22 21:10:29 +02:00
rstoyanchev b7fc4bc5c8 Polishing contribution
Closes gh-33716
2024-10-22 17:37:45 +01:00
Ilya Serkov 657e002ed7 Fix HttpHeaders.setHost() to remove the header
See gh-33716
2024-10-22 17:37:44 +01:00
rstoyanchev 9252e741e1 Default webmvc handling of disconnected client errors
Closes gh-33753
2024-10-22 15:47:29 +01:00
rstoyanchev 5271f5b8a1 Polishing 2024-10-22 14:50:33 +01:00
Sam Brannen 626f4279f6 Merge branch '6.1.x' 2024-10-22 13:11:39 +02:00
Sam Brannen fb0a108254 Improve Javadoc for core SpEL APIs 2024-10-22 13:04:29 +02:00
Sam Brannen c98f314665 Throw ParseException for unsupported character in SpEL expression
Prior to this commit, the SpEL Tokenizer threw an IllegalStateException
when an unsupported character was detected in a SpEL expression;
however, the Javadoc for ExpressionParser.parseExpression() states that
it throws a ParseException if an exception occurred during parsing.

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

Closes gh-33767
2024-10-22 13:03:08 +02:00
Stéphane Nicoll 382b51a8d4 Polish 2024-10-22 11:33:52 +02:00
Sébastien Deleuze 2b0c1fa210 Improve the discoverability of Java and Kotlin APIs
This commit add external links in the refdoc to Java
and Kotlin APIs to make them more discoverable.

Closes gh-32835
2024-10-22 08:40:28 +02:00
Brian Clozel ec895534c5 Merge branch '6.1.x' 2024-10-21 18:34:49 +02:00
Sehwan Lim d22924c728 Fix incorrect regex rendering in MVC controller documentation
This commit fixes the issue where the regex pattern in the reference documentation
was not rendering correctly for the `/projects/{project:[a-z]+}/versions` mapping.

Closes gh-33766
2024-10-21 18:32:44 +02:00
Juergen Hoeller 267d3a36ae Merge branch '6.1.x'
# Conflicts:
#	spring-context/src/main/java/org/springframework/instrument/classloading/jboss/JBossLoadTimeWeaver.java
2024-10-21 18:14:50 +02:00
Juergen Hoeller e235e661d8 Polishing 2024-10-21 18:13:42 +02:00
Juergen Hoeller 09fe0adb40 Load-time weaving support for WildFly 24+
Closes gh-33728
2024-10-21 18:13:34 +02:00
Sam Brannen 1784ccb200 Upgrade to JUnit 5.11.3
Closes gh-33764
2024-10-21 16:58:14 +02:00
Sébastien Deleuze 57eb4823d2 Stop submitting Antora UI PR for 6.0.x and main branches
Closes gh-33765
2024-10-21 16:54:04 +02:00
Brian Clozel ab2f1f8885 Merge branch '6.1.x' 2024-10-21 16:20:57 +02:00
Brian Clozel 84e762b470 Fix double accounting of JMS process observations
Prior to this commit, the instrumentation of the processing of JMS
messages would happen a different levels of the hierarchy, accounting
for alli known implementations, `SimpleMessageListenerContainer` and
`DefaultMessageListenerContainer` as well as various use cases and
`MessageListener` variants.

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

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

Fixes gh-33758
2024-10-21 16:09:58 +02:00
Juergen Hoeller a1dd2e7c96 Merge branch '6.1.x' 2024-10-21 15:39:10 +02:00
Juergen Hoeller e90a2da05d Clarify defensive impact of allowEagerInit flag for type matching
Closes gh-33740
2024-10-21 15:38:25 +02:00
Juergen Hoeller ee1fe8a1e9 Consistently evaluate defaultCandidate flag on constructors and methods
Closes gh-33762
2024-10-21 13:53:28 +02:00
Sam Brannen 6f9413ba31 Merge branch '6.1.x' 2024-10-21 12:20:59 +02:00
Sam Brannen d48f388c6a Polish Javadoc for @⁠DateTimeFormat 2024-10-21 12:19:35 +02:00
RollW 6c93c67dd4 Add Kotlin code samples to the AOT documentation
Closes gh-33761
2024-10-21 11:21:05 +02:00
Sam Brannen 599d53465b Merge branch '6.1.x' 2024-10-20 17:43:45 +02:00
Sam Brannen bbbb7c396e Update Date/Time formatting tests for pre/post JDK 20
This commit updates our Date/Time formatting/printing tests to
demonstrate that the use of fallback patterns can help mitigate
locale-based parsing/formatting issues beginning with JDK 20.

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

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

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

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

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

Closes gh-33733
2024-10-19 14:54:29 +02:00
Sam Brannen 23f19a0fad Add link to existing bug report 2024-10-19 13:06:21 +02:00
Sam Brannen 14c476bd42 Verify @⁠MockitoBean can replace Spring AOP advised beans
See gh-33742
2024-10-18 17:53:44 +02:00
Sam Brannen a8b36f7ad5 Verify @⁠MockitoBean can replace beans that have @⁠Async methods
See gh-33742
2024-10-18 17:38:02 +02:00
Sam Brannen d5e334339a Verify @⁠MockitoBean can replace a scoped-proxy
See gh-33742
2024-10-18 17:14:59 +02:00
Sam Brannen c0ad9c0081 Verify @⁠MockitoBean & @⁠MockitoSpyBean can be used with @⁠ContextHierarchy
See gh-33742
2024-10-18 17:10:53 +02:00
Sam Brannen 2aa3f40257 Polishing 2024-10-18 16:55:40 +02:00
Sam Brannen 3842f12d09 Verify that the MockitoExtension and SpringExtension can be combined
See gh-33742
2024-10-18 16:51:52 +02:00
Brian Clozel 5f14703105 Merge branch '6.1.x' 2024-10-18 14:43:54 +02:00
Brian Clozel 67d78eb61c Avoid Servlet observations failures for invalid HTTP status
Prior to this commit, the `DefaultServerRequestObservationConvention`
for Servlet failed when the HTTP response status was invalid (for
example, set to "0").

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

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

For now, Undertow 2.3.17.Final is still used.

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

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

This commit ensures that buffers are released on cancel signals.

Fixes gh-33731
2024-10-18 10:51:38 +02:00
Brian Clozel 9a761579c0 Remove ServerResponse.AsyncBuilder interface
This new interface is not strictly necessary and introduces a binary
incompatibility. This commit merges back all the method definitions into
`SseBuilder` and `StreamBuilder` interfaces.

Closes gh-33736
2024-10-18 09:09:41 +02:00
430 changed files with 11134 additions and 2655 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: Asking for help
- name: Community Support
url: https://stackoverflow.com/tags/spring
about: The Spring team is using StackOverflow for questions.
about: Please ask and answer questions on StackOverflow with the tag `spring`.
+1 -1
View File
@@ -1,5 +1,5 @@
---
name: Issue
name: General
about: Bugs, enhancements, documentation, tasks.
title: ''
labels: ''
-2
View File
@@ -20,8 +20,6 @@ jobs:
toolchain: false
- version: 21
toolchain: true
- version: 22
toolchain: true
- version: 23
toolchain: true
exclude:
@@ -16,7 +16,7 @@ jobs:
name: Update on Supported Branches
strategy:
matrix:
branch: [ '6.0.x', '6.1.x', 'main' ]
branch: [ '6.1.x' ]
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
name: Update
+1 -1
View File
@@ -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.2/api/",
// "https://junit.org/junit5/docs/5.11.3/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/",
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.18.1");
checkstyle.setToolVersion("10.20.1");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+2 -1
View File
@@ -42,7 +42,8 @@ asciidoc:
spring-framework-reference: '{spring-framework-docs-root}/{spring-version}/reference'
#
# Other Spring portfolio projects
spring-boot-docs: '{docs-site}/spring-boot/docs/current/reference/html'
spring-boot-docs: '{docs-site}/spring-boot'
spring-boot-docs-ref: '{spring-boot-docs}/reference'
spring-boot-issues: '{spring-github-org}/spring-boot/issues'
# TODO add more projects / links or just build up on {docs-site}?
# TODO rename the below using new conventions
+3 -2
View File
@@ -451,5 +451,6 @@
** xref:languages/groovy.adoc[]
** xref:languages/dynamic.adoc[]
* xref:appendix.adoc[]
* {spring-framework-wiki}[Wiki]
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
* {spring-framework-wiki}[Wiki, window=_blank, role=link-external]
@@ -23,6 +23,12 @@ The following table lists all currently supported Spring properties.
|===
| Name | Description
| `spring.aop.ajc.ignore`
| Instructs Spring to ignore ajc-compiled aspects for Spring AOP proxying, restoring traditional
Spring behavior for scenarios where both weaving and AspectJ auto-proxying are enabled. See
{spring-framework-api}++/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.html#IGNORE_AJC_PROPERTY_NAME++[`AbstractAspectJAdvisorFactory`]
for details.
| `spring.aot.enabled`
| Indicates the application should run with AOT generated artifacts. See
xref:core/aot.adoc[Ahead of Time Optimizations] and
@@ -32,7 +38,7 @@ for details.
| `spring.beaninfo.ignore`
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
JavaBeans `Introspector`. See
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`StandardBeanInfoFactory`]
for details.
| `spring.cache.reactivestreams.ignore`
@@ -49,15 +55,13 @@ for details.
| `spring.context.checkpoint`
| Property that specifies a common context checkpoint. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#CHECKPOINT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
| `spring.context.exit`
| Property for terminating the JVM when the context reaches a specific phase. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
+119 -8
View File
@@ -36,7 +36,7 @@ NOTE: At the moment, AOT is focused on allowing Spring applications to be deploy
We intend to support more JVM-based use cases in future generations.
[[aot.basics]]
== AOT engine overview
== AOT Engine Overview
The entry point of the AOT engine for processing an `ApplicationContext` is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
@@ -225,7 +225,7 @@ When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
[[aot.running]]
== Running with AOT optimizations
== Running with AOT Optimizations
AOT is a mandatory step to transform a Spring application to a native executable, so it
is automatically enabled when running in this mode. It is possible to use those optimizations
@@ -244,7 +244,7 @@ However, keep in mind that some optimizations are made at build time based on a
This section lists the best practices that make sure your application is ready for AOT.
[[aot.bestpractices.bean-registration]]
== Programmatic bean registration
=== Programmatic Bean Registration
The AOT engine takes care of the `@Configuration` model and any callback that might be
invoked as part of processing your configuration. If you need to register additional
@@ -266,7 +266,7 @@ notion of a classpath. For cases like this, it is crucial that the scanning happ
build time.
[[aot.bestpractices.bean-type]]
=== Expose The Most Precise Bean Type
=== Expose the Most Precise Bean Type
While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type.
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods.
@@ -290,6 +290,19 @@ Java::
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration(proxyBeanMethods = false)
class UserConfiguration {
@Bean
fun myInterface(): MyInterface = MyImplementation()
}
----
======
In the example above, the declared type for the `myInterface` bean is `MyInterface`.
@@ -314,6 +327,19 @@ Java::
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration(proxyBeanMethods = false)
class UserConfiguration {
@Bean
fun myInterface() = MyImplementation()
}
----
======
If you are registering bean definitions programmatically, consider using `RootBeanBefinition` as it allows to specify a `ResolvableType` that handles generics.
@@ -326,21 +352,21 @@ However, this is not a best practice and flagging the preferred constructor with
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
[[aot.bestpractices.comlext-data-structure]]
=== Avoid Complex Data Structure for Constructor Parameters and Properties
[[aot.bestpractices.complex-data-structures]]
=== Avoid Complex Data Structures for Constructor Parameters and Properties
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
Rather than using such structure, decomposing to simple types or referring to a bean that is built as such is recommended.
Rather than using such structures, decomposing to simple types or referring to a bean that is built as such is recommended.
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
To use it, register its fully qualified name in `META-INF/spring/aot.factories` using the `Delegate` as the key.
[[aot.bestpractices.custom-arguments]]
=== Avoid Creating Bean with Custom Arguments
=== Avoid Creating Beans with Custom Arguments
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
@@ -352,6 +378,20 @@ For instance, autowiring on fields and methods will be skipped as they are handl
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
[[aot.bestpractices.circular-dependencies]]
=== Avoid Circular Dependencies
Certain use cases can result in circular dependencies between one or more beans. With the
regular runtime, it may be possible to wire those circular dependencies via `@Autowired`
on setter methods or fields. However, an AOT-optimized context will fail to start with
explicit circular dependencies.
In an AOT-optimized application, you should therefore strive to avoid circular
dependencies. If that is not possible, you can use `@Lazy` injection points or
`ObjectProvider` to lazily access or retrieve the necessary collaborating beans. See
xref:core/beans/classpath-scanning.adoc#beans-factorybeans-annotations-lazy-injection-points[this tip]
for further information.
[[aot.bestpractices.factory-bean]]
=== FactoryBean
@@ -371,6 +411,15 @@ Java::
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class ClientFactoryBean<T : AbstractClient> : FactoryBean<T> {
// ...
}
----
======
A concrete client declaration should provide a resolved generic for the client, as shown in the following example:
@@ -391,6 +440,19 @@ Java::
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration(proxyBeanMethods = false)
class UserConfiguration {
@Bean
fun myClient() = ClientFactoryBean<MyClient>(...)
}
----
======
If the `FactoryBean` bean definition is registered programmatically, make sure to follow these steps:
@@ -412,6 +474,16 @@ Java::
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
// ...
registry.registerBeanDefinition("myClient", beanDefinition)
----
======
[[aot.bestpractices.jpa]]
@@ -433,6 +505,19 @@ Java::
return factoryBean;
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Bean
fun customDBEntityManagerFactory(dataSource: DataSource): LocalContainerEntityManagerFactoryBean {
val factoryBean = LocalContainerEntityManagerFactoryBean()
factoryBean.dataSource = dataSource
factoryBean.setPackagesToScan("com.example.app")
return factoryBean
}
----
======
To make sure the scanning occurs ahead of time, a `PersistenceManagedTypes` bean must be declared and used by the
@@ -458,6 +543,25 @@ Java::
return factoryBean;
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Bean
fun persistenceManagedTypes(resourceLoader: ResourceLoader): PersistenceManagedTypes {
return PersistenceManagedTypesScanner(resourceLoader)
.scan("com.example.app")
}
@Bean
fun customDBEntityManagerFactory(dataSource: DataSource, managedTypes: PersistenceManagedTypes): LocalContainerEntityManagerFactoryBean {
val factoryBean = LocalContainerEntityManagerFactoryBean()
factoryBean.dataSource = dataSource
factoryBean.setManagedTypes(managedTypes)
return factoryBean
}
----
======
[[aot.hints]]
@@ -479,6 +583,13 @@ Java::
----
runtimeHints.resources().registerPattern("config/app.properties");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
runtimeHints.resources().registerPattern("config/app.properties")
----
======
A number of contracts are handled automatically during AOT processing.
@@ -480,11 +480,15 @@ factory method and other bean definition properties, such as a qualifier value t
the `@Qualifier` annotation. Other method-level annotations that can be specified are
`@Scope`, `@Lazy`, and custom qualifier annotations.
TIP: In addition to its role for component initialization, you can also place the `@Lazy`
[[beans-factorybeans-annotations-lazy-injection-points]]
[TIP]
====
In addition to its role for component initialization, you can also place the `@Lazy`
annotation on injection points marked with `@Autowired` or `@Inject`. In this context,
it leads to the injection of a lazy-resolution proxy. However, such a proxy approach
is rather limited. For sophisticated lazy interactions, in particular in combination
with optional dependencies, we recommend `ObjectProvider<MyTargetBean>` instead.
====
Autowired fields and methods are supported, as previously discussed, with additional
support for autowiring of `@Bean` methods. The following example shows how to do so:
@@ -883,11 +883,12 @@ for example, for processing all events asynchronously and/or for handling listen
== Convenient Access to Low-level Resources
For optimal usage and understanding of application contexts, you should familiarize
yourself with Spring's `Resource` abstraction, as described in xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources].
yourself with Spring's `Resource` abstraction, as described in
xref:core/resources.adoc[Resources].
An application context is a `ResourceLoader`, which can be used to load `Resource` objects.
A `Resource` is essentially a more feature rich version of the JDK `java.net.URL` class.
In fact, the implementations of the `Resource` wrap an instance of `java.net.URL`, where
In fact, implementations of `Resource` wrap an instance of `java.net.URL`, where
appropriate. A `Resource` can obtain low-level resources from almost any location in a
transparent fashion, including from the classpath, a filesystem location, anywhere
describable with a standard URL, and some other variations. If the resource location
@@ -102,8 +102,8 @@ following snippet:
----
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
<bean id="theClientBean" class="...">
<property name="targetName" ref="theTargetBean"/>
</bean>
----
@@ -444,18 +444,27 @@ component. This section discusses both of these options.
The compiler can operate in one of three modes, which are captured in the
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows.
* `OFF` (default): The compiler is switched off.
* `IMMEDIATE`: In immediate mode, the expressions are compiled as soon as possible. This
is typically after the first interpreted evaluation. If the compiled expression fails
(typically due to a type changing, as described earlier), the caller of the expression
evaluation receives an exception.
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and
compiled mode over time. After some number of interpreted runs, they switch to compiled
form and, if something goes wrong with the compiled form (such as a type changing, as
described earlier), the expression automatically switches back to interpreted form
again. Sometime later, it may generate another compiled form and switch to it.
Basically, the exception that the user gets in `IMMEDIATE` mode is instead handled
internally.
`OFF` ::
The compiler is switched off, and all expressions will be evaluated in _interpreted_
mode. This is the default mode.
`IMMEDIATE` ::
In immediate mode, expressions are compiled as soon as possible, typically after the
first interpreted evaluation. If evaluation of the compiled expression fails (for
example, due to a type changing, as described earlier), the caller of the expression
evaluation receives an exception. If the types of various expression elements change
over time, consider switching to `MIXED` mode or turning off the compiler.
`MIXED` ::
In mixed mode, expression evaluation silently switches between _interpreted_ and
_compiled_ over time. After some number of successful interpreted runs, the expression
gets compiled. If evaluation of the compiled expression fails (for example, due to a
type changing), that failure will be caught internally, and the system will switch back
to interpreted mode for the given expression. Basically, the exception that the caller
receives in `IMMEDIATE` mode is instead handled internally. Sometime later, the
compiler may generate another compiled form and switch to it. This cycle of switching
between interpreted and compiled mode will continue until the system determines that it
does not make sense to continue trying — for example, when a certain failure threshold
has been reached — at which point the system will permanently switch to interpreted
mode for the given expression.
`IMMEDIATE` mode exists because `MIXED` mode could cause issues for expressions that
have side effects. If a compiled expression blows up after partially succeeding, it
@@ -9,7 +9,7 @@ known as _JUL_ or `java.util.logging`) if neither Log4j 2.x nor SLF4J is availab
Put Log4j 2.x or Logback (or another SLF4J provider) in your classpath, without any extra
bridges, and let the framework auto-adapt to your choice. For further information see the
{spring-boot-docs}/features.html#features.logging[Spring
{spring-boot-docs-ref}/features/logging.html[Spring
Boot Logging Reference Documentation].
[NOTE]
@@ -196,7 +196,7 @@ Kotlin::
When used as `org.springframework.validation.Validator`, `LocalValidatorFactoryBean`
invokes the underlying `jakarta.validation.Validator`, and then adapts
``ContraintViolation``s to ``FieldError``s, and registers them with the `Errors` object
``ConstraintViolation``s to ``FieldError``s, and registers them with the `Errors` object
passed into the `validate` method.
@@ -281,11 +281,11 @@ Kotlin::
A portable format annotation API exists in the `org.springframework.format.annotation`
package. You can use `@NumberFormat` to format `Number` fields such as `Double` and
`Long`, `@DurationFormat` to format `Duration` fields in ISO8601 and simplified styles,
and `@DateTimeFormat` to format `java.util.Date`, `java.util.Calendar`, `Long`
(for millisecond timestamps) as well as JSR-310 `java.time`.
`Long`, `@DurationFormat` to format `Duration` fields in ISO-8601 and simplified styles,
and `@DateTimeFormat` to format fields such as `java.util.Date`, `java.util.Calendar`,
and `Long` (for millisecond timestamps) as well as JSR-310 `java.time` types.
The following example uses `@DateTimeFormat` to format a `java.util.Date` as an ISO Date
The following example uses `@DateTimeFormat` to format a `java.util.Date` as an ISO date
(yyyy-MM-dd):
[tabs]
@@ -311,6 +311,29 @@ Kotlin::
----
======
For further details, see the javadoc for
{spring-framework-api}/format/annotation/DateTimeFormat.html[`@DateTimeFormat`],
{spring-framework-api}/format/annotation/DurationFormat.html[`@DurationFormat`], and
{spring-framework-api}/format/annotation/NumberFormat.html[`@NumberFormat`].
[WARNING]
====
Style-based formatting and parsing rely on locale-sensitive patterns which may change
depending on the Java runtime. Specifically, applications that rely on date, time, or
number parsing and formatting may encounter incompatible changes in behavior when running
on JDK 20 or higher.
Using an ISO standardized format or a concrete pattern that you control allows for
reliable system-independent and locale-independent parsing and formatting of date, time,
and number values.
For `@DateTimeFormat`, the use of fallback patterns can also help to address
compatibility issues.
For further details, see the
https://github.com/spring-projects/spring-framework/wiki/Date-and-Time-Formatting-with-JDK-20-and-higher[Date and Time Formatting with JDK 20 and higher]
page in the Spring Framework wiki.
====
[[format-FormatterRegistry-SPI]]
== The `FormatterRegistry` SPI
@@ -198,7 +198,7 @@ methods it offers can be found in the {spring-framework-api}/validation/Errors.h
Validators may also get locally invoked for the immediate validation of a given object,
not involving a binding process. As of 6.1, this has been simplified through a new
`Validator.validateObject(Object)` method which is available by default now, returning
a simple ´Errors` representation which can be inspected: typically calling `hasErrors()`
a simple `Errors` representation which can be inspected: typically calling `hasErrors()`
or the new `failOnError` method for turning the error summary message into an exception
(for example, `validator.validateObject(myObject).failOnError(IllegalArgumentException::new)`).
@@ -142,8 +142,7 @@ Alternatively, specify a custom `persistenceXmlLocation` on your
META-INF/my-persistence.xml) and include only a descriptor with that name in your
application jar files. Because the Jakarta EE server looks only for default
`META-INF/persistence.xml` files, it ignores such custom persistence units and, hence,
avoids conflicts with a Spring-driven JPA setup upfront. (This applies to Resin 3.1, for
example.)
avoids conflicts with a Spring-driven JPA setup upfront.
.When is load-time weaving required?
****
@@ -7,7 +7,7 @@ Metrics can help you to track error rates, usage patterns, performance, and more
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
You can learn more about {spring-boot-docs}/actuator.html#actuator.metrics[configuring the observability infrastructure in Spring Boot].
You can learn more about {spring-boot-docs-ref}/actuator/observability.html[configuring the observability infrastructure in Spring Boot].
[[observability.list]]
@@ -38,6 +38,7 @@ RestClient customClient = RestClient.builder()
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
@@ -55,6 +56,7 @@ val customClient = RestClient.builder()
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
@@ -13,7 +13,7 @@ Most of the code samples of the reference documentation are
provided in Kotlin in addition to Java.
The easiest way to build a Spring application with Kotlin is to leverage Spring Boot and
its {spring-boot-docs}/boot-features-kotlin.html[dedicated Kotlin support].
its {spring-boot-docs-ref}/features/kotlin.html[dedicated Kotlin support].
{spring-site-guides}/tutorials/spring-boot-kotlin/[This comprehensive tutorial]
will teach you how to build Spring Boot applications with Kotlin using https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
@@ -186,7 +186,7 @@ Therefore, if you wish to use the `@Value` annotation in Kotlin, you need to esc
character by writing pass:q[`@Value("\${property}")`].
NOTE: If you use Spring Boot, you should probably use
{spring-boot-docs}/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties[`@ConfigurationProperties`]
{spring-boot-docs-ref}/features/external-config.html#features.external-config.typesafe-configuration-properties[`@ConfigurationProperties`]
instead of `@Value` annotations.
As an alternative, you can customize the property placeholder prefix by declaring the
@@ -323,7 +323,7 @@ The recommended testing framework is https://junit.org/junit5/[JUnit 5] along wi
https://mockk.io/[Mockk] for mocking.
NOTE: If you are using Spring Boot, see
{spring-boot-docs}/features.html#features.kotlin.testing[this related documentation].
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
[[constructor-injection]]
@@ -39,8 +39,17 @@ xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overrid
and the original instance is wrapped in a Mockito spy. This strategy requires that
exactly one candidate bean exists.
NOTE: Only _singleton_ beans can be overridden. Any attempt to override a non-singleton
bean will result in an exception.
[TIP]
====
Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
will result in an exception.
When using `@MockitoBean` to mock a bean created by a `FactoryBean`, the `FactoryBean`
will be replaced with a singleton mock of the type of object created by the `FactoryBean`.
When using `@MockitoSpyBean` to create a spy for a `FactoryBean`, a spy will be created
for the object created by the `FactoryBean`, not for the `FactoryBean` itself.
====
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
@@ -82,7 +82,7 @@ Java::
<2> The result of this static method will be used as the instance and injected into the field.
======
[NOTE]
[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.
@@ -92,5 +92,12 @@ fully-qualified method name following the syntax `<fully-qualified class name>#<
for example, `methodName = "org.example.TestUtils#createCustomService"`.
====
NOTE: Only _singleton_ beans can be overridden. Any attempt to override a non-singleton
bean will result in an exception.
[TIP]
====
Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
will result in an exception.
When overriding a bean created by a `FactoryBean`, the `FactoryBean` will be replaced
with a singleton bean corresponding to the value returned from the `@TestBean` factory
method.
====
@@ -7,7 +7,7 @@ streams because there is no way to cancel the server stream from the client side
To test infinite streams, you'll need to
xref:testing/webtestclient.adoc#webtestclient-server-config[bind to] a running server,
or when using Spring Boot,
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server].
{spring-boot-docs-ref}/testing/spring-boot-applications.html#testing.spring-boot-applications.with-running-server[test with a running server].
`MockMvcWebTestClient` does support asynchronous responses, and even streaming responses.
The limitation is that it can't influence the server to stop, and therefore the server
@@ -21,7 +21,7 @@ for rendering JSON, XML, and other formats through `@ResponseBody` methods.
Alternatively, you may consider the full end-to-end integration testing support from
Spring Boot with `@SpringBootTest`. See the
{spring-boot-docs}/spring-boot-features.html#boot-features-testing[Spring Boot Reference Guide].
{spring-boot-docs-ref}/testing/spring-boot-applications.html[Spring Boot Reference Guide].
There are pros and cons for each approach. The options provided in Spring MVC Test are
different stops on the scale from classic unit testing to full integration testing. To be
@@ -57,6 +57,19 @@ defined by the corresponding `BeanOverrideStrategy`:
`WRAP`::
Retrieves the original bean and wraps it.
[TIP]
====
Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
will result in an exception.
When replacing a bean created by a `FactoryBean`, the `FactoryBean` itself will be
replaced with a singleton bean corresponding to bean override instance created by the
applicable `BeanOverrideHandler`.
When wrapping a bean created by a `FactoryBean`, the object created by the `FactoryBean`
will be wrapped, not the `FactoryBean` itself.
====
[NOTE]
====
In contrast to Spring's autowiring mechanism (for example, resolution of an `@Autowired`
@@ -71,6 +84,3 @@ Alternatively, the user can directly provide the bean name in the custom annotat
`BeanOverrideProcessor` implementations may also internally compute a bean name based on
a convention or some other method.
====
NOTE: Only _singleton_ beans can be overridden. Any attempt to override a non-singleton
bean will result in an exception.
@@ -10,6 +10,7 @@ by default, exactly in the following order:
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[] .
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
instance.
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
@@ -25,7 +26,6 @@ by default, exactly in the following order:
* `EventPublishingTestExecutionListener`: Publishes test execution events to the test's
`ApplicationContext` (see xref:testing/testcontext-framework/test-execution-events.adoc[Test Execution Events]).
* `MockitoResetTestExecutionListener`: Resets mocks as configured by `@MockitoBean` or `@MockitoSpyBean`.
* `BeanOverrideTestExecutionListener`: Provides support for xref:testing/testcontext-framework/bean-overriding.adoc[] .
[[testcontext-tel-config-registering-tels]]
== Registering `TestExecutionListener` Implementations
@@ -47,7 +47,7 @@ Kotlin::
NOTE: When using Spring Boot, you may want to use `@Configuration` classes of type `WebFluxConfigurer` but without
`@EnableWebFlux` to keep Spring Boot WebFlux customizations. See more details in
xref:#webflux-config-customize[the WebFlux config API section] and in
{spring-boot-docs}/web.html#web.reactive.webflux.auto-configuration[the dedicated Spring Boot documentation].
{spring-boot-docs-ref}/web/reactive.html#web.reactive.webflux.auto-configuration[the dedicated Spring Boot documentation].
The preceding example registers a number of Spring WebFlux
xref:web/webflux/dispatcher-handler.adoc#webflux-special-bean-types[infrastructure beans] and adapts to dependencies
@@ -94,7 +94,8 @@ class WebConfig : WebFluxConfigurer {
[.small]#xref:web/webmvc/mvc-config/conversion.adoc[See equivalent in the Servlet stack]#
By default, formatters for various number and date types are installed, along with support
for customization via `@NumberFormat`, `@DurationFormat` and `@DateTimeFormat` on fields.
for customization via `@NumberFormat`, `@DurationFormat`, and `@DateTimeFormat` on fields
and parameters.
To register custom formatters and converters in Java config, use the following:
@@ -349,7 +350,6 @@ which customizes Jackson's default properties with the following ones:
It also automatically registers the following well-known modules if they are detected on the classpath:
* {jackson-github-org}/jackson-datatype-joda[`jackson-datatype-joda`]: Support for Joda-Time types.
* {jackson-github-org}/jackson-datatype-jsr310[`jackson-datatype-jsr310`]: Support for Java 8 Date and Time API types.
* {jackson-github-org}/jackson-datatype-jdk8[`jackson-datatype-jdk8`]: Support for other Java 8 types, such as `Optional`.
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
@@ -419,6 +419,24 @@ controllers. However, when you use it with Spring Security, we advise relying on
See the section on xref:web/webflux-cors.adoc[CORS] and the xref:web/webflux-cors.adoc#webflux-cors-webfilter[CORS `WebFilter`] for more details.
[[filters.url-handler]]
=== URL Handler
[.small]#xref:web/webmvc/filters.adoc#filters.url-handler[See equivalent in the Servlet stack]#
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
Adding trailing slash variants to all mapping declarations is not the best way to handle this use case.
The `UrlHandlerFilter` web filter has been designed for this purpose. It can be configured to:
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
* mutate the request to act as if the request was sent without a trailing slash and continue the processing of the request.
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
[[webflux-exception-handler]]
== Exceptions
@@ -9,7 +9,11 @@ The `spring-web` module provides some useful filters:
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
* xref:web/webmvc/filters.adoc#filters-shallow-etag[Shallow ETag]
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations.
If you are using Spring Boot, you can
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare them as beans and configure them as part of your application].
[[filters-http-put]]
@@ -109,4 +113,22 @@ See the sections on xref:web/webmvc-cors.adoc[CORS] and the xref:web/webmvc-cors
[[filters.url-handler]]
== URL Handler
[.small]#xref:web/webflux/reactive-spring.adoc#filters.url-handler[See equivalent in the Reactive stack]#
In previous Spring Framework versions, Spring MVC could be configured to ignore trailing slashes in URL paths
when mapping incoming requests on controller methods. This could be done by enabling the `setUseTrailingSlashMatch`
option on the `PathMatchConfigurer`. This means that sending a "GET /home/" request would be handled by a controller
method annotated with `@GetMapping("/home")`.
This option has been retired, but applications are still expected to handle such requests in a safe way.
The `UrlHandlerFilter` Servlet filter has been designed for this purpose. It can be configured to:
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
* wrap the request to act as if the request was sent without a trailing slash and continue the processing of the request.
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
@@ -4,7 +4,8 @@
[.small]#xref:web/webflux/config.adoc#webflux-config-conversion[See equivalent in the Reactive stack]#
By default, formatters for various number and date types are installed, along with support
for customization via `@NumberFormat`, `@DurationFormat` and `@DateTimeFormat` on fields.
for customization via `@NumberFormat`, `@DurationFormat`, and `@DateTimeFormat` on fields
and parameters.
To register custom formatters and converters, use the following:
@@ -21,7 +21,7 @@ The caveat to overriding the `/` Servlet mapping is that the `RequestDispatcher`
default Servlet must be retrieved by name rather than by path. The
`DefaultServletHttpRequestHandler` tries to auto-detect the default Servlet for
the container at startup time, using a list of known names for most of the major Servlet
containers (including Tomcat, Jetty, GlassFish, JBoss, Resin, WebLogic, and WebSphere).
containers (including Tomcat, Jetty, GlassFish, JBoss, WebLogic, and WebSphere).
If the default Servlet has been custom-configured with a different name, or if a
different Servlet container is being used where the default Servlet name is unknown,
then you must explicitly provide the default Servlet's name, as the following example shows:
@@ -7,7 +7,7 @@ You can use the `@EnableWebMvc` annotation to enable MVC configuration with prog
include-code::./WebConfiguration[tag=snippet,indent=0]
NOTE: When using Spring Boot, you may want to use `@Configuration` classes of type `WebMvcConfigurer` but without `@EnableWebMvc` to keep Spring Boot MVC customizations. See more details in xref:web/webmvc/mvc-config/customize.adoc[the MVC Config API section] and in {spring-boot-docs}/web.html#web.servlet.spring-mvc.auto-configuration[the dedicated Spring Boot documentation].
NOTE: When using Spring Boot, you may want to use `@Configuration` classes of type `WebMvcConfigurer` but without `@EnableWebMvc` to keep Spring Boot MVC customizations. See more details in xref:web/webmvc/mvc-config/customize.adoc[the MVC Config API section] and in {spring-boot-docs-ref}/web/servlet.html#web.servlet.spring-mvc.auto-configuration[the dedicated Spring Boot documentation].
The preceding example registers a number of Spring MVC
xref:web/webmvc/mvc-servlet/special-bean-types.adoc[infrastructure beans] and adapts to dependencies
@@ -11,7 +11,7 @@ You can also customize the list of configured message converters at the end by o
TIP: In a Spring Boot application, the `WebMvcAutoConfiguration` adds any
`HttpMessageConverter` beans it detects, in addition to default converters. Hence, in a
Boot application, prefer to use the {spring-boot-docs}/web.html#web.servlet.spring-mvc.message-converters[HttpMessageConverters]
Boot application, prefer to use the {spring-boot-docs-ref}/web/servlet.html#web.servlet.spring-mvc.message-converters[HttpMessageConverters]
mechanism. Or alternatively, use `extendMessageConverters` to modify message converters
at the end.
@@ -35,7 +35,6 @@ This builder customizes Jackson's default properties as follows:
It also automatically registers the following well-known modules if they are detected on the classpath:
* {jackson-github-org}/jackson-datatype-joda[jackson-datatype-joda]: Support for Joda-Time types.
* {jackson-github-org}/jackson-datatype-jsr310[jackson-datatype-jsr310]: Support for Java 8 Date and Time API types.
* {jackson-github-org}/jackson-datatype-jdk8[jackson-datatype-jdk8]: Support for other Java 8 types, such as `Optional`.
* {jackson-github-org}/jackson-module-kotlin[jackson-module-kotlin]: Support for Kotlin classes and data classes.
@@ -119,7 +119,7 @@ Some example patterns:
* `+"/resources/*.png"+` - match zero or more characters in a path segment
* `+"/resources/**"+` - match multiple path segments
* `+"/projects/{project}/versions"+` - match a path segment and capture it as a variable
* `+"/projects/{project:[a-z]+}/versions"+` - match and capture a variable with a regex
* `++"/projects/{project:[a-z]+}/versions"++` - match and capture a variable with a regex
Captured URI variables can be accessed with `@PathVariable`. For example:
@@ -111,7 +111,7 @@ the lifecycle of the Servlet container, Spring Boot uses Spring configuration to
bootstrap itself and the embedded Servlet container. `Filter` and `Servlet` declarations
are detected in Spring configuration and registered with the Servlet container.
For more details, see the
{spring-boot-docs}/web.html#web.servlet.embedded-container[Spring Boot documentation].
{spring-boot-docs-ref}/web/servlet.html#web.servlet.embedded-container[Spring Boot documentation].
@@ -45,7 +45,7 @@ input is ignored. This is in contrast to property binding which by default binds
request parameter for which there is a matching property.
If neither a dedicated model object nor constructor binding is sufficient, and you must
use property binding, we strongy recommend registering `allowedFields` patterns (case
use property binding, we strongly recommend registering `allowedFields` patterns (case
sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set.
For example:
+1 -1
View File
@@ -4,7 +4,7 @@
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.11.1",
"@springio/antora-extensions": "1.14.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.web.webflux.filters.urlhandler;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.reactive.UrlHandlerFilter;
public class UrlHandlerFilterConfiguration {
public void configureUrlHandlerFilter() {
// tag::config[]
UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").mutateRequest()
.build();
// end::config[]
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.web.webmvc.filters.urlhandler;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.UrlHandlerFilter;
public class UrlHandlerFilterConfiguration {
public void configureUrlHandlerFilter() {
// tag::config[]
UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").wrapRequest()
.build();
// end::config[]
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.web.webflux.filters.urlhandler
import org.springframework.http.HttpStatus
import org.springframework.web.filter.reactive.UrlHandlerFilter
class UrlHandlerFilterConfiguration {
@Suppress("UNUSED_VARIABLE")
fun configureUrlHandlerFilter() {
// tag::config[]
val urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").mutateRequest()
.build()
// end::config[]
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.web.webmvc.filters.urlhandler
import org.springframework.http.HttpStatus
import org.springframework.web.filter.UrlHandlerFilter
class UrlHandlerFilterConfiguration {
@Suppress("UNUSED_VARIABLE")
fun configureUrlHandlerFilter() {
// tag::config[]
val urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").wrapRequest()
.build()
// end::config[]
}
}
+29 -29
View File
@@ -7,32 +7,32 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.0"))
api(platform("io.micrometer:micrometer-bom:1.14.0-RC1"))
api(platform("io.netty:netty-bom:4.1.113.Final"))
api(platform("com.fasterxml.jackson:jackson-bom:2.18.1"))
api(platform("io.micrometer:micrometer-bom:1.14.0"))
api(platform("io.netty:netty-bom:4.1.115.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.0-RC1"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.23"))
api(platform("io.projectreactor:reactor-bom:2024.0.0"))
api(platform("io.rsocket:rsocket-bom:1.1.4"))
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.26.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.13"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.13"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.15"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.15"))
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.2"))
api(platform("org.mockito:mockito-bom:5.14.1"))
api(platform("org.junit:junit-bom:5.11.3"))
api(platform("org.mockito:mockito-bom:5.14.2"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.6.2")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
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.10.1")
api("com.google.protobuf:protobuf-java-util:4.27.1")
api("com.h2database:h2:2.2.224")
api("com.google.code.gson:gson:2.11.0")
api("com.google.protobuf:protobuf-java-util:4.28.3")
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")
api("com.rometools:rome:1.19.0")
@@ -44,7 +44,7 @@ dependencies {
api("com.sun.xml.bind:jaxb-impl:3.0.2")
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.1.0")
api("com.thoughtworks.xstream:xstream:1.4.20")
api("com.thoughtworks.xstream:xstream:1.4.21")
api("commons-io:commons-io:2.15.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("io.micrometer:context-propagation:1.1.1")
@@ -54,9 +54,9 @@ dependencies {
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.8")
api("io.reactivex.rxjava3:rxjava:3.1.9")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.17.Final")
api("io.undertow:undertow-core:2.3.18.Final")
api("io.undertow:undertow-servlet:2.3.17.Final")
api("io.undertow:undertow-websockets-jsr:2.3.17.Final")
api("io.vavr:vavr:0.10.4")
@@ -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")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3")
api("org.apache.httpcomponents.client5:httpclient5:5.4.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
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")
@@ -116,7 +116,7 @@ dependencies {
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.7")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
@@ -129,25 +129,25 @@ dependencies {
api("org.hamcrest:hamcrest:2.2")
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.2")
api("org.htmlunit:htmlunit:4.2.0")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.6.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.8.0")
api("org.jruby:jruby:9.4.9.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.3")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.20.0")
api("org.seleniumhq.selenium:selenium-java:4.20.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.26.0")
api("org.seleniumhq.selenium:selenium-java:4.26.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.16")
api("org.testng:testng:7.9.0")
api("org.testng:testng:7.10.2")
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.xmlunit:xmlunit-assertj:2.9.1")
api("org.xmlunit:xmlunit-matchers:2.9.1")
api("org.yaml:snakeyaml:2.2")
api("org.xmlunit:xmlunit-assertj:2.10.0")
api("org.xmlunit:xmlunit-matchers:2.10.0")
api("org.yaml:snakeyaml:2.3")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.0-SNAPSHOT
version=6.2.0
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+3 -2
View File
@@ -55,8 +55,9 @@ plugins.withType(JavaPlugin).configureEach {
languageVersion = testLanguageVersion
}
// Enable Java experimental support in Bytebuddy
// Remove when JDK 22 is supported by Mockito
if (testLanguageVersion == JavaLanguageVersion.of(22)) {
// Bytebuddy 1.15.4 supports JDK <= 24
// see https://github.com/raphw/byte-buddy/blob/master/release-notes.md
if (testLanguageVersion.compareTo(JavaLanguageVersion.of(24)) > 0 ) {
jvmArgs("-Dnet.bytebuddy.experimental=true")
}
}
@@ -56,33 +56,33 @@ import kotlin.annotation.AnnotationTarget.TYPE
@SpringJUnitConfig(InterceptorConfig::class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class AspectJAutoProxyInterceptorKotlinIntegrationTests(
@Autowired val echo: Echo,
@Autowired val firstAdvisor: TestPointcutAdvisor,
@Autowired val secondAdvisor: TestPointcutAdvisor,
@Autowired val countingAspect: CountingAspect,
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
@Autowired val echo: Echo,
@Autowired val firstAdvisor: TestPointcutAdvisor,
@Autowired val secondAdvisor: TestPointcutAdvisor,
@Autowired val countingAspect: CountingAspect,
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
@Test
fun `Multiple interceptors with regular function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
assertThat(echo.echo(value)).isEqualTo(value)
@Test
fun `Multiple interceptors with regular function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
assertThat(echo.echo(value)).isEqualTo(value)
assertThat(firstAdvisor.interceptor.invocations).singleElement().matches { String::class.java.isAssignableFrom(it) }
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { String::class.java.isAssignableFrom(it) }
}
}
@Test
fun `Multiple interceptors with suspending function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
runBlocking {
assertThat(echo.suspendingEcho(value)).isEqualTo(value)
}
@Test
fun `Multiple interceptors with suspending function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
runBlocking {
assertThat(echo.suspendingEcho(value)).isEqualTo(value)
}
assertThat(firstAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
}
}
@Test // gh-33095
fun `Aspect and reactive transactional with suspending function`() {
@@ -113,17 +113,17 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
assertThat(countingAspect.counter).`as`("aspect applied once per key").isEqualTo(2)
}
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@EnableCaching
open class InterceptorConfig {
open class InterceptorConfig {
@Bean
open fun firstAdvisor() = TestPointcutAdvisor().apply { order = 0 }
@Bean
open fun firstAdvisor() = TestPointcutAdvisor().apply { order = 0 }
@Bean
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
@Bean
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
@Bean
open fun countingAspect() = CountingAspect()
@@ -138,34 +138,34 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
return ConcurrentMapCacheManager()
}
@Bean
open fun echo(): Echo {
return Echo()
}
}
@Bean
open fun echo(): Echo {
return Echo()
}
}
class TestMethodInterceptor: MethodInterceptor {
class TestMethodInterceptor: MethodInterceptor {
var invocations: MutableList<Class<*>> = mutableListOf()
var invocations: MutableList<Class<*>> = mutableListOf()
@Suppress("RedundantNullableReturnType")
override fun invoke(invocation: MethodInvocation): Any? {
val result = invocation.proceed()
invocations.add(result!!.javaClass)
return result
}
@Suppress("RedundantNullableReturnType")
override fun invoke(invocation: MethodInvocation): Any? {
val result = invocation.proceed()
invocations.add(result!!.javaClass)
return result
}
}
}
class TestPointcutAdvisor : StaticMethodMatcherPointcutAdvisor(TestMethodInterceptor()) {
class TestPointcutAdvisor : StaticMethodMatcherPointcutAdvisor(TestMethodInterceptor()) {
val interceptor: TestMethodInterceptor
get() = advice as TestMethodInterceptor
val interceptor: TestMethodInterceptor
get() = advice as TestMethodInterceptor
override fun matches(method: Method, targetClass: Class<*>): Boolean {
return targetClass == Echo::class.java && method.name.lowercase().endsWith("echo")
}
}
override fun matches(method: Method, targetClass: Class<*>): Boolean {
return targetClass == Echo::class.java && method.name.lowercase().endsWith("echo")
}
}
@Target(CLASS, FUNCTION, ANNOTATION_CLASS, TYPE)
@Retention(AnnotationRetention.RUNTIME)
@@ -185,16 +185,16 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
}
}
open class Echo {
open class Echo {
open fun echo(value: String): String {
return value
}
open fun echo(value: String): String {
return value
}
open suspend fun suspendingEcho(value: String): String {
delay(1)
return value
}
open suspend fun suspendingEcho(value: String): String {
delay(1)
return value
}
@Transactional
@Counting
@@ -212,6 +212,6 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
return "$value ${cacheCounter++}"
}
}
}
}
@@ -18,6 +18,7 @@ package org.springframework.aop.aspectj.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.StringTokenizer;
@@ -37,6 +38,7 @@ import org.aspectj.lang.reflect.PerClauseKind;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.SpringProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
@@ -58,6 +60,23 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
private static final String AJC_MAGIC = "ajc$";
/**
* System property that instructs Spring to ignore ajc-compiled aspects
* for Spring AOP proxying, restoring traditional Spring behavior for
* scenarios where both weaving and AspectJ auto-proxying are enabled.
* <p>The default is "false". Consider switching this to "true" if you
* encounter double execution of your aspects in a given build setup.
* Note that we recommend restructuring your AspectJ configuration to
* avoid such double exposure of an AspectJ aspect to begin with.
* @since 6.1.15
*/
public static final String IGNORE_AJC_PROPERTY_NAME = "spring.aop.ajc.ignore";
private static final boolean shouldIgnoreAjcCompiledAspects =
SpringProperties.getFlag(IGNORE_AJC_PROPERTY_NAME);
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@@ -67,7 +86,8 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
@Override
public boolean isAspect(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null &&
(!shouldIgnoreAjcCompiledAspects || !compiledByAjc(clazz)));
}
@Override
@@ -114,6 +134,15 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
private static boolean compiledByAjc(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
/**
* Enum for AspectJ annotation types.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -281,8 +281,8 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
* @param returnType the declared return type (potentially a {@link Future} variant)
* @return the execution result (potentially a corresponding {@link Future} handle)
*/
@SuppressWarnings("removal")
@Nullable
@SuppressWarnings("deprecation")
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return executor.submitCompletable(task);
@@ -16,30 +16,32 @@
package org.springframework.aop.framework;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Objects;
import org.aopalliance.intercept.MethodInterceptor;
import org.assertj.core.api.WithAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.lang.Nullable;
import static org.mockito.BDDMockito.doAnswer;
import static org.mockito.BDDMockito.doThrow;
import static org.mockito.BDDMockito.mock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
/**
* @author Mikaël Francoeur
* @author Sam Brannen
* @since 6.2
* @see JdkProxyExceptionHandlingTests
* @see CglibProxyExceptionHandlingTests
*/
abstract class ProxyExceptionHandlingTests implements WithAssertions {
abstract class AbstractProxyExceptionHandlingTests {
private static final RuntimeException uncheckedException = new RuntimeException();
@@ -47,14 +49,12 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
private static final UndeclaredCheckedException undeclaredCheckedException = new UndeclaredCheckedException();
protected final MyClass target = mock(MyClass.class);
protected final MyClass target = mock();
protected final ProxyFactory proxyFactory = new ProxyFactory(target);
@Nullable
protected MyInterface proxy;
@Nullable
private Throwable throwableSeenByCaller;
@@ -63,46 +63,24 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
Mockito.clearInvocations(target);
}
protected void assertProxyType(Object proxy) {
}
protected abstract void assertProxyType(Object proxy);
private void invokeProxy() {
throwableSeenByCaller = catchThrowable(() -> Objects.requireNonNull(proxy).doSomething());
}
@SuppressWarnings("SameParameterValue")
private Answer<?> sneakyThrow(Throwable throwable) {
private static Answer<?> sneakyThrow(Throwable throwable) {
return invocation -> {
throw throwable;
};
}
static class JdkAopProxyTests extends ProxyExceptionHandlingTests {
@Override
protected void assertProxyType(Object proxy) {
assertThat(Proxy.isProxyClass(proxy.getClass())).isTrue();
}
}
static class CglibAopProxyTests extends ProxyExceptionHandlingTests {
@BeforeEach
void setup() {
proxyFactory.setProxyTargetClass(true);
}
@Override
protected void assertProxyType(Object proxy) {
assertThat(Enhancer.isEnhanced(proxy.getClass())).isTrue();
}
}
@Nested
class WhenThereIsOneInterceptor {
class WhenThereIsOneInterceptorTests {
@Nullable
private Throwable throwableSeenByInterceptor;
@@ -110,13 +88,13 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
@BeforeEach
void beforeEach() {
proxyFactory.addAdvice(captureThrowable());
proxy = (MyInterface) proxyFactory.getProxy(ProxyExceptionHandlingTests.class.getClassLoader());
proxy = (MyInterface) proxyFactory.getProxy(getClass().getClassLoader());
assertProxyType(proxy);
}
@Test
void targetThrowsUndeclaredCheckedException() throws DeclaredCheckedException {
doAnswer(sneakyThrow(undeclaredCheckedException)).when(target).doSomething();
willAnswer(sneakyThrow(undeclaredCheckedException)).given(target).doSomething();
invokeProxy();
assertThat(throwableSeenByInterceptor).isSameAs(undeclaredCheckedException);
assertThat(throwableSeenByCaller)
@@ -126,7 +104,7 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
@Test
void targetThrowsDeclaredCheckedException() throws DeclaredCheckedException {
doThrow(declaredCheckedException).when(target).doSomething();
willThrow(declaredCheckedException).given(target).doSomething();
invokeProxy();
assertThat(throwableSeenByInterceptor).isSameAs(declaredCheckedException);
assertThat(throwableSeenByCaller).isSameAs(declaredCheckedException);
@@ -134,7 +112,7 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
@Test
void targetThrowsUncheckedException() throws DeclaredCheckedException {
doThrow(uncheckedException).when(target).doSomething();
willThrow(uncheckedException).given(target).doSomething();
invokeProxy();
assertThat(throwableSeenByInterceptor).isSameAs(uncheckedException);
assertThat(throwableSeenByCaller).isSameAs(uncheckedException);
@@ -155,17 +133,17 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
@Nested
class WhenThereAreNoInterceptors {
class WhenThereAreNoInterceptorsTests {
@BeforeEach
void beforeEach() {
proxy = (MyInterface) proxyFactory.getProxy(ProxyExceptionHandlingTests.class.getClassLoader());
proxy = (MyInterface) proxyFactory.getProxy(getClass().getClassLoader());
assertProxyType(proxy);
}
@Test
void targetThrowsUndeclaredCheckedException() throws DeclaredCheckedException {
doAnswer(sneakyThrow(undeclaredCheckedException)).when(target).doSomething();
willAnswer(sneakyThrow(undeclaredCheckedException)).given(target).doSomething();
invokeProxy();
assertThat(throwableSeenByCaller)
.isInstanceOf(UndeclaredThrowableException.class)
@@ -174,21 +152,21 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
@Test
void targetThrowsDeclaredCheckedException() throws DeclaredCheckedException {
doThrow(declaredCheckedException).when(target).doSomething();
willThrow(declaredCheckedException).given(target).doSomething();
invokeProxy();
assertThat(throwableSeenByCaller).isSameAs(declaredCheckedException);
}
@Test
void targetThrowsUncheckedException() throws DeclaredCheckedException {
doThrow(uncheckedException).when(target).doSomething();
willThrow(uncheckedException).given(target).doSomething();
invokeProxy();
assertThat(throwableSeenByCaller).isSameAs(uncheckedException);
}
}
protected interface MyInterface {
interface MyInterface {
void doSomething() throws DeclaredCheckedException;
}
@@ -202,11 +180,11 @@ abstract class ProxyExceptionHandlingTests implements WithAssertions {
}
@SuppressWarnings("serial")
protected static class UndeclaredCheckedException extends Exception {
private static class UndeclaredCheckedException extends Exception {
}
@SuppressWarnings("serial")
protected static class DeclaredCheckedException extends Exception {
private static class DeclaredCheckedException extends Exception {
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.framework;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.cglib.proxy.Enhancer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Mikaël Francoeur
* @since 6.2
* @see JdkProxyExceptionHandlingTests
*/
class CglibProxyExceptionHandlingTests extends AbstractProxyExceptionHandlingTests {
@BeforeEach
void setup() {
proxyFactory.setProxyTargetClass(true);
}
@Override
protected void assertProxyType(Object proxy) {
assertThat(Enhancer.isEnhanced(proxy.getClass())).isTrue();
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.framework;
import java.lang.reflect.Proxy;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Mikaël Francoeur
* @since 6.2
* @see CglibProxyExceptionHandlingTests
*/
class JdkProxyExceptionHandlingTests extends AbstractProxyExceptionHandlingTests {
@Override
protected void assertProxyType(Object proxy) {
assertThat(Proxy.isProxyClass(proxy.getClass())).isTrue();
}
}
@@ -31,17 +31,17 @@ import kotlin.coroutines.Continuation
*/
class AopUtilsKotlinTests {
@Test
fun `Invoking suspending function should return Mono`() {
val value = "foo"
val method = ReflectionUtils.findMethod(WithoutInterface::class.java, "handle",
@Test
fun `Invoking suspending function should return Mono`() {
val value = "foo"
val method = ReflectionUtils.findMethod(WithoutInterface::class.java, "handle",
String::class. java, Continuation::class.java)!!
val continuation = Continuation<Any>(CoroutineName("test")) { }
val continuation = Continuation<Any>(CoroutineName("test")) { }
val result = AopUtils.invokeJoinpointUsingReflection(WithoutInterface(), method, arrayOf(value, continuation))
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
assertThat(it.block()).isEqualTo(value)
}
}
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
assertThat(it.block()).isEqualTo(value)
}
}
@Test
fun `Invoking suspending function on bridged method should return Mono`() {
@@ -54,11 +54,11 @@ class AopUtilsKotlinTests {
}
}
@Suppress("unused")
suspend fun suspendingFunction(value: String): String {
delay(1)
return value
}
@Suppress("unused")
suspend fun suspendingFunction(value: String): String {
delay(1)
return value
}
class WithoutInterface {
suspend fun handle(value: String): String {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,10 +28,10 @@ import org.springframework.scheduling.annotation.Async;
* <p>This aspect routes methods marked with the {@link Async} annotation as well as methods
* in classes marked with the same. Any method expected to be routed asynchronously must
* return either {@code void}, {@link Future}, or a subtype of {@link Future} (in particular,
* Spring's {@link org.springframework.util.concurrent.ListenableFuture}). This aspect,
* therefore, will produce a compile-time error for methods that violate this constraint
* on the return type. If, however, a class marked with {@code @Async} contains a method
* that violates this constraint, it produces only a warning.
* {@link java.util.concurrent.CompletableFuture}). This aspect, therefore, will produce a
* compile-time error for methods that violate this constraint on the return type. If,
* however, a class marked with {@code @Async} contains a method that violates this
* constraint, it produces only a warning.
*
* <p>This aspect needs to be injected with an implementation of a task-oriented
* {@link java.util.concurrent.Executor} to activate it for a specific thread pool,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,9 +92,13 @@ public interface ListableBeanFactory extends BeanFactory {
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
* of instances, including availability and uniqueness options.
* @param requiredType type the bean must match; can be an interface or superclass
* @param allowEagerInit whether stream-based access may initialize <i>lazy-init
* singletons</i> and <i>objects created by FactoryBeans</i> (or by factory methods
* with a "factory-bean" reference) for the type check
* @param allowEagerInit whether stream access may introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return a corresponding provider handle
* @since 5.3
* @see #getBeanProvider(ResolvableType, boolean)
@@ -112,9 +116,13 @@ public interface ListableBeanFactory extends BeanFactory {
* injection points. For programmatically retrieving a list of beans matching a
* specific type, specify the actual bean type as an argument here and subsequently
* use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options.
* @param allowEagerInit whether stream-based access may initialize <i>lazy-init
* singletons</i> and <i>objects created by FactoryBeans</i> (or by factory methods
* with a "factory-bean" reference) for the type check
* @param allowEagerInit whether stream access may introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return a corresponding provider handle
* @since 5.3
* @see #getBeanProvider(ResolvableType)
@@ -175,11 +183,13 @@ public interface ListableBeanFactory extends BeanFactory {
* @param type the generically typed class or interface to match
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
* <i>objects created by FactoryBeans</i> (or by factory methods with a
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
* @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references.
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return the names of beans (or objects created by FactoryBeans) matching
* the given object type (including subclasses), or an empty array if none
* @since 5.2
@@ -236,11 +246,13 @@ public interface ListableBeanFactory extends BeanFactory {
* @param type the class or interface to match, or {@code null} for all bean names
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
* <i>objects created by FactoryBeans</i> (or by factory methods with a
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
* @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references.
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return the names of beans (or objects created by FactoryBeans) matching
* the given object type (including subclasses), or an empty array if none
* @see FactoryBean#getObjectType
@@ -300,11 +312,13 @@ public interface ListableBeanFactory extends BeanFactory {
* @param type the class or interface to match, or {@code null} for all concrete beans
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
* <i>objects created by FactoryBeans</i> (or by factory methods with a
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
* @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
* and <i>objects created by FactoryBeans</i> - or by factory methods with a
* "factory-bean" reference - for the type check. Note that FactoryBeans need to be
* eagerly initialized to determine their type: So be aware that passing in "true"
* for this flag will initialize FactoryBeans and "factory-bean" references.
* for this flag will initialize FactoryBeans and "factory-bean" references. Only
* actually necessary initialization for type checking purposes will be performed;
* constructor and method invocations will still be avoided as far as possible.
* @return a Map with the matching beans, containing the bean names as
* keys and the corresponding bean instances as values
* @throws BeansException if a bean could not be created
@@ -32,6 +32,8 @@ import org.springframework.lang.Nullable;
* <p>In a {@link BeanFactory} environment, every {@code ObjectProvider} obtained
* from the factory will be bound to its {@code BeanFactory} for a specific bean
* type, matching all provider calls against factory-registered bean definitions.
* Note that all such calls dynamically operate on the underlying factory state,
* freshly resolving the requested target object on every call.
*
* <p>As of 5.1, this interface extends {@link Iterable} and provides {@link Stream}
* support. It can be therefore be used in {@code for} loops, provides {@link #forEach}
@@ -154,26 +154,35 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
*/
@Override
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
boolean match = super.isAutowireCandidate(bdHolder, descriptor);
if (match) {
match = checkQualifiers(bdHolder, descriptor.getAnnotations());
if (match) {
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());
if (!super.isAutowireCandidate(bdHolder, descriptor)) {
return false;
}
Boolean checked = checkQualifiers(bdHolder, descriptor.getAnnotations());
if (checked != Boolean.FALSE) {
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
Boolean methodChecked = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());
if (methodChecked != null && checked == null) {
checked = methodChecked;
}
}
}
}
return match;
return (checked == Boolean.TRUE ||
(checked == null && ((RootBeanDefinition) bdHolder.getBeanDefinition()).isDefaultCandidate()));
}
/**
* Match the given qualifier annotations against the candidate bean definition.
* @return {@code false} if a qualifier has been found but not matched,
* {@code true} if a qualifier has been found and matched,
* {@code null} if no qualifier has been found at all
*/
protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
@Nullable
protected Boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
boolean qualifierFound = false;
if (!ObjectUtils.isEmpty(annotationsToSearch)) {
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
@@ -217,7 +226,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
}
}
}
return (qualifierFound || ((RootBeanDefinition) bdHolder.getBeanDefinition()).isDefaultCandidate());
return (qualifierFound ? true : null);
}
/**
@@ -1996,12 +1996,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* @param requiredType the target dependency type to match against
* @return the name of the candidate with the highest priority,
* or {@code null} if none found
* @throws NoUniqueBeanDefinitionException if multiple beans are detected with
* the same highest priority value
* @see #getPriority(Object)
*/
@Nullable
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {
String highestPriorityBeanName = null;
Integer highestPriority = null;
boolean highestPriorityConflictDetected = false;
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
@@ -2010,13 +2013,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (candidatePriority != null) {
if (highestPriority != null) {
if (candidatePriority.equals(highestPriority)) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"Multiple beans found with the same priority ('" + highestPriority +
"') among candidates: " + candidates.keySet());
highestPriorityConflictDetected = true;
}
else if (candidatePriority < highestPriority) {
highestPriorityBeanName = candidateBeanName;
highestPriority = candidatePriority;
highestPriorityConflictDetected = false;
}
}
else {
@@ -2026,6 +2028,13 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
}
}
if (highestPriorityConflictDetected) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"Multiple beans found with the same highest priority (" + highestPriority +
") among candidates: " + candidates.keySet());
}
return highestPriorityBeanName;
}
@@ -1718,18 +1718,73 @@ class DefaultListableBeanFactoryTests {
assertThat(bean.getBeanName()).isEqualTo("bd1");
}
/**
* {@code determineHighestPriorityCandidate()} should reject duplicate
* priorities for the highest priority detected.
*
* @see #getBeanByTypeWithMultipleNonHighestPriorityCandidates()
*/
@Test
void getBeanByTypeWithMultiplePriority() {
void getBeanByTypeWithMultipleHighestPriorityCandidates() {
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
RootBeanDefinition bd1 = new RootBeanDefinition(HighPriorityTestBean.class);
RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class);
RootBeanDefinition bd2 = new RootBeanDefinition(LowPriorityTestBean.class);
RootBeanDefinition bd3 = new RootBeanDefinition(HighPriorityTestBean.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
lbf.registerBeanDefinition("bd3", bd3);
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(TestBean.class))
.withMessageContaining("Multiple beans found with the same priority")
.withMessageContaining("5"); // conflicting priority
.withMessageContaining("Multiple beans found with the same highest priority (5) among candidates: ");
}
/**
* {@code determineHighestPriorityCandidate()} should ignore duplicate
* priorities for any priority other than the highest, and the order in
* which beans is declared should not affect the outcome.
*
* @see #getBeanByTypeWithMultipleHighestPriorityCandidates()
*/
@Test // gh-33733
void getBeanByTypeWithMultipleNonHighestPriorityCandidates() {
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService1.class,
PriorityService2A.class,
PriorityService2B.class,
PriorityService3.class
);
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService3.class,
PriorityService2B.class,
PriorityService2A.class,
PriorityService1.class
);
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService2A.class,
PriorityService1.class,
PriorityService2B.class,
PriorityService3.class
);
getBeanByTypeWithMultipleNonHighestPriorityCandidates(
PriorityService2A.class,
PriorityService3.class,
PriorityService1.class,
PriorityService2B.class
);
}
private void getBeanByTypeWithMultipleNonHighestPriorityCandidates(Class<?>... classes) {
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
for (Class<?> clazz : classes) {
lbf.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz));
}
PriorityService bean = lbf.getBean(PriorityService.class);
assertThat(bean).isExactlyInstanceOf(PriorityService1.class);
}
@Test
@@ -3519,6 +3574,26 @@ class DefaultListableBeanFactoryTests {
}
interface PriorityService {
}
@Priority(1)
static class PriorityService1 implements PriorityService {
}
@Priority(2)
static class PriorityService2A implements PriorityService {
}
@Priority(2)
static class PriorityService2B implements PriorityService {
}
@Priority(3)
static class PriorityService3 implements PriorityService {
}
@Priority(5)
private static class HighPriorityTestBean extends TestBean {
}
@@ -43,36 +43,36 @@ import javax.lang.model.element.Modifier
*/
class InstanceSupplierCodeGeneratorKotlinTests {
private val generationContext = TestGenerationContext()
private val generationContext = TestGenerationContext()
private val beanFactory = DefaultListableBeanFactory()
@Test
fun generateWhenHasDefaultConstructor() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBean::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean = getBean<KotlinTestBean>(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBean::class.java)
Assertions.assertThat(compiled.sourceFile).contains("InstanceSupplier.using(KotlinTestBean::new)")
}
Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT))
}
@Test
fun generateWhenHasDefaultConstructor() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBean::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean = getBean<KotlinTestBean>(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBean::class.java)
Assertions.assertThat(compiled.sourceFile).contains("InstanceSupplier.using(KotlinTestBean::new)")
}
Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT))
}
@Test
fun generateWhenConstructorHasOptionalParameter() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBeanWithOptionalParameter::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean: KotlinTestBeanWithOptionalParameter = getBean(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBeanWithOptionalParameter::class.java)
Assertions.assertThat(compiled.sourceFile)
.contains("return BeanInstanceSupplier.<KotlinTestBeanWithOptionalParameter>forConstructor();")
}
Assertions.assertThat<TypeHint>(getReflectionHints().getTypeHint(KotlinTestBeanWithOptionalParameter::class.java))
.satisfies(hasMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
}
@Test
fun generateWhenConstructorHasOptionalParameter() {
val beanDefinition: BeanDefinition = RootBeanDefinition(KotlinTestBeanWithOptionalParameter::class.java)
val beanFactory = DefaultListableBeanFactory()
compile(beanFactory, beanDefinition) { instanceSupplier, compiled ->
val bean: KotlinTestBeanWithOptionalParameter = getBean(beanFactory, beanDefinition, instanceSupplier)
Assertions.assertThat(bean).isInstanceOf(KotlinTestBeanWithOptionalParameter::class.java)
Assertions.assertThat(compiled.sourceFile)
.contains("return BeanInstanceSupplier.<KotlinTestBeanWithOptionalParameter>forConstructor();")
}
Assertions.assertThat<TypeHint>(getReflectionHints().getTypeHint(KotlinTestBeanWithOptionalParameter::class.java))
.satisfies(hasMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
}
@Test
fun generateWhenHasFactoryMethodWithNoArg() {
@@ -107,27 +107,27 @@ class InstanceSupplierCodeGeneratorKotlinTests {
}
}
private fun getReflectionHints(): ReflectionHints {
return generationContext.runtimeHints.reflection()
}
private fun getReflectionHints(): ReflectionHints {
return generationContext.runtimeHints.reflection()
}
private fun hasConstructorWithMode(mode: ExecutableMode): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.constructors()).anySatisfy(hasMode(mode))
}
}
private fun hasConstructorWithMode(mode: ExecutableMode): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.constructors()).anySatisfy(hasMode(mode))
}
}
private fun hasMemberCategory(category: MemberCategory): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.memberCategories).contains(category)
}
}
private fun hasMemberCategory(category: MemberCategory): ThrowingConsumer<TypeHint> {
return ThrowingConsumer {
Assertions.assertThat(it.memberCategories).contains(category)
}
}
private fun hasMode(mode: ExecutableMode): ThrowingConsumer<ExecutableHint> {
return ThrowingConsumer {
Assertions.assertThat(it.mode).isEqualTo(mode)
}
}
private fun hasMode(mode: ExecutableMode): ThrowingConsumer<ExecutableHint> {
return ThrowingConsumer {
Assertions.assertThat(it.mode).isEqualTo(mode)
}
}
private fun hasMethodWithMode(mode: ExecutableMode): ThrowingConsumer<TypeHint> {
return ThrowingConsumer { hint: TypeHint ->
@@ -135,48 +135,48 @@ class InstanceSupplierCodeGeneratorKotlinTests {
}
}
@Suppress("UNCHECKED_CAST")
private fun <T> getBean(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
instanceSupplier: InstanceSupplier<*>): T {
(beanDefinition as RootBeanDefinition).instanceSupplier = instanceSupplier
beanFactory.registerBeanDefinition("testBean", beanDefinition)
return beanFactory.getBean("testBean") as T
}
@Suppress("UNCHECKED_CAST")
private fun <T> getBean(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
instanceSupplier: InstanceSupplier<*>): T {
(beanDefinition as RootBeanDefinition).instanceSupplier = instanceSupplier
beanFactory.registerBeanDefinition("testBean", beanDefinition)
return beanFactory.getBean("testBean") as T
}
private fun compile(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
result: BiConsumer<InstanceSupplier<*>, Compiled>) {
private fun compile(beanFactory: DefaultListableBeanFactory, beanDefinition: BeanDefinition,
result: BiConsumer<InstanceSupplier<*>, Compiled>) {
val freshBeanFactory = DefaultListableBeanFactory(beanFactory)
freshBeanFactory.registerBeanDefinition("testBean", beanDefinition)
val registeredBean = RegisteredBean.of(freshBeanFactory, "testBean")
val typeBuilder = DeferredTypeBuilder()
val generateClass = generationContext.generatedClasses.addForFeature("TestCode", typeBuilder)
val generator = InstanceSupplierCodeGenerator(
generationContext, generateClass.name,
generateClass.methods, false
)
val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
Assertions.assertThat(instantiationDescriptor).isNotNull()
val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
typeBuilder.set { type: TypeSpec.Builder ->
type.addModifiers(Modifier.PUBLIC)
type.addSuperinterface(
ParameterizedTypeName.get(
Supplier::class.java,
InstanceSupplier::class.java
)
)
type.addMethod(
MethodSpec.methodBuilder("get")
.addModifiers(Modifier.PUBLIC)
.returns(InstanceSupplier::class.java)
.addStatement("return \$L", generatedCode).build()
)
}
generationContext.writeGeneratedContent()
TestCompiler.forSystem().with(generationContext).compile {
result.accept(it.getInstance(Supplier::class.java).get() as InstanceSupplier<*>, it)
}
}
val freshBeanFactory = DefaultListableBeanFactory(beanFactory)
freshBeanFactory.registerBeanDefinition("testBean", beanDefinition)
val registeredBean = RegisteredBean.of(freshBeanFactory, "testBean")
val typeBuilder = DeferredTypeBuilder()
val generateClass = generationContext.generatedClasses.addForFeature("TestCode", typeBuilder)
val generator = InstanceSupplierCodeGenerator(
generationContext, generateClass.name,
generateClass.methods, false
)
val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
Assertions.assertThat(instantiationDescriptor).isNotNull()
val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
typeBuilder.set { type: TypeSpec.Builder ->
type.addModifiers(Modifier.PUBLIC)
type.addSuperinterface(
ParameterizedTypeName.get(
Supplier::class.java,
InstanceSupplier::class.java
)
)
type.addMethod(
MethodSpec.methodBuilder("get")
.addModifiers(Modifier.PUBLIC)
.returns(InstanceSupplier::class.java)
.addStatement("return \$L", generatedCode).build()
)
}
generationContext.writeGeneratedContent()
TestCompiler.forSystem().with(generationContext).compile {
result.accept(it.getInstance(Supplier::class.java).get() as InstanceSupplier<*>, it)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -303,6 +303,17 @@ public class CaffeineCacheManager implements CacheManager {
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
}
/**
* Remove the specified cache from this cache manager, applying to
* custom caches as well as dynamically registered caches at runtime.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.customCacheNames.remove(name);
this.cacheMap.remove(name);
}
/**
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
* abstraction for the specified cache name.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ import org.springframework.util.concurrent.ListenableFutureTask;
* @see org.springframework.core.task.TaskExecutor
* @see SchedulerFactoryBean#setTaskExecutor
*/
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, InitializingBean, DisposableBean {
@@ -32,6 +32,10 @@ import org.springframework.lang.Nullable;
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()}, and
* {@link #errorHandler()} for detailed instructions.
*
* <p><b>NOTE: A {@code CachingConfigurer} will get initialized early.</b>
* Do not inject common dependencies into autowired fields directly; instead, consider
* declaring a lazy {@link org.springframework.beans.factory.ObjectProvider} for those.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -175,6 +175,15 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
return cache;
}
/**
* Remove the specified cache from this cache manager.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.cacheMap.remove(name);
}
private void recreateCaches() {
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
entry.setValue(createConcurrentMapCache(entry.getKey()));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.context.annotation;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
@@ -27,7 +28,6 @@ import java.util.Set;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
import org.springframework.beans.factory.config.DependencyDescriptor;
@@ -35,7 +35,6 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Complete implementation of the
@@ -85,47 +84,13 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
}
private Object buildLazyResolutionProxy(
final DependencyDescriptor descriptor, @Nullable final String beanName, boolean classOnly) {
DependencyDescriptor descriptor, @Nullable String beanName, boolean classOnly) {
BeanFactory beanFactory = getBeanFactory();
Assert.state(beanFactory instanceof DefaultListableBeanFactory,
"BeanFactory needs to be a DefaultListableBeanFactory");
final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;
if (!(getBeanFactory() instanceof DefaultListableBeanFactory dlbf)) {
throw new IllegalStateException("Lazy resolution only supported with DefaultListableBeanFactory");
}
TargetSource ts = new TargetSource() {
@Override
public Class<?> getTargetClass() {
return descriptor.getDependencyType();
}
@Override
@SuppressWarnings("NullAway")
public Object getTarget() {
Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null);
Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);
if (target == null) {
Class<?> type = getTargetClass();
if (Map.class == type) {
return Collections.emptyMap();
}
else if (List.class == type) {
return Collections.emptyList();
}
else if (Set.class == type || Collection.class == type) {
return Collections.emptySet();
}
throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
"Optional dependency not present for lazy injection point");
}
if (autowiredBeanNames != null) {
for (String autowiredBeanName : autowiredBeanNames) {
if (dlbf.containsBean(autowiredBeanName)) {
dlbf.registerDependentBean(autowiredBeanName, beanName);
}
}
}
return target;
}
};
TargetSource ts = new LazyDependencyTargetSource(dlbf, descriptor, beanName);
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
@@ -137,4 +102,96 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
return (classOnly ? pf.getProxyClass(classLoader) : pf.getProxy(classLoader));
}
@SuppressWarnings("serial")
private static class LazyDependencyTargetSource implements TargetSource, Serializable {
private final DefaultListableBeanFactory beanFactory;
private final DependencyDescriptor descriptor;
@Nullable
private final String beanName;
@Nullable
private transient volatile Object cachedTarget;
public LazyDependencyTargetSource(DefaultListableBeanFactory beanFactory,
DependencyDescriptor descriptor, @Nullable String beanName) {
this.beanFactory = beanFactory;
this.descriptor = descriptor;
this.beanName = beanName;
}
@Override
public Class<?> getTargetClass() {
return this.descriptor.getDependencyType();
}
@Override
@SuppressWarnings("NullAway")
public Object getTarget() {
Object cachedTarget = this.cachedTarget;
if (cachedTarget != null) {
return cachedTarget;
}
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Object target = this.beanFactory.doResolveDependency(
this.descriptor, this.beanName, autowiredBeanNames, null);
if (target == null) {
Class<?> type = getTargetClass();
if (Map.class == type) {
target = Collections.emptyMap();
}
else if (List.class == type) {
target = Collections.emptyList();
}
else if (Set.class == type || Collection.class == type) {
target = Collections.emptySet();
}
else {
throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType(),
"Optional dependency not present for lazy injection point");
}
}
else {
if (target instanceof Map<?, ?> map && Map.class == getTargetClass()) {
target = Collections.unmodifiableMap(map);
}
else if (target instanceof List<?> list && List.class == getTargetClass()) {
target = Collections.unmodifiableList(list);
}
else if (target instanceof Set<?> set && Set.class == getTargetClass()) {
target = Collections.unmodifiableSet(set);
}
else if (target instanceof Collection<?> coll && Collection.class == getTargetClass()) {
target = Collections.unmodifiableCollection(coll);
}
}
boolean cacheable = true;
for (String autowiredBeanName : autowiredBeanNames) {
if (!this.beanFactory.containsBean(autowiredBeanName)) {
cacheable = false;
}
else {
if (this.beanName != null) {
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
}
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
cacheable = false;
}
}
if (cacheable) {
this.cachedTarget = target;
}
}
return target;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,8 @@ import java.lang.annotation.Target;
* <p>In addition to its role for component initialization, this annotation may also be placed
* on injection points marked with {@link org.springframework.beans.factory.annotation.Autowired}
* or {@link jakarta.inject.Inject}: In that context, it leads to the creation of a
* lazy-resolution proxy for all affected dependencies, as an alternative to using
* lazy-resolution proxy for the affected dependency, caching it on first access in case of
* a singleton or re-resolving it on every access otherwise. This is an alternative to using
* {@link org.springframework.beans.factory.ObjectFactory} or {@link jakarta.inject.Provider}.
* Please note that such a lazy-resolution proxy will always be injected; if the target
* dependency does not exist, you will only be able to find out through an exception on
@@ -310,7 +310,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
return new Object[] {event};
}
@SuppressWarnings({"deprecation", "unchecked"})
@SuppressWarnings({"removal", "unchecked", "deprecation"})
protected void handleResult(Object result) {
if (reactiveStreamsPresent && new ReactiveResultHandler().subscribeToPublisher(result)) {
if (logger.isTraceEnabled()) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,15 +25,29 @@ import java.lang.annotation.Target;
/**
* Declares that a field or method parameter should be formatted as a date or time.
*
* <p>Supports formatting by style pattern, ISO date time pattern, or custom format pattern string.
* <p>Formatting applies to parsing a date/time object from a string as well as printing a
* date/time object to a string.
*
* <p>Supports formatting by style pattern, ISO date/time pattern, or custom format pattern string.
* Can be applied to {@link java.util.Date}, {@link java.util.Calendar}, {@link Long} (for
* millisecond timestamps) as well as JSR-310 {@code java.time} value types.
*
* <p>For style-based formatting, set the {@link #style} attribute to the desired style pattern code.
* The first character of the code is the date style, and the second character is the time style.
* Specify a character of 'S' for short style, 'M' for medium, 'L' for long, and 'F' for full.
* The date or time may be omitted by specifying the style character '-' &mdash; for example,
* 'M-' specifies a medium format for the date with no time.
* The date or time may be omitted by specifying the style character '-'. For example,
* 'M-' specifies a medium format for the date with no time. The supported style pattern codes
* correlate to the enum constants defined in {@link java.time.format.FormatStyle}.
*
* <p><strong>WARNING</strong>: Style-based formatting and parsing rely on locale-sensitive
* patterns which may change depending on the Java runtime. Specifically, applications that
* rely on date/time parsing and formatting may encounter incompatible changes in behavior
* when running on JDK 20 or higher. Using an ISO standardized format or a concrete pattern
* that you control allows for reliable system-independent and locale-independent parsing and
* formatting of date/time values. The use of {@linkplain #fallbackPatterns() fallback patterns}
* can also help to address compatibility issues. For further details, see the
* <a href="https://github.com/spring-projects/spring-framework/wiki/Date-and-Time-Formatting-with-JDK-20-and-higher">
* Date and Time Formatting with JDK 20 and higher</a> page in the Spring Framework wiki.
*
* <p>For ISO-based formatting, set the {@link #iso} attribute to the desired {@link ISO} format,
* such as {@link ISO#DATE}.
@@ -65,6 +79,8 @@ import java.lang.annotation.Target;
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
* @see java.text.DateFormat
* @see java.text.SimpleDateFormat
* @see java.time.format.DateTimeFormatter
*/
@Documented
@@ -77,6 +93,8 @@ public @interface DateTimeFormat {
* <p>Defaults to 'SS' for short date, short time. Set this attribute when you
* wish to format your field or method parameter in accordance with a common
* style other than the default style.
* <p>See the {@linkplain DateTimeFormat class-level documentation} for further
* details.
* @see #fallbackPatterns
*/
String style() default "SS";
@@ -93,13 +111,13 @@ public @interface DateTimeFormat {
/**
* The custom pattern to use to format the field or method parameter.
* <p>Defaults to empty String, indicating no custom pattern String has been
* <p>Defaults to an empty String, indicating no custom pattern String has been
* specified. Set this attribute when you wish to format your field or method
* parameter in accordance with a custom date time pattern not represented by
* a style or ISO format.
* <p>Note: This pattern follows the original {@link java.text.SimpleDateFormat} style,
* as also supported by Joda-Time, with strict parsing semantics towards overflows
* (for example, rejecting a Feb 29 value for a non-leap-year). As a consequence, 'yy'
* <p>Note: This pattern follows the original {@link java.text.SimpleDateFormat}
* style, with strict parsing semantics towards overflows (for example, rejecting
* a {@code Feb 29} value for a non-leap-year). As a consequence, 'yy'
* characters indicate a year in the traditional style, not a "year-of-era" as in the
* {@link java.time.format.DateTimeFormatter} specification (i.e. 'yy' turns into 'uu'
* when going through a {@code DateTimeFormatter} with strict resolution mode).
@@ -121,7 +139,6 @@ public @interface DateTimeFormat {
* or {@link #style} attribute is always used for printing. For details on
* which time zone is used for fallback patterns, see the
* {@linkplain DateTimeFormat class-level documentation}.
* <p>Fallback patterns are not supported for Joda-Time value types.
* @since 5.3.5
*/
String[] fallbackPatterns() default {};
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
/**
* Declares that a field or method parameter should be formatted as a
* {@link java.time.Duration}, according to the specified {@code style}.
* {@link java.time.Duration}, according to the specified {@link #style style}.
*
* @author Simon Baslé
* @since 6.2
@@ -40,15 +40,15 @@ import org.springframework.lang.Nullable;
public @interface DurationFormat {
/**
* Which {@code Style} to use for parsing and printing a {@code Duration}.
* Defaults to the JDK style ({@link Style#ISO8601}).
* The {@code Style} to use for parsing and printing a {@code Duration}.
* <p>Defaults to the JDK style ({@link Style#ISO8601}).
*/
Style style() default Style.ISO8601;
/**
* Define which {@link Unit} to fall back to in case the {@code style()}
* needs a unit for either parsing or printing, and none is explicitly
* provided in the input ({@code Unit.MILLIS} if unspecified).
* The {@link Unit} to fall back to in case the {@code style()} needs a unit
* for either parsing or printing, and none is explicitly provided in the input.
* <p>Defaults to {@link Unit#MILLIS} if unspecified.
*/
Unit defaultUnit() default Unit.MILLIS;
@@ -59,14 +59,15 @@ public @interface DurationFormat {
/**
* Simple formatting based on a short suffix, for example '1s'.
* Supported unit suffixes are: {@code ns, us, ms, s, m, h, d}.
* This corresponds to nanoseconds, microseconds, milliseconds, seconds,
* minutes, hours and days respectively.
* <p>Supported unit suffixes include: {@code ns, us, ms, s, m, h, d}.
* Those correspond to nanoseconds, microseconds, milliseconds, seconds,
* minutes, hours, and days, respectively.
* <p>Note that when printing a {@code Duration}, this style can be
* lossy if the selected unit is bigger than the resolution of the
* duration. For example, * {@code Duration.ofMillis(5).plusNanos(1234)}
* duration. For example, {@code Duration.ofMillis(5).plusNanos(1234)}
* would get truncated to {@code "5ms"} when printing using
* {@code ChronoUnit.MILLIS}. Fractional durations are not supported.
* {@code ChronoUnit.MILLIS}.
* <p>Fractional durations are not supported.
*/
SIMPLE,
@@ -80,22 +81,24 @@ public @interface DurationFormat {
/**
* Like {@link #SIMPLE}, but allows multiple segments ordered from
* largest-to-smallest units of time, like {@code 1h12m27s}.
* <p>
* A single minus sign ({@code -}) is allowed to indicate the whole
* <p>A single minus sign ({@code -}) is allowed to indicate the whole
* duration is negative. Spaces are allowed between segments, and a
* negative duration with spaced segments can optionally be surrounded
* by parenthesis after the minus sign, like so: {@code -(34m 57s)}.
* by parentheses after the minus sign, like so: {@code -(34m 57s)}.
*/
COMPOSITE
}
/**
* Duration format unit, which mirrors a subset of {@link ChronoUnit} and
* allows conversion to and from supported {@code ChronoUnit} as well as
* converting durations to longs. The enum includes its corresponding suffix
* in the {@link Style#SIMPLE simple} Duration format style.
* allows conversion to and from a supported {@code ChronoUnit} as well as
* conversion from durations to longs.
*
* <p>The enum includes its corresponding suffix in the {@link Style#SIMPLE simple}
* {@code Duration} format style.
*/
enum Unit {
/**
* Nanoseconds ({@code "ns"}).
*/
@@ -153,7 +156,7 @@ public @interface DurationFormat {
/**
* Convert this {@code DurationFormat.Unit} to a simple {@code String}
* suffix, suitable for the {@link Style#SIMPLE} style.
* suffix, suitable for the {@link Style#SIMPLE SIMPLE} style.
*/
public String asSuffix() {
return this.suffix;
@@ -173,9 +176,9 @@ public @interface DurationFormat {
* Print a {@code Duration} as a {@code String}, converting it to a long
* value using this unit's precision via {@link #longValue(Duration)}
* and appending this unit's simple {@link #asSuffix() suffix}.
* @param value the {@code Duration} to convert to String
* @param value the {@code Duration} to convert to a String
* @return the String representation of the {@code Duration} in the
* {@link Style#SIMPLE SIMPLE style}
* {@link Style#SIMPLE SIMPLE} style
*/
public String print(Duration value) {
return longValue(value) + asSuffix();
@@ -187,8 +190,8 @@ public @interface DurationFormat {
* bigger than the actual resolution of the duration.
* <p>For example, {@code Duration.ofMillis(5).plusNanos(1234)} would
* get truncated to {@code 5} for unit {@code MILLIS}.
* @param value the {@code Duration} to convert to long
* @return the long value for the Duration in this Unit
* @param value the {@code Duration} to convert to a long
* @return the long value for the {@code Duration} in this {@code Unit}
*/
public long longValue(Duration value) {
return this.longValue.apply(value);
@@ -196,8 +199,8 @@ public @interface DurationFormat {
/**
* Get the {@code Unit} corresponding to the given {@code ChronoUnit}.
* @throws IllegalArgumentException if that particular ChronoUnit isn't
* supported
* @throws IllegalArgumentException if the given {@code ChronoUnit} is
* not supported
*/
public static Unit fromChronoUnit(@Nullable ChronoUnit chronoUnit) {
if (chronoUnit == null) {
@@ -213,7 +216,7 @@ public @interface DurationFormat {
/**
* Get the {@code Unit} corresponding to the given {@code String} suffix.
* @throws IllegalArgumentException if that particular suffix is unknown
* @throws IllegalArgumentException if the given suffix is not supported
*/
public static Unit fromSuffix(String suffix) {
for (Unit candidate : values()) {
@@ -1,5 +1,5 @@
/**
* Annotations for declaratively configuring field formatting rules.
* Annotations for declaratively configuring field and parameter formatting rules.
*/
@NonNullApi
@NonNullFields
@@ -181,7 +181,6 @@ public class DateFormatter implements Formatter<Date> {
* <li>'F' = Full</li>
* <li>'-' = Omitted</li>
* </ul>
* This method mimics the styles supported by Joda-Time.
* @param stylePattern two characters from the set {"S", "M", "L", "F", "-"}
* @since 3.2
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,7 +116,7 @@ public class DateTimeFormatterFactory {
}
/**
* Set the two characters to use to format date values, in Joda-Time style.
* Set the two characters to use to format date values.
* <p>The first character is used for the date style; the second is for
* the time style. Supported characters are:
* <ul>
@@ -126,9 +126,9 @@ public class DateTimeFormatterFactory {
* <li>'F' = Full</li>
* <li>'-' = Omitted</li>
* </ul>
* <p>This method mimics the styles supported by Joda-Time. Note that
* JSR-310 natively favors {@link java.time.format.FormatStyle} as used for
* {@link #setDateStyle}, {@link #setTimeStyle} and {@link #setDateTimeStyle}.
* <p>Note that JSR-310 natively favors {@link java.time.format.FormatStyle}
* as used for {@link #setDateStyle}, {@link #setTimeStyle}, and
* {@link #setDateTimeStyle}.
* @param style two characters from the set {"S", "M", "L", "F", "-"}
*/
public void setStylePattern(String style) {
@@ -38,7 +38,7 @@ abstract class DateTimeFormatterUtils {
* @see ResolverStyle#STRICT
*/
static DateTimeFormatter createStrictDateTimeFormatter(String pattern) {
// Using strict resolution to align with Joda-Time and standard DateFormat behavior:
// Using strict resolution to align with standard DateFormat behavior:
// otherwise, an overflow like, for example, Feb 29 for a non-leap-year wouldn't get rejected.
// However, with strict resolution, a year digit needs to be specified as 'u'...
String patternToUse = StringUtils.replace(pattern, "yy", "uu");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -97,7 +97,7 @@ public class DefaultFormattingConversionService extends FormattingConversionServ
/**
* Add formatters appropriate for most environments: including number formatters,
* JSR-354 Money &amp; Currency formatters, JSR-310 Date-Time and/or Joda-Time formatters,
* JSR-354 Money &amp; Currency formatters, and JSR-310 Date-Time formatters,
* depending on the presence of the corresponding API on the classpath.
* @param formatterRegistry the service to register default formatters with
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,24 +32,19 @@ import org.springframework.lang.Nullable;
import org.springframework.util.StringValueResolver;
/**
* A factory providing convenient access to a {@code FormattingConversionService}
* configured with converters and formatters for common types such as numbers and
* datetimes.
* A factory providing convenient access to a {@link FormattingConversionService}
* configured with converters and formatters for common types such as numbers, dates,
* and times.
*
* <p>Additional converters and formatters can be registered declaratively through
* {@link #setConverters(Set)} and {@link #setFormatters(Set)}. Another option
* is to register converters and formatters in code by implementing the
* {@link FormatterRegistrar} interface. You can then configure provide the set
* of registrars to use through {@link #setFormatterRegistrars(Set)}.
*
* <p>A good example for registering converters and formatters in code is
* {@code JodaTimeFormatterRegistrar}, which registers a number of
* date-related formatters and converters. For a more detailed list of cases
* see {@link #setFormatterRegistrars(Set)}
* {@link FormatterRegistrar} interface. You can then provide the set of registrars
* to use through {@link #setFormatterRegistrars(Set)}.
*
* <p>Like all {@code FactoryBean} implementations, this class is suitable for
* use when configuring a Spring application context using Spring {@code <beans>}
* XML. When configuring the container with
* XML configuration files. When configuring the container with
* {@link org.springframework.context.annotation.Configuration @Configuration}
* classes, simply instantiate, configure and return the appropriate
* {@code FormattingConversionService} object from a
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,9 +53,6 @@ import org.springframework.util.ReflectionUtils;
* web application). There is no direct API dependency between this LoadTimeWeaver
* adapter and the underlying ClassLoader, just a 'loose' method contract.
*
* <p>This is the LoadTimeWeaver to use, for example, with the Resin application server
* version 3.1+.
*
* @author Costin Leau
* @author Juergen Hoeller
* @since 2.0
@@ -17,6 +17,7 @@
package org.springframework.instrument.classloading.jboss;
import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -26,12 +27,14 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingFunction;
/**
* {@link LoadTimeWeaver} implementation for JBoss's instrumentable ClassLoader.
* Thanks to Ales Justin and Marius Bogoevici for the initial prototype.
*
* <p>This weaver supports WildFly 13+.
* <p>This weaver supports WildFly 13-23 (DelegatingClassFileTransformer) as well as
* WildFly 24+ (DelegatingClassTransformer), as of Spring Framework 6.1.15.
*
* @author Costin Leau
* @author Juergen Hoeller
@@ -39,9 +42,15 @@ import org.springframework.util.ReflectionUtils;
*/
public class JBossLoadTimeWeaver implements LoadTimeWeaver {
private static final String DELEGATING_TRANSFORMER_CLASS_NAME =
private static final String LEGACY_DELEGATING_TRANSFORMER_CLASS_NAME =
"org.jboss.as.server.deployment.module.DelegatingClassFileTransformer";
private static final String DELEGATING_TRANSFORMER_CLASS_NAME =
"org.jboss.as.server.deployment.module.DelegatingClassTransformer";
private static final String CLASS_TRANSFORMER_CLASS_NAME =
"org.jboss.modules.ClassTransformer";
private static final String WRAPPER_TRANSFORMER_CLASS_NAME =
"org.jboss.modules.JLIClassTransformer";
@@ -52,6 +61,8 @@ public class JBossLoadTimeWeaver implements LoadTimeWeaver {
private final Method addTransformer;
private final ThrowingFunction<Object, Object> adaptTransformer;
/**
* Create a new instance of the {@link JBossLoadTimeWeaver} class using
@@ -90,18 +101,29 @@ public class JBossLoadTimeWeaver implements LoadTimeWeaver {
wrappedTransformer.setAccessible(true);
suggestedTransformer = wrappedTransformer.get(suggestedTransformer);
}
if (!suggestedTransformer.getClass().getName().equals(DELEGATING_TRANSFORMER_CLASS_NAME)) {
Class<?> transformerType = ClassFileTransformer.class;
if (suggestedTransformer.getClass().getName().equals(LEGACY_DELEGATING_TRANSFORMER_CLASS_NAME)) {
this.adaptTransformer = (t -> t);
}
else if (suggestedTransformer.getClass().getName().equals(DELEGATING_TRANSFORMER_CLASS_NAME)) {
transformerType = classLoader.loadClass(CLASS_TRANSFORMER_CLASS_NAME);
Constructor<?> adaptedTransformer = classLoader.loadClass(WRAPPER_TRANSFORMER_CLASS_NAME)
.getConstructor(ClassFileTransformer.class);
this.adaptTransformer = adaptedTransformer::newInstance;
}
else {
throw new IllegalStateException(
"Transformer not of the expected type DelegatingClassFileTransformer: " +
"Transformer not of expected type DelegatingClass(File)Transformer: " +
suggestedTransformer.getClass().getName());
}
this.delegatingTransformer = suggestedTransformer;
Method addTransformer = ReflectionUtils.findMethod(this.delegatingTransformer.getClass(),
"addTransformer", ClassFileTransformer.class);
"addTransformer", transformerType);
if (addTransformer == null) {
throw new IllegalArgumentException(
"Could not find 'addTransformer' method on JBoss DelegatingClassFileTransformer: " +
"Could not find 'addTransformer' method on JBoss DelegatingClass(File)Transformer: " +
this.delegatingTransformer.getClass().getName());
}
addTransformer.setAccessible(true);
@@ -116,7 +138,7 @@ public class JBossLoadTimeWeaver implements LoadTimeWeaver {
@Override
public void addTransformer(ClassFileTransformer transformer) {
try {
this.addTransformer.invoke(this.delegatingTransformer, transformer);
this.addTransformer.invoke(this.delegatingTransformer, this.adaptTransformer.apply(transformer));
}
catch (Throwable ex) {
throw new IllegalStateException("Could not add transformer on JBoss ClassLoader: " + this.classLoader, ex);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,17 +35,18 @@ import org.springframework.aot.hint.annotation.Reflective;
* <p>In terms of target method signatures, any parameter types are supported.
* However, the return type is constrained to either {@code void} or
* {@link java.util.concurrent.Future}. In the latter case, you may declare the
* more specific {@link org.springframework.util.concurrent.ListenableFuture} or
* {@link java.util.concurrent.CompletableFuture} types which allow for richer
* interaction with the asynchronous task and for immediate composition with
* further processing steps.
* more specific {@link java.util.concurrent.CompletableFuture} type which allows
* for richer interaction with the asynchronous task and for immediate composition
* with further processing steps.
*
* <p>A {@code Future} handle returned from the proxy will be an actual asynchronous
* {@code Future} that can be used to track the result of the asynchronous method
* execution. However, since the target method needs to implement the same signature,
* it will have to return a temporary {@code Future} handle that just passes a value
* through: for example, Spring's {@link AsyncResult}, EJB 3.1's {@link jakarta.ejb.AsyncResult},
* or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}.
* {@code (Completable)Future} that can be used to track the result of the
* asynchronous method execution. However, since the target method needs to implement
* the same signature, it will have to return a temporary {@code Future} handle that
* just passes a value after computation in the execution thread: typically through
* {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}. The
* provided value will be exposed to the caller through the actual asynchronous
* {@code Future} handle at runtime.
*
* @author Juergen Hoeller
* @author Chris Beams
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,12 +31,10 @@ import org.springframework.util.concurrent.SuccessCallback;
* A pass-through {@code Future} handle that can be used for method signatures
* which are declared with a {@code Future} return type for asynchronous execution.
*
* <p>As of Spring 4.1, this class implements {@link ListenableFuture}, not just
* <p>As of Spring 4.1, this class implements {@code ListenableFuture}, not just
* plain {@link java.util.concurrent.Future}, along with the corresponding support
* in {@code @Async} processing.
*
* <p>As of Spring 4.2, this class also supports passing execution exceptions back
* to the caller.
* in {@code @Async} processing. As of 7.0, this will be turned back to a plain
* {@code Future} in order to focus on compatibility with existing common usage.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
@@ -48,6 +46,7 @@ import org.springframework.util.concurrent.SuccessCallback;
* @deprecated as of 6.0, in favor of {@link CompletableFuture}
*/
@Deprecated(since = "6.0")
@SuppressWarnings("removal")
public class AsyncResult<V> implements ListenableFuture<V> {
@Nullable
@@ -145,7 +144,7 @@ public class AsyncResult<V> implements ListenableFuture<V> {
* @since 4.2
* @see Future#get()
*/
public static <V> ListenableFuture<V> forValue(V value) {
public static <V> org.springframework.util.concurrent.ListenableFuture<V> forValue(V value) {
return new AsyncResult<>(value, null);
}
@@ -157,7 +156,7 @@ public class AsyncResult<V> implements ListenableFuture<V> {
* @since 4.2
* @see ExecutionException
*/
public static <V> ListenableFuture<V> forExecutionException(Throwable ex) {
public static <V> org.springframework.util.concurrent.ListenableFuture<V> forExecutionException(Throwable ex) {
return new AsyncResult<>(null, ex);
}
@@ -62,7 +62,7 @@ import org.springframework.util.concurrent.ListenableFuture;
* @see DefaultManagedTaskExecutor
* @see ThreadPoolTaskExecutor
*/
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
private static final Executor STUB_EXECUTOR = (task -> {
@@ -41,7 +41,6 @@ import org.springframework.scheduling.support.TaskUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ErrorHandler;
import org.springframework.util.concurrent.ListenableFuture;
/**
* Adapter that takes a {@code java.util.concurrent.ScheduledExecutorService} and
@@ -219,15 +218,15 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
return super.submit(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
}
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
public org.springframework.util.concurrent.ListenableFuture<?> submitListenable(Runnable task) {
return super.submitListenable(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
}
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
public <T> org.springframework.util.concurrent.ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
}
@@ -36,6 +36,8 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.lang.Nullable;
/**
@@ -75,6 +77,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
protected final Log logger = LogFactory.getLog(getClass());
private boolean virtualThreads = false;
private ThreadFactory threadFactory = this;
private boolean threadNamePrefixSet = false;
@@ -104,6 +108,25 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
private volatile boolean lateShutdown;
/**
* Specify whether to use virtual threads instead of platform threads.
* This is off by default, setting up a traditional platform thread pool.
* <p>Set this flag to {@code true} on Java 21 or higher for a tightly
* managed thread pool setup with virtual threads. In contrast to
* {@link SimpleAsyncTaskExecutor}, this is integrated with Spring's
* lifecycle management for stopping and restarting execution threads,
* including an early stop signal for a graceful shutdown arrangement.
* <p>Specify either this or {@link #setThreadFactory}, not both.
* @since 6.2
* @see #setThreadFactory
* @see VirtualThreadTaskExecutor#getVirtualThreadFactory()
* @see SimpleAsyncTaskExecutor#setVirtualThreads
*/
public void setVirtualThreads(boolean virtualThreads) {
this.virtualThreads = virtualThreads;
this.threadFactory = this;
}
/**
* Set the ThreadFactory to use for the ExecutorService's thread pool.
* The default is the underlying ExecutorService's default thread factory.
@@ -120,6 +143,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
*/
public void setThreadFactory(@Nullable ThreadFactory threadFactory) {
this.threadFactory = (threadFactory != null ? threadFactory : this);
this.virtualThreads = false;
}
@Override
@@ -282,7 +306,9 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
if (!this.threadNamePrefixSet && this.beanName != null) {
setThreadNamePrefix(this.beanName + "-");
}
this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
ThreadFactory factory = (this.virtualThreads ?
new VirtualThreadTaskExecutor(getThreadNamePrefix()).getVirtualThreadFactory() : this.threadFactory);
this.executor = initializeExecutor(factory, this.rejectedExecutionHandler);
this.lifecycleDelegate = new ExecutorLifecycleDelegate(this.executor);
}
@@ -44,7 +44,6 @@ import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable;
import org.springframework.scheduling.support.TaskUtils;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
import org.springframework.util.concurrent.ListenableFuture;
/**
* A simple implementation of Spring's {@link TaskScheduler} interface, using
@@ -80,7 +79,9 @@ import org.springframework.util.concurrent.ListenableFuture;
* but rather just the hand-off to an execution thread.</b> As a consequence,
* a {@link ScheduledFuture} handle (for example, from {@link #schedule(Runnable, Instant)})
* represents that hand-off rather than the actual completion of the provided task
* (or series of repeated tasks).
* (or series of repeated tasks). Also, this scheduler participates in lifecycle
* management to a limited degree only, stopping trigger firing and fixed-delay
* task execution but not stopping the execution of handed-off tasks.
*
* <p>As an alternative to the built-in thread-per-task capability, this scheduler
* can also be configured with a separate target executor for scheduled task
@@ -268,15 +269,15 @@ public class SimpleAsyncTaskScheduler extends SimpleAsyncTaskExecutor implements
return super.submit(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
}
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
public org.springframework.util.concurrent.ListenableFuture<?> submitListenable(Runnable task) {
return super.submitListenable(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
}
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
public <T> org.springframework.util.concurrent.ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
}
@@ -80,7 +80,7 @@ import org.springframework.util.concurrent.ListenableFutureTask;
* @see ThreadPoolExecutorFactoryBean
* @see ConcurrentTaskExecutor
*/
@SuppressWarnings({"serial", "deprecation"})
@SuppressWarnings({"serial", "deprecation", "removal"})
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
@@ -74,7 +74,7 @@ import org.springframework.util.concurrent.ListenableFutureTask;
* @see ThreadPoolTaskExecutor
* @see SimpleAsyncTaskScheduler
*/
@SuppressWarnings({"serial", "deprecation"})
@SuppressWarnings({"serial", "deprecation", "removal"})
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
@@ -18,16 +18,20 @@ package org.springframework.validation.beanvalidation;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.NoProviderFoundException;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.metadata.BeanDescriptor;
import jakarta.validation.metadata.ConstraintDescriptor;
import jakarta.validation.metadata.ConstructorDescriptor;
import jakarta.validation.metadata.MethodDescriptor;
import jakarta.validation.metadata.ContainerElementTypeDescriptor;
import jakarta.validation.metadata.ExecutableDescriptor;
import jakarta.validation.metadata.MethodType;
import jakarta.validation.metadata.ParameterDescriptor;
import jakarta.validation.metadata.PropertyDescriptor;
@@ -36,13 +40,17 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.KotlinDetector;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* AOT {@code BeanRegistrationAotProcessor} that adds additional hints
@@ -80,8 +88,8 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
@Nullable
private static Validator getValidatorIfAvailable() {
try {
return Validation.buildDefaultValidatorFactory().getValidator();
try (ValidatorFactory validator = Validation.buildDefaultValidatorFactory()) {
return validator.getValidator();
}
catch (NoProviderFoundException ex) {
logger.info("No Bean Validation provider available - skipping validation constraint hint inference");
@@ -95,64 +103,134 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
return null;
}
Class<?> beanClass = registeredBean.getBeanClass();
Set<Class<?>> validatedClasses = new HashSet<>();
Set<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses = new HashSet<>();
processAheadOfTime(beanClass, validatedClasses, constraintValidatorClasses);
if (!validatedClasses.isEmpty() || !constraintValidatorClasses.isEmpty()) {
return new AotContribution(validatedClasses, constraintValidatorClasses);
}
return null;
}
private static void processAheadOfTime(Class<?> clazz, Collection<Class<?>> validatedClasses,
Collection<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses) {
Assert.notNull(validator, "Validator can't be null");
BeanDescriptor descriptor;
try {
descriptor = validator.getConstraintsForClass(registeredBean.getBeanClass());
descriptor = validator.getConstraintsForClass(clazz);
}
catch (RuntimeException ex) {
if (KotlinDetector.isKotlinType(registeredBean.getBeanClass()) && ex instanceof ArrayIndexOutOfBoundsException) {
if (KotlinDetector.isKotlinType(clazz) && ex instanceof ArrayIndexOutOfBoundsException) {
// See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857
logger.warn("Skipping validation constraint hint inference for bean " + registeredBean.getBeanName() +
logger.warn("Skipping validation constraint hint inference for class " + clazz +
" due to an ArrayIndexOutOfBoundsException at validator level");
}
else if (ex instanceof TypeNotPresentException) {
logger.debug("Skipping validation constraint hint inference for bean " +
registeredBean.getBeanName() + " due to a TypeNotPresentException at validator level: " + ex.getMessage());
logger.debug("Skipping validation constraint hint inference for class " +
clazz + " due to a TypeNotPresentException at validator level: " + ex.getMessage());
}
else {
logger.warn("Skipping validation constraint hint inference for bean " +
registeredBean.getBeanName(), ex);
logger.warn("Skipping validation constraint hint inference for class " + clazz, ex);
}
return null;
return;
}
Set<ConstraintDescriptor<?>> constraintDescriptors = new HashSet<>();
for (MethodDescriptor methodDescriptor : descriptor.getConstrainedMethods(MethodType.NON_GETTER, MethodType.GETTER)) {
for (ParameterDescriptor parameterDescriptor : methodDescriptor.getParameterDescriptors()) {
constraintDescriptors.addAll(parameterDescriptor.getConstraintDescriptors());
processExecutableDescriptor(descriptor.getConstrainedMethods(MethodType.NON_GETTER, MethodType.GETTER), constraintValidatorClasses);
processExecutableDescriptor(descriptor.getConstrainedConstructors(), constraintValidatorClasses);
processPropertyDescriptors(descriptor.getConstrainedProperties(), constraintValidatorClasses);
if (!constraintValidatorClasses.isEmpty() && shouldProcess(clazz)) {
validatedClasses.add(clazz);
}
ReflectionUtils.doWithFields(clazz, field -> {
Class<?> type = field.getType();
if (Iterable.class.isAssignableFrom(type) || List.class.isAssignableFrom(type) || Optional.class.isAssignableFrom(type)) {
ResolvableType resolvableType = ResolvableType.forField(field);
Class<?> genericType = resolvableType.getGeneric(0).toClass();
if (shouldProcess(genericType)) {
validatedClasses.add(clazz);
processAheadOfTime(genericType, validatedClasses, constraintValidatorClasses);
}
}
if (Map.class.isAssignableFrom(type)) {
ResolvableType resolvableType = ResolvableType.forField(field);
Class<?> keyGenericType = resolvableType.getGeneric(0).toClass();
Class<?> valueGenericType = resolvableType.getGeneric(1).toClass();
if (shouldProcess(keyGenericType)) {
validatedClasses.add(clazz);
processAheadOfTime(keyGenericType, validatedClasses, constraintValidatorClasses);
}
if (shouldProcess(valueGenericType)) {
validatedClasses.add(clazz);
processAheadOfTime(valueGenericType, validatedClasses, constraintValidatorClasses);
}
}
});
}
private static boolean shouldProcess(Class<?> clazz) {
return !clazz.getCanonicalName().startsWith("java.");
}
private static void processExecutableDescriptor(Set<? extends ExecutableDescriptor> executableDescriptors,
Collection<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses) {
for (ExecutableDescriptor executableDescriptor : executableDescriptors) {
for (ParameterDescriptor parameterDescriptor : executableDescriptor.getParameterDescriptors()) {
for (ConstraintDescriptor<?> constraintDescriptor : parameterDescriptor.getConstraintDescriptors()) {
constraintValidatorClasses.addAll(constraintDescriptor.getConstraintValidatorClasses());
}
for (ContainerElementTypeDescriptor typeDescriptor : parameterDescriptor.getConstrainedContainerElementTypes()) {
for (ConstraintDescriptor<?> constraintDescriptor : typeDescriptor.getConstraintDescriptors()) {
constraintValidatorClasses.addAll(constraintDescriptor.getConstraintValidatorClasses());
}
}
}
}
for (ConstructorDescriptor constructorDescriptor : descriptor.getConstrainedConstructors()) {
for (ParameterDescriptor parameterDescriptor : constructorDescriptor.getParameterDescriptors()) {
constraintDescriptors.addAll(parameterDescriptor.getConstraintDescriptors());
}
private static void processPropertyDescriptors(Set<PropertyDescriptor> propertyDescriptors,
Collection<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses) {
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
for (ConstraintDescriptor<?> constraintDescriptor : propertyDescriptor.getConstraintDescriptors()) {
constraintValidatorClasses.addAll(constraintDescriptor.getConstraintValidatorClasses());
}
for (ContainerElementTypeDescriptor typeDescriptor : propertyDescriptor.getConstrainedContainerElementTypes()) {
for (ConstraintDescriptor<?> constraintDescriptor : typeDescriptor.getConstraintDescriptors()) {
constraintValidatorClasses.addAll(constraintDescriptor.getConstraintValidatorClasses());
}
}
}
for (PropertyDescriptor propertyDescriptor : descriptor.getConstrainedProperties()) {
constraintDescriptors.addAll(propertyDescriptor.getConstraintDescriptors());
}
if (!constraintDescriptors.isEmpty()) {
return new AotContribution(constraintDescriptors);
}
return null;
}
}
private static class AotContribution implements BeanRegistrationAotContribution {
private final Collection<ConstraintDescriptor<?>> constraintDescriptors;
private final Collection<Class<?>> validatedClasses;
private final Collection<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses;
public AotContribution(Collection<ConstraintDescriptor<?>> constraintDescriptors) {
this.constraintDescriptors = constraintDescriptors;
public AotContribution(Collection<Class<?>> validatedClasses,
Collection<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses) {
this.validatedClasses = validatedClasses;
this.constraintValidatorClasses = constraintValidatorClasses;
}
@Override
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
for (ConstraintDescriptor<?> constraintDescriptor : this.constraintDescriptors) {
for (Class<?> constraintValidatorClass : constraintDescriptor.getConstraintValidatorClasses()) {
generationContext.getRuntimeHints().reflection().registerType(constraintValidatorClass,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
ReflectionHints hints = generationContext.getRuntimeHints().reflection();
for (Class<?> validatedClass : this.validatedClasses) {
hints.registerType(validatedClass, MemberCategory.DECLARED_FIELDS);
}
for (Class<? extends ConstraintValidator<?, ?>> constraintValidatorClass : this.constraintValidatorClasses) {
hints.registerType(constraintValidatorClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package example.scannable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import jakarta.annotation.PostConstruct;
@@ -51,9 +52,8 @@ public class AutowiredQualifierFooService implements FooService {
}
@Override
@SuppressWarnings("deprecation")
public Future<String> asyncFoo(int id) {
return new org.springframework.scheduling.annotation.AsyncResult<>(this.fooDao.findFoo(id));
return CompletableFuture.completedFuture(this.fooDao.findFoo(id));
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package example.scannable;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import jakarta.annotation.PostConstruct;
@@ -91,10 +92,9 @@ public abstract class FooServiceImpl implements FooService {
}
@Override
@SuppressWarnings("deprecation")
public Future<String> asyncFoo(int id) {
Assert.state(ServiceInvocationCounter.getThreadLocalCount() != null, "Thread-local counter not exposed");
return new org.springframework.scheduling.annotation.AsyncResult<>(fooDao().findFoo(id));
return CompletableFuture.completedFuture(fooDao().findFoo(id));
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package example.scannable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.springframework.context.annotation.Scope;
@@ -33,9 +34,8 @@ public class ScopedProxyTestBean implements FooService {
}
@Override
@SuppressWarnings("deprecation")
public Future<String> asyncFoo(int id) {
return new org.springframework.scheduling.annotation.AsyncResult<>("bar");
return CompletableFuture.completedFuture("bar");
}
@Override
@@ -86,7 +86,7 @@ class CacheOperationExpressionEvaluatorTests {
AnnotatedClass target = new AnnotatedClass();
Method method = ReflectionUtils.findMethod(
AnnotatedClass.class, "multipleCaching", Object.class, Object.class);
Object[] args = new Object[] {new Object(), new Object()};
Object[] args = {"arg1", "arg2"};
Collection<ConcurrentMapCache> caches = Collections.singleton(new ConcurrentMapCache("test"));
EvaluationContext evalCtx = this.eval.createEvaluationContext(caches, method, args,
@@ -155,7 +155,7 @@ class CacheOperationExpressionEvaluatorTests {
AnnotatedClass target = new AnnotatedClass();
Method method = ReflectionUtils.findMethod(
AnnotatedClass.class, "multipleCaching", Object.class, Object.class);
Object[] args = new Object[] {new Object(), new Object()};
Object[] args = new Object[] {"arg1", "arg2"};
Collection<ConcurrentMapCache> caches = Collections.singleton(new ConcurrentMapCache("test"));
return this.eval.createEvaluationContext(
caches, method, args, target, target.getClass(), method, result);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,8 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
@@ -67,7 +69,7 @@ class LazyAutowiredAnnotationBeanPostProcessorTests {
}
@Test
void lazyResourceInjectionWithField() {
void lazyResourceInjectionWithField() throws Exception {
doTestLazyResourceInjection(FieldResourceInjectionBean.class);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
@@ -84,9 +86,36 @@ class LazyAutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBeans()).isNotEmpty();
assertThat(bean.getTestBeans().get(0).getName()).isNull();
assertThat(ac.getBeanFactory().containsSingleton("testBean")).isTrue();
TestBean tb = (TestBean) ac.getBean("testBean");
tb.setName("tb");
assertThat(bean.getTestBean().getName()).isSameAs("tb");
assertThat(bean.getTestBeans() instanceof Advised).isTrue();
TargetSource targetSource = ((Advised) bean.getTestBeans()).getTargetSource();
assertThat(targetSource.getTarget()).isSameAs(targetSource.getTarget());
ac.close();
}
@Test
void lazyResourceInjectionWithFieldForPrototype() {
doTestLazyResourceInjection(FieldResourceInjectionBean.class);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
RootBeanDefinition abd = new RootBeanDefinition(FieldResourceInjectionBean.class);
abd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
ac.registerBeanDefinition("annotatedBean", abd);
RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class);
tbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
tbd.setLazyInit(true);
ac.registerBeanDefinition("testBean", tbd);
ac.refresh();
FieldResourceInjectionBean bean = ac.getBean("annotatedBean", FieldResourceInjectionBean.class);
assertThat(bean.getTestBeans()).isNotEmpty();
TestBean tb = bean.getTestBeans().get(0);
assertThat(bean.getTestBeans().get(0)).isNotSameAs(tb);
ac.close();
}
@@ -195,6 +195,28 @@ class BeanMethodQualificationTests {
ctx.close();
}
@Test
void customWithConstructor() {
AnnotationConfigApplicationContext ctx = context(CustomConfig.class, CustomPojoWithConstructor.class);
CustomPojoWithConstructor pojo = ctx.getBean(CustomPojoWithConstructor.class);
assertThat(pojo.plainBean).isNull();
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
ctx.close();
}
@Test
void customWithMethod() {
AnnotationConfigApplicationContext ctx = context(CustomConfig.class, CustomPojoWithMethod.class);
CustomPojoWithMethod pojo = ctx.getBean(CustomPojoWithMethod.class);
assertThat(pojo.plainBean).isNull();
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
ctx.close();
}
@Test
void beanNamesForAnnotation() {
AnnotationConfigApplicationContext ctx = context(StandardConfig.class);
@@ -327,6 +349,7 @@ class BeanMethodQualificationTests {
}
}
@Configuration
static class EffectivePrimaryConfig {
@@ -346,6 +369,7 @@ class BeanMethodQualificationTests {
}
}
@Component @Lazy
static class StandardPojo {
@@ -418,6 +442,35 @@ class BeanMethodQualificationTests {
}
@InterestingPojo
static class CustomPojoWithConstructor {
TestBean plainBean;
TestBean testBean;
public CustomPojoWithConstructor(Optional<TestBean> plainBean, @InterestingNeed TestBean testBean) {
this.plainBean = plainBean.orElse(null);
this.testBean = testBean;
}
}
@InterestingPojo
static class CustomPojoWithMethod {
TestBean plainBean;
TestBean testBean;
@Autowired
public void applyDependencies(Optional<TestBean> plainBean, @InterestingNeed TestBean testBean) {
this.plainBean = plainBean.orElse(null);
this.testBean = testBean;
}
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface Boring {
@@ -280,7 +280,7 @@ class AnnotationDrivenEventListenerTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
void listenableFutureReply() {
load(TestEventListener.class, ReplyEventListener.class);
org.springframework.util.concurrent.SettableListenableFuture<String> future =
@@ -339,6 +339,70 @@ class DateFormattingTests {
assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02");
}
/**
* {@link SimpleDateBean#styleDateTimeWithFallbackPatternsForPreAndPostJdk20}
* configures "SS" as the date/time style to use. Thus, we have to be aware
* of the following if we do not configure fallback patterns for parsing.
*
* <ul>
* <li>JDK &le; 19 requires a standard space before the "PM".
* <li>JDK &ge; 20 requires a narrow non-breaking space (NNBSP) before the "PM".
* </ul>
*
* <p>To avoid compatibility issues between JDK versions, we have configured
* two fallback patterns which emulate the "SS" style: <code>"MM/dd/yy h:mm a"</code>
* matches against a standard space before the "PM", and <code>"MM/dd/yy h:mm&#92;u202Fa"</code>
* matches against a narrow non-breaking space (NNBSP) before the "PM".
*
* <p>Thus, the following should theoretically be supported on any JDK (or at least
* JDK 17 - 23, where we have tested it).
*
* @see #patternDateTime(String)
*/
@ParameterizedTest(name = "input date: {0}") // gh-33151
@ValueSource(strings = {"10/31/09, 12:00 PM", "10/31/09, 12:00\u202FPM"})
void styleDateTime_PreAndPostJdk20(String propertyValue) {
String propertyName = "styleDateTimeWithFallbackPatternsForPreAndPostJdk20";
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add(propertyName, propertyValue);
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
String value = binder.getBindingResult().getFieldValue(propertyName).toString();
// Since the "SS" style is always used for printing and the underlying format
// changes depending on the JDK version, we cannot be certain that a normal
// space is used before the "PM". Consequently we have to use a regular
// expression to match against any Unicode space character (\p{Zs}).
assertThat(value).startsWith("10/31/09").matches(".+?12:00\\p{Zs}PM");
}
/**
* To avoid the use of Locale-based styles (such as "MM") for
* {@link SimpleDateBean#patternDateTimeWithFallbackPatternForPreAndPostJdk20}, we have configured a
* primary pattern (<code>"MM/dd/yy h:mm a"</code>) that matches against a standard space
* before the "PM" and a fallback pattern (<code>"MM/dd/yy h:mm&#92;u202Fa"</code> that matches
* against a narrow non-breaking space (NNBSP) before the "PM".
*
* <p>Thus, the following should theoretically be supported on any JDK (or at least
* JDK 17 - 23, where we have tested it).
*
* @see #styleDateTime(String)
*/
@ParameterizedTest(name = "input date: {0}") // gh-33151
@ValueSource(strings = {"10/31/09 3:45 PM", "10/31/09 3:45\u202FPM"})
void patternDateTime_PreAndPostJdk20(String propertyValue) {
String propertyName = "patternDateTimeWithFallbackPatternForPreAndPostJdk20";
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add(propertyName, propertyValue);
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
String value = binder.getBindingResult().getFieldValue(propertyName).toString();
// Since the "MM/dd/yy h:mm a" primary pattern is always used for printing, we
// can be certain that a normal space is used before the "PM".
assertThat(value).matches("10/31/09 3:45 PM");
}
@Test
void patternDateWithUnsupportedPattern() {
String propertyValue = "210302";
@@ -389,12 +453,23 @@ class DateFormattingTests {
@DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
private Date styleDateWithFallbackPatterns;
// "SS" style matches either a standard space or a narrow non-breaking space (NNBSP) before AM/PM,
// depending on the version of the JDK.
// Fallback patterns match a standard space OR a narrow non-breaking space (NNBSP) before AM/PM.
@DateTimeFormat(style = "SS", fallbackPatterns = { "M/d/yy, h:mm a", "M/d/yy, h:mm\u202Fa" })
private Date styleDateTimeWithFallbackPatternsForPreAndPostJdk20;
@DateTimeFormat(pattern = "M/d/yy h:mm")
private Date patternDate;
@DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
private Date patternDateWithFallbackPatterns;
// Primary pattern matches a standard space before AM/PM.
// Fallback pattern matches a narrow non-breaking space (NNBSP) before AM/PM.
@DateTimeFormat(pattern = "MM/dd/yy h:mm a", fallbackPatterns = "MM/dd/yy h:mm\u202Fa")
private Date patternDateTimeWithFallbackPatternForPreAndPostJdk20;
@DateTimeFormat(iso = ISO.DATE)
private Date isoDate;
@@ -459,6 +534,14 @@ class DateFormattingTests {
this.styleDateWithFallbackPatterns = styleDateWithFallbackPatterns;
}
public Date getStyleDateTimeWithFallbackPatternsForPreAndPostJdk20() {
return this.styleDateTimeWithFallbackPatternsForPreAndPostJdk20;
}
public void setStyleDateTimeWithFallbackPatternsForPreAndPostJdk20(Date styleDateTimeWithFallbackPatternsForPreAndPostJdk20) {
this.styleDateTimeWithFallbackPatternsForPreAndPostJdk20 = styleDateTimeWithFallbackPatternsForPreAndPostJdk20;
}
public Date getPatternDate() {
return this.patternDate;
}
@@ -475,6 +558,14 @@ class DateFormattingTests {
this.patternDateWithFallbackPatterns = patternDateWithFallbackPatterns;
}
public Date getPatternDateTimeWithFallbackPatternForPreAndPostJdk20() {
return this.patternDateTimeWithFallbackPatternForPreAndPostJdk20;
}
public void setPatternDateTimeWithFallbackPatternForPreAndPostJdk20(Date patternDateTimeWithFallbackPatternForPreAndPostJdk20) {
this.patternDateTimeWithFallbackPatternForPreAndPostJdk20 = patternDateTimeWithFallbackPatternForPreAndPostJdk20;
}
public Date getIsoDate() {
return this.isoDate;
}
@@ -618,6 +618,41 @@ class DateTimeFormattingTests {
assertThat(bindingResult.getFieldValue(propertyName)).asString().matches("12:00:00\\p{Zs}PM");
}
/**
* {@link DateTimeBean#styleLocalTimeWithFallbackPatternsForPreAndPostJdk20}
* configures "-M" as the time style to use. Thus, we have to be aware
* of the following if we do not configure fallback patterns for parsing.
*
* <ul>
* <li>JDK &le; 19 requires a standard space before the "PM".
* <li>JDK &ge; 20 requires a narrow non-breaking space (NNBSP) before the "PM".
* </ul>
*
* <p>To avoid compatibility issues between JDK versions, we have configured
* two fallback patterns which emulate the "-M" style: <code>"HH:mm:ss a"</code>
* matches against a standard space before the "PM", and <code>"HH:mm:ss&#92;u202Fa"</code>
* matches against a narrow non-breaking space (NNBSP) before the "PM".
*
* <p>Thus, the following should theoretically be supported on any JDK (or at least
* JDK 17 - 23, where we have tested it).
*/
@ParameterizedTest(name = "input date: {0}") // gh-33151
@ValueSource(strings = { "12:00:00 PM", "12:00:00\u202FPM" })
void styleLocalTime_PreAndPostJdk20(String propertyValue) {
String propertyName = "styleLocalTimeWithFallbackPatternsForPreAndPostJdk20";
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add(propertyName, propertyValue);
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
String value = binder.getBindingResult().getFieldValue(propertyName).toString();
// Since the "-M" style is always used for printing and the underlying format
// changes depending on the JDK version, we cannot be certain that a normal
// space is used before the "PM". Consequently we have to use a regular
// expression to match against any Unicode space character (\p{Zs}).
assertThat(value).matches("12:00:00\\p{Zs}PM");
}
@ParameterizedTest(name = "input date: {0}")
@ValueSource(strings = {"2021-03-02T12:00:00", "2021-03-02 12:00:00", "3/2/21 12:00"})
void isoLocalDateTime(String propertyValue) {
@@ -695,6 +730,12 @@ class DateTimeFormattingTests {
@DateTimeFormat(style = "-M", fallbackPatterns = {"HH:mm:ss", "HH:mm"})
private LocalTime styleLocalTimeWithFallbackPatterns;
// "-M" style matches either a standard space or a narrow non-breaking space (NNBSP) before AM/PM,
// depending on the version of the JDK.
// Fallback patterns match a standard space OR a narrow non-breaking space (NNBSP) before AM/PM.
@DateTimeFormat(style = "-M", fallbackPatterns = {"HH:mm:ss a", "HH:mm:ss\u202Fa"})
private LocalTime styleLocalTimeWithFallbackPatternsForPreAndPostJdk20;
private LocalDateTime localDateTime;
@DateTimeFormat(style = "MM")
@@ -798,6 +839,15 @@ class DateTimeFormattingTests {
this.styleLocalTimeWithFallbackPatterns = styleLocalTimeWithFallbackPatterns;
}
public LocalTime getStyleLocalTimeWithFallbackPatternsForPreAndPostJdk20() {
return this.styleLocalTimeWithFallbackPatternsForPreAndPostJdk20;
}
public void setStyleLocalTimeWithFallbackPatternsForPreAndPostJdk20(
LocalTime styleLocalTimeWithFallbackPatternsForPreAndPostJdk20) {
this.styleLocalTimeWithFallbackPatternsForPreAndPostJdk20 = styleLocalTimeWithFallbackPatternsForPreAndPostJdk20;
}
public LocalDateTime getLocalDateTime() {
return this.localDateTime;
}
@@ -275,7 +275,7 @@ class AsyncAnnotationBeanPostProcessorTests {
Future<Object> failWithFuture();
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
org.springframework.util.concurrent.ListenableFuture<Object> failWithListenableFuture();
void failWithVoid();
@@ -310,7 +310,7 @@ class AsyncAnnotationBeanPostProcessorTests {
@Async
@Override
@SuppressWarnings("deprecation")
@SuppressWarnings({"deprecation", "removal"})
public org.springframework.util.concurrent.ListenableFuture<Object> failWithListenableFuture() {
throw new UnsupportedOperationException("failWithListenableFuture");
}
@@ -51,7 +51,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Juergen Hoeller
* @author Chris Beams
*/
@SuppressWarnings({ "resource", "deprecation" })
@SuppressWarnings({"resource", "deprecation", "removal"})
class AsyncExecutionTests {
private static String originalThreadName;
@@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
class AsyncResultTests {
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
public void asyncResultWithCallbackAndValue() throws Exception {
String value = "val";
final Set<String> values = new HashSet<>(1);
@@ -54,7 +54,7 @@ class AsyncResultTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
public void asyncResultWithCallbackAndException() {
IOException ex = new IOException();
final Set<Throwable> values = new HashSet<>(1);
@@ -71,15 +71,15 @@ class AsyncResultTests {
});
assertThat(values).singleElement().isSameAs(ex);
assertThatExceptionOfType(ExecutionException.class)
.isThrownBy(future::get)
.withCause(ex);
.isThrownBy(future::get)
.withCause(ex);
assertThatExceptionOfType(ExecutionException.class)
.isThrownBy(future.completable()::get)
.withCause(ex);
.isThrownBy(future.completable()::get)
.withCause(ex);
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
public void asyncResultWithSeparateCallbacksAndValue() throws Exception {
String value = "val";
final Set<String> values = new HashSet<>(1);
@@ -92,7 +92,7 @@ class AsyncResultTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
public void asyncResultWithSeparateCallbacksAndException() {
IOException ex = new IOException();
final Set<Throwable> values = new HashSet<>(1);
@@ -100,11 +100,11 @@ class AsyncResultTests {
future.addCallback(result -> new AssertionError("Success callback not expected: " + result), values::add);
assertThat(values).singleElement().isSameAs(ex);
assertThatExceptionOfType(ExecutionException.class)
.isThrownBy(future::get)
.withCause(ex);
.isThrownBy(future::get)
.withCause(ex);
assertThatExceptionOfType(ExecutionException.class)
.isThrownBy(future.completable()::get)
.withCause(ex);
.isThrownBy(future.completable()::get)
.withCause(ex);
}
}
@@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
abstract class AbstractSchedulingTaskExecutorTests {
@SuppressWarnings("deprecation")
@SuppressWarnings("removal")
private org.springframework.core.task.AsyncListenableTaskExecutor executor;
protected String testName;
@@ -64,7 +64,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
this.executor = buildExecutor();
}
@SuppressWarnings("deprecation")
@SuppressWarnings("removal")
protected abstract org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor();
@AfterEach
@@ -125,7 +125,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableRunnable() {
TestTask task = new TestTask(this.testName, 1);
// Act
@@ -156,7 +156,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
void submitFailingListenableRunnable() {
TestTask task = new TestTask(this.testName, 0);
org.springframework.util.concurrent.ListenableFuture<?> future = executor.submitListenable(task);
@@ -185,7 +185,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableRunnableWithGetAfterShutdown() throws Exception {
org.springframework.util.concurrent.ListenableFuture<?> future1 = executor.submitListenable(new TestTask(this.testName, -1));
org.springframework.util.concurrent.ListenableFuture<?> future2 = executor.submitListenable(new TestTask(this.testName, -1));
@@ -260,7 +260,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableCallable() {
TestCallable task = new TestCallable(this.testName, 1);
// Act
@@ -275,7 +275,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
void submitFailingListenableCallable() {
TestCallable task = new TestCallable(this.testName, 0);
// Act
@@ -291,7 +291,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("deprecation")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableCallableWithGetAfterShutdown() throws Exception {
org.springframework.util.concurrent.ListenableFuture<?> future1 = executor.submitListenable(new TestCallable(this.testName, -1));
org.springframework.util.concurrent.ListenableFuture<?> future2 = executor.submitListenable(new TestCallable(this.testName, -1));

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