Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel e354390837 Release v6.2.12 2025-10-16 08:56:30 +02:00
263 changed files with 3082 additions and 6803 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/",
+2 -4
View File
@@ -13,9 +13,7 @@ content:
- url: https://github.com/spring-projects/spring-framework
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
branches: ['main', '{6..9}.+({1..9}).x']
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
start_path: framework-docs
asciidoc:
@@ -38,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
@@ -50,8 +50,9 @@ XML configuration:
The preceding XML is more succinct. However, typos are discovered at runtime rather than
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
completion when you create bean definitions. Such IDE assistance is highly recommended.
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
that supports automatic property completion when you create bean definitions. Such IDE
assistance is highly recommended.
You can also configure a `java.util.Properties` instance, as follows:
@@ -339,11 +339,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
modularity, but determining exactly where the autowired bean definitions are declared is
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
explicit in the code, and this may be just fine. Note that the
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
showing how everything is wired, which may be all you need. Also, your Java IDE can
easily find all declarations and uses of the `AccountRepository` type and quickly show
you the location of `@Bean` methods that return that type.
explicit in the code, and this may be just fine. Remember that the
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
can render graphs showing how everything is wired, which may be all you need. Also,
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
and quickly show you the location of `@Bean` methods that return that type.
In cases where this ambiguity is not acceptable and you wish to have direct navigation
from within your IDE from one `@Configuration` class to another, consider autowiring the
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
Language] but offers additional features, most notably method invocation and basic string
templating functionality.
While there are several other Java expression languages available -- OGNL, MVEL, and
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
Spring community with a single well supported expression language that can be used across
all the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements for
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
is based on a technology-agnostic API that lets other expression language implementations
be integrated, should the need arise.
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
EL, to name a few -- the Spring Expression Language was created to provide the Spring
community with a single well supported expression language that can be used across all
the products in the Spring portfolio. Its language features are driven by the
requirements of the projects in the Spring portfolio, including tooling requirements
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
That said, SpEL is based on a technology-agnostic API that lets other expression language
implementations be integrated, should the need arise.
While SpEL serves as the foundation for expression evaluation within the Spring
portfolio, it is not directly tied to Spring and can be used independently. To
@@ -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();
----
@@ -319,17 +319,9 @@ progresses.
== Testing
This section addresses testing with the combination of Kotlin and Spring Framework.
The recommended testing framework is https://junit.org/[JUnit Jupiter] along with
The recommended testing framework is https://junit.org/junit5/[JUnit 5] along with
https://mockk.io/[Mockk] for mocking.
[TIP]
====
Kotlin lets you specify meaningful test function names between backticks (```).
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
in this section.
====
NOTE: If you are using Spring Boot, see
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
@@ -360,6 +352,7 @@ file with a `spring.test.constructor.autowire.mode = all` property.
[[per_class-lifecycle]]
=== `PER_CLASS` Lifecycle
Kotlin lets you specify meaningful test function names between backticks (```).
With JUnit Jupiter (JUnit 5), Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
@@ -187,7 +187,7 @@ default mode may be set via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
The default mode may also be configured as a
https://docs.junit.org/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
If the `spring.test.constructor.autowire.mode` property is not set, test class
constructors will not be automatically autowired.
@@ -5,25 +5,24 @@ It is important to be able to perform some integration testing without requiring
deployment to your application server or connecting to other enterprise infrastructure.
Doing so lets you test things such as:
* The correct wiring of your Spring components.
* Data access using JDBC or an ORM tool.
** This can include such things as the correctness of SQL statements, Hibernate queries,
JPA entity mappings, and so forth.
* The correct wiring of your Spring IoC container contexts.
* Data access using JDBC or an ORM tool. This can include such things as the correctness
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
The Spring Framework provides first-class support for integration testing in the
`spring-test` module. The name of the actual JAR file might include the release version,
depending on where you get it from (see the
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
for details). This library includes the `org.springframework.test` package, which
`spring-test` module. The name of the actual JAR file might include the release version
and might also be in the long `org.springframework.test` form, depending on where you get
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
for an explanation). This library includes the `org.springframework.test` package, which
contains valuable classes for integration testing with a Spring container. This testing
does not rely on an application server or other deployment environment. Such tests are
slower to run than unit tests but much faster than the equivalent Selenium tests or
remote tests that rely on deployment to an application server.
Unit and integration testing support is provided in the form of the annotation-driven
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
framework is agnostic of the actual testing framework in use, which allows
instrumentation of tests in various environments, including JUnit, TestNG, and others.
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
agnostic of the actual testing framework in use, which allows instrumentation of tests
in various environments, including JUnit, TestNG, and others.
The following section provides an overview of the high-level goals of Spring's
integration support, and the rest of this chapter then focuses on dedicated topics:
@@ -8,9 +8,9 @@ use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
== Why Geb and MockMvc?
Geb is backed by WebDriver, so it offers many of the
xref:testing/mockmvc/htmlunit/webdriver.adoc#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
@@ -22,7 +22,7 @@ TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://docs.junit.org/current/user-guide/#extensions-conditions[conditional
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
@@ -160,7 +160,7 @@ for further details.
=== Dependency Injection with the `SpringExtension`
The `SpringExtension` implements the
link:https://docs.junit.org/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
constructors, test methods, and test lifecycle callback methods.
@@ -362,7 +362,7 @@ of `PlatformTransactionManager` within the test's `ApplicationContext`, you can
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
`@Configuration` class. Consult the
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager(org.springframework.test.context.TestContext,java.lang.String)[javadoc
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
@@ -75,7 +75,7 @@ infrastructure and controller declarations and use it to handle requests via moc
and response objects, without a running server.
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext(org.springframework.context.ApplicationContext)[WebHttpHandlerBuilder]
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
requests:
@@ -44,7 +44,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the WebFlux Java configuration to declare such mappings, which results in a single,
global map passed to all `HandlerMapping` implementations.
@@ -57,7 +57,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
@@ -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()
}
----
======
@@ -114,6 +114,6 @@ and others) and is equivalent to `required=false`.
| Any other argument
| If a method argument is not matched to any of the above, it is, by default, resolved as
a `@RequestParam` if it is a simple type, as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
or as a `@ModelAttribute`, otherwise.
|===
@@ -205,7 +205,7 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
@@ -74,6 +74,6 @@ When a `@RequestParam` annotation is declared on a `Map<String, String>` or
Note that use of `@RequestParam` is optional -- for example, to set its attributes. By
default, any argument that is a simple value type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
and is not resolved by any other argument resolver is treated as if it were annotated
with `@RequestParam`.
@@ -87,6 +87,6 @@ Reactor provides a dedicated operator for that, `Flux#collectList()`.
| Other return values
| If a return value remains unresolved in any other way, it is treated as a model
attribute, unless it is a simple type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
in which case it remains unresolved.
|===
@@ -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
@@ -207,7 +207,7 @@ was not provided (for example, model attribute was returned) or an async return
view resolution scenarios. Explore the options in your IDE with code completion.
* `Model`, `Map`: Extra model attributes to be added to the model for the request.
* Any other: Any other return value (except for simple types, as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
is treated as a model attribute to be added to the model. The attribute name is derived
from the class name by using {spring-framework-api}/core/Conventions.html[conventions],
unless a handler method `@ModelAttribute` annotation is present.
@@ -71,7 +71,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
use the MVC Java configuration or the XML namespace to declare such mappings, which results
in a single global map being passed to all `HandlerMapping` instances.
@@ -84,7 +84,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
accepted, for example, `allowCredentials` and `maxAge`, the local overrides the global value. See
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
[TIP]
@@ -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).
@@ -175,7 +175,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
| `TypeMismatchException`
| (default)
| `+{0}+` property name, `+{1}+` property value, `+{2}+` simple name of required type
| `+{0}+` property name, `+{1}+` property value
| `UnsatisfiedServletRequestParameterException`
| (default)
@@ -215,7 +215,7 @@ the content negotiation during the error handling phase will decide which conten
| Any other return value
| If a return value is not matched to any of the above and is not a simple type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
by default, it is treated as a model attribute to be added to the model. If it is a simple type,
it remains unresolved.
|===
@@ -135,6 +135,6 @@ and others) and is equivalent to `required=false`.
| Any other argument
| If a method argument is not matched to any of the earlier values in this table and it is
a simple type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
it is resolved as a `@RequestParam`. Otherwise, it is resolved as a `@ModelAttribute`.
|===
@@ -250,7 +250,7 @@ xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
@@ -117,6 +117,6 @@ Kotlin::
Note that use of `@RequestParam` is optional (for example, to set its attributes).
By default, any argument that is a simple value type (as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
and is not resolved by any other argument resolver, is treated as if it were annotated
with `@RequestParam`.
@@ -98,6 +98,6 @@ supported for all return values.
| Other return values
| If a return value remains unresolved in any other way, it is treated as a model
attribute, unless it is a simple type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
in which case it remains unresolved.
|===
@@ -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:
@@ -221,7 +234,7 @@ When multiple patterns match a URL, the best match must be selected. This is don
one of the following depending on whether use of parsed `PathPattern` is enabled for use or not:
* {spring-framework-api}/web/util/pattern/PathPattern.html#SPECIFICITY_COMPARATOR[`PathPattern.SPECIFICITY_COMPARATOR`]
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator(java.lang.String)[`AntPathMatcher.getPatternComparator(String path)`]
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator-java.lang.String-[`AntPathMatcher.getPatternComparator(String path)`]
Both help to sort patterns with more specific ones on top. A pattern is more specific if
it has a lower count of URI variables (counted as 1), single wildcards (counted as 1),
@@ -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.
|===
+1 -1
View File
@@ -4,7 +4,7 @@
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.7",
"@springio/antora-extensions": "1.14.2",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
+12 -12
View File
@@ -7,33 +7,33 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
api(platform("io.micrometer:micrometer-bom:1.14.14"))
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.13"))
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")
api("com.google.code.gson:gson:2.13.2")
api("com.google.protobuf:protobuf-java-util:4.32.1")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.10.0")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
api("com.rometools:rome:1.19.0")
api("com.squareup.okhttp3:mockwebserver:3.14.9")
@@ -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.15
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,17 +375,6 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
return true;
}
}
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
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)));
}
/**
@@ -585,7 +585,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
public void registerContainedBean(String containedBeanName, String containingBeanName) {
synchronized (this.containedBeanMap) {
Set<String> containedBeans =
this.containedBeanMap.computeIfAbsent(containingBeanName, key -> new LinkedHashSet<>(8));
this.containedBeanMap.computeIfAbsent(containingBeanName, k -> new LinkedHashSet<>(8));
if (!containedBeans.add(containedBeanName)) {
return;
}
@@ -604,7 +604,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, key -> new LinkedHashSet<>(8));
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
@@ -612,7 +612,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, key -> new LinkedHashSet<>(8));
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
@@ -74,10 +74,10 @@ import org.springframework.util.StringUtils;
* @author Rob Harrop
* @since 26.11.2003
* @see DefaultListableBeanFactory
* @deprecated 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));
@@ -2112,8 +2073,8 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -2724,7 +2685,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("nonNullBean", "Test");
bf.registerBeanDefinition("mixedNullableInjectionBean",
new RootBeanDefinition(MixedNullableInjectionBean.class));
MixedNullableInjectionBean mixedNullableInjectionBean = bf.getBean(MixedNullableInjectionBean.class);
assertThat(mixedNullableInjectionBean.nonNullBean).isNotNull();
assertThat(mixedNullableInjectionBean.nullableBean).isNull();
@@ -2735,7 +2695,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("nonNullBean", "Test");
bf.registerBeanDefinition("mixedOptionalInjectionBean",
new RootBeanDefinition(MixedOptionalInjectionBean.class));
MixedOptionalInjectionBean mixedOptionalInjectionBean = bf.getBean(MixedOptionalInjectionBean.class);
assertThat(mixedOptionalInjectionBean.nonNullBean).isNotNull();
assertThat(mixedOptionalInjectionBean.nullableBean).isNull();
@@ -3299,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;
@@ -3413,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 {
@@ -444,8 +444,8 @@ class InjectAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -788,6 +788,7 @@ class InjectAnnotationBeanPostProcessorTests {
private ConfigurableListableBeanFactory beanFactory;
public ConstructorResourceInjectionBean() {
throw new UnsupportedOperationException();
}
@@ -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
@@ -51,7 +51,8 @@ class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCacheCache>
this.cacheManager.createCache(CACHE_NAME_NO_NULL, new MutableConfiguration<>());
this.nativeCache = this.cacheManager.getCache(CACHE_NAME);
this.cache = new JCacheCache(this.nativeCache);
Cache<Object, Object> nativeCacheNoNull = this.cacheManager.getCache(CACHE_NAME_NO_NULL);
Cache<Object, Object> nativeCacheNoNull =
this.cacheManager.getCache(CACHE_NAME_NO_NULL);
this.cacheNoNull = new JCacheCache(nativeCacheNoNull, false);
}
@@ -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
@@ -113,7 +113,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
builder.setUnless(getAttributeValue(opElement, "unless", ""));
builder.setSync(Boolean.parseBoolean(getAttributeValue(opElement, "sync", "false")));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -136,7 +136,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
builder.setBeforeInvocation(Boolean.parseBoolean(after.trim()));
}
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -150,7 +150,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
parserContext.getReaderContext(), new CachePutOperation.Builder());
builder.setUnless(getAttributeValue(opElement, "unless", ""));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
col.add(builder.build());
}
@@ -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));
}
}
}
@@ -172,25 +172,22 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
}
}
boolean cacheable = false;
if (!autowiredBeanNames.isEmpty()) {
cacheable = true;
for (String autowiredBeanName : autowiredBeanNames) {
if (!this.beanFactory.containsBean(autowiredBeanName)) {
boolean cacheable = true;
for (String autowiredBeanName : autowiredBeanNames) {
if (!this.beanFactory.containsBean(autowiredBeanName)) {
cacheable = false;
}
else {
if (this.beanName != null) {
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
}
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
cacheable = false;
}
else {
if (this.beanName != null) {
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
}
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
cacheable = false;
}
}
}
}
if (cacheable) {
this.cachedTarget = target;
if (cacheable) {
this.cachedTarget = target;
}
}
return target;
@@ -630,7 +630,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
finishRefresh();
}
catch (RuntimeException | Error ex) {
catch (RuntimeException | Error ex ) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
@@ -227,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
@@ -383,7 +383,7 @@ public class ScheduledAnnotationBeanPostProcessor
try {
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
this.registrar::getObservationRegistry,
this.reactiveSubscriptions.computeIfAbsent(bean, key -> new CopyOnWriteArrayList<>()));
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
@@ -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
@@ -19,7 +19,6 @@ package org.springframework.context.annotation.configuration;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -30,7 +29,6 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@@ -102,16 +100,6 @@ class AutowiredConfigurationTests {
context.close();
}
@Test
void testAutowiredConfigurationMethodDependenciesWithQualifier() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
QualifiedAutowiredMethodConfig.class);
assertThat(context.getBeansOfType(Colour.class)).isEmpty();
assertThat(context.getBean(TestBean.class).getName()).isEmpty();
context.close();
}
@Test
void testAutowiredSingleConstructorSupported() {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
@@ -309,25 +297,6 @@ class AutowiredConfigurationTests {
}
@Configuration
static class QualifiedAutowiredMethodConfig {
@Bean
@Qualifier("testBean")
public TestBean testBean(Optional<Colour> colour, Optional<List<Colour>> colours) {
if (!colour.isEmpty() || !colours.isEmpty()) {
throw new IllegalStateException("Unexpected match: " + colour + " " + colours);
}
return new TestBean("");
}
@Bean
public List<?> someList() {
return Collections.singletonList(new TestBean("shouldNotMatch"));
}
}
@Configuration
static class AutowiredConstructorConfig {
@@ -29,7 +29,6 @@ class ApplicationContextLifecycleTests {
@Test
void beansStart() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
@@ -40,14 +39,12 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.isRunning()).as(error).isTrue();
assertThat(bean3.isRunning()).as(error).isTrue();
assertThat(bean4.isRunning()).as(error).isTrue();
context.close();
}
@Test
void beansStop() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
@@ -58,21 +55,18 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.isRunning()).as(startError).isTrue();
assertThat(bean3.isRunning()).as(startError).isTrue();
assertThat(bean4.isRunning()).as(startError).isTrue();
context.stop();
String stopError = "bean was not stopped";
assertThat(bean1.isRunning()).as(stopError).isFalse();
assertThat(bean2.isRunning()).as(stopError).isFalse();
assertThat(bean3.isRunning()).as(stopError).isFalse();
assertThat(bean4.isRunning()).as(stopError).isFalse();
context.close();
}
@Test
void startOrder() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
@@ -87,14 +81,12 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.getStartOrder()).as(orderError).isGreaterThan(bean1.getStartOrder());
assertThat(bean3.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder());
assertThat(bean4.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder());
context.close();
}
@Test
void stopOrder() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
context.start();
context.stop();
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
@@ -110,7 +102,6 @@ class ApplicationContextLifecycleTests {
assertThat(bean2.getStopOrder()).as(orderError).isLessThan(bean1.getStopOrder());
assertThat(bean3.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder());
assertThat(bean4.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder());
context.close();
}
@@ -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;
@@ -22,7 +22,7 @@ public class CustomizerRegistry {
Class<? extends KeyFactoryCustomizer> klass = customizer.getClass();
for (Class type : customizerTypes) {
if (type.isAssignableFrom(klass)) {
List<KeyFactoryCustomizer> list = customizers.computeIfAbsent(type, key -> new ArrayList<>());
List<KeyFactoryCustomizer> list = customizers.computeIfAbsent(type, k -> new ArrayList<>());
list.add(customizer);
}
}
@@ -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
@@ -28,6 +28,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the
@@ -99,25 +100,25 @@ 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) {
// Gather all methods with matching name and parameter size.
List<Method> candidateMethods = new ArrayList<>();
ReflectionUtils.doWithMethods(userClass, candidateMethods::add,
candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
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.
@@ -148,13 +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) {
@@ -170,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));
}
/**
@@ -184,33 +186,24 @@ 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) {
// First, compare return type.
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(genericMethod, clazz);
if (!ClassUtils.resolvePrimitiveIfNecessary(genericReturnType.toClass()).isAssignableFrom(
ClassUtils.resolvePrimitiveIfNecessary(candidateMethod.getReturnType()))) {
return false;
}
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
for (int i = 0; i < candidateParameters.length; i++) {
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz);
if (!ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()).equals(
ClassUtils.resolvePrimitiveIfNecessary(candidateParameters[i]))) {
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
Class<?> candidateParameter = candidateParameters[i];
if (candidateParameter.isArray()) {
// An array type: compare the component type.
if (!candidateParameter.componentType().equals(genericParameter.getComponentType().toClass())) {
return false;
}
}
// A non-array type: compare the type itself.
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(
ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
return false;
}
}
@@ -280,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.
@@ -112,7 +112,9 @@ final class AnnotationTypeMapping {
this.root = (source != null ? source.getRoot() : this);
this.distance = (source == null ? 0 : source.getDistance() + 1);
this.annotationType = annotationType;
this.metaTypes = merge((source != null ? source.getMetaTypes() : null), annotationType);
this.metaTypes = merge(
source != null ? source.getMetaTypes() : null,
annotationType);
this.annotation = annotation;
this.attributes = AttributeMethods.forAnnotationType(annotationType);
this.mirrorSets = new MirrorSets();
@@ -76,7 +76,6 @@ final class AnnotationTypeMappings {
private void addAllMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
while (!queue.isEmpty()) {
@@ -271,19 +270,11 @@ final class AnnotationTypeMappings {
*/
AnnotationTypeMappings get(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
AnnotationTypeMappings result = this.mappings.get(annotationType);
if (result != null) {
return result;
}
result = createMappings(annotationType, visitedAnnotationTypes);
AnnotationTypeMappings existing = this.mappings.putIfAbsent(annotationType, result);
return (existing != null ? existing : result);
return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
}
private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType,
visitedAnnotationTypes);
}
@@ -894,7 +894,8 @@ public abstract class AnnotationUtils {
private static Map<String, DefaultValueHolder> getDefaultValues(
Class<? extends Annotation> annotationType) {
return defaultValuesCache.computeIfAbsent(annotationType, AnnotationUtils::computeDefaultValues);
return defaultValuesCache.computeIfAbsent(annotationType,
AnnotationUtils::computeDefaultValues);
}
private static Map<String, DefaultValueHolder> computeDefaultValues(
@@ -133,14 +133,6 @@ import org.springframework.util.Assert;
* please use standard Java reflection or Spring's {@link AnnotationUtils}
* for simple annotation retrieval purposes.
*
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
* its attributes references a {@link Class} or {@link Enum}
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
* annotation will not be accessible via the {@code MergedAnnotations} API.
* To assist with diagnosing such scenarios, you can set the log level for
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
* {@code INFO}, or {@code WARN}.
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
@@ -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
@@ -490,7 +490,7 @@ public class GenericConversionService implements ConfigurableConversionService {
}
private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) {
return this.converters.computeIfAbsent(convertiblePair, key -> new ConvertersForPair());
return this.converters.computeIfAbsent(convertiblePair, k -> new ConvertersForPair());
}
public void remove(Class<?> sourceType, Class<?> targetType) {
@@ -188,7 +188,7 @@ final class SubscriberInputStream extends InputStream implements Subscriber<Data
}
private void resume() {
if (this.parkedThread.get() != READY) {
if (this.parkedThread != READY) {
Object old = this.parkedThread.getAndSet(READY);
if (old != READY) {
LockSupport.unpark((Thread)old);
@@ -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,29 +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 = null;
boolean absolute = false;
if (path.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
try {
pathFile = new File(ResourceUtils.toURI(path));
absolute = true;
}
catch (URISyntaxException | IllegalArgumentException ex) {
// Fall back to plain String constructor below.
}
}
if (pathFile == null) {
pathFile = new File(path);
absolute = pathFile.isAbsolute();
}
File candidate = (absolute ? pathFile : new File(parent, path));
// For relative paths, enforce security check: must be under parent.
// For absolute paths, just verify file exists (matching JVM behavior).
if (candidate.isFile() && (absolute ||
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));
}
}
@@ -323,8 +323,8 @@ public class SpringFactoriesLoader {
SpringFactoriesLoader.class.getClassLoader());
Map<String, Factories> factoriesCache = cache.computeIfAbsent(
resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
Factories factories = factoriesCache.computeIfAbsent(resourceLocation,
key -> new Factories(loadFactoriesResource(resourceClassLoader, resourceLocation)));
Factories factories = factoriesCache.computeIfAbsent(resourceLocation, key ->
new Factories(loadFactoriesResource(resourceClassLoader, resourceLocation)));
return new SpringFactoriesLoader(classLoader, factories.byType());
}
@@ -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));
@@ -36,19 +36,11 @@ import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
/**
* Defines access to the annotations on a specific {@link AnnotationMetadata class}
* or {@link MethodMetadata method}, in a form that does not necessarily require
* Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
* or {@link MethodMetadata method}), in a form that does not necessarily require
* class loading of the types being inspected. Note, however, that classes for
* encountered annotations will be loaded.
*
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
* its attributes references a {@link Class} or {@link Enum}
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
* annotation will not be accessible via the {@code AnnotatedTypeMetadata} API.
* To assist with diagnosing such scenarios, you can set the log level for
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
* {@code INFO}, or {@code WARN}.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Mark Pollack
@@ -25,17 +25,9 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
/**
* Interface that defines abstract access to the annotations on a specific
* Interface that defines abstract access to the annotations of a specific
* class, in a form that does not require that class to be loaded yet.
*
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
* its attributes references a {@link Class} or {@link Enum}
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
* annotation will not be accessible via the {@code AnnotationMetadata} API.
* To assist with diagnosing such scenarios, you can set the log level for
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
* {@code INFO}, or {@code WARN}.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Phillip Webb

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