Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel e354390837 Release v6.2.12 2025-10-16 08:56:30 +02:00
182 changed files with 2781 additions and 6215 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
## Continuous Integration Builds
CI builds are defined with [GitHub Actions workflows](.github/workflows).
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
## Stay in Touch
+1 -1
View File
@@ -97,7 +97,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.14.1/api/",
// "https://junit.org/junit5/docs/5.14.0/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
+1 -1
View File
@@ -36,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.20/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
@@ -676,7 +676,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
[source,java,indent=0,subs="verbatim,quotes"]
----
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
.paramSource(new Actor("Leonor", "Watling"))
.paramSource(new Actor("Leonor", "Watling")
.update();
----
@@ -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#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.
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.
[[mockmvc-server-htmlunit-geb-setup]]
== MockMvc and Geb Setup
@@ -28,8 +28,7 @@ def setup() {
----
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-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
@@ -63,10 +62,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#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:
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:
[source,groovy]
----
@@ -7,7 +7,8 @@ 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:
@@ -44,7 +45,7 @@ Kotlin::
======
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
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
@@ -76,7 +77,7 @@ Kotlin::
======
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
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:
@@ -143,10 +144,10 @@ Kotlin::
======
The preceding code improves on our
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.
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.
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 <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
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,11 +259,10 @@ Kotlin::
======
--
This improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
by leveraging the Page Object Pattern. As we mentioned in
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
HtmlUnit, but it is much easier with WebDriver. Consider the following
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
`CreateMessagePage` implementation:
--
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
</form>
----
How do we ensure that our form produces the correct request to create a new message? A
How do we ensure that our form produce 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 above, we could perform end-to-end integration testing,
To resolve the issues mentioned earlier, 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 items such as auto-generated IDs, timestamps, and others can
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
be difficult.
These challenges do not mean that we should abandon end-to-end integration testing
@@ -448,7 +448,7 @@ Java::
----
@GetMapping
FragmentsRendering handle() {
return FragmentsRendering.fragment("posts").fragment("comments").build();
return FragmentsRendering.with("posts").fragment("comments").build();
}
----
@@ -458,7 +458,7 @@ Kotlin::
----
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.fragment("posts").fragment("comments").build()
return FragmentsRendering.with("posts").fragment("comments").build()
}
----
======
@@ -89,7 +89,40 @@ Kotlin::
You can map requests by using glob patterns and wildcards:
include::partial$web/uri-patterns.adoc[leveloffset=+1]
[cols="2,3,5"]
|===
|Pattern |Description |Example
| `+?+`
| Matches one character
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
| `+*+`
| Matches zero or more characters within a path segment
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`
| `+**+`
| Matches zero or more path segments until the end of the path
| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
`+"/resources/**/file.png"+` is invalid as `+**+` is only allowed at the end of the path.
| `+{name}+`
| Matches a path segment and captures it as a variable named "name"
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment.
| `{name:[a-z]+}`
| Matches the regexp `[a-z]+` as a path variable named "name"
| `/projects/{project:[a-z]+}/versions` matches `/projects/spring/versions` but not `/projects/spring1/versions`
| `+{*path}+`
| Matches zero or more path segments until the end of the path and captures it as a variable named "path"
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
|===
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
@@ -7,23 +7,19 @@ Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
1. Java Bean Validation is applied individually to an
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
it is a command object rather than a container such as `Map` or `Collection`, it does not
have `Errors` or `BindingResult` immediately after in the method signature, and does not
otherwise require method validation (see next). `WebExchangeBindException` is the
exception raised when validating a method parameter individually.
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
resolvers validate a method argument individually if the method parameter is annotated
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
discussed next). The exception raised in this case is `WebExchangeBindException`.
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
method for the return value, and it supersedes any validation that would be applied
otherwise to a method parameter individually because method validation covers both
method parameter constraints and nested constraints via `@Valid`.
`HandlerMethodValidationException` is the exception raised validation is applied
to the method.
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
directly on method parameters, or on the method (for the return value), then method
validation must be applied, and that supersedes validation at the method argument level
because method validation covers both method parameter constraints and nested constraints
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
Applications must handle both `WebExchangeBindException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
@@ -48,7 +48,7 @@ Java::
----
@GetMapping
FragmentsRendering handle() {
return FragmentsRendering.fragment("posts").fragment("comments").build();
return FragmentsRendering.with("posts").fragment("comments").build();
}
----
@@ -58,7 +58,7 @@ Kotlin::
----
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.fragment("posts").fragment("comments").build()
return FragmentsRendering.with("posts").fragment("comments").build()
}
----
======
@@ -3,10 +3,7 @@
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-filters[See equivalent in the Reactive stack]#
In the Servlet API, you can add a `jakarta.servlet.Filter` to apply interception-style logic
before and after the rest of the processing chain of filters and the target `Servlet`.
The `spring-web` module has a number of built-in `Filter` implementations:
The `spring-web` module provides some useful filters:
* xref:web/webmvc/filters.adoc#filters-http-put[Form Data]
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
@@ -14,19 +11,9 @@ The `spring-web` module has a number of built-in `Filter` implementations:
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
There are also base class implementations for use in Spring applications:
* `GenericFilterBean` -- base class for a `Filter` configured as a Spring bean;
integrates with the Spring `ApplicationContext` lifecycle.
* `OncePerRequestFilter` -- extension of `GenericFilterBean` that supports a single
invocation at the start of a request, i.e. during the `REQUEST` dispatch phase, and
ignoring further handling via `FORWARD` dispatches. The filter also provides control
over whether the `Filter` gets involved in `ASYNC` and `ERROR` dispatches.
Servlet filters can be configured in `web.xml` or via Servlet annotations.
In a Spring Boot application , you can
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare Filter's as beans]
and Boot will have them configured.
Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations.
If you are using Spring Boot, you can
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare them as beans and configure them as part of your application].
[[filters-http-put]]
@@ -422,7 +422,7 @@ reactive types from the controller method.
Reactive return values are handled as follows:
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
include `CompletionStage` (JDK), `Mono` (Reactor), and `Single` (RxJava).
include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
@@ -103,9 +103,22 @@ Spring WebFlux. It was enabled for use in Spring MVC from version 5.3 and is ena
default from version 6.0. See xref:web/webmvc/mvc-config/path-matching.adoc[MVC config] for
customizations of path matching options.
You can map requests by using glob patterns and wildcards:
`PathPattern` supports the same pattern syntax as `AntPathMatcher`. In addition, it also
supports the capturing pattern, for example, `+{*spring}+`, for matching 0 or more path segments
at the end of a path. `PathPattern` also restricts the use of `+**+` for matching multiple
path segments such that it's only allowed at the end of a pattern. This eliminates many
cases of ambiguity when choosing the best matching pattern for a given request.
For full pattern syntax please refer to
{spring-framework-api}/web/util/pattern/PathPattern.html[PathPattern] and
{spring-framework-api}/util/AntPathMatcher.html[AntPathMatcher].
include::partial$web/uri-patterns.adoc[leveloffset=+1]
Some example patterns:
* `+"/resources/ima?e.png"+` - match one character in a path segment
* `+"/resources/*.png"+` - match zero or more characters in a path segment
* `+"/resources/**"+` - match multiple path segments
* `+"/projects/{project}/versions"+` - match a path segment and capture it as a variable
* `++"/projects/{project:[a-z]+}/versions"++` - match and capture a variable with a regex
Captured URI variables can be accessed with `@PathVariable`. For example:
@@ -7,26 +7,22 @@ Spring MVC has built-in xref:core/validation/validator.adoc[validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
1. Java Bean Validation is applied individually to an
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
it is a command object rather than a container such as `Map` or `Collection`, it does not
have `Errors` or `BindingResult` immediately after in the method signature, and does not
otherwise require method validation (see next). `MethodArgumentNotValidException` is the
exception raised when validating a method parameter individually.
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
resolvers validate a method argument individually if the method parameter is annotated
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
method for the return value, and it supersedes any validation that would be applied
otherwise to a method parameter individually because method validation covers both
method parameter constraints and nested constraints via `@Valid`.
`HandlerMethodValidationException` is the exception raised validation is applied
to the method.
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
directly on method parameters, or on the method (for the return value), then method
validation must be applied, and that supersedes validation at the method argument level
because method validation covers both method parameter constraints and nested constraints
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
Applications should handle both `MethodArgumentNotValidException` and
`HandlerMethodValidationException` since either may be raised depending on the controller
Applications must handle both `MethodArgumentNotValidException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
method signature. The two exceptions, however are designed to be very similar, and can be
handled with almost identical code. The main difference is that the former is for a single
object while the latter is for a list of method parameters.
@@ -1,51 +0,0 @@
[cols="2,3,5"]
|===
|Pattern |Description |Example
| `spring`
| Literal pattern
| `+"/spring"+` matches `+"/spring"+`
| `+?+`
| Matches one character
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
| `+*+`
| Matches zero or more characters within a path segment
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`.
`+"/projects/*"+` matches `+"/projects/spring"+` but does not match `+"/projects"+` as the path segment is not present.
| `+**+`
| Matches zero or more path segments
| `+"/resources/**"+` matches `+"/resources"+`, `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
`+"/**/info"+` matches `+"/info"+`, `+"/spring/info"+` and `+"/spring/framework/info"+`
`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path.
`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
| `+{name}+`
| Similar to `+*+`, but also captures the path segment as a variable named "name"
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment.
| `{name:[a-z]+}`
| Matches the regexp `"[a-z]+"` as a path variable named "name"
| `"/projects/{project:[a-z]+}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"`
| `+{*path}+`
| Similar to `+**+`, but also captures the path segments as a variable named "path"
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+`
`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path.
`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
|===
+11 -11
View File
@@ -7,26 +7,26 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
api(platform("io.micrometer:micrometer-bom:1.14.13"))
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4.1"))
api(platform("io.micrometer:micrometer-bom:1.14.12"))
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.11"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
api(platform("org.apache.groovy:groovy-bom:4.0.28"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.27.6"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.30"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.30"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.28"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.28"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.14.1"))
api(platform("org.junit:junit-bom:5.14.0"))
api(platform("org.mockito:mockito-bom:5.20.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.4")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.2.3")
api("com.github.ben-manes.caffeine:caffeine:3.2.2")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
@@ -129,7 +129,7 @@ dependencies {
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.18.0")
api("org.htmlunit:htmlunit:4.17.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.13.0")
api("org.junit.support:testng-engine:1.0.5")
@@ -137,8 +137,8 @@ dependencies {
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
api("org.seleniumhq.selenium:selenium-java:4.38.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.36.1")
api("org.seleniumhq.selenium:selenium-java:4.36.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.17")
api("org.testng:testng:7.11.0")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.14
version=6.2.12
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
@@ -62,7 +62,7 @@ public interface ProxyMethodInvocation extends MethodInvocation {
MethodInvocation invocableClone(Object... arguments);
/**
* Set the arguments to be used on subsequent invocations in any advice
* Set the arguments to be used on subsequent invocations in the any advice
* in this chain.
* @param arguments the argument array
*/
@@ -41,8 +41,8 @@ abstract class CoroutinesUtils {
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
@SuppressWarnings({"unchecked", "rawtypes"})
static Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
(Continuation<Object>) continuation);
@@ -566,7 +566,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = ClassUtils.getUserClass(clazz);
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
@@ -586,11 +586,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (method.isBridge()) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(method);
if (ann != null && method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
@@ -608,7 +609,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
methodElements.add(new AutowiredMethodElement(method, required, pd));
}
});
@@ -180,6 +180,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
* {@code true} if a qualifier has been found and matched,
* {@code null} if no qualifier has been found at all
*/
@Nullable
protected Boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
boolean qualifierFound = false;
@@ -374,14 +375,6 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
return true;
}
}
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
for (Annotation annotation : methodParam.getMethodAnnotations()) {
if (isQualifier(annotation.annotationType())) {
return true;
}
}
}
return false;
}
@@ -29,7 +29,6 @@ import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.CollectionDelegate;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.MapDelegate;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
@@ -58,7 +57,6 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
* <li>{@link LinkedHashMap}</li>
* <li>{@link BeanReference}</li>
* <li>{@link TypedStringValue}</li>
* <li>{@link AutowiredPropertyMarker}</li>
* </ul>
* When combined with {@linkplain ValueCodeGeneratorDelegates#INSTANCES the
* delegates for common value types}, this should be added first as they have
@@ -70,8 +68,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
new ManagedMapDelegate(),
new LinkedHashMapDelegate(),
new BeanReferenceDelegate(),
new TypedStringValueDelegate(),
new AutowiredPropertyMarkerDelegate()
new TypedStringValueDelegate()
);
@@ -219,20 +216,4 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
return valueCodeGenerator.generateCode(value);
}
}
/**
* {@link Delegate} for {@link AutowiredPropertyMarker} types.
*/
private static class AutowiredPropertyMarkerDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
if (value instanceof AutowiredPropertyMarker) {
return CodeBlock.of("$T.INSTANCE", AutowiredPropertyMarker.class);
}
return null;
}
}
}
@@ -32,6 +32,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Rob Harrop
* @since 1.1
* @see PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
*/
public abstract class BeanDefinitionReaderUtils {
@@ -43,6 +43,7 @@ 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 {
@@ -1653,8 +1653,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
@SuppressWarnings("NullAway") // Dataflow analysis limitation
@Nullable
@SuppressWarnings("NullAway")
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
@@ -1991,8 +1991,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
for (String candidate : candidateNames) {
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
(!multiple || matchesBeanName(candidate, descriptor.getDependencyName()) ||
getAutowireCandidateResolver().hasQualifier(descriptor))) {
(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
@@ -2263,12 +2262,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
/**
* Determine whether the given dependency name matches the bean name or the aliases
* Determine whether the given candidate name matches the bean name or the aliases
* stored in this bean definition.
*/
protected boolean matchesBeanName(String beanName, @Nullable String dependencyName) {
return (dependencyName != null &&
(dependencyName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), dependencyName)));
protected boolean matchesBeanName(String beanName, @Nullable String candidateName) {
return (candidateName != null &&
(candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName)));
}
/**
@@ -74,10 +74,10 @@ import org.springframework.util.StringUtils;
* @author Rob Harrop
* @since 26.11.2003
* @see DefaultListableBeanFactory
* @deprecated in favor of Spring's common bean definition formats and/or
* custom BeanDefinitionReader implementations
* @deprecated as of 5.3, in favor of Spring's common bean definition formats
* and/or custom reader implementations
*/
@Deprecated(since = "5.3")
@Deprecated
public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
/**
@@ -472,7 +472,7 @@ abstract class AbstractPropertyAccessorTests {
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
assertThat(name).as("name is OK").isEqualTo(target.getName());
accessor.setPropertyValues(new MutablePropertyValues());
// Check it's unchanged
// Check its unchanged
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
assertThat(name).as("name is OK").isEqualTo(target.getName());
}
@@ -1275,6 +1275,88 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean().get("testBean2")).isNull();
}
@Test
void fieldInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb1 = new TestBean("tb1");
TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
bf.registerSingleton("testBean2", tb2);
bf.registerAlias("testBean1", "testBean");
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
}
@Test
void methodInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndMultipleMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
.isThrownBy(() -> bf.getBean("annotatedBean"))
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
}
@Test
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
rbd2.setAutowireCandidate(false);
bf.registerBeanDefinition("testBean2", rbd2);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
TestBean tb = bf.getBean("testBean1", TestBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndNoMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isNull();
assertThat(bean.getTestBean()).isNull();
}
@Test
void constructorInjectionWithTypedMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
@@ -1327,19 +1409,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
@Test
void constructorInjectionWithPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(NamedMapConstructorInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
NamedMapConstructorInjectionBean bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
}
@Test
void constructorInjectionWithQualifiedPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapConstructorInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
@@ -1428,114 +1497,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBeanSet()).contains(tb1, tb2);
}
@Test
void fieldInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb1 = new TestBean("tb1");
TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
bf.registerSingleton("testBean2", tb2);
bf.registerAlias("testBean1", "testBean");
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
}
@Test
void methodInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndMultipleMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
.isThrownBy(() -> bf.getBean("annotatedBean"))
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
}
@Test
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
rbd2.setAutowireCandidate(false);
bf.registerBeanDefinition("testBean2", rbd2);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
TestBean tb = bf.getBean("testBean1", TestBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndNoMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isNull();
assertThat(bean.getTestBean()).isNull();
}
@Test
void methodInjectionWithPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(NamedMapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
NamedMapMethodInjectionBean bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
}
@Test
void methodInjectionWithQualifiedPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("myTestBeanMap", new RootBeanDefinition(HashMap.class));
QualifiedMapMethodInjectionBean bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
}
@Test
void selfReference() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class));
@@ -3297,21 +3258,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
}
public static class NamedMapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
public NamedMapConstructorInjectionBean(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
public static class QualifiedMapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap;
@@ -3411,37 +3357,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
}
public static class NamedMapMethodInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
public static class QualifiedMapMethodInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
@Qualifier("myTestBeanMap")
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
@SuppressWarnings("serial")
public static class ObjectFactoryFieldInjectionBean implements Serializable {
@@ -16,7 +16,6 @@
package org.springframework.beans.factory.aot;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -35,7 +34,6 @@ import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -628,22 +626,6 @@ class BeanDefinitionMethodGeneratorTests {
-> assertThat(customPropertyValue.value()).isEqualTo("test")));
}
@Test
void generateBeanDefinitionMethodWhenHasAutowiredPropertyGeneratesMethod() {
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(CustomBean.class).addAutowiredProperty("innerBean")
.getBeanDefinition();
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) ->
assertThat(actual.getPropertyValues().get("innerBean"))
.isSameAs(AutowiredPropertyMarker.INSTANCE));
}
@Test
void generateBeanDefinitionMethodWhenHasAotContributionsAppliesContributions() {
RegisteredBean registeredBean = registerBean(
@@ -39,7 +39,6 @@ import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.ValueCodeGenerator;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -487,16 +486,4 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
}
@Nested
class AutowiredPropertyMarkerTests {
@Test
void generateWhenAutowiredPropertyMarker() {
compile(AutowiredPropertyMarker.INSTANCE, (instance, compiler) ->
assertThat(instance).isInstanceOf(AutowiredPropertyMarker.class)
.isSameAs(AutowiredPropertyMarker.INSTANCE));
}
}
}
@@ -28,7 +28,6 @@ import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.FieldRetrievingFactoryBean;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.parsing.ComponentDefinition;
@@ -47,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Juergen Hoeller
* @author Mark Fisher
*/
@SuppressWarnings("rawtypes")
class UtilNamespaceHandlerTests {
private DefaultListableBeanFactory beanFactory;
@@ -55,7 +55,7 @@ class UtilNamespaceHandlerTests {
@BeforeEach
void setup() {
void setUp() {
this.beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory);
reader.setEventListener(this.listener);
@@ -109,17 +109,17 @@ class UtilNamespaceHandlerTests {
@Test
void testSimpleMap() {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
Map<?, ?> map = (Map) this.beanFactory.getBean("simpleMap");
assertThat(map.get("foo")).isEqualTo("bar");
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
Map<?, ?> map2 = (Map) this.beanFactory.getBean("simpleMap");
assertThat(map).isSameAs(map2);
}
@Test
void testScopedMap() {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
Map<?, ?> map = (Map) this.beanFactory.getBean("scopedMap");
assertThat(map.get("foo")).isEqualTo("bar");
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
Map<?, ?> map2 = (Map) this.beanFactory.getBean("scopedMap");
assertThat(map2.get("foo")).isEqualTo("bar");
assertThat(map).isNotSameAs(map2);
}
@@ -164,23 +164,17 @@ class UtilNamespaceHandlerTests {
}
@Test
void testMapWithRef() throws Exception {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithRef");
void testMapWithRef() {
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithRef");
assertThat(map).isInstanceOf(TreeMap.class);
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
assertThat(this.beanFactory.resolveDependency(
new DependencyDescriptor(getClass().getDeclaredField("mapWithRef"), true), null))
.isSameAs(map);
}
@Test
void testMapWithTypes() throws Exception {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithTypes");
void testMapWithTypes() {
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithTypes");
assertThat(map).isInstanceOf(LinkedCaseInsensitiveMap.class);
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
assertThat(this.beanFactory.resolveDependency(
new DependencyDescriptor(getClass().getDeclaredField("mapWithTypes"), true), null))
.isSameAs(map);
}
@Test
@@ -246,11 +240,11 @@ class UtilNamespaceHandlerTests {
void testCircularCollections() {
TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionsBean");
assertThat(bean.getSomeList()).singleElement().isSameAs(bean);
assertThat(bean.getSomeSet()).singleElement().isSameAs(bean);
assertThat(bean.getSomeList()).singleElement().isEqualTo(bean);
assertThat(bean.getSomeSet()).singleElement().isEqualTo(bean);
assertThat(bean.getSomeMap()).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -261,17 +255,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isTrue();
assertThat(list).singleElement().isSameAs(bean);
assertThat(list).singleElement().isEqualTo(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
assertThat(set).singleElement().isSameAs(bean);
assertThat(set).singleElement().isEqualTo(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -282,17 +276,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
assertThat(list).singleElement().isSameAs(bean);
assertThat(list).singleElement().isEqualTo(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isTrue();
assertThat(set).singleElement().isSameAs(bean);
assertThat(set).singleElement().isEqualTo(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -303,17 +297,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
assertThat(list).singleElement().isSameAs(bean);
assertThat(list).singleElement().isEqualTo(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
assertThat(set).singleElement().isSameAs(bean);
assertThat(set).singleElement().isEqualTo(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isTrue();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -378,9 +372,4 @@ class UtilNamespaceHandlerTests {
assertThat(props).as("Incorrect property value").containsEntry("foo2", "local2");
}
// For DependencyDescriptor resolution
private Map<String, TestBean> mapWithRef;
private Map<String, TestBean> mapWithTypes;
}
@@ -59,8 +59,6 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
private boolean jedi;
private String favoriteCafé;
private ITestBean spouse;
private String touchy;
@@ -211,14 +209,6 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
this.jedi = jedi;
}
public String getFavoriteCafé() {
return this.favoriteCafé;
}
public void setFavoriteCafé(String favoriteCafé) {
this.favoriteCafé = favoriteCafé;
}
@Override
public ITestBean getSpouse() {
return this.spouse;
@@ -77,7 +77,7 @@ public class CaffeineCacheManager implements CacheManager {
private boolean allowNullValues = true;
private volatile boolean dynamic = true;
private boolean dynamic = true;
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
@@ -102,15 +102,10 @@ 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>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()
* <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.
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -250,6 +245,11 @@ public class CaffeineCacheManager implements CacheManager {
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
@Nullable
public Cache getCache(String name) {
@@ -260,33 +260,6 @@ 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,
@@ -330,6 +303,16 @@ public class CaffeineCacheManager implements CacheManager {
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
}
/**
* Remove the specified cache from this cache manager, applying to
* custom caches as well as dynamically registered caches at runtime.
* @param name the name of the cache
* @since 6.1.15
*/
public void removeCache(String name) {
this.customCacheNames.remove(name);
this.cacheMap.remove(name);
}
/**
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
@@ -25,7 +25,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
@@ -98,10 +97,6 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
return null;
}
// The method may be on an interface, but we need metadata from the target class.
// If the target class is null, the method will be unchanged.
@@ -24,6 +24,7 @@ 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;
@@ -41,7 +42,7 @@ class CaffeineCacheManagerTests {
@Test
@SuppressWarnings("cast")
void dynamicMode() {
CaffeineCacheManager cm = new CaffeineCacheManager();
CacheManager cm = new CaffeineCacheManager();
Cache cache1 = cm.getCache("c1");
assertThat(cache1).isInstanceOf(CaffeineCache.class);
@@ -75,14 +76,6 @@ 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
@@ -138,24 +131,11 @@ 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
@@ -23,14 +23,14 @@ import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented for explicitly specifying how caches are resolved
* and how keys are generated for annotation-driven cache management.
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableCaching} that wish or need to specify
* explicitly how caches are resolved and how keys are generated for annotation-driven
* cache management.
*
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableCaching}.
* See @{@link EnableCaching} for general examples and context; see
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()},
* and {@link #errorHandler()} for detailed instructions.
* <p>See @{@link EnableCaching} for general examples and context; see
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()}, and
* {@link #errorHandler()} for detailed instructions.
*
* <p><b>NOTE: A {@code CachingConfigurer} will get initialized early.</b>
* Do not inject common dependencies into autowired fields directly; instead, consider
@@ -54,7 +54,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private volatile boolean dynamic = true;
private boolean dynamic = true;
private boolean allowNullValues = true;
@@ -82,15 +82,10 @@ 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>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()
* <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.
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
@@ -165,6 +160,11 @@ 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,23 +175,6 @@ 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
@@ -27,7 +27,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -140,10 +139,6 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip setBeanFactory method on BeanFactoryAware.
if (method.getDeclaringClass() == BeanFactoryAware.class) {
return null;
}
// The method may be on an interface, but we need metadata from the target class.
// If the target class is null, the method will be unchanged.
@@ -18,6 +18,8 @@ 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;
@@ -30,29 +32,45 @@ import org.springframework.lang.Nullable;
* for disabling caching, typically used for backing cache declarations
* without an actual backing store.
*
* <p>This implementation will simply accept any items into the cache,
* not actually storing them.
* <p>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> cacheMap = new ConcurrentHashMap<>(16);
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(16);
private final Set<String> cacheNames = new LinkedHashSet<>(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) {
return this.cacheMap.computeIfAbsent(name, NoOpCache::new);
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);
}
/**
* This implementation returns the name of the caches previously requested.
*/
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
synchronized (this.cacheNames) {
return Collections.unmodifiableSet(this.cacheNames);
}
}
}
@@ -149,6 +149,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private static final boolean jndiPresent = ClassUtils.isPresent(
"javax.naming.InitialContext", CommonAnnotationBeanPostProcessor.class.getClassLoader());
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
@Nullable
private static final Class<? extends Annotation> jakartaResourceType;
@@ -158,8 +160,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
@Nullable
private static final Class<? extends Annotation> ejbAnnotationType;
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
static {
jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
if (jakartaResourceType != null) {
@@ -424,7 +424,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = ClassUtils.getUserClass(clazz);
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
@@ -455,23 +455,24 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (method.isBridge()) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
if (ejbAnnotationType != null && method.isAnnotationPresent(ejbAnnotationType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
if (method.getParameterCount() != 1) {
throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
}
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new EjbRefElement(method, method, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
}
}
else if (jakartaResourceType != null && method.isAnnotationPresent(jakartaResourceType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -480,13 +481,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new ResourceElement(method, method, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new ResourceElement(method, bridgedMethod, pd));
}
}
}
else if (javaxResourceType != null && method.isAnnotationPresent(javaxResourceType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -495,8 +496,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new LegacyResourceElement(method, method, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
}
}
}
@@ -227,6 +227,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
* @param beanFactory the bean factory to load bean definitions into
* @throws BeansException if parsing of the bean definitions failed
* @throws IOException if loading of bean definition files failed
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
@@ -78,6 +78,8 @@ 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");
@@ -99,6 +101,7 @@ 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 {
@@ -25,18 +25,18 @@ import java.lang.annotation.Target;
/**
* Declares that a field or method parameter should be formatted as a number.
*
* <p>Supports formatting by style or custom pattern string. Can be applied to
* any JDK {@code Number} types such as {@code Double} and {@code Long}.
* <p>Supports formatting by style or custom pattern string. Can be applied
* to any JDK {@code Number} type such as {@code Double} and {@code Long}.
*
* <p>For style-based formatting, set the {@link #style} attribute to the desired
* {@link Style}. For custom formatting, set the {@link #pattern} attribute to the
* desired number pattern, such as {@code "#,###.##"}.
* <p>For style-based formatting, set the {@link #style} attribute to be the
* desired {@link Style}. For custom formatting, set the {@link #pattern}
* attribute to be the number pattern, such as {@code #, ###.##}.
*
* <p>Each attribute is mutually exclusive, so only set one attribute per
* annotation (the one most convenient for your formatting needs). When the
* {@link #pattern} attribute is specified, it takes precedence over the
* {@link #style} attribute. When no annotation attributes are specified, the
* default format applied is style-based for either number or currency,
* annotation instance (the one most convenient one for your formatting needs).
* When the {@link #pattern} attribute is specified, it takes precedence over
* the {@link #style} attribute. When no annotation attributes are specified,
* the default format applied is style-based for either number of currency,
* depending on the annotated field or method parameter type.
*
* @author Keith Donald
@@ -50,21 +50,19 @@ import java.lang.annotation.Target;
public @interface NumberFormat {
/**
* The style pattern to use to format the field or method parameter.
* The style pattern to use to format the field.
* <p>Defaults to {@link Style#DEFAULT} for general-purpose number formatting
* for most annotated types, except for money types which default to currency
* formatting.
* <p>Set this attribute when you wish to format your field or method parameter
* in accordance with a common style other than the default style.
* formatting. Set this attribute when you wish to format your field in
* accordance with a common style other than the default style.
*/
Style style() default Style.DEFAULT;
/**
* The custom pattern to use to format the field or method parameter.
* <p>Defaults to an empty String, indicating no custom pattern has been
* specified.
* <p>Set this attribute when you wish to format your field or method parameter
* in accordance with a custom number pattern not represented by a style.
* The custom pattern to use to format the field.
* <p>Defaults to empty String, indicating no custom pattern String has been specified.
* Set this attribute when you wish to format your field in accordance with a
* custom number pattern not represented by a style.
*/
String pattern() default "";
@@ -22,18 +22,13 @@ import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented for customizing the {@link Executor} instance used when
* processing async method invocations or the {@link AsyncUncaughtExceptionHandler}
* instance used to process exceptions thrown from async methods with a {@code void}
* return type.
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableAsync} that wish to customize the
* {@link Executor} instance used when processing async method invocations or the
* {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from
* async method with {@code void} return type.
*
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableAsync}.
* See the @{@link EnableAsync} javadoc for usage examples.
*
* <p><b>NOTE: An {@code AsyncConfigurer} will get initialized early.</b>
* Do not inject common dependencies into autowired fields directly; instead, consider
* declaring a lazy {@link org.springframework.beans.factory.ObjectProvider} for those.
* <p>See @{@link EnableAsync} for usage examples.
*
* @author Chris Beams
* @author Stephane Nicoll
@@ -16,8 +16,6 @@
package org.springframework.validation;
import java.util.HexFormat;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -128,18 +126,8 @@ public class FieldError extends ObjectError {
// We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but
// keep including the full nullSafeToString representation for backwards compatibility.
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
"': rejected value [" + formatRejectedValue() + "]; " +
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
resolvableToString();
}
private String formatRejectedValue() {
// Special handling of byte[], to be moved into ObjectUtils in 7.0
if (this.rejectedValue instanceof byte[] bytes && bytes.length != 0) {
return "{" + HexFormat.of().formatHex(bytes) + "}";
}
return ObjectUtils.nullSafeToString(this.rejectedValue);
}
}
@@ -19,6 +19,7 @@ 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;
@@ -30,7 +31,7 @@ class ConcurrentMapCacheManagerTests {
@Test
void testDynamicMode() {
ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager();
CacheManager cm = new ConcurrentMapCacheManager();
Cache cache1 = cm.getCache("c1");
assertThat(cache1).isInstanceOf(ConcurrentMapCache.class);
Cache cache1again = cm.getCache("c1");
@@ -64,14 +65,6 @@ 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
@@ -114,24 +107,11 @@ 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
@@ -503,6 +503,10 @@ class SpringValidatorAdapterTests {
private static final String ID = "id";
@Override
public void initialize(AnythingValid constraintAnnotation) {
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
List<Field> fieldsErrors = new ArrayList<>();
@@ -425,6 +425,10 @@ class ValidatorFactoryTests {
@Autowired
private Environment environment;
@Override
public void initialize(NameAddressValid constraintAnnotation) {
}
@Override
public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
if (value.expectsAutowiredValidator) {
@@ -491,6 +495,10 @@ class ValidatorFactoryTests {
public static class InnerValidator implements ConstraintValidator<InnerValid, InnerBean> {
@Override
public void initialize(InnerValid constraintAnnotation) {
}
@Override
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
@@ -534,6 +542,10 @@ class ValidatorFactoryTests {
public static class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {
@Override
public void initialize(NotXList constraintAnnotation) {
}
@Override
public boolean isValid(List<String> list, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
@@ -193,16 +193,12 @@ public class ClassReader {
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
this.classFileBuffer = classFileBuffer;
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
// SPRING PATCH: leniently try to parse newer class files as well
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
// throw new IllegalArgumentException(
// "Unsupported class file major version " + readShort(classFileOffset + 6));
// }
// END OF PATCH
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V25) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
// Create the constant pool arrays. The constant_pool_count field is after the magic,
// minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
@@ -290,7 +290,6 @@ public interface Opcodes {
int V23 = 0 << 16 | 67;
int V24 = 0 << 16 | 68;
int V25 = 0 << 16 | 69;
int V26 = 0 << 16 | 70;
/**
* Version flag indicating that the class is using 'preview' features.
@@ -1473,7 +1473,7 @@ final class SymbolTable {
/**
* Another entry (and so on recursively) having the same hash code (modulo the size of {@link
* SymbolTable#labelEntries}) as this one.
* SymbolTable#labelEntries}}) as this one.
*/
LabelEntry next;
@@ -110,7 +110,7 @@ class BridgeMethodResolver {
&& currentMethod != null) {
Signature target = new Signature(name, desc);
// If the target signature is the same as the current,
// we shouldn't change our bridge because invokespecial
// we shouldn't change our bridge becaues invokespecial
// is the only way to make progress (otherwise we'll
// get infinite recursion). This would typically
// only happen when a bridge method is created to widen
@@ -100,15 +100,15 @@ public final class BridgeMethodResolver {
}
private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
Class<?> userClass = targetClass;
if (!bridgeMethod.isBridge()) {
if (!bridgeMethod.isBridge() && localBridge) {
userClass = ClassUtils.getUserClass(targetClass);
if (userClass == targetClass) {
return bridgeMethod;
}
}
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass));
Method bridgedMethod = cache.get(cacheKey);
if (bridgedMethod == null) {
@@ -118,7 +118,7 @@ public final class BridgeMethodResolver {
ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) {
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod, targetClass));
searchCandidates(candidateMethods, bridgeMethod));
}
if (bridgedMethod == null) {
// A bridge method was passed in but we couldn't find the bridged method.
@@ -149,16 +149,14 @@ public final class BridgeMethodResolver {
* @return the bridged method, or {@code null} if none found
*/
@Nullable
private static Method searchCandidates(
List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
if (candidateMethods.isEmpty()) {
return null;
}
Method previousMethod = null;
boolean sameSig = true;
for (Method candidateMethod : candidateMethods) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, targetClass)) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
return candidateMethod;
}
else if (previousMethod != null) {
@@ -174,12 +172,12 @@ public final class BridgeMethodResolver {
* Determines whether the bridge {@link Method} is the bridge for the
* supplied candidate {@link Method}.
*/
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> targetClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, targetClass)) {
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
return true;
}
Method method = findGenericDeclaration(bridgeMethod);
return (method != null && isResolvedTypeMatch(method, candidateMethod, targetClass));
return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
}
/**
@@ -188,25 +186,14 @@ public final class BridgeMethodResolver {
* are equal after resolving all types against the declaringType, otherwise
* returns {@code false}.
*/
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> targetClass) {
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
Type[] genericParameters = genericMethod.getGenericParameterTypes();
if (genericParameters.length != candidateMethod.getParameterCount()) {
return false;
}
Class<?> clazz = targetClass;
while (clazz != null && clazz != Object.class && clazz != genericMethod.getDeclaringClass()) {
if (checkResolvedTypeMatch(genericMethod, candidateMethod, clazz)) {
return true;
}
clazz = clazz.getSuperclass();
}
return false;
}
private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> clazz) {
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
for (int i = 0; i < candidateParameters.length; i++) {
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz);
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
Class<?> candidateParameter = candidateParameters[i];
if (candidateParameter.isArray()) {
// An array type: compare the component type.
@@ -286,9 +273,7 @@ public final class BridgeMethodResolver {
* introduced in Java 6 to fix <a href="https://bugs.openjdk.org/browse/JDK-6342411">
* JDK-6342411</a>.
* @return whether signatures match as described
* @deprecated as of 6.2.13: not necessary anymore due to {@link #getMostSpecificMethod}
*/
@Deprecated(since = "6.2.13", forRemoval = true)
public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
if (bridgeMethod == bridgedMethod) {
// Same method: for common purposes, return true to proceed as if it was a visibility bridge.
@@ -50,7 +50,7 @@ public abstract class Hints {
/**
* Create a map with a single hint via {@link Collections#singletonMap}.
* Create a map wit a single hint via {@link Collections#singletonMap}.
* @param hintName the hint name
* @param value the hint value
* @return the created map
@@ -617,12 +617,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private Set<ClassPathManifestEntry> getClassPathManifestEntriesFromJar(File jar) throws IOException {
URL base = jar.toURI().toURL();
File parent = jar.getAbsoluteFile().getParentFile();
try (JarFile jarFile = new JarFile(jar)) {
Manifest manifest = jarFile.getManifest();
Attributes attributes = (manifest != null ? manifest.getMainAttributes() : null);
String classPath = (attributes != null ? attributes.getValue(Name.CLASS_PATH) : null);
Set<ClassPathManifestEntry> manifestEntries = new LinkedHashSet<>();
if (StringUtils.hasLength(classPath)) {
StringTokenizer tokenizer = new StringTokenizer(classPath);
@@ -632,15 +630,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// See jdk.internal.loader.URLClassPath.JarLoader.tryResolveFile(URL, String)
continue;
}
// 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));
// 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() ||
candidate.getCanonicalPath().contains(parent.getCanonicalPath()))) {
File candidate = new File(parent, path);
if (candidate.isFile() && candidate.getCanonicalPath().contains(parent.getCanonicalPath())) {
manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches));
}
}
@@ -313,15 +313,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
this.concurrencyThrottle.beforeAccess();
try {
doExecute(new TaskTrackingRunnable(taskToUse));
}
catch (Throwable ex) {
// Release concurrency permit if thread creation fails
this.concurrencyThrottle.afterAccess();
throw new TaskRejectedException(
"Failed to start execution thread for task: " + task, ex);
}
doExecute(new TaskTrackingRunnable(taskToUse));
}
else if (this.activeThreads != null) {
doExecute(new TaskTrackingRunnable(taskToUse));
@@ -1483,16 +1483,15 @@ public abstract class ClassUtils {
}
/**
* Get the closest publicly accessible method in the supplied method's type hierarchy that
* has a method signature equivalent to the supplied method, if possible.
* <p>This method recursively searches the class hierarchy and implemented interfaces for
* an equivalent method that is {@code public}, declared in a {@code public} type, and
* {@linkplain Module#isExported(String, Module) exported} to {@code spring-core}.
* <p>If the supplied method is not {@code public} or is {@code static}, or if a publicly
* accessible equivalent method cannot be found, the supplied method will be returned,
* indicating that no such equivalent method exists. Consequently, callers of this method
* must manually validate the accessibility of the returned method if public access is a
* requirement.
* Get the closest publicly accessible (and exported) method in the supplied method's type
* hierarchy that has a method signature equivalent to the supplied method, if possible.
* <p>Otherwise, this method recursively searches the class hierarchy and implemented
* interfaces for an equivalent method that is {@code public} and declared in a
* {@code public} type.
* <p>If a publicly accessible equivalent method cannot be found, the supplied method
* will be returned, indicating that no such equivalent method exists. Consequently,
* callers of this method must manually validate the accessibility of the returned method
* if public access is a requirement.
* <p>This is particularly useful for arriving at a public exported type on the Java
* Module System which allows the method to be invoked via reflection without an illegal
* access warning. This is also useful for invoking methods via a public API in bytecode
@@ -1509,11 +1508,10 @@ public abstract class ClassUtils {
*/
public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nullable Class<?> targetClass) {
Class<?> declaringClass = method.getDeclaringClass();
// If the method is not public, or it's static, or its declaring class is public and exported
// already, we can abort the search immediately (avoiding reflection as well as cache access).
if (!Modifier.isPublic(method.getModifiers()) || Modifier.isStatic(method.getModifiers()) ||
(Modifier.isPublic(declaringClass.getModifiers()) &&
declaringClass.getModule().isExported(declaringClass.getPackageName(), ClassUtils.class.getModule()))) {
// If the method is not public or its declaring class is public and exported already,
// we can abort the search immediately (avoiding reflection as well as cache access).
if (!Modifier.isPublic(method.getModifiers()) || (Modifier.isPublic(declaringClass.getModifiers()) &&
declaringClass.getModule().isExported(declaringClass.getPackageName(), ClassUtils.class.getModule()))) {
return method;
}
@@ -20,10 +20,8 @@ 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;
@@ -31,19 +29,15 @@ 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;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.lang.Nullable;
/**
* A {@link ConcurrentHashMap} variant that uses {@link ReferenceType#SOFT soft} or
* A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
* {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}.
*
* <p>This class can be used as an alternative to
@@ -108,18 +102,6 @@ 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.
@@ -383,118 +365,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
});
}
@Override
@Nullable
public V computeIfAbsent(@Nullable K key, Function<? super K, ? extends V> mappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
if (entry != null) {
return entry.getValue();
}
V value = mappingFunction.apply(key);
// Add entry only if not null
if (value != null) {
Assert.state(entries != null, "No entries segment");
entries.add(value);
}
return value;
}
});
}
@Override
@Nullable
public V computeIfPresent(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
if (entry != null) {
V oldValue = entry.getValue();
V value = remappingFunction.apply(key, oldValue);
if (value != null) {
// Replace entry
entry.setValue(value);
return value;
}
else {
// Remove entry
if (ref != null) {
ref.release();
}
}
}
return null;
}
});
}
@Override
@Nullable
public V compute(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
V oldValue = null;
if (entry != null) {
oldValue = entry.getValue();
}
V value = remappingFunction.apply(key, oldValue);
if (value != null) {
if (entry != null) {
// Replace entry
entry.setValue(value);
}
else {
// Add entry
Assert.state(entries != null, "No entries segment");
entries.add(value);
}
return value;
}
else {
// Remove entry
if (ref != null) {
ref.release();
}
}
return null;
}
});
}
@Override
@Nullable
public V merge(@Nullable K key, @Nullable V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
if (entry != null) {
V oldValue = entry.getValue();
V newValue = remappingFunction.apply(oldValue, value);
if (newValue != null) {
// Replace entry
entry.setValue(newValue);
return newValue;
}
else {
// Remove entry
if (ref != null) {
ref.release();
}
return null;
}
}
else {
// Add entry
Assert.state(entries != null, "No entries segment");
entries.add(value);
return value;
}
}
});
}
@Override
public void clear() {
for (Segment segment : this.segments) {
@@ -544,26 +414,6 @@ 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);
@@ -664,7 +514,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
* @return the result of the operation
*/
@Nullable
private <T> T doTask(final int hash, @Nullable final Object key, final Task<T> task) {
public <T> T doTask(final int hash, @Nullable final Object key, final Task<T> task) {
boolean resize = task.hasOption(TaskOption.RESIZE);
if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
restructureIfNecessary(resize);
@@ -729,6 +579,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
private void restructure(boolean allowResize, @Nullable Reference<K, V> ref) {
boolean needsResize;
lock();
try {
int expectedCount = this.count.get();
@@ -744,7 +595,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
// Estimate new count, taking into account count inside lock and items that
// will be purged.
boolean needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold);
needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold);
boolean resizing = false;
int restructureSize = this.references.length;
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
@@ -785,8 +636,8 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
while (ref != null) {
if (!toPurge.contains(ref)) {
Entry<K, V> entry = ref.get();
// Also filter out null references that are now null:
// They should be polled from the queue in a later restructure call.
// Also filter out null references that are now null
// they should be polled from the queue in a later restructure call.
if (entry != null) {
purgedRef = this.referenceManager.createReference(
entry, ref.getHash(), purgedRef);
@@ -798,7 +649,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
this.references[i] = purgedRef;
}
}
this.count.set(newCount);
this.count.set(Math.max(newCount, 0));
}
finally {
unlock();
@@ -1005,7 +856,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
/**
* Internal entry-set implementation.
*/
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
@@ -1041,140 +892,13 @@ 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 final class EntryIterator implements Iterator<Map.Entry<K, V>> {
private class EntryIterator implements Iterator<Map.Entry<K, V>> {
private int segmentIndex;
@@ -145,32 +145,26 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
@Override
public Set<K> keySet() {
Set<K> keySet = this.keySet;
if (keySet == null) {
keySet = Collections.unmodifiableSet(this.delegate.keySet());
this.keySet = keySet;
if (this.keySet == null) {
this.keySet = Collections.unmodifiableSet(this.delegate.keySet());
}
return keySet;
return this.keySet;
}
@Override
public Set<Entry<K, List<V>>> entrySet() {
Set<Entry<K, List<V>>> entrySet = this.entrySet;
if (entrySet == null) {
entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
this.entrySet = entrySet;
if (this.entrySet == null) {
this.entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
}
return entrySet;
return this.entrySet;
}
@Override
public Collection<List<V>> values() {
Collection<List<V>> values = this.values;
if (values == null) {
values = new UnmodifiableValueCollection<>(this.delegate.values());
this.values = values;
if (this.values == null) {
this.values = new UnmodifiableValueCollection<>(this.delegate.values());
}
return values;
return this.values;
}
// unsupported
@@ -30,10 +30,6 @@ final class VirtualThreadDelegate {
private final Thread.Builder threadBuilder = Thread.ofVirtual();
public VirtualThreadDelegate() {
// Matching constructor in dummy version, avoiding jar verification issues.
}
public ThreadFactory virtualThreadFactory() {
return this.threadBuilder.factory();
}
@@ -429,14 +429,14 @@ class BridgeMethodResolverTests {
}
public abstract static class AbstractAdder<T extends Serializable> implements Adder<T> {
public abstract static class AbstractDateAdder implements Adder<Date> {
@Override
public abstract void add(T item);
public abstract void add(Date date);
}
public static class DateAdder extends AbstractAdder<Date> {
public static class DateAdder extends AbstractDateAdder {
@Override
public void add(Date date) {
@@ -337,21 +337,6 @@ class PathMatchingResourcePatternResolverTests {
assertThat(result.replace("\\", "/")).contains("!!!!").contains("/lib/asset.jar!/assets/file.txt");
}
@Test
void javaDashJarFindsAbsoluteClassPathManifestEntries() throws Exception {
Path assetJar = this.temp.resolve("dependency").resolve("asset.jar");
Files.createDirectories(assetJar.getParent());
writeAssetJar(assetJar);
writeApplicationJarWithAbsolutePath(this.temp.resolve("app.jar"), assetJar);
String java = ProcessHandle.current().info().command().get();
Process process = new ProcessBuilder(java, "-jar", "app.jar")
.directory(this.temp.toFile())
.start();
assertThat(process.waitFor()).isZero();
String result = StreamUtils.copyToString(process.getInputStream(), StandardCharsets.UTF_8);
assertThat(result.replace("\\", "/")).contains("!!!!").contains("asset.jar!/assets/file.txt");
}
private void writeAssetJar(Path path) throws Exception {
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()))) {
jar.putNextEntry(new ZipEntry("assets/"));
@@ -407,35 +392,6 @@ class PathMatchingResourcePatternResolverTests {
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
}
private void writeApplicationJarWithAbsolutePath(Path path, Path assetJar) throws Exception {
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.put(Name.CLASS_PATH, buildSpringClassPath() + assetJar.toAbsolutePath());
mainAttributes.put(Name.MAIN_CLASS, ClassPathManifestEntriesTestApplication.class.getName());
mainAttributes.put(Name.MANIFEST_VERSION, "1.0");
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(path.toFile()), manifest)) {
String appClassResource = ClassUtils.convertClassNameToResourcePath(
ClassPathManifestEntriesTestApplication.class.getName()) + ClassUtils.CLASS_FILE_SUFFIX;
String folder = "";
for (String name : appClassResource.split("/")) {
if (!name.endsWith(ClassUtils.CLASS_FILE_SUFFIX)) {
folder += name + "/";
jar.putNextEntry(new ZipEntry(folder));
jar.closeEntry();
}
else {
jar.putNextEntry(new ZipEntry(folder + name));
try (InputStream in = getClass().getResourceAsStream(name)) {
in.transferTo(jar);
}
jar.closeEntry();
}
}
}
assertThat(new FileSystemResource(path).exists()).isTrue();
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
}
private String buildSpringClassPath() throws Exception {
return copyClasses(PathMatchingResourcePatternResolver.class, "spring-core") +
copyClasses(LogFactory.class, "commons-logging");
@@ -16,9 +16,6 @@
package org.springframework.core.task;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.util.ConcurrencyThrottleSupport;
@@ -27,12 +24,6 @@ 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.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
/**
* @author Rick Evans
@@ -78,59 +69,6 @@ class SimpleAsyncTaskExecutorTests {
}
}
/**
* Verify that when thread creation fails in doExecute() while concurrency
* limiting is active, the concurrency permit is properly released to
* prevent permanent deadlock.
*
* <p>This test reproduces a critical bug where OutOfMemoryError from
* Thread.start() causes the executor to permanently deadlock:
* <ol>
* <li>beforeAccess() increments concurrencyCount
* <li>doExecute() throws Error before thread starts
* <li>TaskTrackingRunnable.run() never executes
* <li>afterAccess() in finally block never called
* <li>Subsequent tasks block forever in onLimitReached()
* </ol>
*
* <p>Test approach: The first execute() should fail with some exception
* (type doesn't matter - could be Error or TaskRejectedException).
* The second execute() is the real test: it should complete without
* deadlock if the permit was properly released.
*/
@Test
void executeFailsToStartThreadReleasesConcurrencyPermit() throws InterruptedException {
// Arrange
SimpleAsyncTaskExecutor executor = spy(new SimpleAsyncTaskExecutor());
executor.setConcurrencyLimit(1); // Enable concurrency limiting
Runnable task = () -> {};
Error failure = new OutOfMemoryError("TEST: Cannot start thread");
// Simulate thread creation failure
doThrow(failure).when(executor).doExecute(any(Runnable.class));
// Act - First execution fails
// Both "before fix" (throws Error) and "after fix" (throws TaskRejectedException)
// should throw some exception here - that's expected and correct
assertThatThrownBy(() -> executor.execute(task))
.isInstanceOf(Throwable.class);
// Arrange - Reset mock to allow second execution to succeed
willCallRealMethod().given(executor).doExecute(any(Runnable.class));
// Assert - Second execution should NOT deadlock
// This is the real test: if permit was leaked, this will timeout
CountDownLatch latch = new CountDownLatch(1);
executor.execute(() -> latch.countDown());
boolean completed = latch.await(1, TimeUnit.SECONDS);
assertThat(completed)
.withFailMessage("Executor should not deadlock if concurrency permit was properly released after first failure")
.isTrue();
}
@Test
void threadNameGetsSetCorrectly() {
String customPrefix = "chankPop#";
@@ -868,49 +868,6 @@ class ClassUtilsTests {
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void staticMethodInPublicClass() throws Exception {
Method originalMethod = PublicSuperclass.class.getMethod("getCacheKey");
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void publicSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
Method originalMethod = PublicSubclass.class.getMethod("getCacheKey");
// Prerequisite: type must be public for this use case.
assertPublic(originalMethod.getDeclaringClass());
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void privateSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
Method originalMethod = PrivateSubclass.class.getMethod("getCacheKey");
// Prerequisite: type must not be public for this use case.
assertNotPublic(originalMethod.getDeclaringClass());
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertNotPubliclyAccessible(publiclyAccessibleMethod);
}
}
@@ -957,10 +914,6 @@ class ClassUtilsTests {
return Modifier.isPublic(member.getModifiers());
}
private static void assertStatic(Member member) {
assertThat(Modifier.isStatic(member.getModifiers())).as("%s must be static", member).isTrue();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@@ -1095,27 +1048,8 @@ class ClassUtilsTests {
String greet(String name);
}
public static class PublicSubclass extends PublicSuperclass {
/**
* This method intentionally has the exact same signature as
* {@link PublicSuperclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "child";
}
}
private static class PrivateSubclass extends PublicSuperclass implements PublicInterface, PrivateInterface {
/**
* This method intentionally has the exact same signature as
* {@link PublicSuperclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "child";
}
@Override
public int getNumber() {
return 2;
@@ -16,7 +16,8 @@
package org.springframework.util;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -24,8 +25,6 @@ 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;
@@ -33,13 +32,12 @@ 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}.
@@ -49,11 +47,13 @@ import static org.assertj.core.api.Assertions.entry;
*/
class ConcurrentReferenceHashMapTests {
private static final Comparator<? super String> NULL_SAFE_STRING_SORT = Comparators.nullsLow();
private TestWeakConcurrentCache<Integer, String> map = new TestWeakConcurrentCache<>();
@Test
void createWithDefaults() {
void shouldCreateWithDefaults() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>();
assertThat(map.getSegmentsSize()).isEqualTo(16);
assertThat(map.getSegment(0).getSize()).isEqualTo(1);
@@ -61,7 +61,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void createWithInitialCapacity() {
void shouldCreateWithInitialCapacity() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32);
assertThat(map.getSegmentsSize()).isEqualTo(16);
assertThat(map.getSegment(0).getSize()).isEqualTo(2);
@@ -69,7 +69,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void createWithInitialCapacityAndLoadFactor() {
void shouldCreateWithInitialCapacityAndLoadFactor() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32, 0.5f);
assertThat(map.getSegmentsSize()).isEqualTo(16);
assertThat(map.getSegment(0).getSize()).isEqualTo(2);
@@ -77,7 +77,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void createWithInitialCapacityAndConcurrentLevel() {
void shouldCreateWithInitialCapacityAndConcurrentLevel() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(16, 2);
assertThat(map.getSegmentsSize()).isEqualTo(2);
assertThat(map.getSegment(0).getSize()).isEqualTo(8);
@@ -85,7 +85,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void createFullyCustom() {
void shouldCreateFullyCustom() {
ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(5, 0.5f, 3);
// concurrencyLevel of 3 ends up as 4 (nearest power of 2)
assertThat(map.getSegmentsSize()).isEqualTo(4);
@@ -95,28 +95,28 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void nonNegativeInitialCapacity() {
void shouldNeedNonNegativeInitialCapacity() {
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 1));
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(-1, 1))
.withMessageContaining("Initial capacity must not be negative");
}
@Test
void positiveLoadFactor() {
void shouldNeedPositiveLoadFactor() {
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 0.1f, 1));
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(0, 0.0f, 1))
.withMessageContaining("Load factor must be positive");
}
@Test
void positiveConcurrencyLevel() {
void shouldNeedPositiveConcurrencyLevel() {
assertThatNoException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(1, 1));
assertThatIllegalArgumentException().isThrownBy(() -> new ConcurrentReferenceHashMap<Integer, String>(1, 0))
.withMessageContaining("Concurrency level must be positive");
}
@Test
void putAndGet() {
void shouldPutAndGet() {
// NOTE we are using mock references so we don't need to worry about GC
assertThat(this.map).isEmpty();
this.map.put(123, "123");
@@ -129,14 +129,14 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void replaceOnDoublePut() {
void shouldReplaceOnDoublePut() {
this.map.put(123, "321");
this.map.put(123, "123");
assertThat(this.map.get(123)).isEqualTo("123");
}
@Test
void putNullKey() {
void shouldPutNullKey() {
assertThat(this.map.get(null)).isNull();
assertThat(this.map.getOrDefault(null, "456")).isEqualTo("456");
this.map.put(null, "123");
@@ -145,7 +145,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void putNullValue() {
void shouldPutNullValue() {
assertThat(this.map.get(123)).isNull();
assertThat(this.map.getOrDefault(123, "456")).isEqualTo("456");
this.map.put(123, "321");
@@ -157,12 +157,12 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void getWithNoItems() {
void shouldGetWithNoItems() {
assertThat(this.map.get(123)).isNull();
}
@Test
void applySupplementalHash() {
void shouldApplySupplementalHash() {
Integer key = 123;
this.map.put(key, "123");
assertThat(this.map.getSupplementalHash()).isNotEqualTo(key.hashCode());
@@ -170,7 +170,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void getFollowingNexts() {
void shouldGetFollowingNexts() {
// Use loadFactor to disable resize
this.map = new TestWeakConcurrentCache<>(1, 10.0f, 1);
this.map.put(1, "1");
@@ -184,7 +184,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void resize() {
void shouldResize() {
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
this.map.put(1, "1");
assertThat(this.map.getSegment(0).getSize()).isEqualTo(1);
@@ -214,7 +214,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void purgeOnGet() {
void shouldPurgeOnGet() {
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
for (int i = 1; i <= 5; i++) {
this.map.put(i, String.valueOf(i));
@@ -229,7 +229,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void purgeOnPut() {
void shouldPurgeOnPut() {
this.map = new TestWeakConcurrentCache<>(1, 0.75f, 1);
for (int i = 1; i <= 5; i++) {
this.map.put(i, String.valueOf(i));
@@ -245,28 +245,28 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void putIfAbsent() {
void shouldPutIfAbsent() {
assertThat(this.map.putIfAbsent(123, "123")).isNull();
assertThat(this.map.putIfAbsent(123, "123b")).isEqualTo("123");
assertThat(this.map.get(123)).isEqualTo("123");
}
@Test
void putIfAbsentWithNullValue() {
void shouldPutIfAbsentWithNullValue() {
assertThat(this.map.putIfAbsent(123, null)).isNull();
assertThat(this.map.putIfAbsent(123, "123")).isNull();
assertThat(this.map.get(123)).isNull();
}
@Test
void putIfAbsentWithNullKey() {
void shouldPutIfAbsentWithNullKey() {
assertThat(this.map.putIfAbsent(null, "123")).isNull();
assertThat(this.map.putIfAbsent(null, "123b")).isEqualTo("123");
assertThat(this.map.get(null)).isEqualTo("123");
}
@Test
void removeKeyAndValue() {
void shouldRemoveKeyAndValue() {
this.map.put(123, "123");
assertThat(this.map.remove(123, "456")).isFalse();
assertThat(this.map.get(123)).isEqualTo("123");
@@ -276,7 +276,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void removeKeyAndValueWithExistingNull() {
void shouldRemoveKeyAndValueWithExistingNull() {
this.map.put(123, null);
assertThat(this.map.remove(123, "456")).isFalse();
assertThat(this.map.get(123)).isNull();
@@ -286,7 +286,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void replaceOldValueWithNewValue() {
void shouldReplaceOldValueWithNewValue() {
this.map.put(123, "123");
assertThat(this.map.replace(123, "456", "789")).isFalse();
assertThat(this.map.get(123)).isEqualTo("123");
@@ -295,7 +295,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void replaceOldNullValueWithNewValue() {
void shouldReplaceOldNullValueWithNewValue() {
this.map.put(123, null);
assertThat(this.map.replace(123, "456", "789")).isFalse();
assertThat(this.map.get(123)).isNull();
@@ -304,61 +304,21 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void replaceValue() {
void shouldReplaceValue() {
this.map.put(123, "123");
assertThat(this.map.replace(123, "456")).isEqualTo("123");
assertThat(this.map.get(123)).isEqualTo("456");
}
@Test
void replaceNullValue() {
void shouldReplaceNullValue() {
this.map.put(123, null);
assertThat(this.map.replace(123, "456")).isNull();
assertThat(this.map.get(123)).isEqualTo("456");
}
@Test
void computeIfAbsent() {
assertThat(this.map.computeIfAbsent(123, k -> "123")).isEqualTo("123");
assertThat(this.map.computeIfAbsent(123, k -> "123b")).isEqualTo("123");
assertThat(this.map.get(123)).isEqualTo("123");
this.map.remove(123);
assertThat(this.map.computeIfAbsent(123, k -> null)).isNull();
assertThat(this.map.containsKey(123)).isFalse();
}
@Test
void computeIfPresent() {
assertThat(this.map.computeIfPresent(123, (k, v) -> "123")).isNull();
this.map.put(123, "123");
assertThat(this.map.computeIfPresent(123, (k, v) -> v + "b")).isEqualTo("123b");
assertThat(this.map.get(123)).isEqualTo("123b");
assertThat(this.map.computeIfPresent(123, (k, v) -> null)).isNull();
assertThat(this.map.containsKey(123)).isFalse();
}
@Test
void compute() {
assertThat(this.map.compute(123, (k, v) -> "123" + v)).isEqualTo("123null");
assertThat(this.map.compute(123, (k, v) -> null)).isNull();
assertThat(this.map.compute(123, (k, v) -> null)).isNull();
assertThat(this.map.compute(123, (k, v) -> "123")).isEqualTo("123");
assertThat(this.map.compute(123, (k, v) -> v + "b")).isEqualTo("123b");
assertThat(this.map.get(123)).isEqualTo("123b");
}
@Test
void merge() {
assertThat(this.map.merge(123, "123", (v1, v2) -> v1 + v2)).isEqualTo("123");
assertThat(this.map.merge(123, null, (v1, v2) -> v1 + v2)).isEqualTo("123null");
assertThat(this.map.merge(123, null, (v1, v2) -> null)).isNull();
assertThat(this.map.merge(123, "123", (v1, v2) -> v1 + v2)).isEqualTo("123");
assertThat(this.map.merge(123, "b", (v1, v2) -> v1 + v2)).isEqualTo("123b");
assertThat(this.map.get(123)).isEqualTo("123b");
}
@Test
void size() {
void shouldGetSize() {
assertThat(this.map).isEmpty();
this.map.put(123, "123");
this.map.put(123, null);
@@ -367,7 +327,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void isEmpty() {
void shouldSupportIsEmpty() {
assertThat(this.map).isEmpty();
this.map.put(123, "123");
this.map.put(123, null);
@@ -376,7 +336,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void containsKey() {
void shouldContainKey() {
assertThat(this.map.containsKey(123)).isFalse();
assertThat(this.map.containsKey(456)).isFalse();
this.map.put(123, "123");
@@ -386,7 +346,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void containsValue() {
void shouldContainValue() {
assertThat(this.map.containsValue("123")).isFalse();
assertThat(this.map.containsValue(null)).isFalse();
this.map.put(123, "123");
@@ -396,7 +356,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void removeWhenKeyIsInMap() {
void shouldRemoveWhenKeyIsInMap() {
this.map.put(123, null);
this.map.put(456, "456");
this.map.put(null, "789");
@@ -407,14 +367,14 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void removeWhenKeyIsNotInMap() {
void shouldRemoveWhenKeyIsNotInMap() {
assertThat(this.map.remove(123)).isNull();
assertThat(this.map.remove(null)).isNull();
assertThat(this.map).isEmpty();
}
@Test
void putAll() {
void shouldPutAll() {
Map<Integer, String> m = new HashMap<>();
m.put(123, "123");
m.put(456, null);
@@ -427,7 +387,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void clear() {
void shouldClear() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
@@ -439,7 +399,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void keySet() {
void shouldGetKeySet() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
@@ -450,178 +410,23 @@ class ConcurrentReferenceHashMapTests {
assertThat(this.map.keySet()).isEqualTo(expected);
}
@Test // gh-35817
void keySetContains() {
@Test
void shouldGetValues() {
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);
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);
}
@Test
void valuesCollection() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
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
void getEntrySet() {
void shouldGetEntrySet() {
this.map.put(123, "123");
this.map.put(456, null);
this.map.put(null, "789");
@@ -633,7 +438,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void getEntrySetFollowingNext() {
void shouldGetEntrySetFollowingNext() {
// Use loadFactor to disable resize
this.map = new TestWeakConcurrentCache<>(1, 10.0f, 1);
this.map.put(1, "1");
@@ -647,7 +452,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void removeViaEntrySet() {
void shouldRemoveViaEntrySet() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
@@ -663,7 +468,7 @@ class ConcurrentReferenceHashMapTests {
}
@Test
void setViaEntrySet() {
void shouldSetViaEntrySet() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
@@ -696,19 +501,8 @@ 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() {
void shouldSupportNullReference() {
// GC could happen during restructure so we must be able to create a reference for a null entry
map.createReferenceManager().createReference(null, 1234, null);
}
@@ -21,15 +21,6 @@ package org.springframework.util;
*/
public class PublicSuperclass {
/**
* This method intentionally has the exact same signature as
* {@link org.springframework.util.ClassUtilsTests.PublicSubclass#getCacheKey()} and
* {@link org.springframework.util.ClassUtilsTests.PrivateSubclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "parent";
}
public String getMessage() {
return "goodbye";
}
@@ -18,10 +18,8 @@ package org.springframework.jdbc.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.AbstractFactoryBean;
@@ -74,26 +72,17 @@ public class SortedResourcesFactoryBean extends AbstractFactoryBean<Resource[]>
protected Resource[] createInstance() throws Exception {
List<Resource> scripts = 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);
for (Resource resource : resources) {
List<Resource> resources = new ArrayList<>(
Arrays.asList(this.resourcePatternResolver.getResources(location)));
resources.sort((r1, r2) -> {
try {
urlCache.put(resource, resource.getURL().toString());
return r1.getURL().toString().compareTo(r2.getURL().toString());
}
catch (IOException ex) {
throw new IllegalStateException(
"Failed to resolve URL for resource [" + resource +
"] from location pattern [" + location + "]", ex);
return 0;
}
}
// Sort using cached URLs
List<Resource> sortedResources = new ArrayList<>(urlCache.keySet());
sortedResources.sort(Comparator.comparing(urlCache::get));
scripts.addAll(sortedResources);
});
scripts.addAll(resources);
}
return scripts.toArray(new Resource[0]);
}
@@ -40,10 +40,11 @@ import org.springframework.util.Assert;
* @author Rod Johnson
* @author Juergen Hoeller
* @see #loadBeanDefinitions
* @deprecated in favor of Spring's common bean definition formats and/or
* custom BeanDefinitionReader implementations
* @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(since = "5.3")
@Deprecated
public class JdbcBeanDefinitionReader {
private final org.springframework.beans.factory.support.PropertiesBeanDefinitionReader propReader;
@@ -126,8 +126,6 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
private boolean enforceReadOnly = false;
private volatile @Nullable Boolean defaultReadOnly;
/**
* Create a new {@code DataSourceTransactionManager} instance.
@@ -272,18 +270,13 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
if (definition.isReadOnly()) {
checkDefaultReadOnly(newCon);
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,
definition.getIsolationLevel(),
(definition.isReadOnly() && !isDefaultReadOnly()));
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
@@ -388,9 +381,8 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
if (txObject.isMustRestoreAutoCommit()) {
con.setAutoCommit(true);
}
DataSourceUtils.resetConnectionAfterTransaction(con,
txObject.getPreviousIsolationLevel(),
(txObject.isReadOnly() && !isDefaultReadOnly()));
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
@@ -407,37 +399,6 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
}
/**
* Check the default {@link Connection#isReadOnly()} flag on a freshly
* obtained connection from the {@code DataSource}, assuming that the
* same flag applies to all connections obtained from the given setup.
* @param newCon the Connection to check
* @since 6.2.13
* @see #isDefaultReadOnly()
*/
private void checkDefaultReadOnly(Connection newCon) {
if (this.defaultReadOnly == null) {
try {
this.defaultReadOnly = newCon.isReadOnly();
}
catch (Throwable ex) {
logger.debug("Could not determine default JDBC Connection isReadOnly - assuming false", ex);
this.defaultReadOnly = false;
}
}
}
/**
* Check whether the default read-only flag has been determined as {@code true},
* assuming that all encountered connections will be read-only by default and
* therefore do not need explicit {@link Connection#setReadOnly} (re)setting.
* @since 6.2.13
* @see #checkDefaultReadOnly(Connection)
*/
private boolean isDefaultReadOnly() {
return (this.defaultReadOnly == Boolean.TRUE);
}
/**
* Prepare the transactional {@code Connection} right after transaction begin.
* <p>The default implementation executes a "SET TRANSACTION READ ONLY" statement
@@ -170,38 +170,19 @@ public abstract class DataSourceUtils {
* @param definition the transaction definition to apply
* @return the previous isolation level, if any
* @throws SQLException if thrown by JDBC methods
* @see #prepareConnectionForTransaction(Connection, int, boolean)
*/
@Nullable
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
throws SQLException {
return prepareConnectionForTransaction(con,
(definition != null ? definition.getIsolationLevel() : TransactionDefinition.ISOLATION_DEFAULT),
(definition != null && definition.isReadOnly()));
}
/**
* Prepare the given Connection with the given transaction semantics.
* @param con the Connection to prepare
* @param isolationLevel the isolation level to apply
* @param setReadOnly whether to set the read-only flag
* @return the previous isolation level, if any
* @throws SQLException if thrown by JDBC methods
* @since 6.2.13
* @see #resetConnectionAfterTransaction(Connection, Integer, boolean)
* @see #resetConnectionAfterTransaction
* @see Connection#setTransactionIsolation
* @see Connection#setReadOnly
*/
@Nullable
static Integer prepareConnectionForTransaction(Connection con, int isolationLevel, boolean setReadOnly)
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
throws SQLException {
Assert.notNull(con, "No Connection specified");
boolean debugEnabled = logger.isDebugEnabled();
// Set read-only flag.
if (setReadOnly) {
if (definition != null && definition.isReadOnly()) {
try {
if (debugEnabled) {
logger.debug("Setting JDBC Connection [" + con + "] read-only");
@@ -224,14 +205,15 @@ public abstract class DataSourceUtils {
// Apply specific isolation level, if any.
Integer previousIsolationLevel = null;
if (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
if (debugEnabled) {
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " + isolationLevel);
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
definition.getIsolationLevel());
}
int currentIsolation = con.getTransactionIsolation();
if (currentIsolation != isolationLevel) {
if (currentIsolation != definition.getIsolationLevel()) {
previousIsolationLevel = currentIsolation;
con.setTransactionIsolation(isolationLevel);
con.setTransactionIsolation(definition.getIsolationLevel());
}
}
@@ -153,9 +153,6 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
*/
public void setReadOnlyDataSource(@Nullable DataSource readOnlyDataSource) {
this.readOnlyDataSource = readOnlyDataSource;
if (getTargetDataSource() == null) {
setTargetDataSource(readOnlyDataSource);
}
}
/**
@@ -398,7 +395,7 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
return null;
}
case "isReadOnly" -> {
return (this.readOnly || getTargetDataSource() == readOnlyDataSource);
return this.readOnly;
}
case "setReadOnly" -> {
this.readOnly = (Boolean) args[0];
@@ -1,40 +0,0 @@
/*
* Copyright 2025-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.support;
import java.sql.Types;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} implementation that registers runtime hints for
* {@link JdbcUtils}.
*
* @author Brian Clozel
* @since 6.2.13
*/
class JdbcUtilsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerType(Types.class, MemberCategory.PUBLIC_FIELDS);
}
}
@@ -80,43 +80,43 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
if (sqlEx instanceof SQLTransientException) {
if (sqlEx instanceof SQLTransientConnectionException) {
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), ex);
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
}
if (sqlEx instanceof SQLTransactionRollbackException) {
if (SQLStateSQLExceptionTranslator.indicatesCannotAcquireLock(sqlEx.getSQLState())) {
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), ex);
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
}
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), ex);
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), sqlEx);
}
if (sqlEx instanceof SQLTimeoutException) {
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex);
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx);
}
}
else if (sqlEx instanceof SQLNonTransientException) {
if (sqlEx instanceof SQLNonTransientConnectionException) {
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), ex);
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
}
if (sqlEx instanceof SQLDataException) {
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex);
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
}
if (sqlEx instanceof SQLIntegrityConstraintViolationException) {
if (SQLStateSQLExceptionTranslator.indicatesDuplicateKey(sqlEx.getSQLState(), sqlEx.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), ex);
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
}
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex);
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
}
if (sqlEx instanceof SQLInvalidAuthorizationSpecException) {
return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), ex);
return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
}
if (sqlEx instanceof SQLSyntaxErrorException) {
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
}
if (sqlEx instanceof SQLFeatureNotSupportedException) {
return new InvalidDataAccessApiUsageException(buildMessage(task, sql, sqlEx), ex);
return new InvalidDataAccessApiUsageException(buildMessage(task, sql, sqlEx), sqlEx);
}
}
else if (sqlEx instanceof SQLRecoverableException) {
return new RecoverableDataAccessException(buildMessage(task, sql, sqlEx), ex);
return new RecoverableDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
}
// Fallback to Spring's own SQL state translation...
@@ -131,35 +131,35 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
logger.debug("Extracted SQL state class '" + classCode + "' from value '" + sqlState + "'");
}
if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) {
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
}
else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) {
if (indicatesDuplicateKey(sqlState, sqlEx.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), ex);
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
}
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex);
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
}
else if (PESSIMISTIC_LOCKING_FAILURE_CODES.contains(classCode)) {
if (indicatesCannotAcquireLock(sqlState)) {
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), ex);
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
}
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), ex);
return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), sqlEx);
}
else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) {
if (indicatesQueryTimeout(sqlState)) {
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex);
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx);
}
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), ex);
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
}
else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) {
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), ex);
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
}
}
// For MySQL: exception class name indicating a timeout?
// (since MySQL doesn't throw the JDBC 4 SQLTimeoutException)
if (sqlEx.getClass().getName().contains("Timeout")) {
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex);
return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx);
}
// Couldn't resolve anything proper - resort to UncategorizedSQLException.
@@ -1,3 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryRuntimeHints,\
org.springframework.jdbc.support.JdbcUtilsRuntimeHints
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryRuntimeHints
@@ -25,7 +25,9 @@ import org.mockito.InOrder;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.jdbc.datasource.DataSourceTransactionManagerTests;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
@@ -51,30 +53,36 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
@Override
@Test
protected void transactionWithExceptionOnCommit() throws Exception {
protected void testTransactionWithExceptionOnCommit() throws Exception {
willThrow(new SQLException("Cannot commit")).given(con).commit();
TransactionTemplate tt = new TransactionTemplate(tm);
// plain TransactionSystemException
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() ->
tt.executeWithoutResult(status -> {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
}));
}
}));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
verify(con).close();
}
@Test
void transactionWithDataAccessExceptionOnCommit() throws Exception {
void testTransactionWithDataAccessExceptionOnCommit() throws Exception {
willThrow(new SQLException("Cannot commit")).given(con).commit();
((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task));
TransactionTemplate tt = new TransactionTemplate(tm);
// specific ConcurrencyFailureException
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
tt.executeWithoutResult(status -> {
// something transactional
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
}
}));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
@@ -82,14 +90,17 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
}
@Test
void transactionWithDataAccessExceptionOnCommitFromLazyExceptionTranslator() throws Exception {
void testTransactionWithDataAccessExceptionOnCommitFromLazyExceptionTranslator() throws Exception {
willThrow(new SQLException("Cannot commit", "40")).given(con).commit();
TransactionTemplate tt = new TransactionTemplate(tm);
// specific ConcurrencyFailureException
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
tt.executeWithoutResult(status -> {
// something transactional
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
}
}));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
@@ -98,7 +109,7 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
@Override
@Test
protected void transactionWithExceptionOnCommitAndRollbackOnCommitFailure() throws Exception {
protected void testTransactionWithExceptionOnCommitAndRollbackOnCommitFailure() throws Exception {
willThrow(new SQLException("Cannot commit")).given(con).commit();
tm.setRollbackOnCommitFailure(true);
@@ -106,9 +117,12 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
// plain TransactionSystemException
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() ->
tt.executeWithoutResult(status -> {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// something transactional
}));
}
}));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
verify(con).rollback();
@@ -117,14 +131,16 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
@Override
@Test
protected void transactionWithExceptionOnRollback() throws Exception {
protected void testTransactionWithExceptionOnRollback() throws Exception {
given(con.getAutoCommit()).willReturn(true);
willThrow(new SQLException("Cannot rollback")).given(con).rollback();
TransactionTemplate tt = new TransactionTemplate(tm);
// plain TransactionSystemException
assertThatExceptionOfType(TransactionSystemException.class).isThrownBy(() ->
tt.executeWithoutResult(status -> {
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertThat(status.getTransactionName()).isEmpty();
assertThat(status.hasTransaction()).isTrue();
assertThat(status.isNewTransaction()).isTrue();
@@ -135,6 +151,31 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
status.setRollbackOnly();
assertThat(status.isRollbackOnly()).isTrue();
assertThat(status.isCompleted()).isFalse();
}
}));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
InOrder ordered = inOrder(con);
ordered.verify(con).setAutoCommit(false);
ordered.verify(con).rollback();
ordered.verify(con).setAutoCommit(true);
verify(con).close();
}
@Test
void testTransactionWithDataAccessExceptionOnRollback() throws Exception {
given(con.getAutoCommit()).willReturn(true);
willThrow(new SQLException("Cannot rollback")).given(con).rollback();
((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task));
TransactionTemplate tt = new TransactionTemplate(tm);
// specific ConcurrencyFailureException
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
status.setRollbackOnly();
}
}));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
@@ -146,43 +187,27 @@ class JdbcTransactionManagerTests extends DataSourceTransactionManagerTests {
}
@Test
void transactionWithDataAccessExceptionOnRollback() throws Exception {
given(con.getAutoCommit()).willReturn(true);
willThrow(new SQLException("Cannot rollback")).given(con).rollback();
((JdbcTransactionManager) tm).setExceptionTranslator((task, sql, ex) -> new ConcurrencyFailureException(task));
TransactionTemplate tt = new TransactionTemplate(tm);
// specific ConcurrencyFailureException
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
tt.executeWithoutResult(status -> status.setRollbackOnly()));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
InOrder ordered = inOrder(con);
ordered.verify(con).setAutoCommit(false);
ordered.verify(con).rollback();
ordered.verify(con).setAutoCommit(true);
verify(con).close();
}
@Test
void transactionWithDataAccessExceptionOnRollbackFromLazyExceptionTranslator() throws Exception {
void testTransactionWithDataAccessExceptionOnRollbackFromLazyExceptionTranslator() throws Exception {
given(con.getAutoCommit()).willReturn(true);
willThrow(new SQLException("Cannot rollback", "40")).given(con).rollback();
TransactionTemplate tt = new TransactionTemplate(tm);
// specific ConcurrencyFailureException
assertThatExceptionOfType(ConcurrencyFailureException.class).isThrownBy(() ->
tt.executeWithoutResult(status -> {
assertThat(status.getTransactionName()).isEmpty();
assertThat(status.hasTransaction()).isTrue();
assertThat(status.isNewTransaction()).isTrue();
assertThat(status.isNested()).isFalse();
assertThat(status.hasSavepoint()).isFalse();
assertThat(status.isReadOnly()).isFalse();
assertThat(status.isRollbackOnly()).isFalse();
status.setRollbackOnly();
assertThat(status.isRollbackOnly()).isTrue();
assertThat(status.isCompleted()).isFalse();
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
assertThat(status.getTransactionName()).isEmpty();
assertThat(status.hasTransaction()).isTrue();
assertThat(status.isNewTransaction()).isTrue();
assertThat(status.isNested()).isFalse();
assertThat(status.hasSavepoint()).isFalse();
assertThat(status.isReadOnly()).isFalse();
assertThat(status.isRollbackOnly()).isFalse();
status.setRollbackOnly();
assertThat(status.isRollbackOnly()).isTrue();
assertThat(status.isCompleted()).isFalse();
}
}));
assertThat(TransactionSynchronizationManager.hasResource(ds)).isFalse();
@@ -1,55 +0,0 @@
/*
* Copyright 2025-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.support;
import java.lang.reflect.Field;
import java.sql.Types;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.aot.AotServices;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JdbcUtilsRuntimeHints}.
*/
class JdbcUtilsRuntimeHintsTests {
private final RuntimeHints hints = new RuntimeHints();
@BeforeEach
void setup() {
AotServices.factories().load(RuntimeHintsRegistrar.class)
.forEach(registrar -> registrar.registerHints(this.hints,
ClassUtils.getDefaultClassLoader()));
}
@Test
void sqlTypesShouldHaveFieldAccess() {
for (Field field : Types.class.getFields()) {
assertThat(RuntimeHintsPredicates.reflection()
.onField(field)).accepts(this.hints);
}
}
}
@@ -181,7 +181,8 @@ class SQLStateSQLExceptionTranslatorTests {
static void assertTranslation(DataAccessException dae, SQLException ex, Class<?> dataAccessExceptionType) {
assertThat(dae).as("Specific translation must not result in null").isNotNull();
assertThat(dae).as("Wrong DataAccessException type returned").isExactlyInstanceOf(dataAccessExceptionType);
assertThat(dae.getCause()).as("The exact same original SQLException must be preserved").isSameAs(ex);
assertThat(dae.getCause()).as("The exact same original SQLException must be preserved").isSameAs(
ex instanceof BatchUpdateException bue ? bue.getNextException() : ex);
}
static BatchUpdateException buildBatchUpdateException(@Nullable String sqlState, SQLException next) {
@@ -398,16 +398,15 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
* Determine the PersistenceUnitInfo to use for the EntityManagerFactory
* created by this bean.
* <p>The default implementation reads in all persistence unit infos from
* {@code persistence.xml}, as defined in the JPA specification, selecting a unit
* by name. If no persistence unit name was specified, it takes the default one
* if configured, or otherwise the first persistence unit as found by the reader.
* {@code persistence.xml}, as defined in the JPA specification.
* If no entity manager name was specified, it takes the first info in the
* array as returned by the reader. Otherwise, it checks for a matching name.
* @param persistenceUnitManager the PersistenceUnitManager to obtain from
* @return the chosen PersistenceUnitInfo
*/
protected PersistenceUnitInfo determinePersistenceUnitInfo(PersistenceUnitManager persistenceUnitManager) {
String persistenceUnitName = getPersistenceUnitName();
if (persistenceUnitName != null) {
return persistenceUnitManager.obtainPersistenceUnitInfo(persistenceUnitName);
if (getPersistenceUnitName() != null) {
return persistenceUnitManager.obtainPersistenceUnitInfo(getPersistenceUnitName());
}
else {
return persistenceUnitManager.obtainDefaultPersistenceUnitInfo();
@@ -193,7 +193,7 @@ public class DefaultPersistenceUnitManager
/**
* Set the {@link PersistenceManagedTypes} to use to build the list of managed types
* for the default persistence unit, as an alternative to entity scanning.
* as an alternative to entity scanning.
* @param managedTypes the managed types
* @since 6.0
*/
@@ -540,33 +540,33 @@ public class DefaultPersistenceUnitManager
* @see #setPackagesToScan
*/
private SpringPersistenceUnitInfo buildDefaultPersistenceUnitInfo() {
SpringPersistenceUnitInfo defaultUnit = new SpringPersistenceUnitInfo();
SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo();
if (this.defaultPersistenceUnitName != null) {
defaultUnit.setPersistenceUnitName(this.defaultPersistenceUnitName);
scannedUnit.setPersistenceUnitName(this.defaultPersistenceUnitName);
}
defaultUnit.setExcludeUnlistedClasses(true);
scannedUnit.setExcludeUnlistedClasses(true);
if (this.managedTypes != null) {
applyManagedTypes(defaultUnit, this.managedTypes);
applyManagedTypes(scannedUnit, this.managedTypes);
}
else if (this.packagesToScan != null) {
PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(
this.resourcePatternResolver, this.managedClassNameFilter);
applyManagedTypes(defaultUnit, scanner.scan(this.packagesToScan));
applyManagedTypes(scannedUnit, scanner.scan(this.packagesToScan));
}
if (this.mappingResources != null) {
for (String mappingFileName : this.mappingResources) {
defaultUnit.addMappingFileName(mappingFileName);
scannedUnit.addMappingFileName(mappingFileName);
}
}
else {
Resource ormXml = getOrmXmlForDefaultPersistenceUnit();
if (ormXml != null) {
defaultUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
if (defaultUnit.getPersistenceUnitRootUrl() == null) {
scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
if (scannedUnit.getPersistenceUnitRootUrl() == null) {
try {
defaultUnit.setPersistenceUnitRootUrl(
scannedUnit.setPersistenceUnitRootUrl(
PersistenceUnitReader.determinePersistenceUnitRootUrl(ormXml));
}
catch (IOException ex) {
@@ -576,7 +576,7 @@ public class DefaultPersistenceUnitManager
}
}
return defaultUnit;
return scannedUnit;
}
private void applyManagedTypes(SpringPersistenceUnitInfo scannedUnit, PersistenceManagedTypes managedTypes) {
@@ -639,9 +639,9 @@ public class DefaultPersistenceUnitManager
/**
* Return the specified {@link MutablePersistenceUnitInfo} from this manager's cache
* of processed persistence units, keeping it in the cache (i.e. not 'obtaining' it
* for use but rather just accessing it for post-processing).
* Return the specified PersistenceUnitInfo from this manager's cache
* of processed persistence units, keeping it in the cache (i.e. not
* 'obtaining' it for use but rather just accessing it for post-processing).
* <p>This can be used in {@link #postProcessPersistenceUnitInfo} implementations,
* detecting existing persistence units of the same name and potentially merging them.
* @param persistenceUnitName the name of the desired persistence unit
@@ -654,12 +654,12 @@ public class DefaultPersistenceUnitManager
}
/**
* Hook method allowing subclasses to customize each {@link MutablePersistenceUnitInfo}.
* Hook method allowing subclasses to customize each PersistenceUnitInfo.
* <p>The default implementation delegates to all registered PersistenceUnitPostProcessors.
* It is usually preferable to register further entity classes, jar files etc there
* rather than in a subclass of this manager, to be able to reuse the post-processors.
* @param pui the chosen persistence unit configuration, as read from
* {@code persistence.xml}. Passed in as MutablePersistenceUnitInfo.
* @param pui the chosen PersistenceUnitInfo, as read from {@code persistence.xml}.
* Passed in as MutablePersistenceUnitInfo.
* @see #setPersistenceUnitPostProcessors
*/
protected void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
@@ -33,10 +33,9 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Spring's mutable equivalent of the JPA
* Spring's base implementation of the JPA
* {@link jakarta.persistence.spi.PersistenceUnitInfo} interface,
* used to bootstrap an {@code EntityManagerFactory} in a container.
* This is the type exposed to {@link PersistenceUnitPostProcessor}.
*
* <p>This implementation is largely a JavaBean, offering mutators
* for all standard {@code PersistenceUnitInfo} properties.
@@ -58,10 +57,10 @@ public class MutablePersistenceUnitInfo implements SmartPersistenceUnitInfo {
private PersistenceUnitTransactionType transactionType;
@Nullable
private DataSource jtaDataSource;
private DataSource nonJtaDataSource;
@Nullable
private DataSource nonJtaDataSource;
private DataSource jtaDataSource;
private final List<String> mappingFileNames = new ArrayList<>();
@@ -57,7 +57,6 @@ public interface PersistenceManagedTypes {
@Nullable
URL getPersistenceUnitRootUrl();
/**
* Create an instance using the specified managed class names.
* @param managedClassNames the managed class names
@@ -64,13 +64,14 @@ import org.springframework.util.ReflectionUtils;
* @author Sebastien Deleuze
* @since 6.0
*/
@SuppressWarnings("unchecked")
class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
private static final boolean jpaPresent = ClassUtils.isPresent("jakarta.persistence.Entity",
PersistenceManagedTypesBeanRegistrationAotProcessor.class.getClassLoader());
@Override
@Nullable
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (jpaPresent) {
if (PersistenceManagedTypes.class.isAssignableFrom(registeredBean.getBeanClass())) {
@@ -81,12 +82,12 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
return null;
}
private static final class JpaManagedTypesBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator {
private static final List<Class<? extends Annotation>> CALLBACK_TYPES = List.of(PreUpdate.class,
PostUpdate.class, PrePersist.class, PostPersist.class, PreRemove.class, PostRemove.class, PostLoad.class);
private static final ParameterizedTypeName LIST_OF_STRINGS_TYPE = ParameterizedTypeName.get(List.class, String.class);
private final RegisteredBean registeredBean;
@@ -101,8 +102,8 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
PersistenceManagedTypes persistenceManagedTypes = this.registeredBean.getBeanFactory()
.getBean(this.registeredBean.getBeanName(), PersistenceManagedTypes.class);
contributeHints(generationContext.getRuntimeHints(),
@@ -137,10 +138,9 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
contributeConverterHints(hints, managedClass);
contributeCallbackHints(hints, managedClass);
contributeHibernateHints(hints, classLoader, managedClass);
contributePackagePrivateHints(hints, managedClass);
}
catch (ClassNotFoundException ex) {
throw new IllegalArgumentException("Failed to instantiate JPA managed class: " + managedClassName, ex);
throw new IllegalArgumentException("Failed to instantiate the managed class: " + managedClassName, ex);
}
}
}
@@ -149,8 +149,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
EntityListeners entityListeners = AnnotationUtils.findAnnotation(managedClass, EntityListeners.class);
if (entityListeners != null) {
for (Class<?> entityListener : entityListeners.value()) {
hints.reflection().registerType(entityListener,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
hints.reflection().registerType(entityListener, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
}
}
}
@@ -170,14 +169,12 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
}
Convert convertClassAnnotation = AnnotationUtils.findAnnotation(managedClass, Convert.class);
if (convertClassAnnotation != null) {
reflectionHints.registerType(convertClassAnnotation.converter(),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
reflectionHints.registerType(convertClassAnnotation.converter(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
ReflectionUtils.doWithFields(managedClass, field -> {
Convert convertFieldAnnotation = AnnotationUtils.findAnnotation(field, Convert.class);
if (convertFieldAnnotation != null && convertFieldAnnotation.converter() != void.class) {
reflectionHints.registerType(convertFieldAnnotation.converter(),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
reflectionHints.registerType(convertFieldAnnotation.converter(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
});
}
@@ -189,11 +186,11 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
method -> CALLBACK_TYPES.stream().anyMatch(method::isAnnotationPresent));
}
@SuppressWarnings("unchecked")
private void contributeHibernateHints(RuntimeHints hints, @Nullable ClassLoader classLoader, Class<?> managedClass) {
ReflectionHints reflection = hints.reflection();
Class<? extends Annotation> embeddableInstantiatorClass =
loadClass("org.hibernate.annotations.EmbeddableInstantiator", classLoader);
Class<? extends Annotation> embeddableInstantiatorClass = loadClass("org.hibernate.annotations.EmbeddableInstantiator", classLoader);
if (embeddableInstantiatorClass != null) {
registerForReflection(reflection,
AnnotationUtils.findAnnotation(managedClass, embeddableInstantiatorClass), "value");
@@ -207,8 +204,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
AnnotationUtils.findAnnotation(method, embeddableInstantiatorClass), "value"));
}
Class<? extends Annotation> valueGenerationTypeClass =
loadClass("org.hibernate.annotations.ValueGenerationType", classLoader);
Class<? extends Annotation> valueGenerationTypeClass = loadClass("org.hibernate.annotations.ValueGenerationType", classLoader);
if (valueGenerationTypeClass != null) {
ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection,
AnnotationUtils.findAnnotation(field, valueGenerationTypeClass), "generatedBy"));
@@ -216,8 +212,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
AnnotationUtils.findAnnotation(method, valueGenerationTypeClass), "generatedBy"));
}
Class<? extends Annotation> idGeneratorTypeClass =
loadClass("org.hibernate.annotations.IdGeneratorType", classLoader);
Class<? extends Annotation> idGeneratorTypeClass = loadClass("org.hibernate.annotations.IdGeneratorType", classLoader);
if (idGeneratorTypeClass != null) {
ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection,
AnnotationUtils.findAnnotation(field, idGeneratorTypeClass), "value"));
@@ -225,8 +220,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
AnnotationUtils.findAnnotation(method, idGeneratorTypeClass), "value"));
}
Class<? extends Annotation> attributeBinderTypeClass =
loadClass("org.hibernate.annotations.AttributeBinderType", classLoader);
Class<? extends Annotation> attributeBinderTypeClass = loadClass("org.hibernate.annotations.AttributeBinderType", classLoader);
if (attributeBinderTypeClass != null) {
ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection,
AnnotationUtils.findAnnotation(field, attributeBinderTypeClass), "binder"));
@@ -235,19 +229,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
}
}
private void contributePackagePrivateHints(RuntimeHints hints, Class<?> managedClass) {
ReflectionHints reflection = hints.reflection();
ReflectionUtils.doWithMethods(managedClass, method ->
reflection.registerMethod(method, ExecutableMode.INVOKE),
method -> {
int modifiers = method.getModifiers();
return !(java.lang.reflect.Modifier.isProtected(modifiers) ||
java.lang.reflect.Modifier.isPrivate(modifiers) ||
java.lang.reflect.Modifier.isPublic(modifiers));
});
}
@SuppressWarnings("unchecked")
@Nullable
private static Class<? extends Annotation> loadClass(String className, @Nullable ClassLoader classLoader) {
try {
@@ -258,14 +239,13 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr
}
}
@SuppressWarnings("NullAway") // Not-null assertion performed in ReflectionHints.registerType
@SuppressWarnings("NullAway")
private void registerForReflection(ReflectionHints reflection, @Nullable Annotation annotation, String attribute) {
if (annotation == null) {
return;
}
Class<?> type = (Class<?>) AnnotationUtils.getAnnotationAttributes(annotation).get(attribute);
reflection.registerType(type, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
Class<?> embeddableInstantiatorClass = (Class<?>) AnnotationUtils.getAnnotationAttributes(annotation).get(attribute);
reflection.registerType(embeddableInstantiatorClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
}
@@ -40,7 +40,6 @@ class SimplePersistenceManagedTypes implements PersistenceManagedTypes {
SimplePersistenceManagedTypes(List<String> managedClassNames, List<String> managedPackages,
@Nullable URL persistenceUnitRootUrl) {
this.managedClassNames = managedClassNames;
this.managedPackages = managedPackages;
this.persistenceUnitRootUrl = persistenceUnitRootUrl;
@@ -50,7 +49,6 @@ class SimplePersistenceManagedTypes implements PersistenceManagedTypes {
this(managedClassNames, managedPackages, null);
}
@Override
public List<String> getManagedClassNames() {
return this.managedClassNames;
@@ -20,6 +20,7 @@ import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
@@ -428,7 +429,7 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = ClassUtils.getUserClass(clazz);
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
@@ -444,20 +445,21 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (method.isBridge()) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
if ((method.isAnnotationPresent(PersistenceContext.class) ||
method.isAnnotationPresent(PersistenceUnit.class)) &&
method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
if ((bridgedMethod.isAnnotationPresent(PersistenceContext.class) ||
bridgedMethod.isAnnotationPresent(PersistenceUnit.class)) &&
method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("Persistence annotations are not supported on static methods");
}
if (method.getParameterCount() != 1) {
throw new IllegalStateException("Persistence annotation requires a single-arg method: " + method);
}
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new PersistenceElement(method, method, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new PersistenceElement(method, bridgedMethod, pd));
}
});
@@ -39,27 +39,27 @@ public abstract class AbstractEntityManagerFactoryBeanTests {
protected static EntityManagerFactory mockEmf;
@BeforeEach
void setup() {
void setUp() {
mockEmf = mock();
}
@AfterEach
void cleanup() {
void tearDown() {
assertThat(TransactionSynchronizationManager.getResourceMap()).isEmpty();
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
assertThat(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).isFalse();
assertThat(TransactionSynchronizationManager.isActualTransactionActive()).isFalse();
}
protected void checkInvariants(AbstractEntityManagerFactoryBean emfb) {
assertThat(EntityManagerFactory.class.isAssignableFrom(emfb.getObjectType())).isTrue();
EntityManagerFactory emf = emfb.getObject();
assertThat(emf instanceof EntityManagerFactoryInfo).as("Object created by factory implements EntityManagerFactoryInfo").isTrue();
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) emf;
assertThat(emfb.getObject()).as("Successive invocations of getObject() return same object").isSameAs(emfi);
assertThat(emfb.getObject()).isSameAs(emfi);
protected void checkInvariants(AbstractEntityManagerFactoryBean demf) {
assertThat(EntityManagerFactory.class.isAssignableFrom(demf.getObjectType())).isTrue();
Object gotObject = demf.getObject();
boolean condition = gotObject instanceof EntityManagerFactoryInfo;
assertThat(condition).as("Object created by factory implements EntityManagerFactoryInfo").isTrue();
EntityManagerFactoryInfo emfi = (EntityManagerFactoryInfo) demf.getObject();
assertThat(demf.getObject()).as("Successive invocations of getObject() return same object").isSameAs(emfi);
assertThat(demf.getObject()).isSameAs(emfi);
assertThat(mockEmf).isSameAs(emfi.getNativeEntityManagerFactory());
}
@@ -1,50 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.orm.jpa.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column
private String model;
Integer getId() {
return id;
}
void setId(Integer id) {
this.id = id;
}
void setModel(String model) {
this.model = model;
}
String getModel() {
return model;
}
}
@@ -38,7 +38,6 @@ import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.domain.Car;
import org.springframework.orm.jpa.domain.DriversLicense;
import org.springframework.orm.jpa.domain.Employee;
import org.springframework.orm.jpa.domain.EmployeeCategoryConverter;
@@ -67,12 +66,13 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
GenericApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(JpaDomainConfiguration.class);
compile(context, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
PersistenceManagedTypes persistenceManagedTypes = freshApplicationContext.getBean(
"persistenceManagedTypes", PersistenceManagedTypes.class);
assertThat(persistenceManagedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
DriversLicense.class.getName(), Person.class.getName(), Employee.class.getName(),
EmployeeLocationConverter.class.getName(), Car.class.getName());
EmployeeLocationConverter.class.getName());
assertThat(persistenceManagedTypes.getManagedPackages()).isEmpty();
assertThat(freshApplicationContext.getBean(
JpaDomainConfiguration.class).scanningInvoked).isFalse();
@@ -105,14 +105,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocation.class)
.withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setId")
.invoke()).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getId")
.invoke()).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setModel")
.invoke()).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getModel")
.invoke()).accepts(hints);
});
}
@@ -129,7 +121,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
@SuppressWarnings("unchecked")
private void compile(GenericApplicationContext applicationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
TestGenerationContext generationContext = new TestGenerationContext();
generator.processAheadOfTime(applicationContext, generationContext);
@@ -140,7 +131,6 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
private GenericApplicationContext toFreshApplicationContext(
ApplicationContextInitializer<GenericApplicationContext> initializer) {
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
initializer.initialize(freshApplicationContext);
freshApplicationContext.refresh();
@@ -154,6 +144,21 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
result.accept(generationContext.getRuntimeHints());
}
public static class JpaDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
@Override
protected String packageToScan() {
return "org.springframework.orm.jpa.domain";
}
}
public static class HibernateDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
@Override
protected String packageToScan() {
return "org.springframework.orm.jpa.hibernate.domain";
}
}
public abstract static class AbstractEntityManagerWithPackagesToScanConfiguration {
@@ -174,13 +179,13 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
@Bean
public PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
this.scanningInvoked = true;
return new PersistenceManagedTypesScanner(resourceLoader).scan(packageToScan());
return new PersistenceManagedTypesScanner(resourceLoader)
.scan(packageToScan());
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
JpaVendorAdapter jpaVendorAdapter, PersistenceManagedTypes persistenceManagedTypes) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
@@ -189,24 +194,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
}
protected abstract String packageToScan();
}
public static class JpaDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
@Override
protected String packageToScan() {
return "org.springframework.orm.jpa.domain";
}
}
public static class HibernateDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration {
@Override
protected String packageToScan() {
return "org.springframework.orm.jpa.hibernate.domain";
}
}
}
@@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.context.testfixture.index.CandidateComponentsTestClassLoader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.orm.jpa.domain.Car;
import org.springframework.orm.jpa.domain.DriversLicense;
import org.springframework.orm.jpa.domain.Employee;
import org.springframework.orm.jpa.domain.EmployeeLocationConverter;
@@ -53,7 +52,7 @@ class PersistenceManagedTypesScannerTests {
PersistenceManagedTypes managedTypes = this.scanner.scan("org.springframework.orm.jpa.domain");
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
Person.class.getName(), DriversLicense.class.getName(), Employee.class.getName(),
EmployeeLocationConverter.class.getName(), Car.class.getName());
EmployeeLocationConverter.class.getName());
assertThat(managedTypes.getManagedPackages()).isEmpty();
}
@@ -67,7 +66,6 @@ class PersistenceManagedTypesScannerTests {
verify(filter).matches(DriversLicense.class.getName());
verify(filter).matches(Employee.class.getName());
verify(filter).matches(EmployeeLocationConverter.class.getName());
verify(filter).matches(Car.class.getName());
verifyNoMoreInteractions(filter);
}
@@ -22,6 +22,7 @@ import java.util.Map;
import javax.sql.DataSource;
import jakarta.persistence.spi.PersistenceUnitInfo;
import jakarta.persistence.spi.PersistenceUnitTransactionType;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -53,7 +54,7 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/META-INF/persistence.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info).hasSize(1);
@@ -70,7 +71,7 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-example1.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info).hasSize(1);
@@ -84,7 +85,7 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-example2.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info).hasSize(1);
@@ -102,7 +103,7 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-example3.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info).hasSize(1);
@@ -128,7 +129,7 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-example4.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info).hasSize(1);
@@ -152,7 +153,7 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-example5.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info).hasSize(1);
@@ -182,11 +183,11 @@ class PersistenceXmlParsingTests {
dataSourceLookup.setDataSources(dataSources);
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), dataSourceLookup);
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).hasSize(2);
SpringPersistenceUnitInfo pu1 = info[0];
PersistenceUnitInfo pu1 = info[0];
assertThat(pu1.getPersistenceUnitName()).isEqualTo("pu1");
@@ -209,7 +210,7 @@ class PersistenceXmlParsingTests {
assertThat(pu1.excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse();
SpringPersistenceUnitInfo pu2 = info[1];
PersistenceUnitInfo pu2 = info[1];
assertThat(pu2.getTransactionType()).isSameAs(PersistenceUnitTransactionType.JTA);
assertThat(pu2.getPersistenceProviderClassName()).isEqualTo("com.acme.AcmePersistence");
@@ -233,7 +234,7 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-example6.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).hasSize(1);
assertThat(info[0].getPersistenceUnitName()).isEqualTo("pu");
assertThat(info[0].getProperties()).isEmpty();
@@ -285,27 +286,27 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-exclude-1.0.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info.length).as("The number of persistence units is incorrect.").isEqualTo(4);
SpringPersistenceUnitInfo noExclude = info[0];
PersistenceUnitInfo noExclude = info[0];
assertThat(noExclude).as("noExclude should not be null.").isNotNull();
assertThat(noExclude.getPersistenceUnitName()).as("noExclude name is not correct.").isEqualTo("NoExcludeElement");
assertThat(noExclude.excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse();
SpringPersistenceUnitInfo emptyExclude = info[1];
PersistenceUnitInfo emptyExclude = info[1];
assertThat(emptyExclude).as("emptyExclude should not be null.").isNotNull();
assertThat(emptyExclude.getPersistenceUnitName()).as("emptyExclude name is not correct.").isEqualTo("EmptyExcludeElement");
assertThat(emptyExclude.excludeUnlistedClasses()).as("emptyExclude should be true.").isTrue();
SpringPersistenceUnitInfo trueExclude = info[2];
PersistenceUnitInfo trueExclude = info[2];
assertThat(trueExclude).as("trueExclude should not be null.").isNotNull();
assertThat(trueExclude.getPersistenceUnitName()).as("trueExclude name is not correct.").isEqualTo("TrueExcludeElement");
assertThat(trueExclude.excludeUnlistedClasses()).as("trueExclude should be true.").isTrue();
SpringPersistenceUnitInfo falseExclude = info[3];
PersistenceUnitInfo falseExclude = info[3];
assertThat(falseExclude).as("falseExclude should not be null.").isNotNull();
assertThat(falseExclude.getPersistenceUnitName()).as("falseExclude name is not correct.").isEqualTo("FalseExcludeElement");
assertThat(falseExclude.excludeUnlistedClasses()).as("falseExclude should be false.").isFalse();
@@ -316,27 +317,27 @@ class PersistenceXmlParsingTests {
PersistenceUnitReader reader = new PersistenceUnitReader(
new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup());
String resource = "/org/springframework/orm/jpa/persistence-exclude-2.0.xml";
SpringPersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource);
assertThat(info).isNotNull();
assertThat(info.length).as("The number of persistence units is incorrect.").isEqualTo(4);
SpringPersistenceUnitInfo noExclude = info[0];
PersistenceUnitInfo noExclude = info[0];
assertThat(noExclude).as("noExclude should not be null.").isNotNull();
assertThat(noExclude.getPersistenceUnitName()).as("noExclude name is not correct.").isEqualTo("NoExcludeElement");
assertThat(noExclude.excludeUnlistedClasses()).as("Exclude unlisted still defaults to false in 2.0.").isFalse();
SpringPersistenceUnitInfo emptyExclude = info[1];
PersistenceUnitInfo emptyExclude = info[1];
assertThat(emptyExclude).as("emptyExclude should not be null.").isNotNull();
assertThat(emptyExclude.getPersistenceUnitName()).as("emptyExclude name is not correct.").isEqualTo("EmptyExcludeElement");
assertThat(emptyExclude.excludeUnlistedClasses()).as("emptyExclude should be true.").isTrue();
SpringPersistenceUnitInfo trueExclude = info[2];
PersistenceUnitInfo trueExclude = info[2];
assertThat(trueExclude).as("trueExclude should not be null.").isNotNull();
assertThat(trueExclude.getPersistenceUnitName()).as("trueExclude name is not correct.").isEqualTo("TrueExcludeElement");
assertThat(trueExclude.excludeUnlistedClasses()).as("trueExclude should be true.").isTrue();
SpringPersistenceUnitInfo falseExclude = info[3];
PersistenceUnitInfo falseExclude = info[3];
assertThat(falseExclude).as("falseExclude should not be null.").isNotNull();
assertThat(falseExclude.getPersistenceUnitName()).as("falseExclude name is not correct.").isEqualTo("FalseExcludeElement");
assertThat(falseExclude.excludeUnlistedClasses()).as("falseExclude should be false.").isFalse();
@@ -46,6 +46,40 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="jibx-marshaller">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation source="java:org.springframework.oxm.jibx.JibxMarshaller">
Defines a JiBX Marshaller. Deprecated as of Spring Framework 5.1.5!
</xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.oxm.jibx.JibxMarshaller"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="target-class" type="classType">
<xsd:annotation>
<xsd:documentation>The target class to be bound with JiBX.</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="target-package" type="xsd:string">
<xsd:annotation>
<xsd:documentation>The target package for the JiBX binding.</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="binding-name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>The binding name used by this marshaller.</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="classType">
<xsd:annotation>
<xsd:documentation source="java:java.lang.Class">A class supported by a marshaller.</xsd:documentation>
@@ -63,9 +63,9 @@ import org.springframework.web.util.WebUtils;
*
* <p>As of Spring 6.0, this set of mocks is designed on a Servlet 6.0 baseline.
*
* <p>Compatible with Servlet 6.0 but can be configured to expose a specific version
* through {@link #setMajorVersion}/{@link #setMinorVersion}; default is 6.0.
* Note that some Servlet SPI support is limited: servlet, filter and listener
* <p>Compatible with Servlet 3.1 but can be configured to expose a specific version
* through {@link #setMajorVersion}/{@link #setMinorVersion}; default is 3.1.
* Note that Servlet 3.1 support is limited: servlet, filter and listener
* registration methods are not supported; neither is JSP configuration.
* We generally do not recommend to unit test your ServletContainerInitializers and
* WebApplicationInitializers which is where those registration methods would be used.
@@ -98,20 +98,6 @@ public interface TestContext extends AttributeAccessor, Serializable {
/**
* Get the {@linkplain Class test class} for this test context.
* <p>Since JUnit Jupiter 5.12, if the
* {@link org.springframework.test.context.junit.jupiter.SpringExtension
* SpringExtension} is used with a {@linkplain
* org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope#TEST_METHOD
* test-method scoped} {@link org.junit.jupiter.api.extension.ExtensionContext
* ExtensionContext}, the {@code Class} returned from this method may refer
* to the test class for the current {@linkplain #getTestMethod() test method},
* which may be a {@link org.junit.jupiter.api.Nested @Nested} test class
* within the class for the {@linkplain #getTestInstance() test instance}.
* Thus, if you need consistent access to the class for the current test
* instance within an implementation of
* {@link TestExecutionListener#prepareTestInstance(TestContext)}, you should
* invoke {@code testContext.getTestInstance().getClass()} instead of
* {@code testContext.getTestClass()}.
* @return the test class (never {@code null})
*/
Class<?> getTestClass();
@@ -150,19 +150,6 @@ public interface TestExecutionListener {
* {@link org.springframework.test.context.junit4.rules.SpringMethodRule
* SpringMethodRule}). In any case, this method must be called prior to any
* framework-specific lifecycle callbacks.
* <p>Since JUnit Jupiter 5.12, if the
* {@link org.springframework.test.context.junit.jupiter.SpringExtension
* SpringExtension} is used with a {@linkplain
* org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope#TEST_METHOD
* test-method scoped} {@link org.junit.jupiter.api.extension.ExtensionContext
* ExtensionContext}, the {@link Class} returned from
* {@link TestContext#getTestClass()} may refer to the test class for the
* current {@linkplain TestContext#getTestMethod() test method}, which may be
* a {@link org.junit.jupiter.api.Nested @Nested} test class within the class
* for the {@linkplain TestContext#getTestInstance() test instance}. Thus, if
* you need consistent access to the class for the current test instance, you
* should invoke {@code testContext.getTestInstance().getClass()} instead of
* {@code testContext.getTestClass()}.
* <p>See the {@linkplain TestExecutionListener class-level documentation}
* for details on wrapping behavior for listeners.
* <p>The default implementation is <em>empty</em>. Can be overridden by
@@ -93,23 +93,15 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
* a corresponding bean override instance.
*/
private static void injectFields(TestContext testContext) {
Object testInstance = testContext.getTestInstance();
// Since JUnit Jupiter 5.12, if the SpringExtension is used with Jupiter's
// TEST_METHOD ExtensionContextScope, the value returned from
// testContext.getTestClass() may refer to the declaring class of the test
// method which is about to be invoked (which may be in a @Nested class
// within the class for the test instance). Thus, we use the class for the
// test instance as the "test class".
Class<?> testClass = testInstance.getClass();
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(testClass);
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(testContext.getTestClass());
if (!handlers.isEmpty()) {
Object testInstance = testContext.getTestInstance();
ApplicationContext applicationContext = testContext.getApplicationContext();
Assert.state(applicationContext.containsBean(BeanOverrideRegistry.BEAN_NAME), () -> """
Test class %s declares @BeanOverride fields %s, but no BeanOverrideHandler has been registered. \
If you are using @ContextHierarchy, ensure that context names for bean overrides match \
configured @ContextConfiguration names.""".formatted(testClass.getSimpleName(),
configured @ContextConfiguration names.""".formatted(testContext.getTestClass().getSimpleName(),
handlers.stream().map(BeanOverrideHandler::getField).filter(Objects::nonNull)
.map(Field::getName).toList()));
BeanOverrideRegistry beanOverrideRegistry = applicationContext.getBean(BeanOverrideRegistry.BEAN_NAME,
@@ -282,7 +282,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* <ol>
* <li>The {@linkplain ParameterContext#getDeclaringExecutable() declaring
* executable} is a {@link Constructor} and
* {@link TestConstructorUtils#isAutowirableConstructor(Executable, PropertyProvider)}
* {@link TestConstructorUtils#isAutowirableConstructor(Constructor, Class, PropertyProvider)}
* returns {@code true}. Note that {@code isAutowirableConstructor()} will be
* invoked with a fallback {@link PropertyProvider} that delegates its lookup
* to {@link ExtensionContext#getConfigurationParameter(String)}.</li>
@@ -296,25 +296,25 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
* constructor. Consequently, no other registered {@link ParameterResolver}
* will be able to resolve parameters.
* @see #resolveParameter
* @see TestConstructorUtils#isAutowirableConstructor(Executable, PropertyProvider)
* @see TestConstructorUtils#isAutowirableConstructor(Constructor, Class)
* @see ParameterResolutionDelegate#isAutowirable
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Parameter parameter = parameterContext.getParameter();
Class<?> parameterType = parameter.getType();
Executable executable = parameter.getDeclaringExecutable();
Class<?> testClass = extensionContext.getRequiredTestClass();
PropertyProvider junitPropertyProvider = propertyName ->
extensionContext.getConfigurationParameter(propertyName).orElse(null);
return (TestConstructorUtils.isAutowirableConstructor(executable, junitPropertyProvider) ||
ApplicationContext.class.isAssignableFrom(parameterType) ||
supportsApplicationEvents(parameterType, executable) ||
return (TestConstructorUtils.isAutowirableConstructor(executable, testClass, junitPropertyProvider) ||
ApplicationContext.class.isAssignableFrom(parameter.getType()) ||
supportsApplicationEvents(parameterContext) ||
ParameterResolutionDelegate.isAutowirable(parameter, parameterContext.getIndex()));
}
private boolean supportsApplicationEvents(Class<?> parameterType, Executable executable) {
if (ApplicationEvents.class.isAssignableFrom(parameterType)) {
Assert.isTrue(executable instanceof Method,
private boolean supportsApplicationEvents(ParameterContext parameterContext) {
if (ApplicationEvents.class.isAssignableFrom(parameterContext.getParameter().getType())) {
Assert.isTrue(parameterContext.getDeclaringExecutable() instanceof Method,
"ApplicationEvents can only be injected into test and lifecycle methods");
return true;
}
@@ -78,7 +78,6 @@ public abstract class TestConstructorUtils {
private TestConstructorUtils() {
}
/**
* Determine if the supplied executable for the given test class is an
* autowirable constructor.
@@ -87,11 +86,8 @@ public abstract class TestConstructorUtils {
* @param executable an executable for the test class
* @param testClass the test class
* @return {@code true} if the executable is an autowirable constructor
* @see #isAutowirableConstructor(Executable, PropertyProvider)
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
* to be removed in Spring Framework 7.1
* @see #isAutowirableConstructor(Executable, Class, PropertyProvider)
*/
@Deprecated(since = "6.2.13", forRemoval = true)
public static boolean isAutowirableConstructor(Executable executable, Class<?> testClass) {
return isAutowirableConstructor(executable, testClass, null);
}
@@ -105,10 +101,7 @@ public abstract class TestConstructorUtils {
* @param testClass the test class
* @return {@code true} if the constructor is autowirable
* @see #isAutowirableConstructor(Constructor, Class, PropertyProvider)
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
* to be removed in Spring Framework 7.1
*/
@Deprecated(since = "6.2.13", forRemoval = true)
public static boolean isAutowirableConstructor(Constructor<?> constructor, Class<?> testClass) {
return isAutowirableConstructor(constructor, testClass, null);
}
@@ -126,10 +119,7 @@ public abstract class TestConstructorUtils {
* @return {@code true} if the executable is an autowirable constructor
* @since 5.3
* @see #isAutowirableConstructor(Constructor, Class, PropertyProvider)
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
* to be removed in Spring Framework 7.1
*/
@Deprecated(since = "6.2.13", forRemoval = true)
public static boolean isAutowirableConstructor(Executable executable, Class<?> testClass,
@Nullable PropertyProvider fallbackPropertyProvider) {
@@ -158,62 +148,16 @@ public abstract class TestConstructorUtils {
* {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}).</li>
* </ol>
* @param constructor a constructor for the test class
* @param testClass the test class, typically the declaring class of the constructor
* @param testClass the test class
* @param fallbackPropertyProvider fallback property provider used to look up
* the value for {@link TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}
* if no such value is found in {@link SpringProperties}; may be {@code null}
* if there is no fallback support
* the value for the default <em>test constructor autowire mode</em> if no
* such value is found in {@link SpringProperties}
* @return {@code true} if the constructor is autowirable
* @since 5.3
* @see #isAutowirableConstructor(Executable, PropertyProvider)
* @deprecated as of 6.2.13, in favor of {@link #isAutowirableConstructor(Executable, PropertyProvider)};
* to be removed in Spring Framework 7.1
*/
@Deprecated(since = "6.2.13", forRemoval = true)
public static boolean isAutowirableConstructor(Constructor<?> constructor, Class<?> testClass,
@Nullable PropertyProvider fallbackPropertyProvider) {
return isAutowirableConstructorInternal(constructor, testClass, fallbackPropertyProvider);
}
/**
* Determine if the supplied {@link Executable} is an autowirable {@link Constructor}.
*
* <p>A constructor is considered to be autowirable if one of the following
* conditions is {@code true}.
*
* <ol>
* <li>The constructor is annotated with {@link Autowired @Autowired},
* {@link jakarta.inject.Inject @jakarta.inject.Inject}, or
* {@link javax.inject.Inject @javax.inject.Inject}.</li>
* <li>{@link TestConstructor @TestConstructor} is <em>present</em> or
* <em>meta-present</em> on the test class with
* {@link TestConstructor#autowireMode() autowireMode} set to
* {@link AutowireMode#ALL ALL}.</li>
* <li>The default <em>test constructor autowire mode</em> has been set to
* {@code ALL} in {@link SpringProperties} or in the supplied fallback
* {@link PropertyProvider}.</li>
* </ol>
* @param executable an {@code Executable} for a test class
* @param fallbackPropertyProvider fallback property provider used to look up
* the value for {@value TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME}
* if no such value is found in {@link SpringProperties}; may be {@code null}
* if there is no fallback support
* @return {@code true} if the executable is an autowirable constructor
* @since 6.2.13
* @see TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME
*/
public static boolean isAutowirableConstructor(Executable executable,
@Nullable PropertyProvider fallbackPropertyProvider) {
return (executable instanceof Constructor<?> constructor &&
isAutowirableConstructorInternal(constructor, constructor.getDeclaringClass(), fallbackPropertyProvider));
}
private static boolean isAutowirableConstructorInternal(Constructor<?> constructor, Class<?> testClass,
@Nullable PropertyProvider fallbackPropertyProvider) {
// Is the constructor annotated with @Autowired/@Inject?
if (isAnnotatedWithAutowiredOrInject(constructor)) {
return true;

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