Compare commits

...

60 Commits

Author SHA1 Message Date
Brian Clozel cf74ce007d Release v6.2.15 2025-12-11 10:58:54 +01:00
Brian Clozel 23625ee698 Do not send null HTTP header value in JdkClientHttpRequest
Prior to this commit, the `JdkClientHttpRequest` would add all values
from `HttpHeaders` to the native request builder. This could cause
`NullPointerException` being thrown at runtime because the `HttpClient`
does not support that.

This commit replicates a fix that was applied to the
`SimpleClientHttpRequest`, turning null values into empty "".

Fixes gh-35996
2025-12-11 09:57:55 +01:00
Brian Clozel c89c4ac614 Upgrade to Reactor 2024.0.13
Closes gh-35987
2025-12-09 22:26:46 +01:00
Brian Clozel 2155e9fb25 Upgrade to Micrometer 1.14.14
Closes gh-35986
2025-12-09 22:26:10 +01:00
Juergen Hoeller 24df35c55a Do not keep target connection after failed settings
Includes aligned setReadOnly exception suppression.

Closes gh-35980

(cherry picked from commit ab33000750)
2025-12-09 13:01:36 +01:00
Brian Clozel 3b1fa369b4 Polishing contribution
Closes gh-35978
2025-12-08 16:11:38 +01:00
Johnny Lim 221adf14a4 Fix SubscriberInputStream.resume()
See gh-35978

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-12-08 16:11:31 +01:00
Juergen Hoeller ad849fb3a2 Accept assignable match for covariant return type
See gh-35936

(cherry picked from commit df27627516)
2025-12-08 12:43:35 +01:00
Juergen Hoeller 8041a09268 Polishing 2025-12-05 16:23:12 +01:00
Juergen Hoeller 97b9517918 Handle absolute file URLs in getClassPathManifestEntriesFromJar
Closes gh-35682

(cherry picked from commit 196c1dd51c)
2025-12-05 16:16:44 +01:00
Sam Brannen e79d8e35a2 Document that annotations which reference types not present are ignored
Add warnings to the class-level Javadoc for MergedAnnotations,
AnnotatedTypeMetadata, AnnotationMetadata, and MethodMetadata to point
out that annotations may be ignored if their attributes reference types
that are not present in the classpath.

Closes gh-35959

(cherry picked from commit b916dc962e)
2025-12-05 16:02:14 +01:00
rstoyanchev 96503a2ea6 Polishing 2025-12-04 09:31:20 +00:00
rstoyanchev 8803d3c5dc Lower bad requests to DEBUG level in AbstractHandshakeHandler
Closes gh-35930
2025-12-04 09:24:11 +00:00
rstoyanchev 5b4728f0bf Avoid http - web.utils package cycle
Closes gh-35952
2025-12-04 09:23:45 +00:00
Juergen Hoeller 1fd1d8d629 Polishing
(cherry picked from commit 07c0213f20)
2025-12-04 00:40:02 +01:00
Juergen Hoeller afc40d7b93 Avoid computeIfAbsent for createMappings which calls back into same map
Closes gh-35944

(cherry picked from commit 667851c0fa)
2025-12-04 00:15:36 +01:00
Sam Brannen 3eb1df0ac0 Update antora-extensions to 1.14.7
Closes gh-35949

(cherry picked from commit c7e24a5c26)
2025-12-02 15:36:45 +01:00
Juergen Hoeller 2aa3b0a77e Select method with resolved return type match (among multiple candidates)
Removes unnecessary array type check for parameters of candidate methods.

Closes gh-35936

(cherry picked from commit 92e9543ad4)
2025-12-01 23:28:09 +01:00
ivonaest 0008106adb Upgrade json-path to 2.10.0
Closes gh-35924
Signed-off-by: ivonaest <ivona.cvija@est.tech>
2025-12-01 15:36:44 +01:00
Juergen Hoeller 8f6d44a86b Improve debug log for received message
(cherry picked from commit 71d18ebabc)
2025-12-01 15:04:42 +01:00
Juergen Hoeller 874498272f Clear remaining invoker resources when releasing shared Connection
Closes gh-35932

(cherry picked from commit 3ccb0786db)
2025-12-01 15:04:39 +01:00
rstoyanchev 2144813bad FragmentsRendering exposes its fragments
Closes gh-35775
2025-12-01 11:20:43 +00:00
John Niang 67a92306f7 BindingContext constructor uses given ReactiveAdapterRegistry arg
Closes gh-35771

Signed-off-by: John Niang <johnniang@foxmail.com>
2025-12-01 11:20:29 +00:00
rstoyanchev bc0731891b Use channelId for ReactorNettyWebSocketSession's id
Closes gh-35883
2025-12-01 11:20:13 +00:00
Juergen Hoeller 3faa7cac4d Polishing 2025-11-30 11:41:24 +01:00
Sam Brannen 3121daf553 Stop linking to 6.1.22-SNAPSHOT reference docs
With this commit, we now include snapshots for main (which currently
correlates to 7.0.x), 6.2.x, and 7.0.x to 9.*.x.

Closes gh-35923

(cherry picked from commit 305a512a55)
2025-11-28 14:06:48 +01:00
Juergen Hoeller 821165488e Use concurrent set for reactive transaction synchronizations
Closes gh-35921

(cherry picked from commit fd25e2f468)
2025-11-28 10:25:54 +01:00
Juergen Hoeller 3b556ba0c0 Polishing 2025-11-27 16:10:56 +01:00
Juergen Hoeller b25f98374b Tighten cacheable decision behind @Lazy injection point
Closes gh-35917

(cherry picked from commit 61d5413c23)
2025-11-27 16:03:27 +01:00
Sam Brannen c2f7cd3401 Convert sentence to tip in Kotlin testing chapter
This commit also moves the text to a more appropriate section of the
chapter.

(cherry picked from commit 24d152cdab)
2025-11-27 12:38:55 +01:00
Juergen Hoeller 22d2810ed0 Narrow method annotation check in hasQualifier to setter methods
Closes gh-35908

(cherry picked from commit 6c3132cb8c)
2025-11-26 23:02:58 +01:00
Tran Ngoc Nhan b39055f293 Fix broken Javadoc links to methods
Closes gh-35904
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-11-26 17:12:18 +01:00
Sam Brannen 140933400d Refer to "Spring Tools" instead of "Spring Tools for Eclipse"
Closes gh-35901

(cherry picked from commit 6504177e7b)
2025-11-26 16:12:27 +01:00
Juergen Hoeller e7a5452a14 Consistent namespace element declarations
(cherry picked from commit f58d0f6aae)
2025-11-26 15:14:31 +01:00
Juergen Hoeller ab96576e67 Expose non-existent resources at the end of the sorted result
Closes gh-35895

(cherry picked from commit c1b6bfb681)
2025-11-26 15:14:28 +01:00
Juergen Hoeller 19b080b73f Clarify JMS sessionTransacted flag for local versus global transaction
Closes gh-35897

(cherry picked from commit 9d4abb63d8)
2025-11-26 15:14:24 +01:00
rstoyanchev f9b4fba97a Add required type to TypeMismatchException message args
Closes gh-35837
2025-11-26 12:50:26 +00:00
Sam Brannen 37e26e0377 Use current links to JUnit documentation
Closes gh-35892

(cherry picked from commit f4ee120a42)
2025-11-26 13:25:54 +01:00
Sam Brannen 85c47a73dd Fix formatting for backticks in Kotlin docs
(cherry picked from commit e625a28f6d)
2025-11-26 13:24:27 +01:00
Sam Brannen ab93020263 Link to Spring Framework Artifacts wiki page
This commit revises the Integration Testing chapter to reference the
"Spring Framework Artifacts" wiki page instead of the nonexistent
"Dependency Management" section of the reference manual.

Closes gh-35890

(cherry picked from commit 45c1cd9295)
2025-11-26 12:55:46 +01:00
github-actions[bot] a4134663a6 Update Antora Spring UI to v0.4.25
Closes gh-35877

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-11-24 14:44:14 +01:00
github-actions[bot] a4b1155ca6 Update Antora Spring UI to v0.4.22
Closes gh-35859
2025-11-24 10:14:50 +01:00
Sébastien Deleuze 1bdd8337c6 Update outdated comments in JdbcOperationsExtensions.kt 2025-11-20 09:55:10 +01:00
Brian Clozel 7a0ea14452 Next development version (v6.2.15-SNAPSHOT) 2025-11-20 09:48:06 +01:00
Juergen Hoeller 59025c5b96 Lazily initialize ProblemDetail for picking up actual status code
Closes gh-35829

(cherry picked from commit 3026f0a49b)
2025-11-19 17:31:27 +01:00
Sam Brannen e5de8b9bc6 Fix link to MockMvc test in HtmlUnit section
See gh-35853

(cherry picked from commit 9fe4e7798d)
2025-11-19 17:19:34 +01:00
Sam Brannen e146e809e5 Polishing
(cherry picked from commit d178930186)
2025-11-19 17:19:34 +01:00
Tran Ngoc Nhan ff62e7355f Fix cross-reference links in HtmlUnit sections
Closes gh-35853

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>

(cherry picked from commit 91d2a51f3f)
2025-11-19 17:19:30 +01:00
Juergen Hoeller 4e97013595 Polishing
(cherry picked from commit f456674529)
2025-11-19 16:21:38 +01:00
Juergen Hoeller bd10b7ae06 Remove javadoc references to deprecated PropertiesBeanDefinitionReader
Closes gh-35836

(cherry picked from commit 35b8fbf901)
2025-11-19 16:21:33 +01:00
Juergen Hoeller e4288170c8 Fix getCacheNames() concurrent access in NoOpCacheManager
Closes gh-35842

(cherry picked from commit 57a1d4007b)
2025-11-18 13:50:13 +01:00
Juergen Hoeller 8545a759a7 Add resetCaches() method to Caffeine/ConcurrentMapCacheManager
Closes gh-35840

(cherry picked from commit bc3431f435)
2025-11-18 13:49:20 +01:00
Juergen Hoeller f94645de17 Narrow Aware interface exclusion check to BeanFactoryAware only
Closes gh-35835

(cherry picked from commit de5b9aab55)
2025-11-18 13:48:32 +01:00
Sam Brannen 8553f97df1 Merge HtmlCharacterEntityDecoderTests into HtmlUtilsTests
See gh-35711

(cherry picked from commit 0342cd0904)
2025-11-17 15:29:52 +01:00
Brian Clozel 030dace2be Polishing contribution
Closes gh-35477
2025-11-17 15:15:39 +01:00
potato 5af1c9b487 Fix HtmlUtils unescape for supplementary chars
See gh-35477

Signed-off-by: potato <65760583+juntae6942@users.noreply.github.com>
2025-11-17 15:15:39 +01:00
Patrick Strawderman f3ed04c9d7 Fix single-check idiom in UnmodifiableMultiValueMap
Read the respective fields only once in the values(), entrySet(), and
keySet() methods.

Closes gh-35822

Signed-off-by: Patrick Strawderman <pstrawderman@netflix.com>

(cherry picked from commit 3b6be3d4d3)
2025-11-17 15:04:49 +01:00
github-actions[bot] 46ee944acc Update Antora Spring UI to v0.4.20
Closes gh-35814
2025-11-17 14:22:35 +01:00
Sam Brannen 6be1c29fda Polish contribution
See gh-35817

(cherry picked from commit 09a8bbc0c7)
2025-11-17 12:28:03 +01:00
Patrick Strawderman 3fa56db88b Fix Spliterator characteristics in ConcurrentReferenceHashMap
The Spliterators returned by values, entrySet, and keySet incorrectly
reported the SIZED characteristic, instead of CONCURRENT. This could
lead to bugs when the map is concurrently modified during a stream
operation.

For keySet and values, the incorrect characteristics are inherited from
AbstractMap, so to rectify that the respective methods are overridden,
and custom collections are provided that report the correct Spliterator
characteristics.

Closes gh-35817

Signed-off-by: Patrick Strawderman <pstrawderman@netflix.com>

(cherry picked from commit ed75906834)
2025-11-17 12:27:43 +01:00
121 changed files with 1203 additions and 496 deletions
+4 -2
View File
@@ -13,7 +13,9 @@ content:
- url: https://github.com/spring-projects/spring-framework
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
branches: ['main', '{6..9}.+({1..9}).x']
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
start_path: framework-docs
asciidoc:
@@ -36,4 +38,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
@@ -50,9 +50,8 @@ XML configuration:
The preceding XML is more succinct. However, typos are discovered at runtime rather than
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
that supports automatic property completion when you create bean definitions. Such IDE
assistance is highly recommended.
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
completion when you create bean definitions. Such IDE assistance is highly recommended.
You can also configure a `java.util.Properties` instance, as follows:
@@ -339,11 +339,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
modularity, but determining exactly where the autowired bean definitions are declared is
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
explicit in the code, and this may be just fine. Remember that the
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
can render graphs showing how everything is wired, which may be all you need. Also,
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
and quickly show you the location of `@Bean` methods that return that type.
explicit in the code, and this may be just fine. Note that the
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
showing how everything is wired, which may be all you need. Also, your Java IDE can
easily find all declarations and uses of the `AccountRepository` type and quickly show
you the location of `@Bean` methods that return that type.
In cases where this ambiguity is not acceptable and you wish to have direct navigation
from within your IDE from one `@Configuration` class to another, consider autowiring the
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
Language] but offers additional features, most notably method invocation and basic string
templating functionality.
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
EL, to name a few -- the Spring Expression Language was created to provide the Spring
community with a single well supported expression language that can be used across all
the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
That said, SpEL is based on a technology-agnostic API that lets other expression language
implementations be integrated, should the need arise.
While there are several other Java expression languages available -- OGNL, MVEL, and
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
Spring community with a single well supported expression language that can be used across
all the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements for
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
is based on a technology-agnostic API that lets other expression language implementations
be integrated, should the need arise.
While SpEL serves as the foundation for expression evaluation within the Spring
portfolio, it is not directly tied to Spring and can be used independently. To
@@ -319,9 +319,17 @@ progresses.
== Testing
This section addresses testing with the combination of Kotlin and Spring Framework.
The recommended testing framework is https://junit.org/junit5/[JUnit 5] along with
The recommended testing framework is https://junit.org/[JUnit Jupiter] along with
https://mockk.io/[Mockk] for mocking.
[TIP]
====
Kotlin lets you specify meaningful test function names between backticks (```).
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
in this section.
====
NOTE: If you are using Spring Boot, see
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
@@ -352,7 +360,6 @@ file with a `spring.test.constructor.autowire.mode = all` property.
[[per_class-lifecycle]]
=== `PER_CLASS` Lifecycle
Kotlin lets you specify meaningful test function names between backticks (```).
With JUnit Jupiter (JUnit 5), Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
@@ -187,7 +187,7 @@ default mode may be set via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
The default mode may also be configured as a
https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
https://docs.junit.org/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
If the `spring.test.constructor.autowire.mode` property is not set, test class
constructors will not be automatically autowired.
@@ -5,24 +5,25 @@ It is important to be able to perform some integration testing without requiring
deployment to your application server or connecting to other enterprise infrastructure.
Doing so lets you test things such as:
* The correct wiring of your Spring IoC container contexts.
* Data access using JDBC or an ORM tool. This can include such things as the correctness
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
* The correct wiring of your Spring components.
* Data access using JDBC or an ORM tool.
** This can include such things as the correctness of SQL statements, Hibernate queries,
JPA entity mappings, and so forth.
The Spring Framework provides first-class support for integration testing in the
`spring-test` module. The name of the actual JAR file might include the release version
and might also be in the long `org.springframework.test` form, depending on where you get
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
for an explanation). This library includes the `org.springframework.test` package, which
`spring-test` module. The name of the actual JAR file might include the release version,
depending on where you get it from (see the
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
for details). This library includes the `org.springframework.test` package, which
contains valuable classes for integration testing with a Spring container. This testing
does not rely on an application server or other deployment environment. Such tests are
slower to run than unit tests but much faster than the equivalent Selenium tests or
remote tests that rely on deployment to an application server.
Unit and integration testing support is provided in the form of the annotation-driven
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
agnostic of the actual testing framework in use, which allows instrumentation of tests
in various environments, including JUnit, TestNG, and others.
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
framework is agnostic of the actual testing framework in use, which allows
instrumentation of tests in various environments, including JUnit, TestNG, and others.
The following section provides an overview of the high-level goals of Spring's
integration support, and the rest of this chapter then focuses on dedicated topics:
@@ -8,9 +8,9 @@ use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
== Why Geb and MockMvc?
Geb is backed by WebDriver, so it offers many of the
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[same benefits] that we get from
WebDriver. However, Geb makes things even easier by taking care of some of the
boilerplate code for us.
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[same benefits]
that we get from WebDriver. However, Geb makes things even easier by taking care of some
of the boilerplate code for us.
[[mockmvc-server-htmlunit-geb-setup]]
== MockMvc and Geb Setup
@@ -28,7 +28,8 @@ def setup() {
----
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
usage, see
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
This ensures that any URL referencing `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
@@ -62,10 +63,10 @@ forwarded to the current page object. This removes a lot of the boilerplate code
needed when using WebDriver directly.
As with direct WebDriver usage, this improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] by using the Page Object
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
`CreateMessagePage` implementation:
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
by using the Page Object Pattern. As mentioned previously, we can use the Page Object
Pattern with HtmlUnit and WebDriver, but it is even easier with Geb. Consider our new
Groovy-based `CreateMessagePage` implementation:
[source,groovy]
----
@@ -7,8 +7,7 @@ to use the raw HtmlUnit libraries.
[[mockmvc-server-htmlunit-mah-setup]]
== MockMvc and HtmlUnit Setup
First, make sure that you have included a test dependency on
`org.htmlunit:htmlunit`.
First, make sure that you have included a test dependency on `org.htmlunit:htmlunit`.
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
`MockMvcWebClientBuilder`, as follows:
@@ -45,7 +44,7 @@ Kotlin::
======
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
This ensures that any URL that references `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
@@ -77,7 +76,7 @@ Kotlin::
======
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
to create a message, as the following example shows:
@@ -144,10 +143,10 @@ Kotlin::
======
The preceding code improves on our
xref:testing/mockmvc/htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways.
First, we no longer have to explicitly verify our form and then create a request that
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
significantly reducing the overhead.
xref:testing/mockmvc/htmlunit/why.adoc#mockmvc-server-htmlunit-why[MockMvc test] in a
number of ways. First, we no longer have to explicitly verify our form and then create a
request that looks like the form. Instead, we request the form, fill it out, and submit
it, thereby significantly reducing the overhead.
Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
@@ -203,7 +203,7 @@ Kotlin::
======
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
usage, see <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
The preceding example ensures that any URL that references `localhost` as the server is
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
@@ -259,10 +259,11 @@ Kotlin::
======
--
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
This improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
by leveraging the Page Object Pattern. As we mentioned in
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
with HtmlUnit, but it is much easier with WebDriver. Consider the following
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
HtmlUnit, but it is much easier with WebDriver. Consider the following
`CreateMessagePage` implementation:
--
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
</form>
----
How do we ensure that our form produce the correct request to create a new message? A
How do we ensure that our form produces the correct request to create a new message? A
naive attempt might resemble the following:
[tabs]
@@ -154,7 +154,7 @@ validation.
[[mockmvc-server-htmlunit-why-integration]]
== Integration Testing to the Rescue?
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
To resolve the issues mentioned above, we could perform end-to-end integration testing,
but this has some drawbacks. Consider testing the view that lets us page through the
messages. We might need the following tests:
@@ -171,7 +171,7 @@ leads to a number of additional challenges:
* Testing can become slow, since each test would need to ensure that the database is in
the correct state.
* Since our database needs to be in a specific state, we cannot run tests in parallel.
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
* Performing assertions on items such as auto-generated IDs, timestamps, and others can
be difficult.
These challenges do not mean that we should abandon end-to-end integration testing
@@ -22,7 +22,7 @@ TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
* Powerful support for link:https://docs.junit.org/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
@@ -160,7 +160,7 @@ for further details.
=== Dependency Injection with the `SpringExtension`
The `SpringExtension` implements the
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
link:https://docs.junit.org/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
constructors, test methods, and test lifecycle callback methods.
@@ -362,7 +362,7 @@ of `PlatformTransactionManager` within the test's `ApplicationContext`, you can
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
`@Configuration` class. Consult the
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager(org.springframework.test.context.TestContext,java.lang.String)[javadoc
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
@@ -75,7 +75,7 @@ infrastructure and controller declarations and use it to handle requests via moc
and response objects, without a running server.
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext(org.springframework.context.ApplicationContext)[WebHttpHandlerBuilder]
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
requests:
@@ -44,7 +44,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the WebFlux Java configuration to declare such mappings, which results in a single,
global map passed to all `HandlerMapping` implementations.
@@ -57,7 +57,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
@@ -114,6 +114,6 @@ and others) and is equivalent to `required=false`.
| Any other argument
| If a method argument is not matched to any of the above, it is, by default, resolved as
a `@RequestParam` if it is a simple type, as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
or as a `@ModelAttribute`, otherwise.
|===
@@ -205,7 +205,7 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
@@ -74,6 +74,6 @@ When a `@RequestParam` annotation is declared on a `Map<String, String>` or
Note that use of `@RequestParam` is optional -- for example, to set its attributes. By
default, any argument that is a simple value type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
and is not resolved by any other argument resolver is treated as if it were annotated
with `@RequestParam`.
@@ -87,6 +87,6 @@ Reactor provides a dedicated operator for that, `Flux#collectList()`.
| Other return values
| If a return value remains unresolved in any other way, it is treated as a model
attribute, unless it is a simple type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
in which case it remains unresolved.
|===
@@ -207,7 +207,7 @@ was not provided (for example, model attribute was returned) or an async return
view resolution scenarios. Explore the options in your IDE with code completion.
* `Model`, `Map`: Extra model attributes to be added to the model for the request.
* Any other: Any other return value (except for simple types, as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
is treated as a model attribute to be added to the model. The attribute name is derived
from the class name by using {spring-framework-api}/core/Conventions.html[conventions],
unless a handler method `@ModelAttribute` annotation is present.
@@ -71,7 +71,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the MVC Java configuration or the XML namespace to declare such mappings, which results
in a single global map being passed to all `HandlerMapping` instances.
@@ -84,7 +84,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, for example, `allowCredentials` and `maxAge`, the local overrides the global value. See
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
@@ -175,7 +175,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
| `TypeMismatchException`
| (default)
| `+{0}+` property name, `+{1}+` property value
| `+{0}+` property name, `+{1}+` property value, `+{2}+` simple name of required type
| `UnsatisfiedServletRequestParameterException`
| (default)
@@ -215,7 +215,7 @@ the content negotiation during the error handling phase will decide which conten
| Any other return value
| If a return value is not matched to any of the above and is not a simple type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
by default, it is treated as a model attribute to be added to the model. If it is a simple type,
it remains unresolved.
|===
@@ -135,6 +135,6 @@ and others) and is equivalent to `required=false`.
| Any other argument
| If a method argument is not matched to any of the earlier values in this table and it is
a simple type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
it is resolved as a `@RequestParam`. Otherwise, it is resolved as a `@ModelAttribute`.
|===
@@ -250,7 +250,7 @@ xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
@@ -117,6 +117,6 @@ Kotlin::
Note that use of `@RequestParam` is optional (for example, to set its attributes).
By default, any argument that is a simple value type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
and is not resolved by any other argument resolver, is treated as if it were annotated
with `@RequestParam`.
@@ -98,6 +98,6 @@ supported for all return values.
| Other return values
| If a return value remains unresolved in any other way, it is treated as a model
attribute, unless it is a simple type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
in which case it remains unresolved.
|===
@@ -221,7 +221,7 @@ When multiple patterns match a URL, the best match must be selected. This is don
one of the following depending on whether use of parsed `PathPattern` is enabled for use or not:
* {spring-framework-api}/web/util/pattern/PathPattern.html#SPECIFICITY_COMPARATOR[`PathPattern.SPECIFICITY_COMPARATOR`]
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator-java.lang.String-[`AntPathMatcher.getPatternComparator(String path)`]
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator(java.lang.String)[`AntPathMatcher.getPatternComparator(String path)`]
Both help to sort patterns with more specific ones on top. A pattern is more specific if
it has a lower count of URI variables (counted as 1), single wildcards (counted as 1),
+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.14.2",
"@springio/antora-extensions": "1.14.7",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
+3 -3
View File
@@ -8,10 +8,10 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
api(platform("io.micrometer:micrometer-bom:1.14.13"))
api(platform("io.micrometer:micrometer-bom:1.14.14"))
api(platform("io.netty:netty-bom:4.1.128.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.12"))
api(platform("io.projectreactor:reactor-bom:2024.0.13"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
@@ -33,7 +33,7 @@ dependencies {
api("com.google.code.gson:gson:2.13.2")
api("com.google.protobuf:protobuf-java-util:4.32.1")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.jayway.jsonpath:json-path:2.10.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
api("com.rometools:rome:1.19.0")
api("com.squareup.okhttp3:mockwebserver:3.14.9")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.14-SNAPSHOT
version=6.2.15
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
@@ -376,9 +376,12 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
}
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
for (Annotation annotation : methodParam.getMethodAnnotations()) {
if (isQualifier(annotation.annotationType())) {
return true;
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
for (Annotation annotation : methodParam.getMethodAnnotations()) {
if (isQualifier(annotation.annotationType())) {
return true;
}
}
}
}
@@ -32,7 +32,6 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Rob Harrop
* @since 1.1
* @see PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
*/
public abstract class BeanDefinitionReaderUtils {
@@ -43,7 +43,6 @@ import org.springframework.core.AliasRegistry;
* @see DefaultListableBeanFactory
* @see org.springframework.context.support.GenericApplicationContext
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see PropertiesBeanDefinitionReader
*/
public interface BeanDefinitionRegistry extends AliasRegistry {
@@ -585,7 +585,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
public void registerContainedBean(String containedBeanName, String containingBeanName) {
synchronized (this.containedBeanMap) {
Set<String> containedBeans =
this.containedBeanMap.computeIfAbsent(containingBeanName, k -> new LinkedHashSet<>(8));
this.containedBeanMap.computeIfAbsent(containingBeanName, key -> new LinkedHashSet<>(8));
if (!containedBeans.add(containedBeanName)) {
return;
}
@@ -604,7 +604,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
this.dependentBeanMap.computeIfAbsent(canonicalName, key -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
@@ -612,7 +612,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, key -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
@@ -74,10 +74,10 @@ import org.springframework.util.StringUtils;
* @author Rob Harrop
* @since 26.11.2003
* @see DefaultListableBeanFactory
* @deprecated as of 5.3, in favor of Spring's common bean definition formats
* and/or custom reader implementations
* @deprecated in favor of Spring's common bean definition formats and/or
* custom BeanDefinitionReader implementations
*/
@Deprecated
@Deprecated(since = "5.3")
public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
/**
@@ -2112,8 +2112,8 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -2724,6 +2724,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("nonNullBean", "Test");
bf.registerBeanDefinition("mixedNullableInjectionBean",
new RootBeanDefinition(MixedNullableInjectionBean.class));
MixedNullableInjectionBean mixedNullableInjectionBean = bf.getBean(MixedNullableInjectionBean.class);
assertThat(mixedNullableInjectionBean.nonNullBean).isNotNull();
assertThat(mixedNullableInjectionBean.nullableBean).isNull();
@@ -2734,6 +2735,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("nonNullBean", "Test");
bf.registerBeanDefinition("mixedOptionalInjectionBean",
new RootBeanDefinition(MixedOptionalInjectionBean.class));
MixedOptionalInjectionBean mixedOptionalInjectionBean = bf.getBean(MixedOptionalInjectionBean.class);
assertThat(mixedOptionalInjectionBean.nonNullBean).isNotNull();
assertThat(mixedOptionalInjectionBean.nullableBean).isNull();
@@ -444,8 +444,8 @@ class InjectAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -788,7 +788,6 @@ class InjectAnnotationBeanPostProcessorTests {
private ConfigurableListableBeanFactory beanFactory;
public ConstructorResourceInjectionBean() {
throw new UnsupportedOperationException();
}
@@ -77,7 +77,7 @@ public class CaffeineCacheManager implements CacheManager {
private boolean allowNullValues = true;
private boolean dynamic = true;
private volatile boolean dynamic = true;
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
@@ -102,10 +102,15 @@ public class CaffeineCacheManager implements CacheManager {
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
* <p>The number of caches and their names will be fixed after a call
* to this method, with no creation of further cache regions at runtime.
* <p>Note that this method replaces existing caches of the given names
* and prevents the creation of further cache regions from here on - but
* does <i>not</i> remove unrelated existing caches. For a full reset,
* consider calling {@link #resetCaches()} before calling this method.
* <p>Calling this method with a {@code null} collection argument resets
* the mode to 'dynamic', allowing for further creation of caches again.
* @see #resetCaches()
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -245,11 +250,6 @@ public class CaffeineCacheManager implements CacheManager {
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
@Nullable
public Cache getCache(String name) {
@@ -260,6 +260,33 @@ public class CaffeineCacheManager implements CacheManager {
return cache;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
/**
* Reset this cache manager's caches, removing them completely for on-demand
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
* @since 6.2.14
*/
public void resetCaches() {
this.cacheMap.values().forEach(Cache::clear);
if (this.dynamic) {
this.cacheMap.keySet().retainAll(this.customCacheNames);
}
}
/**
* 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);
}
/**
* Register the given native Caffeine Cache instance with this cache manager,
@@ -303,16 +330,6 @@ 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}
@@ -25,7 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
@@ -98,8 +98,8 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
return null;
}
@@ -24,7 +24,6 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec;
import org.junit.jupiter.api.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import static org.assertj.core.api.Assertions.assertThat;
@@ -42,7 +41,7 @@ class CaffeineCacheManagerTests {
@Test
@SuppressWarnings("cast")
void dynamicMode() {
CacheManager cm = new CaffeineCacheManager();
CaffeineCacheManager cm = new CaffeineCacheManager();
Cache cache1 = cm.getCache("c1");
assertThat(cache1).isInstanceOf(CaffeineCache.class);
@@ -76,6 +75,14 @@ class CaffeineCacheManagerTests {
cache1.evict("key3");
assertThat(cache1.get("key3", () -> (String) null)).isNull();
assertThat(cache1.get("key3", () -> (String) null)).isNull();
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isSameAs(cache2);
cm.resetCaches();
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
}
@Test
@@ -131,11 +138,24 @@ class CaffeineCacheManagerTests {
cm.setAllowNullValues(true);
Cache cache1y = cm.getCache("c1");
Cache cache2y = cm.getCache("c2");
cache1y.put("key3", null);
assertThat(cache1y.get("key3").get()).isNull();
cache1y.evict("key3");
assertThat(cache1y.get("key3")).isNull();
cache2y.put("key4", "value4");
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.resetCaches();
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4")).isNull();
}
@Test
@@ -51,8 +51,7 @@ class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCacheCache>
this.cacheManager.createCache(CACHE_NAME_NO_NULL, new MutableConfiguration<>());
this.nativeCache = this.cacheManager.getCache(CACHE_NAME);
this.cache = new JCacheCache(this.nativeCache);
Cache<Object, Object> nativeCacheNoNull =
this.cacheManager.getCache(CACHE_NAME_NO_NULL);
Cache<Object, Object> nativeCacheNoNull = this.cacheManager.getCache(CACHE_NAME_NO_NULL);
this.cacheNoNull = new JCacheCache(nativeCacheNoNull, false);
}
@@ -54,7 +54,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private boolean dynamic = true;
private volatile boolean dynamic = true;
private boolean allowNullValues = true;
@@ -82,10 +82,15 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
* <p>The number of caches and their names will be fixed after a call
* to this method, with no creation of further cache regions at runtime.
* <p>Note that this method replaces existing caches of the given names
* and prevents the creation of further cache regions from here on - but
* does <i>not</i> remove unrelated existing caches. For a full reset,
* consider calling {@link #resetCaches()} before calling this method.
* <p>Calling this method with a {@code null} collection argument resets
* the mode to 'dynamic', allowing for further creation of caches again.
* @see #resetCaches()
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -160,11 +165,6 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
@Nullable
public Cache getCache(String name) {
@@ -175,6 +175,23 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
return cache;
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
/**
* Reset this cache manager's caches, removing them completely for on-demand
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
* @since 6.2.14
*/
public void resetCaches() {
this.cacheMap.values().forEach(Cache::clear);
if (this.dynamic) {
this.cacheMap.clear();
}
}
/**
* Remove the specified cache from this cache manager.
* @param name the name of the cache
@@ -113,7 +113,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
builder.setUnless(getAttributeValue(opElement, "unless", ""));
builder.setSync(Boolean.parseBoolean(getAttributeValue(opElement, "sync", "false")));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -136,7 +136,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
builder.setBeforeInvocation(Boolean.parseBoolean(after.trim()));
}
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -150,7 +150,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
parserContext.getReaderContext(), new CachePutOperation.Builder());
builder.setUnless(getAttributeValue(opElement, "unless", ""));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -27,7 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -140,8 +140,8 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
return null;
}
@@ -18,8 +18,6 @@ package org.springframework.cache.support;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -32,45 +30,29 @@ import org.springframework.lang.Nullable;
* for disabling caching, typically used for backing cache declarations
* without an actual backing store.
*
* <p>Will simply accept any items into the cache not actually storing them.
* <p>This implementation will simply accept any items into the cache,
* not actually storing them.
*
* @author Costin Leau
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 3.1
* @see NoOpCache
*/
public class NoOpCacheManager implements CacheManager {
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(16);
private final Set<String> cacheNames = new LinkedHashSet<>(16);
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
/**
* This implementation always returns a {@link Cache} implementation that will not store items.
* Additionally, the request cache will be remembered by the manager for consistency.
*/
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.caches.get(name);
if (cache == null) {
this.caches.computeIfAbsent(name, NoOpCache::new);
synchronized (this.cacheNames) {
this.cacheNames.add(name);
}
}
return this.caches.get(name);
return this.cacheMap.computeIfAbsent(name, NoOpCache::new);
}
/**
* This implementation returns the name of the caches previously requested.
*/
@Override
public Collection<String> getCacheNames() {
synchronized (this.cacheNames) {
return Collections.unmodifiableSet(this.cacheNames);
}
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
}
@@ -172,22 +172,25 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
}
}
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)) {
boolean cacheable = false;
if (!autowiredBeanNames.isEmpty()) {
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;
}
}
if (cacheable) {
this.cachedTarget = target;
}
return target;
@@ -630,7 +630,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
finishRefresh();
}
catch (RuntimeException | Error ex ) {
catch (RuntimeException | Error ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
@@ -227,7 +227,6 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
* @param beanFactory the bean factory to load bean definitions into
* @throws BeansException if parsing of the bean definitions failed
* @throws IOException if loading of bean definition files failed
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
@@ -78,8 +78,6 @@ import org.springframework.util.Assert;
* GenericApplicationContext ctx = new GenericApplicationContext();
* XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
* xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
* PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
* propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
* ctx.refresh();
*
* MyBean myBean = (MyBean) ctx.getBean("myBean");
@@ -101,7 +99,6 @@ import org.springframework.util.Assert;
* @see #registerBeanDefinition
* @see #refresh()
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
*/
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
@@ -383,7 +383,7 @@ public class ScheduledAnnotationBeanPostProcessor
try {
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
this.registrar::getObservationRegistry,
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
this.reactiveSubscriptions.computeIfAbsent(bean, key -> new CopyOnWriteArrayList<>()));
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
@@ -19,7 +19,6 @@ package org.springframework.cache.concurrent;
import org.junit.jupiter.api.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,7 +30,7 @@ class ConcurrentMapCacheManagerTests {
@Test
void testDynamicMode() {
CacheManager cm = new ConcurrentMapCacheManager();
ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager();
Cache cache1 = cm.getCache("c1");
assertThat(cache1).isInstanceOf(ConcurrentMapCache.class);
Cache cache1again = cm.getCache("c1");
@@ -65,6 +64,14 @@ class ConcurrentMapCacheManagerTests {
assertThat(cache1.get("key3").get()).isNull();
cache1.evict("key3");
assertThat(cache1.get("key3")).isNull();
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isSameAs(cache2);
cm.resetCaches();
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
}
@Test
@@ -107,11 +114,24 @@ class ConcurrentMapCacheManagerTests {
cm.setAllowNullValues(true);
Cache cache1y = cm.getCache("c1");
Cache cache2y = cm.getCache("c2");
cache1y.put("key3", null);
assertThat(cache1y.get("key3").get()).isNull();
cache1y.evict("key3");
assertThat(cache1y.get("key3")).isNull();
cache2y.put("key4", "value4");
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.removeCache("c1");
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
cm.resetCaches();
assertThat(cm.getCache("c1")).isNull();
assertThat(cm.getCache("c2")).isSameAs(cache2y);
assertThat(cache2y.get("key4")).isNull();
}
@Test
@@ -19,6 +19,7 @@ package org.springframework.context.annotation.configuration;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -29,6 +30,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@@ -100,6 +102,16 @@ class AutowiredConfigurationTests {
context.close();
}
@Test
void testAutowiredConfigurationMethodDependenciesWithQualifier() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
QualifiedAutowiredMethodConfig.class);
assertThat(context.getBeansOfType(Colour.class)).isEmpty();
assertThat(context.getBean(TestBean.class).getName()).isEmpty();
context.close();
}
@Test
void testAutowiredSingleConstructorSupported() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
@@ -297,6 +309,25 @@ class AutowiredConfigurationTests {
}
@Configuration
static class QualifiedAutowiredMethodConfig {
@Bean
@Qualifier("testBean")
public TestBean testBean(Optional<Colour> colour, Optional<List<Colour>> colours) {
if (!colour.isEmpty() || !colours.isEmpty()) {
throw new IllegalStateException("Unexpected match: " + colour + " " + colours);
}
return new TestBean("");
}
@Bean
public List<?> someList() {
return Collections.singletonList(new TestBean("shouldNotMatch"));
}
}
@Configuration
static class AutowiredConstructorConfig {
@@ -29,6 +29,7 @@ class ApplicationContextLifecycleTests {
@Test
void beansStart() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
@@ -39,12 +40,14 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.isRunning()).as(error).isTrue();
assertThat(bean3.isRunning()).as(error).isTrue();
assertThat(bean4.isRunning()).as(error).isTrue();
context.close();
}
@Test
void beansStop() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
@@ -55,18 +58,21 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.isRunning()).as(startError).isTrue();
assertThat(bean3.isRunning()).as(startError).isTrue();
assertThat(bean4.isRunning()).as(startError).isTrue();
context.stop();
String stopError = "bean was not stopped";
assertThat(bean1.isRunning()).as(stopError).isFalse();
assertThat(bean2.isRunning()).as(stopError).isFalse();
assertThat(bean3.isRunning()).as(stopError).isFalse();
assertThat(bean4.isRunning()).as(stopError).isFalse();
context.close();
}
@Test
void startOrder() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
@@ -81,12 +87,14 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.getStartOrder()).as(orderError).isGreaterThan(bean1.getStartOrder());
assertThat(bean3.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder());
assertThat(bean4.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder());
context.close();
}
@Test
void stopOrder() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
context.stop();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
@@ -102,6 +110,7 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.getStopOrder()).as(orderError).isLessThan(bean1.getStopOrder());
assertThat(bean3.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder());
assertThat(bean4.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder());
context.close();
}
@@ -22,7 +22,7 @@ public class CustomizerRegistry {
Class<? extends KeyFactoryCustomizer> klass = customizer.getClass();
for (Class type : customizerTypes) {
if (type.isAssignableFrom(klass)) {
List<KeyFactoryCustomizer> list = customizers.computeIfAbsent(type, k -> new ArrayList<>());
List<KeyFactoryCustomizer> list = customizers.computeIfAbsent(type, key -> new ArrayList<>());
list.add(customizer);
}
}
@@ -28,7 +28,6 @@ import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the
@@ -114,8 +113,8 @@ public final class BridgeMethodResolver {
if (bridgedMethod == null) {
// Gather all methods with matching name and parameter size.
List<Method> candidateMethods = new ArrayList<>();
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter);
ReflectionUtils.doWithMethods(userClass, candidateMethods::add,
candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
if (!candidateMethods.isEmpty()) {
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod, targetClass));
@@ -152,9 +151,6 @@ public final class BridgeMethodResolver {
private static Method searchCandidates(
List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
if (candidateMethods.isEmpty()) {
return null;
}
Method previousMethod = null;
boolean sameSig = true;
for (Method candidateMethod : candidateMethods) {
@@ -204,19 +200,17 @@ public final class BridgeMethodResolver {
}
private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> clazz) {
// First, compare return type.
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(genericMethod, clazz);
if (!ClassUtils.resolvePrimitiveIfNecessary(genericReturnType.toClass()).isAssignableFrom(
ClassUtils.resolvePrimitiveIfNecessary(candidateMethod.getReturnType()))) {
return false;
}
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
for (int i = 0; i < candidateParameters.length; i++) {
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz);
Class<?> candidateParameter = candidateParameters[i];
if (candidateParameter.isArray()) {
// An array type: compare the component type.
if (!candidateParameter.componentType().equals(genericParameter.getComponentType().toClass())) {
return false;
}
}
// A non-array type: compare the type itself.
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(
ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
if (!ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()).equals(
ClassUtils.resolvePrimitiveIfNecessary(candidateParameters[i]))) {
return false;
}
}
@@ -112,9 +112,7 @@ final class AnnotationTypeMapping {
this.root = (source != null ? source.getRoot() : this);
this.distance = (source == null ? 0 : source.getDistance() + 1);
this.annotationType = annotationType;
this.metaTypes = merge(
source != null ? source.getMetaTypes() : null,
annotationType);
this.metaTypes = merge((source != null ? source.getMetaTypes() : null), annotationType);
this.annotation = annotation;
this.attributes = AttributeMethods.forAnnotationType(annotationType);
this.mirrorSets = new MirrorSets();
@@ -76,6 +76,7 @@ final class AnnotationTypeMappings {
private void addAllMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
while (!queue.isEmpty()) {
@@ -270,11 +271,19 @@ final class AnnotationTypeMappings {
*/
AnnotationTypeMappings get(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
AnnotationTypeMappings result = this.mappings.get(annotationType);
if (result != null) {
return result;
}
result = createMappings(annotationType, visitedAnnotationTypes);
AnnotationTypeMappings existing = this.mappings.putIfAbsent(annotationType, result);
return (existing != null ? existing : result);
}
private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType,
visitedAnnotationTypes);
}
@@ -894,8 +894,7 @@ public abstract class AnnotationUtils {
private static Map<String, DefaultValueHolder> getDefaultValues(
Class<? extends Annotation> annotationType) {
return defaultValuesCache.computeIfAbsent(annotationType,
AnnotationUtils::computeDefaultValues);
return defaultValuesCache.computeIfAbsent(annotationType, AnnotationUtils::computeDefaultValues);
}
private static Map<String, DefaultValueHolder> computeDefaultValues(
@@ -133,6 +133,14 @@ import org.springframework.util.Assert;
* please use standard Java reflection or Spring's {@link AnnotationUtils}
* for simple annotation retrieval purposes.
*
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
* its attributes references a {@link Class} or {@link Enum}
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
* annotation will not be accessible via the {@code MergedAnnotations} API.
* To assist with diagnosing such scenarios, you can set the log level for
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
* {@code INFO}, or {@code WARN}.
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
@@ -490,7 +490,7 @@ public class GenericConversionService implements ConfigurableConversionService {
}
private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) {
return this.converters.computeIfAbsent(convertiblePair, k -> new ConvertersForPair());
return this.converters.computeIfAbsent(convertiblePair, key -> new ConvertersForPair());
}
public void remove(Class<?> sourceType, Class<?> targetType) {
@@ -188,7 +188,7 @@ final class SubscriberInputStream extends InputStream implements Subscriber<Data
}
private void resume() {
if (this.parkedThread != READY) {
if (this.parkedThread.get() != READY) {
Object old = this.parkedThread.getAndSet(READY);
if (old != READY) {
LockSupport.unpark((Thread)old);
@@ -634,12 +634,26 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
// Handle absolute paths correctly: do not apply parent to absolute paths.
File pathFile = new File(path);
File candidate = (pathFile.isAbsolute() ? pathFile : new File(parent, path));
File pathFile = null;
boolean absolute = false;
if (path.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
try {
pathFile = new File(ResourceUtils.toURI(path));
absolute = true;
}
catch (URISyntaxException | IllegalArgumentException ex) {
// Fall back to plain String constructor below.
}
}
if (pathFile == null) {
pathFile = new File(path);
absolute = pathFile.isAbsolute();
}
File candidate = (absolute ? pathFile : new File(parent, path));
// For relative paths, enforce security check: must be under parent.
// For absolute paths, just verify file exists (matching JVM behavior).
if (candidate.isFile() && (pathFile.isAbsolute() ||
if (candidate.isFile() && (absolute ||
candidate.getCanonicalPath().contains(parent.getCanonicalPath()))) {
manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches));
}
@@ -323,8 +323,8 @@ public class SpringFactoriesLoader {
SpringFactoriesLoader.class.getClassLoader());
Map<String, Factories> factoriesCache = cache.computeIfAbsent(
resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
Factories factories = factoriesCache.computeIfAbsent(resourceLocation, key ->
new Factories(loadFactoriesResource(resourceClassLoader, resourceLocation)));
Factories factories = factoriesCache.computeIfAbsent(resourceLocation,
key -> new Factories(loadFactoriesResource(resourceClassLoader, resourceLocation)));
return new SpringFactoriesLoader(classLoader, factories.byType());
}
@@ -36,11 +36,19 @@ import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
/**
* Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
* or {@link MethodMetadata method}), in a form that does not necessarily require
* Defines access to the annotations on a specific {@link AnnotationMetadata class}
* or {@link MethodMetadata method}, in a form that does not necessarily require
* class loading of the types being inspected. Note, however, that classes for
* encountered annotations will be loaded.
*
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
* its attributes references a {@link Class} or {@link Enum}
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
* annotation will not be accessible via the {@code AnnotatedTypeMetadata} API.
* To assist with diagnosing such scenarios, you can set the log level for
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
* {@code INFO}, or {@code WARN}.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Mark Pollack
@@ -25,9 +25,17 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
/**
* Interface that defines abstract access to the annotations of a specific
* Interface that defines abstract access to the annotations on a specific
* class, in a form that does not require that class to be loaded yet.
*
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
* its attributes references a {@link Class} or {@link Enum}
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
* annotation will not be accessible via the {@code AnnotationMetadata} API.
* To assist with diagnosing such scenarios, you can set the log level for
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
* {@code INFO}, or {@code WARN}.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Phillip Webb
@@ -17,9 +17,17 @@
package org.springframework.core.type;
/**
* Interface that defines abstract access to the annotations of a specific
* Interface that defines abstract access to the annotations on a specific
* method, in a form that does not require that method's class to be loaded yet.
*
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
* its attributes references a {@link Class} or {@link Enum}
* {@linkplain TypeNotPresentException that is not present in the classpath},
* that annotation will not be accessible via the {@code MethodMetadata} API.
* To assist with diagnosing such scenarios, you can set the log level for
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
* {@code INFO}, or {@code WARN}.
*
* @author Juergen Hoeller
* @author Mark Pollack
* @author Chris Beams
@@ -20,8 +20,10 @@ import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
@@ -29,6 +31,8 @@ import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -104,6 +108,18 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Nullable
private volatile Set<Map.Entry<K, V>> entrySet;
/**
* Late binding key set.
*/
@Nullable
private Set<K> keySet;
/**
* Late binding values collection.
*/
@Nullable
private Collection<V> values;
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
@@ -528,6 +544,26 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
return entrySet;
}
@Override
public Set<K> keySet() {
Set<K> keySet = this.keySet;
if (keySet == null) {
keySet = new KeySet();
this.keySet = keySet;
}
return keySet;
}
@Override
public Collection<V> values() {
Collection<V> values = this.values;
if (values == null) {
values = new Values();
this.values = values;
}
return values;
}
@Nullable
private <T> T doTask(@Nullable Object key, Task<T> task) {
int hash = getHash(key);
@@ -969,7 +1005,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
/**
* Internal entry-set implementation.
*/
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
@@ -1005,13 +1041,140 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
@Override
public Spliterator<Map.Entry<K, V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
}
}
/**
* Internal key-set implementation.
*/
private final class KeySet extends AbstractSet<K> {
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
@Override
public int size() {
return ConcurrentReferenceHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentReferenceHashMap.this.isEmpty();
}
@Override
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
@Override
public boolean contains(Object k) {
return ConcurrentReferenceHashMap.this.containsKey(k);
}
@Override
public Spliterator<K> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
}
}
/**
* Internal key iterator implementation.
*/
private final class KeyIterator implements Iterator<K> {
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public void remove() {
this.iterator.remove();
}
@Override
public K next() {
return this.iterator.next().getKey();
}
}
/**
* Internal values collection implementation.
*/
private final class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
@Override
public int size() {
return ConcurrentReferenceHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentReferenceHashMap.this.isEmpty();
}
@Override
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
@Override
public boolean contains(Object v) {
return ConcurrentReferenceHashMap.this.containsValue(v);
}
@Override
public Spliterator<V> spliterator() {
return Spliterators.spliterator(this, Spliterator.CONCURRENT);
}
}
/**
* Internal value iterator implementation.
*/
private final class ValueIterator implements Iterator<V> {
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public void remove() {
this.iterator.remove();
}
@Override
public V next() {
return this.iterator.next().getValue();
}
}
/**
* Internal entry iterator implementation.
*/
private class EntryIterator implements Iterator<Map.Entry<K, V>> {
private final class EntryIterator implements Iterator<Map.Entry<K, V>> {
private int segmentIndex;
@@ -145,26 +145,32 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
@Override
public Set<K> keySet() {
if (this.keySet == null) {
this.keySet = Collections.unmodifiableSet(this.delegate.keySet());
Set<K> keySet = this.keySet;
if (keySet == null) {
keySet = Collections.unmodifiableSet(this.delegate.keySet());
this.keySet = keySet;
}
return this.keySet;
return keySet;
}
@Override
public Set<Entry<K, List<V>>> entrySet() {
if (this.entrySet == null) {
this.entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
Set<Entry<K, List<V>>> entrySet = this.entrySet;
if (entrySet == null) {
entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
this.entrySet = entrySet;
}
return this.entrySet;
return entrySet;
}
@Override
public Collection<List<V>> values() {
if (this.values == null) {
this.values = new UnmodifiableValueCollection<>(this.delegate.values());
Collection<List<V>> values = this.values;
if (values == null) {
values = new UnmodifiableValueCollection<>(this.delegate.values());
this.values = values;
}
return this.values;
return values;
}
// unsupported
@@ -125,7 +125,7 @@ public class SimpleNamespaceContext implements NamespaceContext {
else {
this.prefixToNamespaceUri.put(prefix, namespaceUri);
Set<String> prefixes =
this.namespaceUriToPrefixes.computeIfAbsent(namespaceUri, k -> new LinkedHashSet<>());
this.namespaceUriToPrefixes.computeIfAbsent(namespaceUri, key -> new LinkedHashSet<>());
prefixes.add(prefix);
}
}
@@ -44,17 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@SuppressWarnings("rawtypes")
class BridgeMethodResolverTests {
private static Method findMethodWithReturnType(String name, Class<?> returnType, Class<SettingsDaoImpl> targetType) {
Method[] methods = targetType.getMethods();
for (Method m : methods) {
if (m.getName().equals(name) && m.getReturnType().equals(returnType)) {
return m;
}
}
return null;
}
@Test
void findBridgedMethod() throws Exception {
Method unbridged = MyFoo.class.getDeclaredMethod("someMethod", String.class, Object.class);
@@ -106,6 +95,32 @@ class BridgeMethodResolverTests {
assertThat(mostSpecificMethod).isSameAs(originalMethod);
}
@Test
void findBridgedMethodWithDefaultMethodInInterfaceHierarchy() throws Exception {
Method getValueDefault = DefaultMethods.class.getMethod("getValue");
Method getValuesDefault = DefaultMethods.class.getMethod("getValues");
Method getValueArgDefault = DefaultMethods.class.getMethod("getValue", Integer.class);
Method getValuesArgDefault = DefaultMethods.class.getMethod("getValues", Integer[].class);
for (Method method : ConcreteMethods.class.getMethods()) {
if (method.getName().equals("getValue")) {
assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo(
method.getParameterCount() > 0 ? getValueArgDefault : getValueDefault);
}
else if (method.getName().equals("getValues")) {
assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo(
method.getParameterCount() > 0 ? getValuesArgDefault : getValuesDefault);
}
}
}
@Test
void findBridgedMethodForCovariantReturnType() throws Exception {
Method originalMethod = OuterSubclass.class.getDeclaredMethod("getInner");
for (Method method: OuterSubclass.class.getDeclaredMethods()) {
assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo(originalMethod);
}
}
@Test
void findBridgedMethodInHierarchyWithBoundedGenerics() throws Exception {
Method originalMethod = Bar.class.getDeclaredMethod("someMethod", Object.class, Object.class);
@@ -161,6 +176,16 @@ class BridgeMethodResolverTests {
assertThat(BridgeMethodResolver.findBridgedMethod(loadWithSettingsReturn)).isEqualTo(method);
}
private static Method findMethodWithReturnType(String name, Class<?> returnType, Class<?> targetType) {
Method[] methods = targetType.getMethods();
for (Method m : methods) {
if (m.getName().equals(name) && m.getReturnType().equals(returnType)) {
return m;
}
}
return null;
}
@Test
void findBridgedMethodFromParent() throws Exception {
Method loadFromParentBridge = SettingsDaoImpl.class.getMethod("loadFromParent");
@@ -408,6 +433,7 @@ class BridgeMethodResolverTests {
public abstract static class SubBar<T extends StringProducer> extends InterBar<T> {
}
public interface StringProducer extends CharSequence {
}
@@ -451,6 +477,69 @@ class BridgeMethodResolverTests {
}
interface InterfaceMethods<T> {
T getValue();
T[] getValues();
T getValue(T arg);
T[] getValues(T[] args);
}
interface DefaultMethods extends InterfaceMethods<Integer> {
default Integer getValue() {
return 0;
}
default Integer[] getValues() {
return new Integer[0];
}
@Override
default Integer getValue(Integer arg) {
return 0;
}
@Override
default Integer[] getValues(Integer[] args) {
return new Integer[0];
}
}
static class ConcreteMethods implements DefaultMethods {
}
static class Outer {
Inner getInner() {
return new Inner();
}
}
static class OuterSubclass extends Outer {
@Override
InnerSubclass getInner() {
return new InnerSubclass();
}
}
static class Inner {
}
static class InnerSubclass extends Inner {
}
public static class Enclosing<T> {
public class Enclosed<S> {
@@ -844,22 +933,18 @@ class BridgeMethodResolverTests {
public interface MessageBroadcaster extends Receiver<MessageEvent> {
}
public static class RemovedMessageEvent extends MessageEvent {
}
public static class NewMessageEvent extends MessageEvent {
}
public static class ModifiedMessageEvent extends MessageEvent {
}
@@ -16,8 +16,7 @@
package org.springframework.util;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -25,6 +24,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
@@ -32,12 +33,13 @@ import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap.Entry;
import org.springframework.util.ConcurrentReferenceHashMap.Reference;
import org.springframework.util.ConcurrentReferenceHashMap.Restructure;
import org.springframework.util.comparator.Comparators;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link ConcurrentReferenceHashMap}.
@@ -47,8 +49,6 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
*/
class ConcurrentReferenceHashMapTests {
private static final Comparator<? super String> NULL_SAFE_STRING_SORT = Comparators.nullsLow();
private TestWeakConcurrentCache<Integer, String> map = new TestWeakConcurrentCache<>();
@@ -450,19 +450,174 @@ class ConcurrentReferenceHashMapTests {
assertThat(this.map.keySet()).isEqualTo(expected);
}
@Test // gh-35817
void keySetContains() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(this.map.keySet()).containsExactlyInAnyOrder(123, 456, null);
}
@Test // gh-35817
void keySetRemove() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(this.map.keySet().remove(123)).isTrue();
assertThat(this.map).doesNotContainKey(123);
assertThat(this.map.keySet().remove(123)).isFalse();
}
@Test // gh-35817
void keySetIterator() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Iterator<Integer> it = this.map.keySet().iterator();
assertThat(it).toIterable().containsExactlyInAnyOrder(123, 456, null);
assertThat(it).isExhausted();
}
@Test // gh-35817
void keySetIteratorRemove() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Iterator<Integer> keySetIterator = this.map.keySet().iterator();
while (keySetIterator.hasNext()) {
Integer key = keySetIterator.next();
if (key != null && key.equals(456)) {
keySetIterator.remove();
}
}
assertThat(this.map).containsOnlyKeys(123, null);
}
@Test // gh-35817
void keySetClear() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
this.map.keySet().clear();
assertThat(this.map).isEmpty();
assertThat(this.map.keySet()).isEmpty();
}
@Test // gh-35817
void keySetAdd() {
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.map.keySet().add(12345));
}
@Test // gh-35817
void keySetStream() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Set<Integer> keys = this.map.keySet().stream().collect(Collectors.toSet());
assertThat(keys).containsExactlyInAnyOrder(123, 456, null);
}
@Test // gh-35817
void keySetSpliteratorCharacteristics() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Spliterator<Integer> spliterator = this.map.keySet().spliterator();
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT, Spliterator.DISTINCT);
assertThat(spliterator.estimateSize()).isEqualTo(3L);
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
}
@Test
void valuesCollection() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
List<String> actual = new ArrayList<>(this.map.values());
List<String> expected = new ArrayList<>();
expected.add("123");
expected.add(null);
expected.add("789");
actual.sort(NULL_SAFE_STRING_SORT);
expected.sort(NULL_SAFE_STRING_SORT);
assertThat(actual).isEqualTo(expected);
assertThat(this.map.values()).containsExactlyInAnyOrder("123", null, "789");
}
@Test // gh-35817
void valuesCollectionAdd() {
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.map.values().add("12345"));
}
@Test // gh-35817
void valuesCollectionClear() {
Collection<String> values = this.map.values();
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(values).hasSize(3);
values.clear();
assertThat(values).isEmpty();
assertThat(this.map).isEmpty();
}
@Test // gh-35817
void valuesCollectionRemoval() {
Collection<String> values = this.map.values();
assertThat(values).isEmpty();
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
assertThat(values).containsExactlyInAnyOrder("123", null, "789");
values.remove(null);
assertThat(values).containsExactlyInAnyOrder("123", "789");
assertThat(map).containsOnly(entry(123, "123"), entry(null, "789"));
values.remove("123");
values.remove("789");
assertThat(values).isEmpty();
assertThat(map).isEmpty();
}
@Test // gh-35817
void valuesCollectionIterator() {
Iterator<String> iterator = this.map.values().iterator();
assertThat(iterator).isExhausted();
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
iterator = this.map.values().iterator();
assertThat(iterator).toIterable().containsExactlyInAnyOrder("123", null, "789");
}
@Test // gh-35817
void valuesCollectionIteratorRemoval() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Iterator<String> iterator = this.map.values().iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if (value != null && value.equals("789")) {
iterator.remove();
}
}
assertThat(iterator).isExhausted();
assertThat(this.map.values()).containsExactlyInAnyOrder("123", null);
assertThat(this.map).containsOnlyKeys(123, 456);
}
@Test // gh-35817
void valuesCollectionStream() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
List<String> values = this.map.values().stream().toList();
assertThat(values).containsExactlyInAnyOrder("123", null, "789");
}
@Test // gh-35817
void valuesCollectionSpliteratorCharacteristics() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
Spliterator<String> spliterator = this.map.values().spliterator();
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT);
assertThat(spliterator.estimateSize()).isEqualTo(3L);
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
}
@Test
@@ -541,6 +696,17 @@ class ConcurrentReferenceHashMapTests {
copy.forEach(entry -> assertThat(entrySet).doesNotContain(entry));
}
@Test // gh-35817
void entrySetSpliteratorCharacteristics() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
Spliterator<Map.Entry<Integer, String>> spliterator = this.map.entrySet().spliterator();
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT, Spliterator.DISTINCT);
assertThat(spliterator.estimateSize()).isEqualTo(3L);
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
}
@Test
void supportNullReference() {
// GC could happen during restructure so we must be able to create a reference for a null entry
@@ -72,20 +72,22 @@ public class SortedResourcesFactoryBean extends AbstractFactoryBean<Resource[]>
@Override
protected Resource[] createInstance() throws Exception {
List<Resource> scripts = new ArrayList<>();
List<Resource> result = new ArrayList<>();
for (String location : this.locations) {
Resource[] resources = this.resourcePatternResolver.getResources(location);
// Cache URLs to avoid repeated I/O during sorting
Map<Resource, String> urlCache = new LinkedHashMap<>(resources.length);
List<Resource> failingResources = new ArrayList<>();
for (Resource resource : resources) {
try {
urlCache.put(resource, resource.getURL().toString());
}
catch (IOException ex) {
throw new IllegalStateException(
"Failed to resolve URL for resource [" + resource +
"] from location pattern [" + location + "]", ex);
if (logger.isDebugEnabled()) {
logger.debug("Failed to resolve " + resource + " for sorting purposes: " + ex);
}
failingResources.add(resource);
}
}
@@ -93,9 +95,10 @@ public class SortedResourcesFactoryBean extends AbstractFactoryBean<Resource[]>
List<Resource> sortedResources = new ArrayList<>(urlCache.keySet());
sortedResources.sort(Comparator.comparing(urlCache::get));
scripts.addAll(sortedResources);
result.addAll(sortedResources);
result.addAll(failingResources);
}
return scripts.toArray(new Resource[0]);
return result.toArray(new Resource[0]);
}
}
@@ -40,11 +40,10 @@ import org.springframework.util.Assert;
* @author Rod Johnson
* @author Juergen Hoeller
* @see #loadBeanDefinitions
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
* @deprecated as of 5.3, in favor of Spring's common bean definition formats
* and/or custom reader implementations
* @deprecated in favor of Spring's common bean definition formats and/or
* custom BeanDefinitionReader implementations
*/
@Deprecated
@Deprecated(since = "5.3")
public class JdbcBeanDefinitionReader {
private final org.springframework.beans.factory.support.PropertiesBeanDefinitionReader propReader;
@@ -202,24 +202,10 @@ public abstract class DataSourceUtils {
boolean debugEnabled = logger.isDebugEnabled();
// Set read-only flag.
if (setReadOnly) {
try {
if (debugEnabled) {
logger.debug("Setting JDBC Connection [" + con + "] read-only");
}
con.setReadOnly(true);
}
catch (SQLException | RuntimeException ex) {
Throwable exToCheck = ex;
while (exToCheck != null) {
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
// Assume it's a connection timeout that would otherwise get lost: for example, from JDBC 4.0
throw ex;
}
exToCheck = exToCheck.getCause();
}
// "read-only not supported" SQLException -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
if (debugEnabled) {
logger.debug("Setting JDBC Connection [" + con + "] read-only");
}
setReadOnlyIfPossible(con);
}
// Apply specific isolation level, if any.
@@ -238,6 +224,31 @@ public abstract class DataSourceUtils {
return previousIsolationLevel;
}
/**
* Apply the read-only hint to the given Connection,
* suppressing exceptions other than timeout-related ones.
* @param con the Connection to prepare
* @throws SQLException in case of a timeout exception
* @since 6.2.15
*/
static void setReadOnlyIfPossible(Connection con) throws SQLException {
try {
con.setReadOnly(true);
}
catch (SQLException | RuntimeException ex) {
Throwable exToCheck = ex;
while (exToCheck != null) {
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
// Assume it's a connection timeout that would otherwise get lost: for example, from JDBC 4.0
throw ex;
}
exToCheck = exToCheck.getCause();
}
// "read-only not supported" SQLException -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
}
}
/**
* Reset the given Connection after a transaction,
* regarding read-only flag and isolation level.
@@ -471,48 +471,56 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
/**
* Return the target Connection, fetching it and initializing it if necessary.
*/
private Connection getTargetConnection(Method operation) throws SQLException {
if (this.target == null) {
// No target Connection held -> fetch one.
if (logger.isTraceEnabled()) {
logger.trace("Connecting to database for operation '" + operation.getName() + "'");
}
// Fetch physical Connection from DataSource.
DataSource dataSource = getDataSourceToUse();
this.target = (this.username != null ? dataSource.getConnection(this.username, this.password) :
dataSource.getConnection());
if (this.target == null) {
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
}
// Apply kept transaction settings, if any.
if (this.readOnly && readOnlyDataSource == null) {
try {
this.target.setReadOnly(true);
}
catch (Exception ex) {
// "read-only not supported" -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
}
}
if (this.transactionIsolation != null &&
!this.transactionIsolation.equals(defaultTransactionIsolation())) {
this.target.setTransactionIsolation(this.transactionIsolation);
}
if (this.autoCommit != null && this.autoCommit != defaultAutoCommit()) {
this.target.setAutoCommit(this.autoCommit);
}
}
else {
private Connection getTargetConnection(Method operation) throws Throwable {
Connection target = this.target;
if (target != null) {
// Target Connection already held -> return it.
if (logger.isTraceEnabled()) {
logger.trace("Using existing database connection for operation '" + operation.getName() + "'");
}
return target;
}
return this.target;
// No target Connection held -> fetch one.
if (logger.isTraceEnabled()) {
logger.trace("Connecting to database for operation '" + operation.getName() + "'");
}
// Fetch physical Connection from DataSource.
DataSource dataSource = getDataSourceToUse();
target = (this.username != null ? dataSource.getConnection(this.username, this.password) :
dataSource.getConnection());
if (target == null) {
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
}
// Apply kept transaction settings, if any.
try {
if (this.readOnly && readOnlyDataSource == null) {
DataSourceUtils.setReadOnlyIfPossible(target);
}
if (this.transactionIsolation != null &&
!this.transactionIsolation.equals(defaultTransactionIsolation())) {
target.setTransactionIsolation(this.transactionIsolation);
}
if (this.autoCommit != null && this.autoCommit != defaultAutoCommit()) {
target.setAutoCommit(this.autoCommit);
}
}
catch (Throwable settingsEx) {
logger.debug("Failed to apply transaction settings to JDBC Connection", settingsEx);
// Close Connection and do not set it as target.
try {
target.close();
}
catch (Throwable closeEx) {
logger.debug("Could not close JDBC Connection after failed settings", closeEx);
}
throw settingsEx;
}
this.target = target;
return target;
}
private DataSource getDataSourceToUse() {
@@ -55,7 +55,7 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<ou
* @since 5.0
*/
@Suppress("DEPRECATION")
// TODO Replace by the vararg variant in Spring Framework 6
// TODO Replace by the vararg variant in Spring Framework 7
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T? =
queryForObject(sql, args, T::class.java) as T
@@ -89,7 +89,7 @@ inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out
* @since 5.0
*/
@Suppress("DEPRECATION")
// TODO Replace by the vararg variant in Spring Framework 6
// TODO Replace by the vararg variant in Spring Framework 7
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
queryForList(sql, args, T::class.java)
@@ -21,6 +21,7 @@ import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
@@ -32,6 +33,7 @@ import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.AbstractDriverBasedDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean;
import org.springframework.jdbc.datasource.init.CannotReadScriptException;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.lang.Nullable;
@@ -67,6 +69,13 @@ class JdbcNamespaceIntegrationTests {
assertCorrectSetup("jdbc-config-pattern.xml", "dataSource");
}
@Test
void createWithNonExistentResource() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> assertCorrectSetup("jdbc-config-nonexistent.xml", "dataSource"))
.withCauseInstanceOf(CannotReadScriptException.class);
}
@Test
void createWithAnonymousDataSourceAndDefaultDatabaseName() {
assertThat(extractDataSourceUrl("jdbc-config-db-name-default-and-anonymous-datasource.xml"))
@@ -5,8 +5,8 @@
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
<jdbc:embedded-database>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
</jdbc:embedded-database>
</beans>
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/jdbc"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
<embedded-database id="dataSource" database-name="customDbName">
<script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
<script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
</embedded-database>
<jdbc:embedded-database id="dataSource" database-name="customDbName">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
</jdbc:embedded-database>
</beans:beans>
</beans>
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/jdbc"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
<embedded-database id="dataSource" database-name="shouldBeOverriddenByGeneratedName" generate-name="true">
<script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
<script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
</embedded-database>
<jdbc:embedded-database id="dataSource" database-name="shouldBeOverriddenByGeneratedName" generate-name="true">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
</jdbc:embedded-database>
</beans:beans>
</beans>
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/jdbc"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
<embedded-database id="dataSource">
<script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
<script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
</embedded-database>
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
</jdbc:embedded-database>
</beans:beans>
</beans>
@@ -5,7 +5,8 @@
xsi:schemaLocation="http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<jdbc:embedded-database id="firstDataSource" />
<jdbc:embedded-database id="secondDataSource" />
<jdbc:embedded-database id="firstDataSource"/>
<jdbc:embedded-database id="secondDataSource"/>
</beans>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
<jdbc:embedded-database id="dataSource" type="HSQL">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/custom-data.sql"/> <!-- does not exist -->
</jdbc:embedded-database>
</beans>
@@ -5,18 +5,18 @@
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
<jdbc:embedded-database id="dataSource" type="HSQL">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
</jdbc:embedded-database>
<jdbc:embedded-database id="h2DataSource" type="H2">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
</jdbc:embedded-database>
<jdbc:embedded-database id="derbyDataSource" type="DERBY">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema-derby.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema-derby.sql"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
</jdbc:embedded-database>
</beans>
@@ -5,11 +5,11 @@
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util-3.1.xsd">
<jdbc:embedded-database id="dataSource" type="HSQL" />
<jdbc:embedded-database id="dataSource" type="HSQL"/>
<jdbc:initialize-database data-source="dataSource" enabled="#{properties['data.source.init']}">
<jdbc:script location="#{properties['schema.scripts']}" />
<jdbc:script location="#{properties['insert.scripts']}" />
<jdbc:script location="#{properties['schema.scripts']}"/>
<jdbc:script location="#{properties['insert.scripts']}"/>
</jdbc:initialize-database>
<util:properties id="properties">
@@ -4,15 +4,14 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
<jdbc:embedded-database id="dataSource" type="HSQL" />
<jdbc:embedded-database id="dataSource" type="HSQL"/>
<jdbc:initialize-database data-source="dataSource" enabled="${data.source.init}">
<jdbc:script location="${schema.scripts}" />
<jdbc:script location="${insert.scripts}" />
<jdbc:script location="${schema.scripts}"/>
<jdbc:script location="${insert.scripts}"/>
</jdbc:initialize-database>
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties">
<value>
schema.scripts=classpath:org/springframework/jdbc/config/db-schema.sql
@@ -246,7 +246,7 @@ public class CachingConnectionFactory extends SingleConnectionFactory {
return null;
}
Deque<Session> sessionList = this.cachedSessions.computeIfAbsent(mode, k -> new ArrayDeque<>());
Deque<Session> sessionList = this.cachedSessions.computeIfAbsent(mode, key -> new ArrayDeque<>());
Session session = null;
synchronized (sessionList) {
if (!sessionList.isEmpty()) {
@@ -156,7 +156,7 @@ public class JmsResourceHolder extends ResourceHolderSupport {
this.sessions.add(session);
if (connection != null) {
Deque<Session> sessions =
this.sessionsPerConnection.computeIfAbsent(connection, k -> new ArrayDeque<>());
this.sessionsPerConnection.computeIfAbsent(connection, key -> new ArrayDeque<>());
sessions.add(session);
}
}
@@ -320,7 +320,7 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe
Observation observation = createObservation(message).start();
Observation.Scope scope = observation.openScope();
if (logger.isDebugEnabled()) {
logger.debug("Received message of type [" + message.getClass() + "] from consumer [" +
logger.debug("Received message of type [" + message.getClass().getName() + "] from consumer [" +
consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" +
sessionToUse + "]");
}
@@ -757,10 +757,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
}
waitCount++;
}
// Clear remaining scheduled invokers, possibly left over as paused tasks
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) {
scheduledInvoker.clearResources();
}
// Clear remaining scheduled invokers, possibly left over as paused tasks.
clearResourcesInScheduledInvokers();
this.scheduledInvokers.clear();
}
catch (InterruptedException ex) {
@@ -910,6 +908,15 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
}
}
/**
* Clear resources in scheduled invokers, even in case of paused tasks.
*/
private void clearResourcesInScheduledInvokers() {
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) {
scheduledInvoker.clearResources();
}
}
/**
* Use a shared JMS Connection depending on the "cacheLevel" setting.
* @see #setCacheLevel
@@ -1431,6 +1438,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
if (activeInvokerCount == 0) {
if (!isRunning()) {
// Proactively release shared Connection when stopped.
clearResourcesInScheduledInvokers();
releaseSharedConnection();
}
if (stopCallback != null) {
@@ -102,12 +102,6 @@ public abstract class JmsAccessor implements InitializingBean {
/**
* Set the transaction mode that is used when creating a JMS {@link Session}.
* Default is "false".
* <p>Note that within a JTA transaction, the parameters passed to
* {@code create(Queue/Topic)Session(boolean transacted, int acknowledgeMode)}
* method are not taken into account. Depending on the Jakarta EE transaction context,
* the container makes its own decisions on these values. Analogously, these
* parameters are not taken into account within a locally managed transaction
* either, since the accessor operates on an existing JMS Session in this case.
* <p>Setting this flag to "true" will use a short local JMS transaction
* when running outside a managed transaction, and a synchronized local
* JMS transaction in case of a managed transaction (other than an XA
@@ -115,6 +109,11 @@ public abstract class JmsAccessor implements InitializingBean {
* transaction being managed alongside the main transaction (which might
* be a native JDBC transaction), with the JMS transaction committing
* right after the main transaction.
* <p>Note that this flag is meant to remain at its default value "false" for
* participating in a global JTA/XA transaction in a Jakarta EE environment.
* While the server may leniently ignore local Session-level transaction
* management in such a scenario, it may also throw unexpected exceptions
* on commit/rollback in case of this Session-level flag being set to "true".
* @see jakarta.jms.Connection#createSession(boolean, int)
*/
public void setSessionTransacted(boolean sessionTransacted) {
@@ -449,13 +449,13 @@ public class StompHeaders implements MultiValueMap<String, String>, Serializable
*/
@Override
public void add(String headerName, @Nullable String headerValue) {
List<String> headerValues = this.headers.computeIfAbsent(headerName, k -> new ArrayList<>(1));
List<String> headerValues = this.headers.computeIfAbsent(headerName, key -> new ArrayList<>(1));
headerValues.add(headerValue);
}
@Override
public void addAll(String headerName, List<? extends String> headerValues) {
List<String> currentValues = this.headers.computeIfAbsent(headerName, k -> new ArrayList<>(1));
List<String> currentValues = this.headers.computeIfAbsent(headerName, key -> new ArrayList<>(1));
currentValues.addAll(headerValues);
}
@@ -249,7 +249,7 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
nativeHeaders = new LinkedMultiValueMap<>(3);
setHeader(NATIVE_HEADERS, nativeHeaders);
}
List<String> values = nativeHeaders.computeIfAbsent(name, k -> new ArrayList<>(1));
List<String> values = nativeHeaders.computeIfAbsent(name, key -> new ArrayList<>(1));
values.add(value);
setModified(true);
}
@@ -25,7 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
@@ -167,8 +167,8 @@ public abstract class AbstractFallbackTransactionAttributeSource
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip methods declared on BeanFactoryAware and co.
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
return null;
}
@@ -18,10 +18,10 @@ package org.springframework.transaction.reactive;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import reactor.core.publisher.Mono;
@@ -196,7 +196,7 @@ public class TransactionSynchronizationManager {
if (isSynchronizationActive()) {
throw new IllegalStateException("Cannot activate transaction synchronization - already active");
}
this.transactionContext.setSynchronizations(new LinkedHashSet<>());
this.transactionContext.setSynchronizations(new CopyOnWriteArraySet<>());
}
/**
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.testfixture.io.SerializationTestUtils;
@@ -59,6 +60,7 @@ class AnnotationTransactionAttributeSourceTests {
private final AnnotationTransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
@Test
void serializable() throws Exception {
TestBean1 tb = new TestBean1();
@@ -123,6 +125,10 @@ class AnnotationTransactionAttributeSourceTests {
void transactionAttributeDeclaredOnInterfaceMethodOnly() {
TransactionAttribute actual = getTransactionAttribute(TestBean2.class, ITestBean2.class, "getAge");
assertThat(actual).satisfies(hasNoRollbackRule());
actual = getTransactionAttribute(TestBean2.class, ITestBean2X.class, "getAge");
assertThat(actual).satisfies(hasNoRollbackRule());
actual = getTransactionAttribute(ITestBean2X.class, ITestBean2X.class, "getAge");
assertThat(actual).satisfies(hasNoRollbackRule());
}
/**
@@ -249,6 +255,7 @@ class AnnotationTransactionAttributeSourceTests {
assertThat(actual.isReadOnly()).isTrue();
}
@Nested
class JtaAttributeTests {
@@ -276,6 +283,7 @@ class AnnotationTransactionAttributeSourceTests {
assertThat(getNameAttr.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_SUPPORTS);
}
static class JtaAnnotatedBean1 implements ITestBean1 {
private String name;
@@ -305,7 +313,6 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
static class JtaAnnotatedBean2 implements ITestBean1 {
@@ -362,7 +369,6 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
interface ITestJta {
@@ -375,9 +381,9 @@ class AnnotationTransactionAttributeSourceTests {
void setName(String name);
}
}
@Nested
class Ejb3AttributeTests {
@@ -448,7 +454,6 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@jakarta.ejb.TransactionAttribute(TransactionAttributeType.SUPPORTS)
static class Ejb3AnnotatedBean2 implements ITestBean1 {
@@ -506,6 +511,7 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@Nested
class GroovyTests {
@@ -519,6 +525,7 @@ class AnnotationTransactionAttributeSourceTests {
assertThat(attributeSource.getTransactionAttribute(getMetaClassMethod, GroovyTestBean.class)).isNull();
}
@Transactional
static class GroovyTestBean implements ITestBean1, GroovyObject {
@@ -571,6 +578,7 @@ class AnnotationTransactionAttributeSourceTests {
}
}
private Consumer<TransactionAttribute> hasRollbackRules(RollbackRuleAttribute... rollbackRuleAttributes) {
return transactionAttribute -> {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
@@ -626,7 +634,12 @@ class AnnotationTransactionAttributeSourceTests {
}
interface ITestBean2X extends ITestBean2 {
interface ITestBean2X extends ITestBean2, BeanNameAware {
@Transactional
int getAge();
void setAge(int age);
String getName();
@@ -735,6 +748,10 @@ class AnnotationTransactionAttributeSourceTests {
this.age = age;
}
@Override
public void setBeanName(String name) {
}
@Override
public String getName() {
return name;
@@ -917,6 +934,7 @@ class AnnotationTransactionAttributeSourceTests {
}
}
@Transactional(label = {"retryable", "long-running"})
static class TestBean11 {
@@ -155,7 +155,7 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
headers.forEach((headerName, headerValues) -> {
if (!DISALLOWED_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) {
for (String headerValue : headerValues) {
builder.header(headerName, headerValue);
builder.header(headerName, (headerValue != null) ? headerValue : "");
}
}
});
@@ -90,7 +90,7 @@ class OkHttp3ClientHttpRequest extends AbstractStreamingClientHttpRequest {
builder.method(this.method.name(), requestBody);
headers.forEach((headerName, headerValues) -> {
for (String headerValue : headerValues) {
builder.addHeader(headerName, headerValue);
builder.addHeader(headerName, (headerValue != null) ? headerValue : "");
}
});
Request request = builder.build();
@@ -209,7 +209,7 @@ final class SubscriberInputStream<T> extends InputStream implements Flow.Subscri
}
private void resume() {
if (this.parkedThread != READY) {
if (this.parkedThread.get() != READY) {
Object old = this.parkedThread.getAndSet(READY);
if (old != READY) {
LockSupport.unpark((Thread) old);

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