mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf74ce007d | |||
| 23625ee698 | |||
| c89c4ac614 | |||
| 2155e9fb25 | |||
| 24df35c55a | |||
| 3b1fa369b4 | |||
| 221adf14a4 | |||
| ad849fb3a2 | |||
| 8041a09268 | |||
| 97b9517918 | |||
| e79d8e35a2 | |||
| 96503a2ea6 | |||
| 8803d3c5dc | |||
| 5b4728f0bf | |||
| 1fd1d8d629 | |||
| afc40d7b93 | |||
| 3eb1df0ac0 | |||
| 2aa3b0a77e | |||
| 0008106adb | |||
| 8f6d44a86b | |||
| 874498272f | |||
| 2144813bad | |||
| 67a92306f7 | |||
| bc0731891b | |||
| 3faa7cac4d | |||
| 3121daf553 | |||
| 821165488e | |||
| 3b556ba0c0 | |||
| b25f98374b | |||
| c2f7cd3401 | |||
| 22d2810ed0 | |||
| b39055f293 | |||
| 140933400d | |||
| e7a5452a14 | |||
| ab96576e67 | |||
| 19b080b73f | |||
| f9b4fba97a | |||
| 37e26e0377 | |||
| 85c47a73dd | |||
| ab93020263 | |||
| a4134663a6 | |||
| a4b1155ca6 | |||
| 1bdd8337c6 | |||
| 7a0ea14452 | |||
| 59025c5b96 | |||
| e5de8b9bc6 | |||
| e146e809e5 | |||
| ff62e7355f | |||
| 4e97013595 | |||
| bd10b7ae06 | |||
| e4288170c8 | |||
| 8545a759a7 | |||
| f94645de17 | |||
| 8553f97df1 | |||
| 030dace2be | |||
| 5af1c9b487 | |||
| f3ed04c9d7 | |||
| 46ee944acc | |||
| 6be1c29fda | |||
| 3fa56db88b |
@@ -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
|
||||
|
||||
+2
-3
@@ -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:
|
||||
|
||||
|
||||
+5
-5
@@ -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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -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`
|
||||
|
||||
+1
-1
@@ -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`.
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -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`.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -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`
|
||||
|
||||
+1
-1
@@ -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`.
|
||||
|
||||
+1
-1
@@ -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),
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
version=6.2.14-SNAPSHOT
|
||||
version=6.2.15
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+6
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -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 {
|
||||
|
||||
-1
@@ -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 {
|
||||
|
||||
|
||||
+3
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
+4
-2
@@ -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();
|
||||
|
||||
+2
-3
@@ -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();
|
||||
}
|
||||
|
||||
Vendored
+37
-20
@@ -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}
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+22
-2
@@ -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
|
||||
|
||||
Vendored
+1
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+27
-10
@@ -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
|
||||
|
||||
+3
-3
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+6
-24
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-13
@@ -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;
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
-1
@@ -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)
|
||||
|
||||
-3
@@ -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 {
|
||||
|
||||
|
||||
+1
-1
@@ -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 '" +
|
||||
|
||||
+22
-2
@@ -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
|
||||
|
||||
+31
@@ -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 {
|
||||
|
||||
|
||||
+9
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -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();
|
||||
|
||||
+10
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+17
-3
@@ -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));
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+165
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+100
-15
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+179
-13
@@ -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
|
||||
|
||||
+9
-6
@@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-4
@@ -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;
|
||||
|
||||
+28
-17
@@ -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.
|
||||
|
||||
+44
-36
@@ -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() {
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
|
||||
+9
@@ -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"))
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+6
-6
@@ -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>
|
||||
|
||||
+6
-6
@@ -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>
|
||||
|
||||
+6
-6
@@ -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>
|
||||
|
||||
+3
-2
@@ -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>
|
||||
|
||||
+14
@@ -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>
|
||||
|
||||
+3
-3
@@ -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
-5
@@ -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
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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 + "]");
|
||||
}
|
||||
|
||||
+12
-4
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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<>());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+23
-5
@@ -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 : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user