Compare commits

..

93 Commits

Author SHA1 Message Date
Sam Brannen 2c18c33ce0 Track operations during SpEL expression evaluation
This commit introduces support for tracking operations during SpEL
expression evaluation. If the maximum number of operations is exceeded,
a SpelEvaluationException is thrown.

The limit can be configured either on a per-use-case basis via
SpelParserConfiguration supplied to the SpelExpressionParser or
globally as a JVM system property or Spring property named
`spring.expression.maxOperations`.

Closes gh-36801
2026-06-08 15:13:44 +02:00
Sam Brannen 83667f808c Ensure getters have non-void return types in SpEL
Closes gh-36800
2026-06-08 15:13:44 +02:00
Sam Brannen 7a8917b137 Improve additional error messages in SpEL
This commit picks up where 987d6cca6d left off.

See gh-36756
2026-06-08 15:13:44 +02:00
Sam Brannen 7baa86536f Further improve pattern caching in SpEL
See gh-36755
2026-06-08 15:13:44 +02:00
Sam Brannen 12b44f2545 Avoid too many character access attempts in AntPathMatcher
Closes gh-36799
2026-06-08 15:13:44 +02:00
Sébastien Deleuze e8f10244e3 Ensure consistent JSP tag attribute processing
Closes gh-36797
2026-06-08 15:13:44 +02:00
Sébastien Deleuze a1826b725c Refine JavaScriptUtils#javaScriptEscape
Closes gh-36795
2026-06-08 15:13:43 +02:00
Sébastien Deleuze 7add5243b9 Prevent special prefixes in default view name resolution
This commit updates the default view name generation logic in
both Spring WebMVC and Spring WebFlux to prevent "redirect:"
and "forward:" (for MVC) prefixes from the incoming request path.

Closes gh-36793
2026-06-08 15:13:43 +02:00
Sébastien Deleuze 9bec52b1ec Add trusted packages to MappingJackson2MessageConverter
This commit introduces trusted packages, specified via the related
setter for untrusted use cases. It allows explicit configuration
of which Java packages are allowed to be deserialized.

Closes gh-36791
2026-06-08 15:13:43 +02:00
Sébastien Deleuze fe68e337b2 Add trusted packages to JacksonJsonMessageConverter
This commit introduces trusted packages, specified via the related
setter for untrusted use cases. It allows explicit configuration
of which Java packages are allowed to be deserialized.

See gh-36791
2026-06-08 15:13:43 +02:00
rstoyanchev 322eef2942 RfcParser rejects invalid IPv6 host
Fix gh-36787
2026-06-08 15:13:43 +02:00
Brian Clozel ba0a9b8923 Upgrade to Reactor 2025.0.6
Closes gh-36884
2026-06-08 14:49:19 +02:00
Brian Clozel 8a192eab1f Upgrade to Micrometer 1.16.6
Closes gh-36883
2026-06-08 10:40:01 +02:00
rstoyanchev 45812cb9d0 Prefer Jackson XML codecs when present
Closes gh-36776
2026-06-05 14:36:05 +01:00
rstoyanchev 95bd3f7c67 Exclude Jackson XML from String encoding/decoding
Closes gh-36775
2026-06-05 14:36:05 +01:00
Sam Brannen 6ca66afc7b Polish contribution
See gh-36871
2026-06-05 15:13:25 +02:00
zhaomeng 1b32d8d41d Include zone ID in CronTrigger's equals() and hashCode() implementations
CronTrigger carries an optional ZoneId since 5.3 that affects
nextExecution; however, prior to this commit, equals() and hashCode()
only considered the cron expression.

This commit ensures that CronTrigger instances with the same cron
expression but different time zones are no longer considered equal.

Closes gh-36871

Signed-off-by: zhaomeng <zhaomeng1.vendor@sensetime.com>
2026-06-05 15:09:14 +02:00
Sam Brannen b4a378186f Fix additional links to Selenium documentation
See gh-36875
2026-06-05 14:56:51 +02:00
leestana01 220fcaa1e3 Fix broken links to Selenium documentation
The links to docs.seleniumhq.org no longer resolve, since the host was
retired.

This commit changes those links to use the current Selenium
documentation at selenium.dev.

Closes gh-36875

Signed-off-by: leestana01 <leestana01@naver.com>
2026-06-05 14:52:15 +02:00
Brian Clozel 80534f9df6 Polishing contribution
This commit adds further fixes in the same area, since there were
similar bugs in the WriteCompletionHandler:
* databuffers were not always emitted when fully read in the onNext hook
* on completion, the iterator was closed too early, before it was fully
  read
* on completion, writing the next bytebuffers from the iterator would
  always reuse the first one and not update the attachment

Closes gh-36714
2026-06-04 12:18:01 +02:00
KimDaehyeon d8bc54d2e7 Fix data loss in DataBufferUtils synchronous write
Prior to this commit, WritableByteChannelSubscriber.hookOnNext() called
iterator.next() exactly once. If a DataBuffer consisted of multiple NIO
ByteBuffers (e.g., NettyDataBuffer wrapping a CompositeByteBuf), only
the first buffer was written to the channel, and the remaining buffers
were silently ignored and lost.

This commit adds the missing while (iterator.hasNext()) outer loop to
ensure all fragmented buffers exposed by the iterator are completely
and safely written to the synchronous channel.

See gh-36714

Signed-off-by: KimDaehyeon <daehyeon3351@gmail.com>
2026-06-04 12:17:57 +02:00
Brian Clozel 6467fca05b Polishing contribution
This fixes a potential regression introduced by the previous commit.
Because the current value was not updated after the temporal was rolled
forward, there were new cases where entire days would be skipped.

Closes gh-36865
2026-06-04 10:22:23 +02:00
arno cddc671a8c Fix CronExpression day skip on midnight DST gap
After rollForward, BitsCronField always searched for the next
matching bit from zero. When daylight saving creates a gap at
the start of a period (e.g. Africa/Cairo), the temporal lands on
a non-zero field value and matching from zero could advance an
entire period too far, skipping the calendar day.

Search from the actual field value in the new period instead,
falling back to zero only when no bit matches in that period.

See gh-36865

Signed-off-by: arno <me@zmovo.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 10:22:17 +02:00
Juergen Hoeller 2e653246b5 Fix concurrency issue against shared cookie field in setLocaleContext
Closes gh-36869
2026-06-03 23:03:39 +02:00
Brian Clozel 86a68a77c4 Support multi-line comments in Server Sent Events
Prior to this commit, comments sent with Server Sent Events could break
the wire format when sent over the network when comments contained line
breaks.
While comments are mainly used for sending keepalive messages, they can
also be used for sending debug data. This commit ensures that line
breaks are properly handled in comments.

Fixes gh-36866
2026-06-03 11:13:32 +02:00
Juergen Hoeller fe27ad24d5 Upgrade to Netty 4.2.15 and Hibernate ORM 7.2.17 2026-06-02 17:30:17 +02:00
Juergen Hoeller ecc847c493 Fix applicability note on setAutoGrowCollectionLimit
Closes gh-36863
2026-06-02 17:28:33 +02:00
daguimu c2191e3ce2 Fix fragment parsing for relative URI in RfcUriParser
When parsing a relative URI such as `path2#foo`, RfcUriParser's
SCHEME_OR_PATH state advanced to FRAGMENT without moving the component
index past the `#` character, so the captured fragment included the
entire input (`path2#foo`) instead of just `foo`. As a result,
`toUriString()` produced `path2#path2#foo`.

Update the SCHEME_OR_PATH `#` transition to advance the component
index to `i + 1`, matching the sibling `?` -> QUERY transition in the
same state and every other `-> FRAGMENT` transition in the parser.
URIs with a `/` in the path are unaffected because they leave
SCHEME_OR_PATH for PATH on the first slash; the WhatWG parser already
handled this case correctly.

Closes gh-36762

Signed-off-by: daguimu <daguimu.geek@gmail.com>
2026-06-01 10:52:37 +01:00
Juergen Hoeller 175c1f9437 Runtime compatibility with JPA 4.0 M4
Closes gh-36784
2026-05-29 13:28:46 +02:00
Juergen Hoeller 0b77c3df45 Detect back-off exhaustion at invoker level as well
See gh-36809
2026-05-29 13:23:30 +02:00
Sam Brannen 6985d00fce Update antora-extensions to 1.14.12
Closes gh-36851
2026-05-28 10:57:11 +02:00
Juergen Hoeller 1e45ad8fa6 Restore back-off on listener setup failure
Supports exponential back-off as well now.

Closes gh-36809
2026-05-27 18:58:37 +02:00
rstoyanchev 6fd45c118d Polishing contribution
Closes gh-36650
2026-05-27 17:19:00 +01:00
Max Guiking f9b75b02da Fix last flag check in JettyWebSocketSession
See gh-36650

Signed-off-by: Max Guiking <mguiking@een.com>
2026-05-27 17:19:00 +01:00
Patrick Strawderman 84efa46e09 DefaultPathContainer uses immutable map for SEPARATORS
Closes gh-36821

Signed-off-by: Patrick Strawderman <pstrawderman@netflix.com>
2026-05-27 17:18:59 +01:00
shenjianeng b3dfba8719 Refactor map initialization using CollectionUtils
Closes gh-36763

Signed-off-by: shenjianeng <ishenjianeng@qq.com>
2026-05-27 17:17:02 +01:00
Juergen Hoeller 2782a2701c Upgrade to Netty 4.2.14 and Hibernate ORM 7.2.16 2026-05-27 16:33:15 +02:00
Juergen Hoeller af2b96192d Force initialization of configuration class in mainline thread
Closes gh-36844
2026-05-27 16:32:37 +02:00
Juergen Hoeller 121c0ac285 Remove scanned class only when conflicting with imported class
Closes gh-36835
2026-05-27 16:31:13 +02:00
Juergen Hoeller 3e585830d7 Fix MethodParameter nestingLevel documentation
Closes gh-36826
2026-05-27 16:29:51 +02:00
Sam Brannen b95caa8331 Upgrade Antora dependencies 2026-05-27 12:15:25 +02:00
Sam Brannen c17939ed5c Polish contribution
See gh-36831
2026-05-27 12:02:50 +02:00
Dennis-Mircea Ciupitu f3bfe27445 Document @⁠Conditional gating of nested @⁠Configuration classes
Closes gh-36831

Signed-off-by: Dennis-Mircea Ciupitu <dennis.mircea.ciupitu@gmail.com>
2026-05-27 12:02:50 +02:00
Sam Brannen 7651d5841f Pin Node.js version to 24.15.0
Prior to this commit, the `antora` Gradle task silently failed to build
the reference documentation, since Antora uses the latest LTS release
for Node.js by default, and the latest LTS apparently does not work for
us.
2026-05-27 12:01:09 +02:00
Sam Brannen 6e122d3aaa Polish contribution
See gh-36833
2026-05-26 16:39:30 +02:00
seonwoo_jung f7be796c1c Expose ClassLoader from DefaultDeserializer
Add a public accessor for the ClassLoader configured on a
DefaultDeserializer instance so that callers no longer need to read the
private field via reflection in order to forward it to a
ConfigurableObjectInputStream subclass.

See gh-36827
Closes gh-36833

Signed-off-by: seonwoo_jung <laborlawseon@kap.kr>
2026-05-26 16:35:37 +02:00
Sam Brannen 1e843fd3ec Polish contribution
See gh-36777
2026-05-20 17:45:24 +02:00
Kai Zander 4bb20ea8db Allow specifying charset to use in ExchangeFilterFunctions#basicAuthentication
Prior to this commit, it was not possible to specify the character set
to use with ExchangeFilterFunctions#basicAuthentication.

To address that, this commit introduces a new

Closes gh-36777

Signed-off-by: Kai Zander <61500114+kzander91@users.noreply.github.com>
2026-05-20 17:45:24 +02:00
dependabot[bot] ac24766510 Upgrade fast-xml-parser to 5.7.0
Closes gh-36691
2026-05-20 16:51:05 +02:00
Sam Brannen d5e85bd95e Remove obsolete code 2026-05-17 15:30:04 +02:00
Sam Brannen c3c96b9f32 Upgrade to Gradle 9.5.1
Closes gh-36744
2026-05-17 13:47:00 +02:00
rstoyanchev 4d98ff5dcf Polishing in data binding docs 2026-05-15 13:50:57 +01:00
rstoyanchev 48262b8efe Further polishing in docs on web binding
See gh-36803
2026-05-14 16:55:53 +01:00
rstoyanchev ca32ac5e7f Polishing in docs
See gh-36803
2026-05-14 16:41:59 +01:00
rstoyanchev 63ccc9d9cc Restructure web binding documentation and refine guidance
Closes gh-36803
2026-05-14 16:36:27 +01:00
Juergen Hoeller 8fe1de4595 Polishing 2026-05-13 19:46:07 +02:00
Juergen Hoeller c048074436 Restrict SpringVersion.getVersion() to "major.minor.patch" format
Closes gh-36785
2026-05-12 16:20:47 +02:00
Juergen Hoeller b7882d703c Expose package-info classes through PersistenceUnitInfo#getAllClassNames()
Closes gh-36784
2026-05-12 13:09:51 +02:00
Juergen Hoeller ca8a1ea375 Upgrade to Log4J 2.26, Groovy 5.0.6, Tomcat 11.0.22, Jetty 12.1.9, Hibernate ORM 7.2.14, Checkstyle 13.4.2 2026-05-11 13:08:34 +02:00
Yanming Zhou cfb8dc6cc8 Fix typos for validateExistingTransaction
Closes gh-36767

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2026-05-09 16:39:50 +02:00
Juergen Hoeller 665c9ad7df Upgrade to Netty 4.2.13 and Hibernate ORM 7.2.13 2026-05-08 16:00:14 +02:00
Juergen Hoeller d3152c11c7 Consistently expose map key quotes
Closes gh-36765
2026-05-08 15:59:57 +02:00
Juergen Hoeller 856e1d5dc8 Avoid ResolvableType#forType contention for implicit cache cleanup
Closes gh-36745
2026-05-08 15:59:46 +02:00
Sam Brannen 987d6cca6d Fix error message for invalid regex in SpEL
Closes gh-36756
2026-05-06 13:53:37 +02:00
shenjianeng ec21dc0f91 Refactor regex pattern caching using computeIfAbsent
Closes gh-36755

Signed-off-by: shenjianeng <ishenjianeng@qq.com>
2026-05-06 13:25:07 +02:00
Brian Clozel 628261d5bb Do not warn against Root Servlet context location
Closes gh-36692
2026-05-05 11:50:28 +02:00
Kai Zander 715d33f001 Remove control character from log message in TransportHandlingSockJsService
Closes gh-36731

Signed-off-by: Kai Zander <61500114+kzander91@users.noreply.github.com>
2026-05-04 17:20:20 +02:00
Sam Brannen bb5142164e Upgrade to Gradle 9.5
Closes gh-36744
2026-05-02 18:38:09 +02:00
박건영(Parkgunyoung) 25b8d64cd0 Fix typo in HttpServiceProxyFactory example
Closes gh-36736

Signed-off-by: 박건영(Parkgunyoung) <parkky3563@gmail.com>
2026-05-02 18:34:00 +02:00
rstoyanchev d72da90d3a Avoid race in InMemoryWebSession
Closes gh-36742
2026-05-01 21:47:37 +01:00
rstoyanchev bff9899905 Switch to JdkIdGenerator in AbstractWebSocketSession
Closes gh-36740
2026-05-01 21:47:26 +01:00
Brian Clozel 41cd6879bd Fix parsing failure for MIME types with quoted pairs
Prior to this commit, MIME types with parameter values that contain a
quoted pair would sometimes fail and parse an incomplete parameter
value.

This commit ensures that the quoted section of the parameter value is
correctly handled.

Fixes gh-36730
2026-05-01 21:47:16 +01:00
Brian Clozel 3a91d90c28 Resolve URL path for versioned webjar directories
Prior to this commit, the `resolveUrlPath` implementation for the
`LiteWebJarsResourceResolver` would always delegate to the resource
chain once the versioned webjar folder has been resolved.

While this aligns with the `ResourceResolver` contract and the fact that
the resource chain does not resolve directories, here the WebJar locator
does support such use cases and we shouldn't get in the way here.

This commit falls back to the resolved versioned WebJar path if no
resource could be resolved and the path ends with "/".

Fixes gh-36726
2026-05-01 21:47:06 +01:00
Juergen Hoeller 08c5280843 Consistent wrapping of BeanCreationExceptions from instance suppliers
Includes tests for circular references and bean definition overrides.

Closes gh-36725
See gh-36648
2026-04-30 14:19:25 +02:00
Juergen Hoeller cd5fee5347 Polishing 2026-04-29 21:52:10 +02:00
Juergen Hoeller 916cb64581 Detect custom deserialized NullValue instances
Closes gh-36727
2026-04-29 21:52:04 +02:00
rstoyanchev b3ef834ae6 Reliably detect broadcast messages
Closes gh-36662
2026-04-29 12:11:16 +01:00
rstoyanchev a9d344b3ed TransportHandlingSockJsService checks remoteAddress
Closes gh-36681
2026-04-29 12:11:16 +01:00
cuitianhao 39a746d30f Set host header consistently in STOMP relay CONNECT frames
StompBrokerRelayMessageHandler only set the host header in CONNECT
frames when virtualHost was explicitly configured. Per STOMP 1.2, the
host header is required on CONNECT frames.

Fall back to relayHost (the TCP connection target) when virtualHost is
not configured, ensuring the host header is always present in both
system session and client session CONNECT frames.

Closes gh-36673

Signed-off-by: cuitianhao <54015884+tianhaocui@users.noreply.github.com>
2026-04-29 12:11:16 +01:00
Brian Clozel 952198b2ee Fix PartGenerator token request while creating tmp file
Prior to this commit, the `PartGenerator` would allow requesting
additional part tokens while in the `CreateFileState`. This is invalid
as any new token emitted would be rejected and would fail the entire
process.
This would only happen if the tmp file creation is slow enough for a new
token to be parsed and emitted.

This commit ensures that no new part token is requested while creating
the temporary file.
This change also fixes lifecycle issues and ensures that buffer
resources are cleaned in case of errors.

Fixes gh-36694
2026-04-28 23:21:05 +02:00
Dmitry Sulman 8d93670430 Support Micrometer context propagation in Kotlin Flow
See gh-36427
Closes gh-36667
Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
2026-04-28 11:10:55 +02:00
Sébastien Deleuze 29a7402adf Fix WebClient context propagation in Kotlin Coroutines
Prior to this commit, thread-local variables like Trace ID were not
automatically propagated into the Reactor Context when making requests
using Kotlin coroutine WebClient extensions like `awaitExchange`.

This commit updates `CoroutineContext.toReactorContext()` to capture
thread-local values via Micrometer Context Propagation when available,
ensuring observations and traces are properly reused.

Closes gh-36182
2026-04-28 11:10:55 +02:00
Sigurd Gerke 3dfd6838c0 Fix a regression on value class parameter handling
This commit fixes a regression introduced by gh-36449 for
nullable value class with an non-null value.

Closes gh-36665
Signed-off-by: Sigurd Gerke <sigurd.gerke@onedata.de>
2026-04-28 11:10:55 +02:00
Brian Clozel 32170e5847 Avoid cache collisions in CachingResourceResolver
Prior to this commit, there could be cache collisions in the
`CachingResourceResolver` because the cache key generation was
incomplete. This commit expands the cache key generation to avoid such
cases.

Fixes gh-36713
2026-04-28 09:28:00 +02:00
Yanming Zhou bfb88cfc1c Remove unnecessary invocations of toString()
Closes gh-36709

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2026-04-27 11:27:29 +03:00
Yanming Zhou c4dfb24116 Update copyright headers in remaining source files
Closes gh-36703

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2026-04-27 11:26:35 +03:00
Brian Clozel 4fb0244b5a Enfoce single version removal in content versioning
Prior to this commit, content based version strategies would remove all
instances of the version string in the request path when trying to
resolve the original resource with the chain.
This can cause issues in rare cases where there is a collision between
the content version and some other version string in the request path.

Because this strategy is based on the contents of the file itself, we
should only remove the last instance of the version string and then
attempt to resolve the original file.

Fixes gh-36698
2026-04-23 15:04:02 +02:00
Brian Clozel 0725bf4941 Warn against unsafe static resource locations
Prior to this commit, `ResourceHandlerUtils` would perform resource
location checks to ensure that the configured location is valid. This
commit also ensures that we log a WARN message if the application
chooses a well-known unsafe location like "classpath:" or the root
Servlet context for serving static resources.

Closes gh-36692
2026-04-23 10:45:15 +02:00
Juergen Hoeller 8965d9bccf Polishing 2026-04-21 20:35:25 +02:00
Juergen Hoeller af7a5716e8 Consistently ignore exceptions for Xerces-specific properties
Closes gh-36682
2026-04-21 20:34:29 +02:00
Brian Clozel 83e7aa1131 Update HttpComponents Javadoc URI
Fixes gh-36685
2026-04-21 18:51:58 +02:00
박동윤 (Park Dong-Yun) 865cf3c117 Avoid redundant URI object creation in WebClientUtils
Prior to this commit, WebClientUtils.getRequestDescription()
created a new URI object on every invocation. Since the URI
constructor includes validation and parsing, which is already
performed by the parameter URI object, this was unnecessarily
expensive for a logging utility.

This commit reuses the original URI's string representation
to avoid redundant parsing.
This commit also strips userInfo, query, and fragment from the
log description even if URIs contain only userInfo or
fragment (without a query).

Closes gh-36641

Signed-off-by: 박동윤 (Park Dong-Yun) <ehddbs7458@gmail.com>
2026-04-21 16:52:33 +02:00
Stéphane Nicoll be70e1c5a7 Next development version (v7.0.8-SNAPSHOT) 2026-04-17 08:33:14 +02:00
272 changed files with 3276 additions and 1021 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ configure([rootProject] + javaProjects) { project ->
//"https://jakarta.ee/specifications/platform/11/apidocs/",
"https://docs.hibernate.org/orm/7.2/javadocs/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://hc.apache.org/httpcomponents-client-5.6.x/current/httpclient5/apidocs/",
"https://hc.apache.org/httpcomponents-client-5.6.x/5.6/httpclient5/apidocs/",
"https://projectreactor.io/docs/core/release/api/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
@@ -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("13.4.0");
checkstyle.setToolVersion("13.4.2");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+4
View File
@@ -20,6 +20,10 @@ antora {
]
}
node {
version = '24.15.0'
}
tasks.named("generateAntoraYml") {
asciidocAttributes = project.provider( {
return ["spring-version": project.version ]
+2
View File
@@ -198,6 +198,7 @@
*** xref:web/webmvc/mvc-uri-building.adoc[]
*** xref:web/webmvc/mvc-ann-async.adoc[]
*** xref:web/webmvc/mvc-range.adoc[]
*** xref:web/webmvc/mvc-data-binding.adoc[]
*** xref:web/webmvc-cors.adoc[]
*** xref:web/webmvc-versioning.adoc[]
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
@@ -295,6 +296,7 @@
*** xref:web/webflux-functional.adoc[]
*** xref:web/webflux/uri-building.adoc[]
*** xref:web/webflux/range.adoc[]
*** xref:web/webflux/data-binding.adoc[]
*** xref:web/webflux-cors.adoc[]
*** xref:web/webflux-versioning.adoc[]
*** xref:web/webflux/ann-rest-exceptions.adoc[]
@@ -74,6 +74,11 @@ expressions used in XML bean definitions, `@Value`, etc.
| The mode to use when compiling expressions for the
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
| `spring.expression.maxOperations`
| The default maximum number of operations permitted during
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
expression evaluation.
| `spring.getenv.ignore`
| Instructs Spring to ignore operating system environment variables if a Spring
`Environment` property -- for example, a placeholder in a configuration String -- isn't
@@ -610,6 +610,19 @@ Kotlin::
See the {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`]
javadoc for more detail.
[NOTE]
====
A `@Conditional` annotation declared on an enclosing `@Configuration` class is only
applied to the registration of a nested `@Configuration` class if the nested class is
reached through the parser's recursion from its enclosing class, or via `@Import`. If a
nested class is discovered independently of its enclosing class — for example, via
`@ComponentScan` or by directly registering it against the application context — it is
processed using only its own `@Conditional` annotations. Thus, if you wish to ensure that
the same `@Conditional` annotations apply in such scenarios, you must redeclare the
relevant annotations on the nested class, or extract them into a composed annotation
which you apply to both the enclosing class and the nested class.
====
[[beans-java-combining]]
== Combining Java and XML Configuration
@@ -395,6 +395,16 @@ set a JVM system property or Spring property named `spring.context.expression.ma
to the maximum expression length needed by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
Similarly, the number of operations performed during the evaluation of a SpEL expression
cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If
you create a `SpelExpressionParser` programmatically (the recommend approach), you can
specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that
you provide to the `SpelExpressionParser`. If you are not able to configure an explicit
value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system
property or Spring property named `spring.expression.maxOperations` to the maximum number
of operations required by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
[[expressions-spel-compilation]]
== SpEL Compilation
@@ -22,7 +22,7 @@ where all the underlying resources have to participate in the service-level tran
NOTE: By default, a participating transaction joins the characteristics of the outer scope,
silently ignoring the local isolation level, timeout value, or read-only flag (if any).
Consider switching the `validateExistingTransactions` flag to `true` on your transaction
Consider switching the `validateExistingTransaction` flag to `true` on your transaction
manager if you want isolation level declarations to be rejected when participating in
an existing transaction with a different isolation level. This non-lenient mode also
rejects read-only mismatches (that is, an inner read-write transaction that tries to participate
@@ -1179,7 +1179,7 @@ built-in decorators to suppress 404 exceptions and return a `ResponseEntity` wit
[source,java,indent=0,subs="verbatim,quotes"]
----
// For RestClient
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restCqlientAdapter)
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restClientAdapter)
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
.build();
@@ -3,7 +3,7 @@
In the previous sections, we have seen how to use MockMvc in conjunction with the raw
HtmlUnit APIs. In this section, we use additional abstractions within the Selenium
https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
https://www.selenium.dev/documentation/webdriver/[WebDriver] to make things even easier.
[[mockmvc-server-htmlunit-webdriver-why]]
== Why WebDriver and MockMvc?
@@ -12,8 +12,8 @@ We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver?
Selenium WebDriver provides a very elegant API that lets us easily organize our code. To
better show how it works, we explore an example in this section.
NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not
require a Selenium Server to run your tests.
NOTE: Despite being a part of https://www.selenium.dev/documentation/[Selenium],
WebDriver does not require a Selenium Server to run your tests.
Suppose we need to ensure that a message is created properly. The tests involve finding
the HTML form input elements, filling them out, and making various assertions.
@@ -308,7 +308,7 @@ interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
@@ -352,7 +352,7 @@ interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
@@ -107,7 +107,4 @@ Kotlin::
[[webflux-ann-initbinder-model-design]]
== Model Design
[.small]#xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[See equivalent in the Servlet stack]#
include::partial$web/web-data-binding-model-design.adoc[]
NOTE: For more guidance on model design, please see xref:web/webflux/data-binding.adoc[Data Binding].
@@ -0,0 +1,36 @@
[[webflux-data-binding]]
= Data Binding
:page-section-summary-toc: 1
[.small]#xref:web/webmvc/mvc-data-binding.adoc[See equivalent in the Servlet stack]#
Data binding is a mechanism that binds string parameters onto an object graph with type conversion.
It is a core mechanism of the Spring Framework that helps with application configuration.
In web applications it makes it easy to access query parameters and form data through richly typed objects rather than through maps of string values.
To learn more about the data binding mechanism, including constructor and setter binding, property name syntax, type conversion,
and more, see xref:core/validation/data-binding.adoc[Data binding] in the Core Technologies section.
For annotated controllers, data binding applies to a
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute] method argument.
For functional endpoints, use the `bind` method of xref:web/webflux-functional.adoc#webflux-fn-request[ServerRequest].
TIP: For browser applications with annotated controllers, you can use
xref:web/webflux/controller/ann-modelattrib-methods.adoc[@ModelAttribute methods]
to initialize additional model attributes for use in rendered views.
Each request uses a separate `WebDataBinder` instance.
For annotated controllers, this instance can be customized through
xref:web/webflux/controller/ann-initbinder.adoc[@InitBinder methods] within a controller, or
across controllers through xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice].
For functional endpoints, use overloaded `ServerRequest.bind` methods.
[[webflux-data-binding-design]]
== Model Design
[.small]#xref:web/webmvc/mvc-data-binding.adoc#mvc-data-binding-design[See equivalent in the Servlet stack]#
include::partial$web/web-data-binding-model-design.adoc[]
@@ -107,7 +107,4 @@ Kotlin::
[[mvc-ann-initbinder-model-design]]
== Model Design
[.small]#xref:web/webflux/controller/ann-initbinder.adoc#webflux-ann-initbinder-model-design[See equivalent in the Reactive stack]#
include::partial$web/web-data-binding-model-design.adoc[]
NOTE: For more guidance on model design, please see xref:web/webmvc/mvc-data-binding.adoc[Data Binding].
@@ -0,0 +1,34 @@
[[mvc-data-binding]]
= Data Binding
:page-section-summary-toc: 1
[.small]#xref:web/webflux/data-binding.adoc[See equivalent in the Reactive stack]#
Data binding is a mechanism that binds string parameters onto an object graph with type conversion.
It is a core mechanism of the Spring Framework that helps with application configuration.
In web applications it makes it easy to access query parameters and form data through richly typed objects rather than through maps of string values.
To learn more about the data binding mechanism, including constructor and setter binding, property name syntax, type conversion,
and more, see xref:core/validation/data-binding.adoc[Data binding] in the Core Technologies section.
For annotated controllers, data binding applies to a
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute] method argument.
For functional endpoints, use the `bind` method of xref:web/webmvc-functional.adoc#webmvc-fn-request[ServerRequest].
TIP: For browser applications with annotated controllers, you can use
xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[@ModelAttribute methods]
to initialize additional model attributes for use in rendered views.
Each request uses a separate `WebDataBinder` instance.
For annotated controllers, this instance can be customized through
xref:web/webmvc/mvc-controller/ann-initbinder.adoc[@InitBinder methods] within a controller, or
across controllers through xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice].
For functional endpoints, use overloaded `ServerRequest.bind` methods.
[[mvc-data-binding-design]]
== Model Design
[.small]#xref:web/webflux/data-binding.adoc#webflux-data-binding-design[See equivalent in the Reactive stack]#
include::partial$web/web-data-binding-model-design.adoc[]
@@ -1,91 +1,53 @@
xref:core/validation/data-binding.adoc[Data binding] for web requests involves
binding request parameters to a model object. By default, request parameters can be bound
to any public property of the model object, which means malicious clients can provide
extra values for properties that exist in the model object graph, but are not expected to
be set. This is why model object design requires careful consideration.
Data binding involves binding untrusted input onto application objects.
For security reasons, it's crucial to ensure that input is properly constrained to expected fields only.
This section provides guidance for safe binding.
TIP: The model object, and its nested object graph is also sometimes referred to as a
_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
First, prefer **immutable object design** for web binding purposes.
It is safe because a constructor naturally constrains binding to expected inputs.
You can use a Java record or a class with a primary constructor, and either can have further nested objects.
See xref:core/validation/data-binding.adoc#data-binding-constructor-binding[Constructor Binding] for details.
A good practice is to use a _dedicated model object_ rather than exposing your domain
model such as JPA or Hibernate entities for web data binding. For example, on a form to
change an email address, create a `ChangeEmailForm` model object that declares only
the properties required for the input:
Another option for safe binding is to use **dedicated objects** designed for the expected input.
Such objects, even if mutable, are safe because they constrain binding to the expected inputs.
[source,java,indent=0,subs="verbatim,quotes"]
----
public class ChangeEmailForm {
private String oldEmailAddress;
private String newEmailAddress;
public void setOldEmailAddress(String oldEmailAddress) {
this.oldEmailAddress = oldEmailAddress;
}
public String getOldEmailAddress() {
return this.oldEmailAddress;
}
public void setNewEmailAddress(String newEmailAddress) {
this.newEmailAddress = newEmailAddress;
}
public String getNewEmailAddress() {
return this.newEmailAddress;
}
}
----
Another good practice is to apply
xref:core/validation/data-binding.adoc#data-binding-constructor-binding[constructor binding],
which uses only the request parameters it needs for constructor arguments, and any other
input is ignored. This is in contrast to property binding which by default binds every
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 strongly recommend registering `allowedFields` patterns (case
sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set.
Domain objects such as JPA or Hibernate entities are generally not safe for web binding
as they likely contain more properties than the expected inputs.
For such cases, it's crucial to declare the properties to expose for binding.
For example:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Controller
public class ChangeEmailController {
public class PersonController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
// See Javadoc for supported pattern syntax
binder.setAllowedFields("firstName", "lastName", "*Address");
}
// @RequestMapping methods, etc.
}
----
You can also register `disallowedFields` patterns (case insensitive). However,
"allowed" configuration is preferred over "disallowed" as it is more explicit and less
prone to mistakes.
NOTE: It is also possible to configure `disallowedFields`, but that's fragile, and
due to be https://github.com/spring-projects/spring-framework/issues/36802[deprecated] in Spring Framework 7.1.
It is easy to overlook fields or introduce additional fields over time that should also be excluded.
By default, constructor and property binding are both used. If you want to use
constructor binding only, you can set the `declarativeBinding` flag on `WebDataBinder`
through an `@InitBinder` method either locally within a controller or globally through an
`@ControllerAdvice`. Turning this flag on ensures that only constructor binding is used
and that property binding is not used unless `allowedFields` patterns are configured.
For example:
By default, `DataBinder` applies both constructor and setter binding.
This is fine with immutable objects and dedicated objects, but for domain objects, you must
remember to set `allowedFields`. To ensure data binding is only used in declarative style where
expected inputs are explicitly declared, you can set `declarativeBinding` on `DataBinder`.
That applies constructor binding always, and setter binding conditionally if `allowedFields` is set.
The following shows how to set this flag globally, or
you can also narrow it through attributes on `ControllerAdvice`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Controller
public class MyController {
@ControllerAdvice
public class ControllerConfig {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setDeclarativeBinding(true);
}
// @RequestMapping methods, etc.
}
----
+5 -5
View File
@@ -1,11 +1,11 @@
{
"dependencies": {
"antora": "3.2.0-alpha.11",
"antora": "3.2.0-alpha.12",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.2",
"@antora/collector-extension": "1.0.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.7",
"fast-xml-parser": "5.3.8",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
"@springio/antora-extensions": "1.14.12",
"fast-xml-parser": "5.7.0",
"@springio/asciidoctor-extensions": "1.0.0-alpha.18"
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-present 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.
+12 -13
View File
@@ -8,15 +8,15 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.20.2"))
api(platform("io.micrometer:micrometer-bom:1.16.5"))
api(platform("io.netty:netty-bom:4.2.12.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.5"))
api(platform("io.micrometer:micrometer-bom:1.16.6"))
api(platform("io.netty:netty-bom:4.2.15.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.6"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:5.0.5"))
api(platform("org.apache.logging.log4j:log4j-bom:2.25.4"))
api(platform("org.apache.groovy:groovy-bom:5.0.6"))
api(platform("org.apache.logging.log4j:log4j-bom:2.26.0"))
api(platform("org.assertj:assertj-bom:3.27.7"))
api(platform("org.eclipse.jetty:jetty-bom:12.1.7"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.7"))
api(platform("org.eclipse.jetty:jetty-bom:12.1.9"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.9"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0"))
api(platform("org.junit:junit-bom:6.0.3"))
@@ -96,10 +96,10 @@ dependencies {
api("org.apache.httpcomponents.client5:httpclient5:5.6")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.4.2")
api("org.apache.poi:poi-ooxml:5.5.1")
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.20")
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.20")
api("org.apache.tomcat:tomcat-util:11.0.20")
api("org.apache.tomcat:tomcat-websocket:11.0.20")
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.22")
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.22")
api("org.apache.tomcat:tomcat-util:11.0.22")
api("org.apache.tomcat:tomcat-websocket:11.0.22")
api("org.aspectj:aspectjrt:1.9.25")
api("org.aspectj:aspectjtools:1.9.25")
api("org.aspectj:aspectjweaver:1.9.25")
@@ -120,7 +120,7 @@ dependencies {
api("org.glassfish:jakarta.el:4.0.2")
api("org.graalvm.sdk:graal-sdk:22.3.1")
api("org.hamcrest:hamcrest:3.0")
api("org.hibernate.orm:hibernate-core:7.2.11.Final")
api("org.hibernate.orm:hibernate-core:7.2.17.Final")
api("org.hibernate.validator:hibernate-validator:9.1.0.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.21.0")
@@ -138,7 +138,6 @@ dependencies {
api("org.seleniumhq.selenium:selenium-java:4.41.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.testng:testng:7.12.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.4")
api("org.xmlunit:xmlunit-matchers:2.10.4")
+1 -1
View File
@@ -1,4 +1,4 @@
version=7.0.7
version=7.0.8-SNAPSHOT
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+1 -1
View File
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+10 -21
View File
@@ -23,8 +23,8 @@
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,7 +65,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@@ -73,21 +73,10 @@ goto fail
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
@@ -163,7 +163,7 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint,
@Override
public String toString() {
return "execution(" + getSignature().toString() + ")";
return "execution(" + getSignature() + ")";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-present 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-present 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.
@@ -956,8 +956,8 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
actualName = propertyName.substring(0, keyStart);
}
String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
if (key.length() > 1 && (key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\""))) {
if (key.length() > 1 && ((key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\"")))) {
key = key.substring(1, key.length() - 1);
}
keys.add(key);
@@ -63,12 +63,14 @@ public interface ConfigurablePropertyAccessor extends PropertyAccessor, Property
* <p>If {@code true}, a {@code null} path location will be populated
* with a default object value and traversed instead of resulting in a
* {@link NullValueInNestedPathException}.
* <p>Default is {@code false} on a plain PropertyAccessor instance.
* <p>Default is {@code false} on a plain accessor.
* @since 4.1
*/
void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
/**
* Return whether "auto-growing" of nested paths has been activated.
* @since 4.1
*/
boolean isAutoGrowNestedPaths();
@@ -154,7 +154,8 @@ public abstract class PropertyAccessorUtils {
PropertyAccessor.PROPERTY_KEY_SUFFIX, keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length());
if (keyEnd != -1) {
String key = sb.substring(keyStart + PropertyAccessor.PROPERTY_KEY_PREFIX.length(), keyEnd);
if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) {
if (key.length() > 1 && ((key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\"")))) {
sb.delete(keyStart + 1, keyStart + 2);
sb.delete(keyEnd - 2, keyEnd - 1);
keyEnd = keyEnd - 2;
@@ -32,6 +32,7 @@ public class AotBeanProcessingException extends AotProcessingException {
private final RootBeanDefinition beanDefinition;
/**
* Create an instance with the {@link RegisteredBean} that fails to be
* processed, a detail message, and an optional root cause.
@@ -65,6 +66,7 @@ public class AotBeanProcessingException extends AotProcessingException {
return sb.toString();
}
/**
* Return the bean definition of the bean that failed to be processed.
*/
@@ -249,7 +249,7 @@ public abstract class YamlProcessor {
}
else {
// It has to be a map key in this case
result.put("[" + key.toString() + "]", value);
result.put("[" + key + "]", value);
}
});
return result;
@@ -606,9 +606,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
throw bce;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
}
if (earlySingletonExposure) {
@@ -1246,8 +1244,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
instance = obtainInstanceFromSupplier(supplier, beanName, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeansException beansException) {
throw beansException;
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
throw bce;
}
throw new BeanCreationException(beanName, "Instantiation of supplied bean failed", ex);
}
@@ -1154,12 +1154,19 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (mbd.isBackgroundInit()) {
Executor executor = getBootstrapExecutor();
if (executor != null) {
// Force initialization of depends-on beans in mainline thread.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
getBean(dep);
}
}
// Force initialization of factory reference in mainline thread.
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
getBean(factoryBeanName);
}
// Instantiate current bean in background thread.
CompletableFuture<?> future = CompletableFuture.runAsync(
() -> instantiateSingletonInBackgroundThread(beanName), executor);
addSingletonFactory(beanName, () -> {
@@ -1413,6 +1413,8 @@ abstract class AbstractPropertyAccessorTests {
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
assertThat(accessor.getPropertyValue("map['].name")).isEqualTo("name9");
assertThat(accessor.getPropertyValue("map[\"].name")).isEqualTo("name9");
assertThat(accessor.getPropertyValue("iterableMap[key1].name")).isEqualTo("nameC");
assertThat(accessor.getPropertyValue("iterableMap[key2][0].name")).isEqualTo("nameA");
assertThat(accessor.getPropertyValue("iterableMap[key2][1].name")).isEqualTo("nameB");
@@ -79,6 +79,10 @@ class PropertyAccessorUtilsTests {
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[key1].name")).isEqualTo("map[key1].name");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map['key1'].name")).isEqualTo("map[key1].name");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"key1\"].name")).isEqualTo("map[key1].name");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map['key1]")).isEqualTo("map['key1]");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"key1]")).isEqualTo("map[\"key1]");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[']")).isEqualTo("map[']");
assertThat(PropertyAccessorUtils.canonicalPropertyName("map[\"]")).isEqualTo("map[\"]");
}
@Test
@@ -76,6 +76,7 @@ public class IndexedTestBean {
TestBean tb6 = new TestBean("name6", 0);
TestBean tb7 = new TestBean("name7", 0);
TestBean tb8 = new TestBean("name8", 0);
TestBean tb9 = new TestBean("name9", 0);
TestBean tbA = new TestBean("nameA", 0);
TestBean tbB = new TestBean("nameB", 0);
TestBean tbC = new TestBean("nameC", 0);
@@ -104,6 +105,8 @@ public class IndexedTestBean {
list.add(tbY);
this.map.put("key4", list);
this.map.put("key5[foo]", tb8);
this.map.put("'", tb9);
this.map.put("\"", tb9);
this.myTestBeans = new MyTestBeans(tbZ);
}
@@ -63,7 +63,7 @@ class TypeHelper {
if (type instanceof DeclaredType declaredType) {
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement instanceof TypeElement) {
return getQualifiedName(enclosingElement) + "$" + declaredType.asElement().getSimpleName().toString();
return getQualifiedName(enclosingElement) + "$" + declaredType.asElement().getSimpleName();
}
else {
return getQualifiedName(declaredType.asElement());
@@ -198,13 +198,13 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache annotation configuration on '" +
ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
ae + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
throw new IllegalStateException("Invalid cache annotation configuration on '" +
ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
ae + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
"These attributes are mutually exclusive: the cache manager is used to configure a" +
"default cache resolver if none is set. If a cache resolver is set, the cache manager" +
"won't be used.");
@@ -222,7 +222,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
if (StringUtils.hasText(builder.getKey()) && StringUtils.hasText(builder.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache advice configuration on '" +
element.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
element + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
@@ -84,7 +84,7 @@ public abstract class AbstractValueAdaptingCache implements Cache {
* @return the value to return to the user
*/
protected @Nullable Object fromStoreValue(@Nullable Object storeValue) {
if (this.allowNullValues && storeValue == NullValue.INSTANCE) {
if (this.allowNullValues && storeValue instanceof NullValue) {
return null;
}
return storeValue;
@@ -783,16 +783,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
});
GeneratedMethod generateMethod = generatedClass.getMethods().add("apply", method -> {
method.addJavadoc("Apply resource autowiring.");
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC,
javax.lang.model.element.Modifier.STATIC);
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.STATIC);
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
method.addParameter(this.target, INSTANCE_PARAMETER);
method.returns(this.target);
method.addCode(generateMethodCode(generatedClass.getName(),
generationContext.getRuntimeHints()));
method.addCode(generateMethodCode(generatedClass.getName(), generationContext.getRuntimeHints()));
});
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
registerHints(generationContext.getRuntimeHints());
}
@@ -341,6 +341,18 @@ import org.springframework.stereotype.Component;
* with the {@code @Profile} annotation to provide two options of the same bean to the
* enclosing {@code @Configuration} class.
*
* <p>A {@link Conditional @Conditional} annotation declared on an enclosing
* {@code @Configuration} class is only applied to the registration of a nested
* {@code @Configuration} class if the nested class is reached through the parser's
* recursion from its enclosing class, or via {@link Import @Import}. If a nested
* class is discovered independently of its enclosing class &mdash; for example,
* via {@link ComponentScan @ComponentScan} or by directly registering it against
* the application context &mdash; it is processed using only its own
* {@code @Conditional} annotations. Thus, if you wish to ensure that the same
* {@code @Conditional} annotations apply in such scenarios, you must redeclare
* the relevant annotations on the nested class, or extract them into a composed
* annotation which you apply to both the enclosing class and the nested class.
*
* <h2>Configuring lazy initialization</h2>
*
* <p>By default, {@code @Bean} methods will be <em>eagerly instantiated</em> at container
@@ -260,9 +260,11 @@ class ConfigurationClassParser {
return;
}
else if (configClass.isScanned()) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
if (existingClass.isImported()) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
}
// An implicitly scanned bean definition should not override an explicit import.
return;
@@ -191,7 +191,7 @@ public class InstrumentationLoadTimeWeaver implements LoadTimeWeaver {
@Override
public String toString() {
return "FilteringClassFileTransformer for: " + this.targetTransformer.toString();
return "FilteringClassFileTransformer for: " + this.targetTransformer;
}
}
@@ -177,7 +177,11 @@ final class BitsCronField extends CronField {
int next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
next = nextSetBit(type().get(temporal));
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
}
if (next == current) {
return temporal;
@@ -191,7 +195,12 @@ final class BitsCronField extends CronField {
next = nextSetBit(current);
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
next = nextSetBit(type().get(temporal));
if (next == -1) {
temporal = type().rollForward(temporal);
next = nextSetBit(0);
}
current = type().get(temporal);
}
}
if (count >= CronExpression.MAX_ATTEMPTS) {
@@ -19,6 +19,7 @@ package org.springframework.scheduling.support;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.TimeZone;
import org.jspecify.annotations.Nullable;
@@ -145,12 +146,13 @@ public class CronTrigger implements Trigger {
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof CronTrigger that &&
this.expression.equals(that.expression)));
this.expression.equals(that.expression) &&
Objects.equals(this.zoneId, that.zoneId)));
}
@Override
public int hashCode() {
return this.expression.hashCode();
return Objects.hash(this.expression, this.zoneId);
}
@Override
@@ -273,8 +273,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
* Specify the limit for array and collection auto-growing.
* <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
* <p>Used for setter/field injection via {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct}.
* <p>Used for setter injection via {@link #bind(PropertyValues)};
* not applicable to field injection, and not to constructor binding
* via {@link #construct} either.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowCollectionLimit
*/
@@ -197,6 +197,16 @@ class BackgroundBootstrapTests {
}
}
@Test
@Timeout(10)
@EnabledForTestGroups(LONG_RUNNING)
void bootstrapWithCustomExecutorAndLazyConfig() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CustomExecutorLazyBeanConfig.class);
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isTrue();
assertThat(ctx.getBeanFactory().containsSingleton("testBean2")).isTrue();
ctx.close();
}
@Configuration(proxyBeanMethods = false)
static class UnmanagedThreadBeanConfig {
@@ -542,4 +552,36 @@ class BackgroundBootstrapTests {
}
}
@Configuration(proxyBeanMethods = false)
static class CustomExecutorLazyBeanConfig {
@Bean
public ThreadPoolTaskExecutor bootstrapExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("Custom-");
executor.setCorePoolSize(2);
executor.initialize();
return executor;
}
@Configuration(proxyBeanMethods = false)
@Lazy
static class LazyBeanConfig {
@Bean(bootstrap = BACKGROUND)
public TestBean testBean1() throws InterruptedException {
Thread.sleep(6000);
return new TestBean();
}
@Bean(bootstrap = BACKGROUND)
@Lazy
public TestBean testBean2() throws InterruptedException {
Thread.sleep(6000);
return new TestBean();
}
}
}
}
@@ -26,6 +26,7 @@ import example.scannable.CustomComponent;
import example.scannable.CustomStereotype;
import example.scannable.DefaultNamedComponent;
import example.scannable.FooService;
import example.scannable.FooServiceImpl;
import example.scannable.MessageBean;
import example.scannable.ScopedProxyTestBean;
import example.scannable_implicitbasepackage.ComponentScanAnnotatedConfigWithImplicitBasePackage;
@@ -43,6 +44,7 @@ import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
@@ -84,6 +86,17 @@ class ComponentScanAnnotationIntegrationTests {
assertContextContainsBean(ctx, "fooServiceImpl");
}
@Test
void controlScanWithExplicitRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.registerBeanDefinition("myFooService", new RootBeanDefinition(FooServiceImpl.class));
ctx.scan(example.scannable.PackageMarker.class.getPackage().getName());
ctx.refresh();
assertContextContainsBean(ctx, "myFooService");
assertContextContainsBean(ctx, "fooServiceImpl");
}
@Test
void viaContextRegistration() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfig.class);
@@ -289,7 +289,7 @@ class AutowiredConfigurationTests {
@Bean
public TestBean testBean(Color color, List<Color> colors) {
return new TestBean(color.toString() + "-" + colors.get(0).toString());
return new TestBean(color + "-" + colors.get(0));
}
}
@@ -303,7 +303,7 @@ class AutowiredConfigurationTests {
return new TestBean("");
}
else {
return new TestBean(color.get() + "-" + colors.get().get(0).toString());
return new TestBean(color.get() + "-" + colors.get().get(0));
}
}
}
@@ -30,6 +30,7 @@ import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
@@ -41,12 +42,15 @@ import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostP
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.testfixture.beans.factory.CircularBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.ImportAwareBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.OverridingBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.env.ConfigurableEnvironment;
@@ -637,6 +641,22 @@ class GenericApplicationContextTests {
assertThat(context.getBean(SampleBeanRegistrar.Bar.class).foo()).isEqualTo(context.getBean(SampleBeanRegistrar.Foo.class));
}
@Test
void beanRegistrarWithCircularReference() {
GenericApplicationContext context = new GenericApplicationContext();
context.register(new CircularBeanRegistrar());
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(context::refresh)
.withRootCauseInstanceOf(BeanCurrentlyInCreationException.class);
}
@Test
void beanRegistrarWithDefinitionOverride() {
GenericApplicationContext context = new GenericApplicationContext();
context.setAllowBeanDefinitionOverriding(false);
assertThatExceptionOfType(BeanDefinitionOverrideException.class).isThrownBy(
() -> context.register(new OverridingBeanRegistrar()));
}
@Test
void importAwareBeanRegistrar() {
GenericApplicationContext context = new GenericApplicationContext();
@@ -16,6 +16,7 @@
package org.springframework.scheduling.support;
import java.time.ZonedDateTime;
import java.util.Arrays;
import org.assertj.core.api.Condition;
@@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Arjen Poutsma
* @author Sam Brannen
* @author Brian Clozel
*/
class BitsCronFieldTests {
@@ -112,6 +114,24 @@ class BitsCronFieldTests {
.has(clear(0)).has(setRange(1, 7));
}
@Test
void nextOrSameWithMidnightGap() {
BitsCronField field = BitsCronField.parseHours("0-23/2");
ZonedDateTime last = ZonedDateTime.parse("2025-04-24T23:00:00+02:00[Africa/Cairo]");
ZonedDateTime expected = ZonedDateTime.parse("2025-04-25T02:00:00+03:00[Africa/Cairo]");
ZonedDateTime actual = field.nextOrSame(last);
assertThat(actual).isEqualTo(expected);
}
@Test
void nextOrSameWithGapAfterRollForward() {
BitsCronField field = BitsCronField.parseHours("0,2");
ZonedDateTime last = ZonedDateTime.parse("2026-03-08T01:00:00-05:00[America/New_York]");
ZonedDateTime expected = ZonedDateTime.parse("2026-03-09T00:00:00-04:00[America/New_York]");
ZonedDateTime actual = field.nextOrSame(last);
assertThat(actual).isEqualTo(expected);
}
private static Condition<BitsCronField> set(int... indices) {
return new Condition<>(String.format("set bits %s", Arrays.toString(indices))) {
@@ -1367,6 +1367,14 @@ class CronExpressionTests {
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
cronExpression = CronExpression.parse("0 0 */2 * * ?");
last = ZonedDateTime.parse("2025-04-24T22:00:00+02:00[Africa/Cairo]");
expected = ZonedDateTime.parse("2025-04-25T02:00:00+03:00[Africa/Cairo]");
actual = cronExpression.next(last);
assertThat(actual).isNotNull();
assertThat(actual).isEqualTo(expected);
}
@Test
@@ -16,6 +16,7 @@
package org.springframework.scheduling.support;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -746,6 +747,20 @@ class CronTriggerTests {
assertThat(nextExecutionTime).isEqualTo(this.calendar.getTime());
}
@Test
void equalsAndHashCodeConsiderZoneId() {
String expression = "0 0 9 * * *";
CronTrigger amsterdam1 = new CronTrigger(expression, ZoneId.of("Europe/Amsterdam"));
CronTrigger amsterdam2 = new CronTrigger(expression, ZoneId.of("Europe/Amsterdam"));
CronTrigger newYork = new CronTrigger(expression, ZoneId.of("America/New_York"));
assertThat(amsterdam1)
.isEqualTo(amsterdam2)
.hasSameHashCodeAs(amsterdam2)
.isNotEqualTo(newYork)
.doesNotHaveSameHashCodeAs(newYork);
}
private static void roundup(Calendar calendar) {
calendar.add(Calendar.SECOND, 1);
@@ -0,0 +1,59 @@
/*
* Copyright 2002-present 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.context.testfixture.beans.factory;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.core.env.Environment;
public class CircularBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean("foo", Foo.class, spec -> spec.supplier(context -> new Foo(context.bean(Bar.class))));
registry.registerAlias("foo", "fooAlias");
registry.registerBean("bar", Bar.class, spec -> spec
.prototype()
.lazyInit()
.description("Custom description")
.supplier(context -> new Bar(context.bean(Foo.class))));
if (env.matchesProfiles("baz")) {
registry.registerBean(Baz.class, spec -> spec
.supplier(context -> new Baz("Hello World!")));
}
registry.registerBean(Init.class);
}
public record Foo(Bar bar) {}
public record Bar(Foo foo) {}
public record Baz(String message) {}
public static class Init {
public boolean initialized = false;
@PostConstruct
public void postConstruct() {
initialized = true;
}
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2002-present 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.context.testfixture.beans.factory;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.core.env.Environment;
public class OverridingBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean("foo", Foo.class);
registry.registerBean("foo", Foo.class);
}
public record Foo() {}
}
@@ -24,7 +24,9 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.support.NullValue;
import static org.assertj.core.api.Assertions.assertThat;
@@ -72,6 +74,12 @@ public abstract class AbstractCacheTests<T extends Cache> {
assertThat(cache.get(key).get()).isNull();
assertThat(cache.get(key, String.class)).isNull();
assertThat(cache.get(key, Object.class)).isNull();
cache.put(key, BeanUtils.instantiateClass(NullValue.class));
assertThat(cache.get(key)).isNotNull();
assertThat(cache.get(key).get()).isNull();
assertThat(cache.get(key, String.class)).isNull();
assertThat(cache.get(key, Object.class)).isNull();
}
@Test
@@ -36,10 +36,9 @@ public abstract class AbstractValueAdaptingCacheTests<T extends AbstractValueAda
protected void cachePutNullValueAllowNullFalse() {
T cache = getCache(false);
String key = createRandomKey();
assertThatIllegalArgumentException().isThrownBy(() ->
cache.put(key, null))
.withMessageContaining(CACHE_NAME_NO_NULL)
.withMessageContaining("is configured to not allow null values but null was provided");
assertThatIllegalArgumentException().isThrownBy(() -> cache.put(key, null))
.withMessageContaining(CACHE_NAME_NO_NULL)
.withMessageContaining("is configured to not allow null values but null was provided");
}
}
@@ -145,7 +145,7 @@ implements CallbackGenerator
private static void superHelper(CodeEmitter e, MethodInfo method, Context context)
{
if (TypeUtils.isAbstract(method.getModifiers())) {
e.throw_exception(ABSTRACT_METHOD_ERROR, method.toString() + " is abstract" );
e.throw_exception(ABSTRACT_METHOD_ERROR, method + " is abstract" );
} else {
e.load_this();
context.emitLoadArgsAndInvoke(e, method);
@@ -134,9 +134,10 @@ public abstract class CoroutinesUtils {
Object arg = args[index];
if (!(parameter.isOptional() && arg == null)) {
KType type = parameter.getType();
if (!type.isMarkedNullable() &&
if (!(type.isMarkedNullable() && arg == null) &&
type.getClassifier() instanceof KClass<?> kClass &&
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) {
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass)) &&
!JvmClassMappingKt.getJavaClass(kClass).isInstance(arg)) {
arg = box(kClass, arg);
}
argMap.put(parameter, arg);
@@ -166,9 +167,10 @@ public abstract class CoroutinesUtils {
private static Object box(KClass<?> kClass, @Nullable Object arg) {
KFunction<?> constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass));
KType type = constructor.getParameters().get(0).getType();
if (!type.isMarkedNullable() &&
if (!(type.isMarkedNullable() && arg == null) &&
type.getClassifier() instanceof KClass<?> parameterClass &&
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) {
KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass)) &&
!JvmClassMappingKt.getJavaClass(parameterClass).isInstance(arg)) {
arg = box(parameterClass, arg);
}
if (!KCallablesJvm.isAccessible(constructor)) {
@@ -117,8 +117,8 @@ public class MethodParameter {
* return type; 0 for the first method parameter; 1 for the second method
* parameter, etc.
* @param nestingLevel the nesting level of the target type
* (typically 1; for example, in case of a List of Lists, 1 would indicate the
* nested List, whereas 2 would indicate the element of the nested List)
* (1 for the top-level type; in case of a List of Lists, 2 would indicate
* the nested List, whereas 3 would indicate the element of the nested List)
*/
public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
Assert.notNull(method, "Method must not be null");
@@ -142,8 +142,8 @@ public class MethodParameter {
* @param constructor the Constructor to specify a parameter for
* @param parameterIndex the index of the parameter
* @param nestingLevel the nesting level of the target type
* (typically 1; for example, in case of a List of Lists, 1 would indicate the
* nested List, whereas 2 would indicate the element of the nested List)
* (1 for the top-level type; in case of a List of Lists, 2 would indicate
* the nested List, whereas 3 would indicate the element of the nested List)
*/
public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
Assert.notNull(constructor, "Constructor must not be null");
@@ -292,8 +292,8 @@ public class MethodParameter {
/**
* Return the nesting level of the target type
* (typically 1; for example, in case of a List of Lists, 1 would indicate the
* nested List, whereas 2 would indicate the element of the nested List).
* (1 for the top-level type; in case of a List of Lists, 2 would indicate
* the nested List, whereas 3 would indicate the element of the nested List).
*/
public int getNestingLevel() {
return this.nestingLevel;
@@ -71,6 +71,8 @@ public class ReactiveAdapterRegistry {
private static final boolean MUTINY_PRESENT;
private static final boolean CONTEXT_PROPAGATION_PRESENT;
static {
ClassLoader classLoader = ReactiveAdapterRegistry.class.getClassLoader();
REACTIVE_STREAMS_PRESENT = ClassUtils.isPresent("org.reactivestreams.Publisher", classLoader);
@@ -78,6 +80,7 @@ public class ReactiveAdapterRegistry {
RXJAVA_3_PRESENT = ClassUtils.isPresent("io.reactivex.rxjava3.core.Flowable", classLoader);
COROUTINES_REACTOR_PRESENT = ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader);
MUTINY_PRESENT = ClassUtils.isPresent("io.smallrye.mutiny.Multi", classLoader);
CONTEXT_PROPAGATION_PRESENT = ClassUtils.isPresent("io.micrometer.context.ContextSnapshotFactory", classLoader);
}
private final List<ReactiveAdapter> adapters = new ArrayList<>();
@@ -356,7 +359,9 @@ public class ReactiveAdapterRegistry {
registry.registerReactiveType(
ReactiveTypeDescriptor.multiValue(kotlinx.coroutines.flow.Flow.class, kotlinx.coroutines.flow.FlowKt::emptyFlow),
source -> kotlinx.coroutines.reactor.ReactorFlowKt.asFlux((kotlinx.coroutines.flow.Flow<?>) source),
CONTEXT_PROPAGATION_PRESENT ?
source -> kotlinx.coroutines.reactor.ReactorFlowKt.asFlux((kotlinx.coroutines.flow.Flow<?>) source, new PropagationContextElement()) :
source -> kotlinx.coroutines.reactor.ReactorFlowKt.asFlux((kotlinx.coroutines.flow.Flow<?>) source),
kotlinx.coroutines.reactive.ReactiveFlowKt::asFlow);
}
}
@@ -1533,9 +1533,6 @@ public class ResolvableType implements Serializable {
return new ResolvableType(type, null, typeProvider, variableResolver);
}
// Purge empty entries on access since we don't have a clean-up thread or the like.
cache.purgeUnreferencedEntries();
// Check the cache - we may have a ResolvableType which has been resolved before...
ResolvableType resultType = new ResolvableType(type, typeProvider, variableResolver);
ResolvableType cachedType = cache.get(resultType);
@@ -44,6 +44,7 @@ import org.jspecify.annotations.Nullable;
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#STRICT_LOCKING_PROPERTY_NAME
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
* @see org.springframework.jndi.JndiLocatorDelegate#IGNORE_JNDI_PROPERTY_NAME
* @see org.springframework.objenesis.SpringObjenesis#IGNORE_OBJENESIS_PROPERTY_NAME
@@ -38,13 +38,27 @@ public final class SpringVersion {
/**
* Return the full version string of the present Spring codebase,
* Return the "major.minor.patch" version string of the present Spring codebase,
* or {@code null} if it cannot be determined.
* @see Package#getImplementationVersion()
*/
public static @Nullable String getVersion() {
Package pkg = SpringVersion.class.getPackage();
return (pkg != null ? pkg.getImplementationVersion() : null);
String version = (pkg != null ? pkg.getImplementationVersion() : null);
if (version != null) {
int idx = version.indexOf('.'); // after major
if (idx != -1) {
idx = version.indexOf('.', idx + 1); // after minor
if (idx != -1) {
idx = version.indexOf('.', idx + 1); // after patch
if (idx != -1) {
// Ignore anything beyond "major.minor.patch"
version = version.substring(0, idx);
}
}
}
}
return version;
}
}
@@ -1138,9 +1138,11 @@ public abstract class DataBufferUtils {
protected void hookOnNext(DataBuffer dataBuffer) {
try {
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
ByteBuffer byteBuffer = iterator.next();
while (byteBuffer.hasRemaining()) {
this.channel.write(byteBuffer);
while (iterator.hasNext()) {
ByteBuffer byteBuffer = iterator.next();
while (byteBuffer.hasRemaining()) {
this.channel.write(byteBuffer);
}
}
}
this.sink.next(dataBuffer);
@@ -1213,6 +1215,11 @@ public abstract class DataBufferUtils {
failed(ex, attachment);
}
}
else {
iterator.close();
this.sink.next(dataBuffer);
request(1);
}
}
@Override
@@ -1236,7 +1243,6 @@ public abstract class DataBufferUtils {
@Override
public void completed(Integer written, Attachment attachment) {
DataBuffer.ByteBufferIterator iterator = attachment.iterator();
iterator.close();
long pos = this.position.addAndGet(written);
ByteBuffer byteBuffer = attachment.byteBuffer();
@@ -1246,9 +1252,11 @@ public abstract class DataBufferUtils {
}
else if (iterator.hasNext()) {
ByteBuffer next = iterator.next();
this.channel.write(next, pos, attachment, this);
Attachment nextAttachment = new Attachment(next, attachment.dataBuffer(), iterator);
this.channel.write(next, pos, nextAttachment, this);
}
else {
iterator.close();
this.sink.next(attachment.dataBuffer());
this.writing.set(false);
@@ -59,6 +59,17 @@ public class DefaultDeserializer implements Deserializer<Object> {
}
/**
* Return the {@link ClassLoader} to use for deserialization, or {@code null}
* to use the "latest user-defined ClassLoader".
* @since 6.2.19
* @see ConfigurableObjectInputStream#ConfigurableObjectInputStream(InputStream, ClassLoader)
*/
public @Nullable ClassLoader getClassLoader() {
return this.classLoader;
}
/**
* Read from the supplied {@code InputStream} and deserialize the contents
* into an object.

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