mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf74ce007d | |||
| 23625ee698 | |||
| c89c4ac614 | |||
| 2155e9fb25 | |||
| 24df35c55a | |||
| 3b1fa369b4 | |||
| 221adf14a4 | |||
| ad849fb3a2 | |||
| 8041a09268 | |||
| 97b9517918 | |||
| e79d8e35a2 | |||
| 96503a2ea6 | |||
| 8803d3c5dc | |||
| 5b4728f0bf | |||
| 1fd1d8d629 | |||
| afc40d7b93 | |||
| 3eb1df0ac0 | |||
| 2aa3b0a77e | |||
| 0008106adb | |||
| 8f6d44a86b | |||
| 874498272f | |||
| 2144813bad | |||
| 67a92306f7 | |||
| bc0731891b | |||
| 3faa7cac4d | |||
| 3121daf553 | |||
| 821165488e | |||
| 3b556ba0c0 | |||
| b25f98374b | |||
| c2f7cd3401 | |||
| 22d2810ed0 | |||
| b39055f293 | |||
| 140933400d | |||
| e7a5452a14 | |||
| ab96576e67 | |||
| 19b080b73f | |||
| f9b4fba97a | |||
| 37e26e0377 | |||
| 85c47a73dd | |||
| ab93020263 | |||
| a4134663a6 | |||
| a4b1155ca6 | |||
| 1bdd8337c6 | |||
| 7a0ea14452 | |||
| 59025c5b96 | |||
| e5de8b9bc6 | |||
| e146e809e5 | |||
| ff62e7355f | |||
| 4e97013595 | |||
| bd10b7ae06 | |||
| e4288170c8 | |||
| 8545a759a7 | |||
| f94645de17 | |||
| 8553f97df1 | |||
| 030dace2be | |||
| 5af1c9b487 | |||
| f3ed04c9d7 | |||
| 46ee944acc | |||
| 6be1c29fda | |||
| 3fa56db88b | |||
| ed444eb0c1 | |||
| da31750e5f | |||
| b853203846 | |||
| 2b04df045a | |||
| 40544e096f | |||
| 23354b0155 | |||
| 5c5367a1be | |||
| 12dd758158 | |||
| 5aec239261 | |||
| d0ff8f9243 | |||
| ba39385cce | |||
| e735c2d9c5 | |||
| 3cb498fa98 | |||
| 335a2c4e21 | |||
| 1714a00492 | |||
| ddb45a9302 | |||
| 38555df3d8 | |||
| 6f81cb625c | |||
| 566078b4e4 | |||
| f0cf7f19f6 | |||
| b5008d33ee | |||
| bce1445d92 | |||
| 285182be27 | |||
| 8bb63081a8 | |||
| a698b1bc0d | |||
| 2060c340ce | |||
| 4f97c41e58 | |||
| 0243059f53 | |||
| b027b73a7a | |||
| c0429dbb09 | |||
| d65de19e7d | |||
| 16822c2fd0 | |||
| 983d39a970 | |||
| 4b7cf85d71 | |||
| a33027703d | |||
| ae804cb2d0 | |||
| 46503d6a45 | |||
| b4a13dddfc | |||
| 661dca5e54 | |||
| b976010a9e | |||
| 15563ee54e | |||
| 97586b525f | |||
| f89737e398 | |||
| ecdf069c5d | |||
| 01a58a253b | |||
| f28e245baa | |||
| 14579b7848 | |||
| 5471961f06 | |||
| 715bc68c16 | |||
| 9c24f7ba8c | |||
| a40647a62f | |||
| b53fb13f30 | |||
| 4b7b280ac3 | |||
| b3aefac9c3 | |||
| b19059f6cb | |||
| b1f5b61bcd | |||
| 810e069bcc | |||
| 62f42cab6b | |||
| 9ee16b8ea3 | |||
| e61884e4bd | |||
| 0f2fc79fb7 | |||
| ba70c1384a | |||
| d24a31d469 | |||
| a5141b187a | |||
| d3c1e678c2 | |||
| 82c34f7b51 | |||
| cb0f26a152 | |||
| bccfbfd630 | |||
| 506b76032f | |||
| 7adcd99ea2 | |||
| 765e30c3a6 | |||
| d7fc07e73c | |||
| 18a879779e | |||
| c53132ecdf | |||
| 03c3ec1577 | |||
| c88bfc54c9 | |||
| a96558c965 | |||
| 1612b7c5db | |||
| 01acb80501 | |||
| f4438ce9e3 | |||
| 5033b9d3c5 | |||
| d733023a29 | |||
| df67c1cf2d | |||
| 05814f7a42 | |||
| ba2bb08589 | |||
| 141df5291d | |||
| dcfe33f427 | |||
| 3a61460f91 | |||
| ee284f2ee6 | |||
| 2591cab561 | |||
| 8456cd1e74 | |||
| 7699b4af9c | |||
| 6c5de48059 | |||
| 9f678ce698 | |||
| 467a484df6 | |||
| 83efe8cff4 | |||
| 2c83144946 | |||
| c165dd5e0e | |||
| 449b85f446 | |||
| c2a66e723f | |||
| 2ee34a5632 | |||
| 1bc82d241a | |||
| 80e7ee321e | |||
| ecd3dd8883 | |||
| 332953c9a4 | |||
| 1cdd56bf02 | |||
| 3041071269 | |||
| 2da821389c | |||
| d484e4f3ff | |||
| 74dc61b8c4 | |||
| 717358b56b | |||
| 836634c47f | |||
| a6f6ecfe6c | |||
| e3da26ebbd | |||
| e1c008f5a3 | |||
| cb849a7071 | |||
| 3e37279db6 | |||
| b3264ec2a8 | |||
| df860fd3cd | |||
| 636523a2f5 | |||
| a19b51b7e0 | |||
| fbdece6759 | |||
| 64d42fefda | |||
| 447cfa18e9 | |||
| ec3d9d6253 | |||
| 5cd2cb38e1 | |||
| e9fb5eb38a | |||
| 5a858915ea | |||
| d85a020e4e | |||
| 0cc79ba366 | |||
| 1e29911292 | |||
| 931686a5ee | |||
| bf715ac23e | |||
| b213344d25 | |||
| 0a48984fab | |||
| cbdd107799 |
@@ -21,7 +21,7 @@ jobs:
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 24
|
||||
- version: 25
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
|
||||
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
|
||||
|
||||
## Continuous Integration Builds
|
||||
|
||||
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
|
||||
CI builds are defined with [GitHub Actions workflows](.github/workflows).
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
|
||||
+2
-2
@@ -86,7 +86,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://docs.hibernate.org/orm/5.6/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
@@ -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.13.4/api/",
|
||||
// "https://junit.org/junit5/docs/5.14.1/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/",
|
||||
|
||||
@@ -13,7 +13,9 @@ content:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: ['main', '{6..9}.+({1..9}).x']
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
@@ -36,4 +38,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
|
||||
|
||||
+16
-21
@@ -50,9 +50,8 @@ XML configuration:
|
||||
|
||||
The preceding XML is more succinct. However, typos are discovered at runtime rather than
|
||||
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
|
||||
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
|
||||
that supports automatic property completion when you create bean definitions. Such IDE
|
||||
assistance is highly recommended.
|
||||
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
|
||||
completion when you create bean definitions. Such IDE assistance is highly recommended.
|
||||
|
||||
You can also configure a `java.util.Properties` instance, as follows:
|
||||
|
||||
@@ -85,11 +84,11 @@ element. The following example shows how to use it:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="theTargetBean" class="..."/>
|
||||
<bean id="collaborator" class="..." />
|
||||
|
||||
<bean id="theClientBean" class="...">
|
||||
<bean id="client" class="...">
|
||||
<property name="targetName">
|
||||
<idref bean="theTargetBean"/>
|
||||
<idref bean="collaborator" />
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
@@ -99,28 +98,24 @@ following snippet:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="theTargetBean" class="..." />
|
||||
<bean id="collaborator" class="..." />
|
||||
|
||||
<bean id="theClientBean" class="...">
|
||||
<property name="targetName" ref="theTargetBean"/>
|
||||
<bean id="client" class="...">
|
||||
<property name="targetName" value="collaborator" />
|
||||
</bean>
|
||||
----
|
||||
|
||||
The first form is preferable to the second, because using the `idref` tag lets the
|
||||
container validate at deployment time that the referenced, named bean actually
|
||||
exists. In the second variation, no validation is performed on the value that is passed
|
||||
to the `targetName` property of the `client` bean. Typos are only discovered (with most
|
||||
container validate at deployment time that the referenced, named bean actually exists. In
|
||||
the second variation, no validation is performed on the value that is passed to the
|
||||
`targetName` property of the `client` bean. Typos are therefore only discovered (with most
|
||||
likely fatal results) when the `client` bean is actually instantiated. If the `client`
|
||||
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting exception
|
||||
may only be discovered long after the container is deployed.
|
||||
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting
|
||||
exception may only be discovered long after the container is deployed.
|
||||
|
||||
NOTE: The `local` attribute on the `idref` element is no longer supported in the 4.0 beans
|
||||
XSD, since it does not provide value over a regular `bean` reference any more. Change
|
||||
your existing `idref local` references to `idref bean` when upgrading to the 4.0 schema.
|
||||
|
||||
A common place (at least in versions earlier than Spring 2.0) where the `<idref/>` element
|
||||
brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors] in a
|
||||
`ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
|
||||
NOTE: A common place (at least in versions earlier than Spring 2.0) where the `<idref/>`
|
||||
element brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors]
|
||||
in a `ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
|
||||
interceptor names prevents you from misspelling an interceptor ID.
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -339,11 +339,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
|
||||
modularity, but determining exactly where the autowired bean definitions are declared is
|
||||
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
|
||||
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
|
||||
explicit in the code, and this may be just fine. Remember that the
|
||||
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
|
||||
can render graphs showing how everything is wired, which may be all you need. Also,
|
||||
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
|
||||
and quickly show you the location of `@Bean` methods that return that type.
|
||||
explicit in the code, and this may be just fine. Note that the
|
||||
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
|
||||
showing how everything is wired, which may be all you need. Also, your Java IDE can
|
||||
easily find all declarations and uses of the `AccountRepository` type and quickly show
|
||||
you the location of `@Bean` methods that return that type.
|
||||
|
||||
In cases where this ambiguity is not acceptable and you wish to have direct navigation
|
||||
from within your IDE from one `@Configuration` class to another, consider autowiring the
|
||||
|
||||
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
|
||||
Language] but offers additional features, most notably method invocation and basic string
|
||||
templating functionality.
|
||||
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
|
||||
EL, to name a few -- the Spring Expression Language was created to provide the Spring
|
||||
community with a single well supported expression language that can be used across all
|
||||
the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements
|
||||
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
|
||||
That said, SpEL is based on a technology-agnostic API that lets other expression language
|
||||
implementations be integrated, should the need arise.
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and
|
||||
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
|
||||
Spring community with a single well supported expression language that can be used across
|
||||
all the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements for
|
||||
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
|
||||
is based on a technology-agnostic API that lets other expression language implementations
|
||||
be integrated, should the need arise.
|
||||
|
||||
While SpEL serves as the foundation for expression evaluation within the Spring
|
||||
portfolio, it is not directly tied to Spring and can be used independently. To
|
||||
|
||||
@@ -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,9 +319,17 @@ progresses.
|
||||
== Testing
|
||||
|
||||
This section addresses testing with the combination of Kotlin and Spring Framework.
|
||||
The recommended testing framework is https://junit.org/junit5/[JUnit 5] along with
|
||||
The recommended testing framework is https://junit.org/[JUnit Jupiter] along with
|
||||
https://mockk.io/[Mockk] for mocking.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
|
||||
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
|
||||
in this section.
|
||||
====
|
||||
|
||||
NOTE: If you are using Spring Boot, see
|
||||
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
|
||||
|
||||
@@ -352,7 +360,6 @@ file with a `spring.test.constructor.autowire.mode = all` property.
|
||||
[[per_class-lifecycle]]
|
||||
=== `PER_CLASS` Lifecycle
|
||||
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
With JUnit Jupiter (JUnit 5), Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
||||
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
|
||||
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ default mode may be set via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
The default mode may also be configured as a
|
||||
https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
|
||||
https://docs.junit.org/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
|
||||
|
||||
If the `spring.test.constructor.autowire.mode` property is not set, test class
|
||||
constructors will not be automatically autowired.
|
||||
|
||||
@@ -5,24 +5,25 @@ It is important to be able to perform some integration testing without requiring
|
||||
deployment to your application server or connecting to other enterprise infrastructure.
|
||||
Doing so lets you test things such as:
|
||||
|
||||
* The correct wiring of your Spring IoC container contexts.
|
||||
* Data access using JDBC or an ORM tool. This can include such things as the correctness
|
||||
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
|
||||
* The correct wiring of your Spring components.
|
||||
* Data access using JDBC or an ORM tool.
|
||||
** This can include such things as the correctness of SQL statements, Hibernate queries,
|
||||
JPA entity mappings, and so forth.
|
||||
|
||||
The Spring Framework provides first-class support for integration testing in the
|
||||
`spring-test` module. The name of the actual JAR file might include the release version
|
||||
and might also be in the long `org.springframework.test` form, depending on where you get
|
||||
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
|
||||
for an explanation). This library includes the `org.springframework.test` package, which
|
||||
`spring-test` module. The name of the actual JAR file might include the release version,
|
||||
depending on where you get it from (see the
|
||||
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
|
||||
for details). This library includes the `org.springframework.test` package, which
|
||||
contains valuable classes for integration testing with a Spring container. This testing
|
||||
does not rely on an application server or other deployment environment. Such tests are
|
||||
slower to run than unit tests but much faster than the equivalent Selenium tests or
|
||||
remote tests that rely on deployment to an application server.
|
||||
|
||||
Unit and integration testing support is provided in the form of the annotation-driven
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
|
||||
agnostic of the actual testing framework in use, which allows instrumentation of tests
|
||||
in various environments, including JUnit, TestNG, and others.
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
|
||||
framework is agnostic of the actual testing framework in use, which allows
|
||||
instrumentation of tests in various environments, including JUnit, TestNG, and others.
|
||||
|
||||
The following section provides an overview of the high-level goals of Spring's
|
||||
integration support, and the rest of this chapter then focuses on dedicated topics:
|
||||
|
||||
@@ -8,9 +8,9 @@ use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
|
||||
== Why Geb and MockMvc?
|
||||
|
||||
Geb is backed by WebDriver, so it offers many of the
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[same benefits] that we get from
|
||||
WebDriver. However, Geb makes things even easier by taking care of some of the
|
||||
boilerplate code for us.
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[same benefits]
|
||||
that we get from WebDriver. However, Geb makes things even easier by taking care of some
|
||||
of the boilerplate code for us.
|
||||
|
||||
[[mockmvc-server-htmlunit-geb-setup]]
|
||||
== MockMvc and Geb Setup
|
||||
@@ -28,7 +28,8 @@ def setup() {
|
||||
----
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
usage, see
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
|
||||
This ensures that any URL referencing `localhost` as the server is directed to our
|
||||
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
||||
@@ -62,10 +63,10 @@ forwarded to the current page object. This removes a lot of the boilerplate code
|
||||
needed when using WebDriver directly.
|
||||
|
||||
As with direct WebDriver usage, this improves on the design of our
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] by using the Page Object
|
||||
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
|
||||
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
|
||||
`CreateMessagePage` implementation:
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by using the Page Object Pattern. As mentioned previously, we can use the Page Object
|
||||
Pattern with HtmlUnit and WebDriver, but it is even easier with Geb. Consider our new
|
||||
Groovy-based `CreateMessagePage` implementation:
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
|
||||
@@ -7,8 +7,7 @@ to use the raw HtmlUnit libraries.
|
||||
[[mockmvc-server-htmlunit-mah-setup]]
|
||||
== MockMvc and HtmlUnit Setup
|
||||
|
||||
First, make sure that you have included a test dependency on
|
||||
`org.htmlunit:htmlunit`.
|
||||
First, make sure that you have included a test dependency on `org.htmlunit:htmlunit`.
|
||||
|
||||
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
|
||||
`MockMvcWebClientBuilder`, as follows:
|
||||
@@ -45,7 +44,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
|
||||
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
|
||||
This ensures that any URL that references `localhost` as the server is directed to our
|
||||
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
||||
@@ -77,7 +76,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
|
||||
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
|
||||
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
|
||||
to create a message, as the following example shows:
|
||||
@@ -144,10 +143,10 @@ Kotlin::
|
||||
======
|
||||
|
||||
The preceding code improves on our
|
||||
xref:testing/mockmvc/htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways.
|
||||
First, we no longer have to explicitly verify our form and then create a request that
|
||||
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
|
||||
significantly reducing the overhead.
|
||||
xref:testing/mockmvc/htmlunit/why.adoc#mockmvc-server-htmlunit-why[MockMvc test] in a
|
||||
number of ways. First, we no longer have to explicitly verify our form and then create a
|
||||
request that looks like the form. Instead, we request the form, fill it out, and submit
|
||||
it, thereby significantly reducing the overhead.
|
||||
|
||||
Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit
|
||||
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
|
||||
|
||||
@@ -203,7 +203,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
usage, see <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
|
||||
|
||||
The preceding example ensures that any URL that references `localhost` as the server is
|
||||
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
|
||||
@@ -259,10 +259,11 @@ Kotlin::
|
||||
======
|
||||
--
|
||||
|
||||
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
This improves on the design of our
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by leveraging the Page Object Pattern. As we mentioned in
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
|
||||
with HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
|
||||
HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
--
|
||||
|
||||
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
|
||||
</form>
|
||||
----
|
||||
|
||||
How do we ensure that our form produce the correct request to create a new message? A
|
||||
How do we ensure that our form produces the correct request to create a new message? A
|
||||
naive attempt might resemble the following:
|
||||
|
||||
[tabs]
|
||||
@@ -154,7 +154,7 @@ validation.
|
||||
[[mockmvc-server-htmlunit-why-integration]]
|
||||
== Integration Testing to the Rescue?
|
||||
|
||||
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
|
||||
To resolve the issues mentioned above, we could perform end-to-end integration testing,
|
||||
but this has some drawbacks. Consider testing the view that lets us page through the
|
||||
messages. We might need the following tests:
|
||||
|
||||
@@ -171,7 +171,7 @@ leads to a number of additional challenges:
|
||||
* Testing can become slow, since each test would need to ensure that the database is in
|
||||
the correct state.
|
||||
* Since our database needs to be in a specific state, we cannot run tests in parallel.
|
||||
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
|
||||
* Performing assertions on items such as auto-generated IDs, timestamps, and others can
|
||||
be difficult.
|
||||
|
||||
These challenges do not mean that we should abandon end-to-end integration testing
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ TestNG:
|
||||
* Dependency injection for test constructors, test methods, and test lifecycle callback
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
|
||||
Injection with the `SpringExtension`] for further details.
|
||||
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
|
||||
* Powerful support for link:https://docs.junit.org/current/user-guide/#extensions-conditions[conditional
|
||||
test execution] based on SpEL expressions, environment variables, system properties,
|
||||
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
@@ -160,7 +160,7 @@ for further details.
|
||||
=== Dependency Injection with the `SpringExtension`
|
||||
|
||||
The `SpringExtension` implements the
|
||||
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
|
||||
link:https://docs.junit.org/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
|
||||
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
|
||||
constructors, test methods, and test lifecycle callback methods.
|
||||
|
||||
|
||||
@@ -362,7 +362,7 @@ of `PlatformTransactionManager` within the test's `ApplicationContext`, you can
|
||||
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
|
||||
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
|
||||
`@Configuration` class. Consult the
|
||||
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
|
||||
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager(org.springframework.test.context.TestContext,java.lang.String)[javadoc
|
||||
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
|
||||
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ infrastructure and controller declarations and use it to handle requests via moc
|
||||
and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
|
||||
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
|
||||
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext(org.springframework.context.ApplicationContext)[WebHttpHandlerBuilder]
|
||||
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
|
||||
requests:
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
|
||||
and, consequently, browsers reject them.
|
||||
|
||||
Each `HandlerMapping` can be
|
||||
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
|
||||
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
|
||||
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
|
||||
use the WebFlux Java configuration to declare such mappings, which results in a single,
|
||||
global map passed to all `HandlerMapping` implementations.
|
||||
@@ -57,7 +57,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
|
||||
The rules for combining global and local configuration are generally additive -- for example,
|
||||
all global and all local origins. For those attributes where only a single value can be
|
||||
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
for more details.
|
||||
|
||||
[TIP]
|
||||
|
||||
@@ -448,7 +448,7 @@ Java::
|
||||
----
|
||||
@GetMapping
|
||||
FragmentsRendering handle() {
|
||||
return FragmentsRendering.with("posts").fragment("comments").build();
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -458,7 +458,7 @@ Kotlin::
|
||||
----
|
||||
@GetMapping
|
||||
fun handle(): FragmentsRendering {
|
||||
return FragmentsRendering.with("posts").fragment("comments").build()
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -310,7 +310,7 @@ Java::
|
||||
Flux<String> source = ... ;
|
||||
Mono<Void> output = session.send(source.map(session::textMessage)); <2>
|
||||
|
||||
return Mono.zip(input, output).then(); <3>
|
||||
return input.and(output); <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -338,7 +338,7 @@ Kotlin::
|
||||
val source: Flux<String> = ...
|
||||
val output = session.send(source.map(session::textMessage)) // <2>
|
||||
|
||||
return Mono.zip(input, output).then() // <3>
|
||||
return input.and(output) // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -114,6 +114,6 @@ and others) and is equivalent to `required=false`.
|
||||
| Any other argument
|
||||
| If a method argument is not matched to any of the above, it is, by default, resolved as
|
||||
a `@RequestParam` if it is a simple type, as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
or as a `@ModelAttribute`, otherwise.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -205,7 +205,7 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
|
||||
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
|
||||
value type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
|
||||
+1
-1
@@ -74,6 +74,6 @@ When a `@RequestParam` annotation is declared on a `Map<String, String>` or
|
||||
|
||||
Note that use of `@RequestParam` is optional -- for example, to set its attributes. By
|
||||
default, any argument that is a simple value type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@RequestParam`.
|
||||
|
||||
+1
-1
@@ -87,6 +87,6 @@ Reactor provides a dedicated operator for that, `Flux#collectList()`.
|
||||
| Other return values
|
||||
| If a return value remains unresolved in any other way, it is treated as a model
|
||||
attribute, unless it is a simple type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
in which case it remains unresolved.
|
||||
|===
|
||||
|
||||
@@ -89,39 +89,7 @@ Kotlin::
|
||||
|
||||
You can map requests by using glob patterns and wildcards:
|
||||
|
||||
[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+`
|
||||
|
||||
| `+{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+`
|
||||
|
||||
|===
|
||||
include::partial$web/uri-patterns.adoc[leveloffset=+1]
|
||||
|
||||
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
|
||||
|
||||
|
||||
@@ -7,19 +7,23 @@ 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. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. Java Bean Validation is applied individually to an
|
||||
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] 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`.
|
||||
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.
|
||||
|
||||
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`.
|
||||
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.
|
||||
|
||||
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.with("posts").fragment("comments").build();
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -58,7 +58,7 @@ Kotlin::
|
||||
----
|
||||
@GetMapping
|
||||
fun handle(): FragmentsRendering {
|
||||
return FragmentsRendering.with("posts").fragment("comments").build()
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-filters[See equivalent in the Reactive stack]#
|
||||
|
||||
The `spring-web` module provides some useful filters:
|
||||
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:
|
||||
|
||||
* xref:web/webmvc/filters.adoc#filters-http-put[Form Data]
|
||||
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
|
||||
@@ -11,9 +14,19 @@ The `spring-web` module provides some useful filters:
|
||||
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
|
||||
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
|
||||
|
||||
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].
|
||||
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.
|
||||
|
||||
|
||||
[[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
|
||||
| `+{0}+` property name, `+{1}+` property value, `+{2}+` simple name of required type
|
||||
|
||||
| `UnsatisfiedServletRequestParameterException`
|
||||
| (default)
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@ the content negotiation during the error handling phase will decide which conten
|
||||
|
||||
| Any other return value
|
||||
| If a return value is not matched to any of the above and is not a simple type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
|
||||
by default, it is treated as a model attribute to be added to the model. If it is a simple type,
|
||||
it remains unresolved.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -135,6 +135,6 @@ and others) and is equivalent to `required=false`.
|
||||
| Any other argument
|
||||
| If a method argument is not matched to any of the earlier values in this table and it is
|
||||
a simple type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
|
||||
it is resolved as a `@RequestParam`. Otherwise, it is resolved as a `@ModelAttribute`.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -250,7 +250,7 @@ xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
|
||||
value type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
|
||||
+1
-1
@@ -117,6 +117,6 @@ Kotlin::
|
||||
|
||||
Note that use of `@RequestParam` is optional (for example, to set its attributes).
|
||||
By default, any argument that is a simple value type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
|
||||
and is not resolved by any other argument resolver, is treated as if it were annotated
|
||||
with `@RequestParam`.
|
||||
|
||||
+1
-1
@@ -98,6 +98,6 @@ supported for all return values.
|
||||
| Other return values
|
||||
| If a return value remains unresolved in any other way, it is treated as a model
|
||||
attribute, unless it is a simple type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
in which case it remains unresolved.
|
||||
|===
|
||||
|
||||
+3
-16
@@ -103,22 +103,9 @@ 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.
|
||||
|
||||
`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].
|
||||
You can map requests by using glob patterns and wildcards:
|
||||
|
||||
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
|
||||
include::partial$web/uri-patterns.adoc[leveloffset=+1]
|
||||
|
||||
Captured URI variables can be accessed with `@PathVariable`. For example:
|
||||
|
||||
@@ -234,7 +221,7 @@ When multiple patterns match a URL, the best match must be selected. This is don
|
||||
one of the following depending on whether use of parsed `PathPattern` is enabled for use or not:
|
||||
|
||||
* {spring-framework-api}/web/util/pattern/PathPattern.html#SPECIFICITY_COMPARATOR[`PathPattern.SPECIFICITY_COMPARATOR`]
|
||||
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator-java.lang.String-[`AntPathMatcher.getPatternComparator(String path)`]
|
||||
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator(java.lang.String)[`AntPathMatcher.getPatternComparator(String path)`]
|
||||
|
||||
Both help to sort patterns with more specific ones on top. A pattern is more specific if
|
||||
it has a lower count of URI variables (counted as 1), single wildcards (counted as 1),
|
||||
|
||||
@@ -7,22 +7,26 @@ 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. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. Java Bean Validation is applied individually to an
|
||||
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] 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`.
|
||||
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.
|
||||
|
||||
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`.
|
||||
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.
|
||||
|
||||
Applications must handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
Applications should handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` since 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.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
[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.
|
||||
|
||||
|===
|
||||
@@ -4,7 +4,7 @@
|
||||
"@antora/atlas-extension": "1.0.0-alpha.2",
|
||||
"@antora/collector-extension": "1.0.0-alpha.3",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.2",
|
||||
"@springio/antora-extensions": "1.14.7",
|
||||
"fast-xml-parser": "4.5.2",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
}
|
||||
|
||||
@@ -7,33 +7,33 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4.1"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.11"))
|
||||
api(platform("io.netty:netty-bom:4.1.127.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.14"))
|
||||
api(platform("io.netty:netty-bom:4.1.128.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.10"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.13"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.28"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.26"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.26"))
|
||||
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.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.13.4"))
|
||||
api(platform("org.mockito:mockito-bom:5.19.0"))
|
||||
api(platform("org.junit:junit-bom:5.14.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.20.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.3")
|
||||
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.2")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.2.3")
|
||||
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.1")
|
||||
api("com.google.protobuf:protobuf-java-util:4.32.0")
|
||||
api("com.google.code.gson:gson:2.13.2")
|
||||
api("com.google.protobuf:protobuf-java-util:4.32.1")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.jayway.jsonpath:json-path:2.10.0")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
@@ -53,11 +53,11 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.11")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.12")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.19.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.19.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.19.Final")
|
||||
api("io.undertow:undertow-core:2.3.20.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.20.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.20.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -90,11 +90,11 @@ dependencies {
|
||||
api("junit:junit:4.13.2")
|
||||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.17.6")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.6")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.6")
|
||||
api("org.apache.activemq:artemis-jakarta-client:2.31.2")
|
||||
api("org.apache.activemq:artemis-junit-5:2.31.2")
|
||||
api("org.apache.activemq:activemq-broker:5.17.7")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.7")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.7")
|
||||
api("org.apache.activemq:artemis-jakarta-client:2.42.0")
|
||||
api("org.apache.activemq:artemis-junit-5:2.42.0")
|
||||
api("org.apache.commons:commons-pool2:2.9.0")
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
@@ -113,9 +113,9 @@ dependencies {
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.easymock:easymock:5.5.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.11")
|
||||
api("org.dom4j:dom4j:2.2.0")
|
||||
api("org.easymock:easymock:5.6.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.12")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
@@ -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.16.0")
|
||||
api("org.htmlunit:htmlunit:4.18.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,16 +137,16 @@ 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.35.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.35.0")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.38.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
api("org.testng:testng:7.11.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.59")
|
||||
api("org.webjars:webjars-locator-lite:1.1.0")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.3")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.3")
|
||||
api("org.yaml:snakeyaml:2.4")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.4")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.4")
|
||||
api("org.yaml:snakeyaml:2.5")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.11-SNAPSHOT
|
||||
version=6.2.15
|
||||
|
||||
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 the any advice
|
||||
* Set the arguments to be used on subsequent invocations in 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);
|
||||
|
||||
@@ -81,13 +81,19 @@ public abstract class ScopedProxyUtils {
|
||||
// Copy autowire settings from original bean definition.
|
||||
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
|
||||
proxyDefinition.setPrimary(targetDefinition.isPrimary());
|
||||
proxyDefinition.setFallback(targetDefinition.isFallback());
|
||||
if (targetDefinition instanceof AbstractBeanDefinition abd) {
|
||||
proxyDefinition.setDefaultCandidate(abd.isDefaultCandidate());
|
||||
proxyDefinition.copyQualifiersFrom(abd);
|
||||
}
|
||||
|
||||
// The target bean should be ignored in favor of the scoped proxy.
|
||||
targetDefinition.setAutowireCandidate(false);
|
||||
targetDefinition.setPrimary(false);
|
||||
targetDefinition.setFallback(false);
|
||||
if (targetDefinition instanceof AbstractBeanDefinition abd) {
|
||||
abd.setDefaultCandidate(false);
|
||||
}
|
||||
|
||||
// Register the target bean as separate bean in the factory.
|
||||
registry.registerBeanDefinition(targetBeanName, targetDefinition);
|
||||
|
||||
+4
-3
@@ -37,7 +37,6 @@ import static org.mockito.Mockito.verify;
|
||||
* Tests for {@link AsyncExecutionInterceptor}.
|
||||
*
|
||||
* @author Bao Ngo
|
||||
* @since 7.0
|
||||
*/
|
||||
class AsyncExecutionInterceptorTests {
|
||||
|
||||
@@ -62,11 +61,13 @@ class AsyncExecutionInterceptorTests {
|
||||
O run();
|
||||
}
|
||||
|
||||
|
||||
static class FutureRunner implements GenericRunner<Future<Void>> {
|
||||
|
||||
@Override
|
||||
public Future<Void> run() {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
});
|
||||
return CompletableFuture.runAsync(() -> {});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@ package org.springframework.aop.scope;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
@@ -25,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
* Tests for {@link ScopedProxyUtils}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.1.10
|
||||
*/
|
||||
class ScopedProxyUtilsTests {
|
||||
@@ -53,15 +63,79 @@ class ScopedProxyUtilsTests {
|
||||
@Test
|
||||
void getOriginalBeanNameForNullTargetBean() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName(null))
|
||||
.withMessage("bean name 'null' does not refer to the target of a scoped proxy");
|
||||
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName(null))
|
||||
.withMessage("bean name 'null' does not refer to the target of a scoped proxy");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOriginalBeanNameForNonScopedTarget() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName("myBean"))
|
||||
.withMessage("bean name 'myBean' does not refer to the target of a scoped proxy");
|
||||
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName("myBean"))
|
||||
.withMessage("bean name 'myBean' does not refer to the target of a scoped proxy");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createScopedProxyTargetAppliesAutowireSettingsToProxyBeanDefinition() {
|
||||
AbstractBeanDefinition targetDefinition = new GenericBeanDefinition();
|
||||
// Opposite of defaults
|
||||
targetDefinition.setAutowireCandidate(false);
|
||||
targetDefinition.setDefaultCandidate(false);
|
||||
targetDefinition.setPrimary(true);
|
||||
targetDefinition.setFallback(true);
|
||||
|
||||
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
|
||||
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(
|
||||
new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false);
|
||||
AbstractBeanDefinition proxyBeanDefinition = (AbstractBeanDefinition) proxyHolder.getBeanDefinition();
|
||||
|
||||
assertThat(proxyBeanDefinition.isAutowireCandidate()).isFalse();
|
||||
assertThat(proxyBeanDefinition.isDefaultCandidate()).isFalse();
|
||||
assertThat(proxyBeanDefinition.isPrimary()).isTrue();
|
||||
assertThat(proxyBeanDefinition.isFallback()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createScopedProxyTargetAppliesBeanAttributesToProxyBeanDefinition() {
|
||||
GenericBeanDefinition targetDefinition = new GenericBeanDefinition();
|
||||
// Opposite of defaults
|
||||
targetDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
targetDefinition.setSource("theSource");
|
||||
targetDefinition.addQualifier(new AutowireCandidateQualifier("myQualifier"));
|
||||
|
||||
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
|
||||
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(
|
||||
new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false);
|
||||
BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition();
|
||||
|
||||
assertThat(proxyBeanDefinition.getRole()).isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
assertThat(proxyBeanDefinition).isInstanceOf(RootBeanDefinition.class);
|
||||
assertThat(proxyBeanDefinition.getPropertyValues()).hasSize(2);
|
||||
assertThat(proxyBeanDefinition.getPropertyValues().get("proxyTargetClass")).isEqualTo(false);
|
||||
assertThat(proxyBeanDefinition.getPropertyValues().get("targetBeanName")).isEqualTo(
|
||||
ScopedProxyUtils.getTargetBeanName("myBean"));
|
||||
|
||||
RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) proxyBeanDefinition;
|
||||
assertThat(rootBeanDefinition.getQualifiers()).hasSize(1);
|
||||
assertThat(rootBeanDefinition.hasQualifier("myQualifier")).isTrue();
|
||||
assertThat(rootBeanDefinition.getSource()).isEqualTo("theSource");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createScopedProxyTargetCleansAutowireSettingsInTargetDefinition() {
|
||||
AbstractBeanDefinition targetDefinition = new GenericBeanDefinition();
|
||||
targetDefinition.setAutowireCandidate(true);
|
||||
targetDefinition.setDefaultCandidate(true);
|
||||
targetDefinition.setPrimary(true);
|
||||
targetDefinition.setFallback(true);
|
||||
|
||||
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
|
||||
ScopedProxyUtils.createScopedProxy(
|
||||
new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false);
|
||||
|
||||
assertThat(targetDefinition.isAutowireCandidate()).isFalse();
|
||||
assertThat(targetDefinition.isDefaultCandidate()).isFalse();
|
||||
assertThat(targetDefinition.isPrimary()).isFalse();
|
||||
assertThat(targetDefinition.isFallback()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
@@ -19,6 +19,7 @@ package org.springframework.aop.framework
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Tests for Kotlin support in [CglibAopProxy].
|
||||
@@ -48,6 +49,13 @@ class CglibAopProxyKotlinTests {
|
||||
assertThatThrownBy { proxy.checkedException() }.isInstanceOf(CheckedException::class.java)
|
||||
}
|
||||
|
||||
@Test // gh-35487
|
||||
fun jvmDefault() {
|
||||
val proxyFactory = ProxyFactory()
|
||||
proxyFactory.setTarget(AddressRepo())
|
||||
proxyFactory.proxy
|
||||
}
|
||||
|
||||
|
||||
open class MyKotlinBean {
|
||||
|
||||
@@ -63,4 +71,24 @@ class CglibAopProxyKotlinTests {
|
||||
}
|
||||
|
||||
class CheckedException() : Exception()
|
||||
|
||||
open class AddressRepo(): CrudRepo<Address, Int>
|
||||
|
||||
interface CrudRepo<E : Any, ID : Any> {
|
||||
fun save(e: E): E {
|
||||
return e
|
||||
}
|
||||
fun delete(id: ID): Long {
|
||||
return 0L
|
||||
}
|
||||
}
|
||||
|
||||
data class Address(
|
||||
val id: Int = 0,
|
||||
val street: String,
|
||||
val version: Int = 0,
|
||||
val createdAt: LocalDateTime? = null,
|
||||
val updatedAt: LocalDateTime? = null,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
+5
-6
@@ -566,7 +566,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
|
||||
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
|
||||
Class<?> targetClass = clazz;
|
||||
Class<?> targetClass = ClassUtils.getUserClass(clazz);
|
||||
|
||||
do {
|
||||
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
|
||||
@@ -586,12 +586,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
|
||||
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
|
||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
|
||||
if (method.isBridge()) {
|
||||
return;
|
||||
}
|
||||
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
|
||||
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
MergedAnnotation<?> ann = findAutowiredAnnotation(method);
|
||||
if (ann != null && method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Autowired annotation is not supported on static methods: " + method);
|
||||
@@ -609,7 +608,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
}
|
||||
boolean required = determineRequiredStatus(ann);
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
methodElements.add(new AutowiredMethodElement(method, required, pd));
|
||||
}
|
||||
});
|
||||
|
||||
+11
-1
@@ -180,7 +180,6 @@ 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;
|
||||
@@ -375,6 +374,17 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
+20
-1
@@ -29,6 +29,7 @@ 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;
|
||||
@@ -57,6 +58,7 @@ 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
|
||||
@@ -68,7 +70,8 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
|
||||
new ManagedMapDelegate(),
|
||||
new LinkedHashMapDelegate(),
|
||||
new BeanReferenceDelegate(),
|
||||
new TypedStringValueDelegate()
|
||||
new TypedStringValueDelegate(),
|
||||
new AutowiredPropertyMarkerDelegate()
|
||||
);
|
||||
|
||||
|
||||
@@ -216,4 +219,20 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -294,9 +294,12 @@ public class InstanceSupplierCodeGenerator {
|
||||
|
||||
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
|
||||
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
|
||||
CodeWarnings codeWarnings = new CodeWarnings();
|
||||
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
|
||||
codeWarnings.detectDeprecation(suppliedType, factoryMethod);
|
||||
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
|
||||
method.addModifiers(PRIVATE_STATIC);
|
||||
codeWarnings.suppress(method);
|
||||
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
|
||||
method.addStatement(generateInstanceSupplierForFactoryMethod(
|
||||
factoryMethod, suppliedType, targetClass, factoryMethod.getName()));
|
||||
|
||||
+13
@@ -152,6 +152,18 @@ public interface ConfigurableListableBeanFactory
|
||||
*/
|
||||
boolean isConfigurationFrozen();
|
||||
|
||||
/**
|
||||
* Mark current thread as main bootstrap thread for singleton instantiation,
|
||||
* with lenient bootstrap locking applying for background threads.
|
||||
* <p>Any such marker is to be removed at the end of the managed bootstrap in
|
||||
* {@link #preInstantiateSingletons()}.
|
||||
* @since 6.2.12
|
||||
* @see #setBootstrapExecutor
|
||||
* @see #preInstantiateSingletons()
|
||||
*/
|
||||
default void prepareSingletonBootstrap() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that all non-lazy-init singletons are instantiated, also considering
|
||||
* {@link org.springframework.beans.factory.FactoryBean FactoryBeans}.
|
||||
@@ -159,6 +171,7 @@ public interface ConfigurableListableBeanFactory
|
||||
* @throws BeansException if one of the singleton beans could not be created.
|
||||
* Note: This may have left the factory with some beans already initialized!
|
||||
* Call {@link #destroySingletons()} for full cleanup in this case.
|
||||
* @see #prepareSingletonBootstrap()
|
||||
* @see #destroySingletons()
|
||||
*/
|
||||
void preInstantiateSingletons() throws BeansException;
|
||||
|
||||
-1
@@ -32,7 +32,6 @@ import org.springframework.util.StringUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @since 1.1
|
||||
* @see PropertiesBeanDefinitionReader
|
||||
* @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
|
||||
*/
|
||||
public abstract class BeanDefinitionReaderUtils {
|
||||
|
||||
-1
@@ -43,7 +43,6 @@ import org.springframework.core.AliasRegistry;
|
||||
* @see DefaultListableBeanFactory
|
||||
* @see org.springframework.context.support.GenericApplicationContext
|
||||
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
|
||||
* @see PropertiesBeanDefinitionReader
|
||||
*/
|
||||
public interface BeanDefinitionRegistry extends AliasRegistry {
|
||||
|
||||
|
||||
+21
-9
@@ -1102,6 +1102,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSingletonBootstrap() {
|
||||
this.mainThreadPrefix = getThreadNamePrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preInstantiateSingletons() throws BeansException {
|
||||
if (logger.isTraceEnabled()) {
|
||||
@@ -1114,7 +1119,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
|
||||
// Trigger initialization of all non-lazy singleton beans...
|
||||
this.preInstantiationThread.set(PreInstantiation.MAIN);
|
||||
this.mainThreadPrefix = getThreadNamePrefix();
|
||||
if (this.mainThreadPrefix == null) {
|
||||
this.mainThreadPrefix = getThreadNamePrefix();
|
||||
}
|
||||
try {
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
for (String beanName : beanNames) {
|
||||
@@ -1474,7 +1481,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@Override
|
||||
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
|
||||
super.registerSingleton(beanName, singletonObject);
|
||||
|
||||
updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
|
||||
this.allBeanNamesByType.remove(Object.class);
|
||||
this.singletonBeanNamesByType.remove(Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1643,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 {
|
||||
|
||||
@@ -1981,7 +1991,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
|
||||
for (String candidate : candidateNames) {
|
||||
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
|
||||
(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
|
||||
(!multiple || matchesBeanName(candidate, descriptor.getDependencyName()) ||
|
||||
getAutowireCandidateResolver().hasQualifier(descriptor))) {
|
||||
addCandidateEntry(result, candidate, descriptor, requiredType);
|
||||
}
|
||||
}
|
||||
@@ -2098,8 +2109,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
boolean candidateLocal = containsBeanDefinition(candidateBeanName);
|
||||
boolean primaryLocal = containsBeanDefinition(primaryBeanName);
|
||||
if (candidateLocal == primaryLocal) {
|
||||
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
|
||||
"more than one 'primary' bean found among candidates: " + candidates.keySet());
|
||||
String message = "more than one 'primary' bean found among candidates: " + candidates.keySet();
|
||||
logger.trace(message);
|
||||
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), message);
|
||||
}
|
||||
else if (candidateLocal) {
|
||||
primaryBeanName = candidateBeanName;
|
||||
@@ -2251,12 +2263,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given candidate name matches the bean name or the aliases
|
||||
* Determine whether the given dependency name matches the bean name or the aliases
|
||||
* stored in this bean definition.
|
||||
*/
|
||||
protected boolean matchesBeanName(String beanName, @Nullable String candidateName) {
|
||||
return (candidateName != null &&
|
||||
(candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName)));
|
||||
protected boolean matchesBeanName(String beanName, @Nullable String dependencyName) {
|
||||
return (dependencyName != null &&
|
||||
(dependencyName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), dependencyName)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -585,7 +585,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
public void registerContainedBean(String containedBeanName, String containingBeanName) {
|
||||
synchronized (this.containedBeanMap) {
|
||||
Set<String> containedBeans =
|
||||
this.containedBeanMap.computeIfAbsent(containingBeanName, k -> new LinkedHashSet<>(8));
|
||||
this.containedBeanMap.computeIfAbsent(containingBeanName, key -> new LinkedHashSet<>(8));
|
||||
if (!containedBeans.add(containedBeanName)) {
|
||||
return;
|
||||
}
|
||||
@@ -604,7 +604,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
|
||||
synchronized (this.dependentBeanMap) {
|
||||
Set<String> dependentBeans =
|
||||
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
|
||||
this.dependentBeanMap.computeIfAbsent(canonicalName, key -> new LinkedHashSet<>(8));
|
||||
if (!dependentBeans.add(dependentBeanName)) {
|
||||
return;
|
||||
}
|
||||
@@ -612,7 +612,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
|
||||
synchronized (this.dependenciesForBeanMap) {
|
||||
Set<String> dependenciesForBean =
|
||||
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
|
||||
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, key -> new LinkedHashSet<>(8));
|
||||
dependenciesForBean.add(canonicalName);
|
||||
}
|
||||
}
|
||||
|
||||
+36
-31
@@ -128,43 +128,48 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
locked = (lockFlag && this.singletonLock.tryLock());
|
||||
}
|
||||
try {
|
||||
Object object = this.factoryBeanObjectCache.get(beanName);
|
||||
if (object == null) {
|
||||
object = doGetObjectFromFactoryBean(factory, beanName);
|
||||
// Only post-process and store if not put there already during getObject() call above
|
||||
// (for example, because of circular reference processing triggered by custom getBean calls)
|
||||
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
|
||||
if (alreadyThere != null) {
|
||||
object = alreadyThere;
|
||||
}
|
||||
else {
|
||||
if (shouldPostProcess) {
|
||||
if (locked) {
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
}
|
||||
try {
|
||||
object = postProcessObjectFromFactoryBean(object, beanName);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanCreationException(beanName,
|
||||
"Post-processing of FactoryBean's singleton object failed", ex);
|
||||
}
|
||||
finally {
|
||||
// Defensively synchronize against non-thread-safe FactoryBean.getObject() implementations,
|
||||
// potentially to be called from a background thread while the main thread currently calls
|
||||
// the same getObject() method within the singleton lock.
|
||||
synchronized (factory) {
|
||||
Object object = this.factoryBeanObjectCache.get(beanName);
|
||||
if (object == null) {
|
||||
object = doGetObjectFromFactoryBean(factory, beanName);
|
||||
// Only post-process and store if not put there already during getObject() call above
|
||||
// (for example, because of circular reference processing triggered by custom getBean calls)
|
||||
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
|
||||
if (alreadyThere != null) {
|
||||
object = alreadyThere;
|
||||
}
|
||||
else {
|
||||
if (shouldPostProcess) {
|
||||
if (locked) {
|
||||
afterSingletonCreation(beanName);
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
}
|
||||
try {
|
||||
object = postProcessObjectFromFactoryBean(object, beanName);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanCreationException(beanName,
|
||||
"Post-processing of FactoryBean's singleton object failed", ex);
|
||||
}
|
||||
finally {
|
||||
if (locked) {
|
||||
afterSingletonCreation(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (containsSingleton(beanName)) {
|
||||
this.factoryBeanObjectCache.put(beanName, object);
|
||||
if (containsSingleton(beanName)) {
|
||||
this.factoryBeanObjectCache.put(beanName, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
finally {
|
||||
if (locked) {
|
||||
|
||||
+3
-3
@@ -74,10 +74,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Rob Harrop
|
||||
* @since 26.11.2003
|
||||
* @see DefaultListableBeanFactory
|
||||
* @deprecated as of 5.3, in favor of Spring's common bean definition formats
|
||||
* and/or custom reader implementations
|
||||
* @deprecated in favor of Spring's common bean definition formats and/or
|
||||
* custom BeanDefinitionReader implementations
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.3")
|
||||
public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -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 its unchanged
|
||||
// Check it's unchanged
|
||||
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
|
||||
assertThat(name).as("name is OK").isEqualTo(target.getName());
|
||||
}
|
||||
|
||||
+7
-2
@@ -87,6 +87,7 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -1798,7 +1799,7 @@ class DefaultListableBeanFactoryTests {
|
||||
|
||||
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
|
||||
.isThrownBy(() -> lbf.getBean(TestBean.class))
|
||||
.withMessageContaining("more than one 'primary'");
|
||||
.withMessageEndingWith("more than one 'primary' bean found among candidates: [bd1, bd2]");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2122,7 +2123,7 @@ class DefaultListableBeanFactoryTests {
|
||||
|
||||
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
|
||||
.isThrownBy(() -> lbf.getBean(ConstructorDependency.class, 42))
|
||||
.withMessageContaining("more than one 'primary'");
|
||||
.withMessageEndingWith("more than one 'primary' bean found among candidates: [bd1, bd2]");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -3223,6 +3224,10 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(lbf.getBeanNamesForType(DerivedTestBean.class)).containsExactly("bd1");
|
||||
assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames);
|
||||
assertThat(lbf.getBeanNamesForType(Object.class)).isSameAs(allBeanNames);
|
||||
|
||||
lbf.registerSingleton("bd3", new Object());
|
||||
assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames);
|
||||
assertThat(lbf.getBeanNamesForType(Object.class)).containsExactly(StringUtils.addStringToArray(allBeanNames, "bd3"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+171
-84
@@ -1275,88 +1275,6 @@ 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);
|
||||
@@ -1409,6 +1327,19 @@ 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);
|
||||
@@ -1497,6 +1428,114 @@ 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));
|
||||
@@ -2073,8 +2112,8 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
|
||||
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
|
||||
|
||||
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
|
||||
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
|
||||
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
|
||||
@@ -2685,6 +2724,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.registerSingleton("nonNullBean", "Test");
|
||||
bf.registerBeanDefinition("mixedNullableInjectionBean",
|
||||
new RootBeanDefinition(MixedNullableInjectionBean.class));
|
||||
|
||||
MixedNullableInjectionBean mixedNullableInjectionBean = bf.getBean(MixedNullableInjectionBean.class);
|
||||
assertThat(mixedNullableInjectionBean.nonNullBean).isNotNull();
|
||||
assertThat(mixedNullableInjectionBean.nullableBean).isNull();
|
||||
@@ -2695,6 +2735,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.registerSingleton("nonNullBean", "Test");
|
||||
bf.registerBeanDefinition("mixedOptionalInjectionBean",
|
||||
new RootBeanDefinition(MixedOptionalInjectionBean.class));
|
||||
|
||||
MixedOptionalInjectionBean mixedOptionalInjectionBean = bf.getBean(MixedOptionalInjectionBean.class);
|
||||
assertThat(mixedOptionalInjectionBean.nonNullBean).isNotNull();
|
||||
assertThat(mixedOptionalInjectionBean.nullableBean).isNull();
|
||||
@@ -3258,6 +3299,21 @@ 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;
|
||||
@@ -3357,6 +3413,37 @@ 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 {
|
||||
|
||||
|
||||
+2
-3
@@ -444,8 +444,8 @@ class InjectAnnotationBeanPostProcessorTests {
|
||||
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
|
||||
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
|
||||
|
||||
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
|
||||
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
|
||||
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
|
||||
@@ -788,7 +788,6 @@ class InjectAnnotationBeanPostProcessorTests {
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
|
||||
public ConstructorResourceInjectionBean() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
+18
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.beans.factory.aot;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -34,6 +35,7 @@ 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;
|
||||
@@ -626,6 +628,22 @@ 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(
|
||||
|
||||
+13
@@ -39,6 +39,7 @@ 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;
|
||||
@@ -486,4 +487,16 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AutowiredPropertyMarkerTests {
|
||||
|
||||
@Test
|
||||
void generateWhenAutowiredPropertyMarker() {
|
||||
compile(AutowiredPropertyMarker.INSTANCE, (instance, compiler) ->
|
||||
assertThat(instance).isInstanceOf(AutowiredPropertyMarker.class)
|
||||
.isSameAs(AutowiredPropertyMarker.INSTANCE));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+30
@@ -418,6 +418,16 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodIsProtectedAndReturnTypeIsDeprecated() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(DeprecatedBean.class)
|
||||
.setFactoryMethodOnBean("deprecatedReturnTypeProtected", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition());
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
private void compileAndCheckWarnings(BeanDefinition beanDefinition) {
|
||||
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition,
|
||||
((instanceSupplier, compiled) -> {})));
|
||||
@@ -464,6 +474,26 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodReturnTypeIsDeprecatedForRemoval() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(DeprecatedForRemovalBean.class)
|
||||
.setFactoryMethodOnBean("deprecatedReturnType", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition());
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodIsProtectedAndReturnTypeIsDeprecatedForRemoval() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(DeprecatedForRemovalBean.class)
|
||||
.setFactoryMethodOnBean("deprecatedReturnTypeProtected", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition());
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
private void compileAndCheckWarnings(BeanDefinition beanDefinition) {
|
||||
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition,
|
||||
((instanceSupplier, compiled) -> {})));
|
||||
|
||||
+33
-22
@@ -28,6 +28,7 @@ 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;
|
||||
@@ -46,7 +47,6 @@ 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,17 +164,23 @@ class UtilNamespaceHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMapWithRef() {
|
||||
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithRef");
|
||||
void testMapWithRef() throws Exception {
|
||||
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() {
|
||||
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithTypes");
|
||||
void testMapWithTypes() throws Exception {
|
||||
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
|
||||
@@ -240,11 +246,11 @@ class UtilNamespaceHandlerTests {
|
||||
void testCircularCollections() {
|
||||
TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionsBean");
|
||||
|
||||
assertThat(bean.getSomeList()).singleElement().isEqualTo(bean);
|
||||
assertThat(bean.getSomeSet()).singleElement().isEqualTo(bean);
|
||||
assertThat(bean.getSomeList()).singleElement().isSameAs(bean);
|
||||
assertThat(bean.getSomeSet()).singleElement().isSameAs(bean);
|
||||
assertThat(bean.getSomeMap()).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isEqualTo(bean);
|
||||
assertThat(value).isSameAs(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -255,17 +261,17 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
List<?> list = bean.getSomeList();
|
||||
assertThat(Proxy.isProxyClass(list.getClass())).isTrue();
|
||||
assertThat(list).singleElement().isEqualTo(bean);
|
||||
assertThat(list).singleElement().isSameAs(bean);
|
||||
|
||||
Set<?> set = bean.getSomeSet();
|
||||
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
|
||||
assertThat(set).singleElement().isEqualTo(bean);
|
||||
assertThat(set).singleElement().isSameAs(bean);
|
||||
|
||||
Map<?, ?> map = bean.getSomeMap();
|
||||
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
|
||||
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isEqualTo(bean);
|
||||
assertThat(value).isSameAs(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -276,17 +282,17 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
List<?> list = bean.getSomeList();
|
||||
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
|
||||
assertThat(list).singleElement().isEqualTo(bean);
|
||||
assertThat(list).singleElement().isSameAs(bean);
|
||||
|
||||
Set<?> set = bean.getSomeSet();
|
||||
assertThat(Proxy.isProxyClass(set.getClass())).isTrue();
|
||||
assertThat(set).singleElement().isEqualTo(bean);
|
||||
assertThat(set).singleElement().isSameAs(bean);
|
||||
|
||||
Map<?, ?> map = bean.getSomeMap();
|
||||
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
|
||||
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isEqualTo(bean);
|
||||
assertThat(value).isSameAs(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -297,17 +303,17 @@ class UtilNamespaceHandlerTests {
|
||||
|
||||
List<?> list = bean.getSomeList();
|
||||
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
|
||||
assertThat(list).singleElement().isEqualTo(bean);
|
||||
assertThat(list).singleElement().isSameAs(bean);
|
||||
|
||||
Set<?> set = bean.getSomeSet();
|
||||
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
|
||||
assertThat(set).singleElement().isEqualTo(bean);
|
||||
assertThat(set).singleElement().isSameAs(bean);
|
||||
|
||||
Map<?, ?> map = bean.getSomeMap();
|
||||
assertThat(Proxy.isProxyClass(map.getClass())).isTrue();
|
||||
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
|
||||
assertThat(key).isEqualTo("foo");
|
||||
assertThat(value).isEqualTo(bean);
|
||||
assertThat(value).isSameAs(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,4 +378,9 @@ class UtilNamespaceHandlerTests {
|
||||
assertThat(props).as("Incorrect property value").containsEntry("foo2", "local2");
|
||||
}
|
||||
|
||||
|
||||
// For DependencyDescriptor resolution
|
||||
private Map<String, TestBean> mapWithRef;
|
||||
private Map<String, TestBean> mapWithTypes;
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -59,6 +59,8 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
|
||||
|
||||
private boolean jedi;
|
||||
|
||||
private String favoriteCafé;
|
||||
|
||||
private ITestBean spouse;
|
||||
|
||||
private String touchy;
|
||||
@@ -209,6 +211,14 @@ 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;
|
||||
|
||||
+10
@@ -33,4 +33,14 @@ public class DeprecatedForRemovalMemberConfiguration {
|
||||
return bean.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public DeprecatedForRemovalBean deprecatedReturnType() {
|
||||
return new DeprecatedForRemovalBean();
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
DeprecatedForRemovalBean deprecatedReturnTypeProtected() {
|
||||
return new DeprecatedForRemovalBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -38,4 +38,9 @@ public class DeprecatedMemberConfiguration {
|
||||
return new DeprecatedBean();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
DeprecatedBean deprecatedReturnTypeProtected() {
|
||||
return new DeprecatedBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -37,12 +37,13 @@ import javax.lang.model.element.TypeElement;
|
||||
|
||||
/**
|
||||
* Annotation {@link Processor} that writes a {@link CandidateComponentsMetadata}
|
||||
* file for spring components.
|
||||
* file for Spring components.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
* @deprecated as of 6.1, in favor of the AOT engine.
|
||||
* @deprecated as of 6.1, in favor of the AOT engine and the forthcoming
|
||||
* support for an AOT-generated Spring components index
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public class CandidateComponentsIndexer implements Processor {
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ import javax.lang.model.element.ElementKind;
|
||||
|
||||
/**
|
||||
* A {@link StereotypesProvider} that extracts a stereotype for each
|
||||
* {@code jakarta.*} or {@code javax.*} annotation <i>present</i> on a class or
|
||||
* interface.
|
||||
* {@code jakarta.*} or {@code javax.*} annotation <i>present</i>
|
||||
* on a class or interface.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 5.0
|
||||
|
||||
Vendored
+37
-20
@@ -77,7 +77,7 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
private boolean dynamic = true;
|
||||
private volatile boolean dynamic = true;
|
||||
|
||||
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
@@ -102,10 +102,15 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
|
||||
/**
|
||||
* Specify the set of cache names for this CacheManager's 'static' mode.
|
||||
* <p>The number of caches and their names will be fixed after a call to this method,
|
||||
* with no creation of further cache regions at runtime.
|
||||
* <p>Calling this with a {@code null} collection argument resets the
|
||||
* mode to 'dynamic', allowing for further creation of caches again.
|
||||
* <p>The number of caches and their names will be fixed after a call
|
||||
* to this method, with no creation of further cache regions at runtime.
|
||||
* <p>Note that this method replaces existing caches of the given names
|
||||
* and prevents the creation of further cache regions from here on - but
|
||||
* does <i>not</i> remove unrelated existing caches. For a full reset,
|
||||
* consider calling {@link #resetCaches()} before calling this method.
|
||||
* <p>Calling this method with a {@code null} collection argument resets
|
||||
* the mode to 'dynamic', allowing for further creation of caches again.
|
||||
* @see #resetCaches()
|
||||
*/
|
||||
public void setCacheNames(@Nullable Collection<String> cacheNames) {
|
||||
if (cacheNames != null) {
|
||||
@@ -245,11 +250,6 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Cache getCache(String name) {
|
||||
@@ -260,6 +260,33 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this cache manager's caches, removing them completely for on-demand
|
||||
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
|
||||
* @since 6.2.14
|
||||
*/
|
||||
public void resetCaches() {
|
||||
this.cacheMap.values().forEach(Cache::clear);
|
||||
if (this.dynamic) {
|
||||
this.cacheMap.keySet().retainAll(this.customCacheNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified cache from this cache manager, applying to
|
||||
* custom caches as well as dynamically registered caches at runtime.
|
||||
* @param name the name of the cache
|
||||
* @since 6.1.15
|
||||
*/
|
||||
public void removeCache(String name) {
|
||||
this.customCacheNames.remove(name);
|
||||
this.cacheMap.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given native Caffeine Cache instance with this cache manager,
|
||||
@@ -303,16 +330,6 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified cache from this cache manager, applying to
|
||||
* custom caches as well as dynamically registered caches at runtime.
|
||||
* @param name the name of the cache
|
||||
* @since 6.1.15
|
||||
*/
|
||||
public void removeCache(String name) {
|
||||
this.customCacheNames.remove(name);
|
||||
this.cacheMap.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
|
||||
|
||||
+5
@@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -97,6 +98,10 @@ 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.
|
||||
|
||||
+22
-2
@@ -24,7 +24,6 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.support.SimpleValueWrapper;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -42,7 +41,7 @@ class CaffeineCacheManagerTests {
|
||||
@Test
|
||||
@SuppressWarnings("cast")
|
||||
void dynamicMode() {
|
||||
CacheManager cm = new CaffeineCacheManager();
|
||||
CaffeineCacheManager cm = new CaffeineCacheManager();
|
||||
|
||||
Cache cache1 = cm.getCache("c1");
|
||||
assertThat(cache1).isInstanceOf(CaffeineCache.class);
|
||||
@@ -76,6 +75,14 @@ class CaffeineCacheManagerTests {
|
||||
cache1.evict("key3");
|
||||
assertThat(cache1.get("key3", () -> (String) null)).isNull();
|
||||
assertThat(cache1.get("key3", () -> (String) null)).isNull();
|
||||
|
||||
cm.removeCache("c1");
|
||||
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2);
|
||||
|
||||
cm.resetCaches();
|
||||
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
|
||||
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,11 +138,24 @@ class CaffeineCacheManagerTests {
|
||||
|
||||
cm.setAllowNullValues(true);
|
||||
Cache cache1y = cm.getCache("c1");
|
||||
Cache cache2y = cm.getCache("c2");
|
||||
|
||||
cache1y.put("key3", null);
|
||||
assertThat(cache1y.get("key3").get()).isNull();
|
||||
cache1y.evict("key3");
|
||||
assertThat(cache1y.get("key3")).isNull();
|
||||
cache2y.put("key4", "value4");
|
||||
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
|
||||
|
||||
cm.removeCache("c1");
|
||||
assertThat(cm.getCache("c1")).isNull();
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2y);
|
||||
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
|
||||
|
||||
cm.resetCaches();
|
||||
assertThat(cm.getCache("c1")).isNull();
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2y);
|
||||
assertThat(cache2y.get("key4")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Vendored
+1
-2
@@ -51,8 +51,7 @@ class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCacheCache>
|
||||
this.cacheManager.createCache(CACHE_NAME_NO_NULL, new MutableConfiguration<>());
|
||||
this.nativeCache = this.cacheManager.getCache(CACHE_NAME);
|
||||
this.cache = new JCacheCache(this.nativeCache);
|
||||
Cache<Object, Object> nativeCacheNoNull =
|
||||
this.cacheManager.getCache(CACHE_NAME_NO_NULL);
|
||||
Cache<Object, Object> nativeCacheNoNull = this.cacheManager.getCache(CACHE_NAME_NO_NULL);
|
||||
this.cacheNoNull = new JCacheCache(nativeCacheNoNull, false);
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -23,14 +23,14 @@ import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Interface to be implemented for explicitly specifying how caches are resolved
|
||||
* and how keys are generated for annotation-driven cache management.
|
||||
*
|
||||
* <p>See @{@link EnableCaching} for general examples and context; see
|
||||
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()}, and
|
||||
* {@link #errorHandler()} for detailed instructions.
|
||||
* <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><b>NOTE: A {@code CachingConfigurer} will get initialized early.</b>
|
||||
* Do not inject common dependencies into autowired fields directly; instead, consider
|
||||
|
||||
Vendored
+27
-10
@@ -54,7 +54,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
|
||||
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
private boolean dynamic = true;
|
||||
private volatile boolean dynamic = true;
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
@@ -82,10 +82,15 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
|
||||
/**
|
||||
* Specify the set of cache names for this CacheManager's 'static' mode.
|
||||
* <p>The number of caches and their names will be fixed after a call to this method,
|
||||
* with no creation of further cache regions at runtime.
|
||||
* <p>Calling this with a {@code null} collection argument resets the
|
||||
* mode to 'dynamic', allowing for further creation of caches again.
|
||||
* <p>The number of caches and their names will be fixed after a call
|
||||
* to this method, with no creation of further cache regions at runtime.
|
||||
* <p>Note that this method replaces existing caches of the given names
|
||||
* and prevents the creation of further cache regions from here on - but
|
||||
* does <i>not</i> remove unrelated existing caches. For a full reset,
|
||||
* consider calling {@link #resetCaches()} before calling this method.
|
||||
* <p>Calling this method with a {@code null} collection argument resets
|
||||
* the mode to 'dynamic', allowing for further creation of caches again.
|
||||
* @see #resetCaches()
|
||||
*/
|
||||
public void setCacheNames(@Nullable Collection<String> cacheNames) {
|
||||
if (cacheNames != null) {
|
||||
@@ -160,11 +165,6 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Cache getCache(String name) {
|
||||
@@ -175,6 +175,23 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this cache manager's caches, removing them completely for on-demand
|
||||
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
|
||||
* @since 6.2.14
|
||||
*/
|
||||
public void resetCaches() {
|
||||
this.cacheMap.values().forEach(Cache::clear);
|
||||
if (this.dynamic) {
|
||||
this.cacheMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified cache from this cache manager.
|
||||
* @param name the name of the cache
|
||||
|
||||
+3
-3
@@ -113,7 +113,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
builder.setUnless(getAttributeValue(opElement, "unless", ""));
|
||||
builder.setSync(Boolean.parseBoolean(getAttributeValue(opElement, "sync", "false")));
|
||||
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
|
||||
col.add(builder.build());
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
builder.setBeforeInvocation(Boolean.parseBoolean(after.trim()));
|
||||
}
|
||||
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
|
||||
col.add(builder.build());
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
parserContext.getReaderContext(), new CachePutOperation.Builder());
|
||||
builder.setUnless(getAttributeValue(opElement, "unless", ""));
|
||||
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
|
||||
col.add(builder.build());
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -139,6 +140,10 @@ 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.
|
||||
|
||||
+6
-24
@@ -18,8 +18,6 @@ package org.springframework.cache.support;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@@ -32,45 +30,29 @@ import org.springframework.lang.Nullable;
|
||||
* for disabling caching, typically used for backing cache declarations
|
||||
* without an actual backing store.
|
||||
*
|
||||
* <p>Will simply accept any items into the cache not actually storing them.
|
||||
* <p>This implementation will simply accept any items into the cache,
|
||||
* not actually storing them.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see NoOpCache
|
||||
*/
|
||||
public class NoOpCacheManager implements CacheManager {
|
||||
|
||||
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(16);
|
||||
|
||||
private final Set<String> cacheNames = new LinkedHashSet<>(16);
|
||||
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
|
||||
/**
|
||||
* This implementation always returns a {@link Cache} implementation that will not store items.
|
||||
* Additionally, the request cache will be remembered by the manager for consistency.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Cache getCache(String name) {
|
||||
Cache cache = this.caches.get(name);
|
||||
if (cache == null) {
|
||||
this.caches.computeIfAbsent(name, NoOpCache::new);
|
||||
synchronized (this.cacheNames) {
|
||||
this.cacheNames.add(name);
|
||||
}
|
||||
}
|
||||
return this.caches.get(name);
|
||||
return this.cacheMap.computeIfAbsent(name, NoOpCache::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the name of the caches previously requested.
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
synchronized (this.cacheNames) {
|
||||
return Collections.unmodifiableSet(this.cacheNames);
|
||||
}
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,19 +17,18 @@
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
/**
|
||||
* Enumeration used to determine whether JDK proxy-based or
|
||||
* Enumeration used to determine whether JDK/CGLIB proxy-based or
|
||||
* AspectJ weaving-based advice should be applied.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
* @see org.springframework.scheduling.annotation.EnableAsync#mode()
|
||||
* @see org.springframework.scheduling.annotation.AsyncConfigurationSelector#selectImports
|
||||
* @see org.springframework.transaction.annotation.EnableTransactionManagement#mode()
|
||||
* @see org.springframework.scheduling.annotation.EnableAsync#mode()
|
||||
*/
|
||||
public enum AdviceMode {
|
||||
|
||||
/**
|
||||
* JDK proxy-based advice.
|
||||
* JDK/CGLIB proxy-based advice.
|
||||
*/
|
||||
PROXY,
|
||||
|
||||
|
||||
+6
-7
@@ -90,7 +90,6 @@ import org.springframework.util.ClassUtils;
|
||||
* @see ScannedGenericBeanDefinition
|
||||
* @see CandidateComponentsIndex
|
||||
*/
|
||||
@SuppressWarnings("removal") // components index
|
||||
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
|
||||
|
||||
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
|
||||
@@ -452,9 +451,9 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
|
||||
Set<BeanDefinition> candidates = new LinkedHashSet<>();
|
||||
try {
|
||||
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
|
||||
String packageSearchPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
|
||||
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
|
||||
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
|
||||
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPattern);
|
||||
boolean traceEnabled = logger.isTraceEnabled();
|
||||
boolean debugEnabled = logger.isDebugEnabled();
|
||||
for (Resource resource : resources) {
|
||||
@@ -537,13 +536,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
* @return whether the class qualifies as a candidate component
|
||||
*/
|
||||
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
|
||||
for (TypeFilter tf : this.excludeFilters) {
|
||||
if (tf.match(metadataReader, getMetadataReaderFactory())) {
|
||||
for (TypeFilter filter : this.excludeFilters) {
|
||||
if (filter.match(metadataReader, getMetadataReaderFactory())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (TypeFilter tf : this.includeFilters) {
|
||||
if (tf.match(metadataReader, getMetadataReaderFactory())) {
|
||||
for (TypeFilter filter : this.includeFilters) {
|
||||
if (filter.match(metadataReader, getMetadataReaderFactory())) {
|
||||
return isConditionMatch(metadataReader);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-17
@@ -149,8 +149,6 @@ 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;
|
||||
|
||||
@@ -160,6 +158,8 @@ 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 = clazz;
|
||||
Class<?> targetClass = ClassUtils.getUserClass(clazz);
|
||||
|
||||
do {
|
||||
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
|
||||
@@ -455,24 +455,23 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
});
|
||||
|
||||
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
|
||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
|
||||
if (method.isBridge()) {
|
||||
return;
|
||||
}
|
||||
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (ejbAnnotationType != null && method.isAnnotationPresent(ejbAnnotationType)) {
|
||||
if (method.equals(BridgeMethodResolver.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(bridgedMethod, clazz);
|
||||
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
currElements.add(new EjbRefElement(method, method, pd));
|
||||
}
|
||||
}
|
||||
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
else if (jakartaResourceType != null && method.isAnnotationPresent(jakartaResourceType)) {
|
||||
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@Resource annotation is not supported on static methods");
|
||||
}
|
||||
@@ -481,13 +480,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(bridgedMethod, clazz);
|
||||
currElements.add(new ResourceElement(method, bridgedMethod, pd));
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
currElements.add(new ResourceElement(method, method, pd));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
else if (javaxResourceType != null && method.isAnnotationPresent(javaxResourceType)) {
|
||||
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@Resource annotation is not supported on static methods");
|
||||
}
|
||||
@@ -496,8 +495,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(bridgedMethod, clazz);
|
||||
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
|
||||
currElements.add(new LegacyResourceElement(method, method, pd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -157,9 +157,17 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
|
||||
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
|
||||
configBeanDef.setScope(scopeMetadata.getScopeName());
|
||||
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
|
||||
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
|
||||
|
||||
String configBeanName;
|
||||
try {
|
||||
configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new IllegalStateException("Failed to generate bean name for imported class '" +
|
||||
configClass.getMetadata().getClassName() + "'", ex);
|
||||
}
|
||||
|
||||
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
|
||||
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
|
||||
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
|
||||
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
|
||||
|
||||
+16
-13
@@ -172,22 +172,25 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
|
||||
}
|
||||
}
|
||||
|
||||
boolean cacheable = true;
|
||||
for (String autowiredBeanName : autowiredBeanNames) {
|
||||
if (!this.beanFactory.containsBean(autowiredBeanName)) {
|
||||
cacheable = false;
|
||||
}
|
||||
else {
|
||||
if (this.beanName != null) {
|
||||
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
|
||||
}
|
||||
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
|
||||
boolean cacheable = false;
|
||||
if (!autowiredBeanNames.isEmpty()) {
|
||||
cacheable = true;
|
||||
for (String autowiredBeanName : autowiredBeanNames) {
|
||||
if (!this.beanFactory.containsBean(autowiredBeanName)) {
|
||||
cacheable = false;
|
||||
}
|
||||
else {
|
||||
if (this.beanName != null) {
|
||||
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
|
||||
}
|
||||
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
|
||||
cacheable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cacheable) {
|
||||
this.cachedTarget = target;
|
||||
}
|
||||
}
|
||||
if (cacheable) {
|
||||
this.cachedTarget = target;
|
||||
}
|
||||
|
||||
return target;
|
||||
|
||||
+2
-2
@@ -63,13 +63,13 @@ public interface AotApplicationContextInitializer<C extends ConfigurableApplicat
|
||||
|
||||
private static <C extends ConfigurableApplicationContext> void initialize(
|
||||
C applicationContext, String... initializerClassNames) {
|
||||
|
||||
Log logger = LogFactory.getLog(AotApplicationContextInitializer.class);
|
||||
ClassLoader classLoader = applicationContext.getClassLoader();
|
||||
logger.debug("Initializing ApplicationContext with AOT");
|
||||
for (String initializerClassName : initializerClassNames) {
|
||||
logger.trace(LogMessage.format("Applying %s", initializerClassName));
|
||||
instantiateInitializer(initializerClassName, classLoader)
|
||||
.initialize(applicationContext);
|
||||
instantiateInitializer(initializerClassName, classLoader).initialize(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,10 +101,9 @@ public @interface EventListener {
|
||||
|
||||
/**
|
||||
* The event classes that this listener handles.
|
||||
* <p>If this attribute is specified with a single value, the
|
||||
* annotated method may optionally accept a single parameter.
|
||||
* However, if this attribute is specified with multiple values,
|
||||
* the annotated method must <em>not</em> declare any parameters.
|
||||
* <p>The annotated method may optionally accept a single parameter
|
||||
* of the given event class, or of a common base class or interface
|
||||
* for all given event classes.
|
||||
*/
|
||||
@AliasFor("value")
|
||||
Class<?>[] classes() default {};
|
||||
|
||||
+2
-4
@@ -45,9 +45,7 @@ import org.springframework.util.MultiValueMap;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 5.0
|
||||
* @deprecated as of 6.1, in favor of the AOT engine.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public class CandidateComponentsIndex {
|
||||
|
||||
private static final AntPathMatcher pathMatcher = new AntPathMatcher(".");
|
||||
@@ -83,7 +81,7 @@ public class CandidateComponentsIndex {
|
||||
public Set<String> getCandidateTypes(String basePackage, String stereotype) {
|
||||
List<Entry> candidates = this.index.get(stereotype);
|
||||
if (candidates != null) {
|
||||
return candidates.parallelStream()
|
||||
return candidates.stream()
|
||||
.filter(t -> t.match(basePackage))
|
||||
.map(t -> t.type)
|
||||
.collect(Collectors.toSet());
|
||||
@@ -94,7 +92,7 @@ public class CandidateComponentsIndex {
|
||||
|
||||
private static class Entry {
|
||||
|
||||
private final String type;
|
||||
final String type;
|
||||
|
||||
private final String packageName;
|
||||
|
||||
|
||||
-3
@@ -38,10 +38,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 5.0
|
||||
* @deprecated as of 6.1, in favor of the AOT engine.
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public final class CandidateComponentsIndexLoader {
|
||||
|
||||
/**
|
||||
|
||||
+4
-1
@@ -630,7 +630,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
finishRefresh();
|
||||
}
|
||||
|
||||
catch (RuntimeException | Error ex ) {
|
||||
catch (RuntimeException | Error ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Exception encountered during context initialization - " +
|
||||
"cancelling refresh attempt: " + ex);
|
||||
@@ -936,6 +936,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
|
||||
// Mark current thread for singleton instantiation with applied bootstrap locking.
|
||||
beanFactory.prepareSingletonBootstrap();
|
||||
|
||||
// Initialize bootstrap executor for this context.
|
||||
if (beanFactory.containsBean(BOOTSTRAP_EXECUTOR_BEAN_NAME) &&
|
||||
beanFactory.isTypeMatch(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)) {
|
||||
|
||||
-1
@@ -227,7 +227,6 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
|
||||
* @param beanFactory the bean factory to load bean definitions into
|
||||
* @throws BeansException if parsing of the bean definitions failed
|
||||
* @throws IOException if loading of bean definition files failed
|
||||
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
|
||||
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
|
||||
*/
|
||||
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
|
||||
|
||||
-3
@@ -78,8 +78,6 @@ import org.springframework.util.Assert;
|
||||
* GenericApplicationContext ctx = new GenericApplicationContext();
|
||||
* XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
|
||||
* xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
|
||||
* PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
|
||||
* propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
|
||||
* ctx.refresh();
|
||||
*
|
||||
* MyBean myBean = (MyBean) ctx.getBean("myBean");
|
||||
@@ -101,7 +99,6 @@ import org.springframework.util.Assert;
|
||||
* @see #registerBeanDefinition
|
||||
* @see #refresh()
|
||||
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
|
||||
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
|
||||
*/
|
||||
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
|
||||
|
||||
|
||||
+18
-16
@@ -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} type such as {@code Double} and {@code Long}.
|
||||
* <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>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>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>Each attribute is mutually exclusive, so only set one attribute per
|
||||
* 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,
|
||||
* 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,
|
||||
* depending on the annotated field or method parameter type.
|
||||
*
|
||||
* @author Keith Donald
|
||||
@@ -50,19 +50,21 @@ import java.lang.annotation.Target;
|
||||
public @interface NumberFormat {
|
||||
|
||||
/**
|
||||
* The style pattern to use to format the field.
|
||||
* The style pattern to use to format the field or method parameter.
|
||||
* <p>Defaults to {@link Style#DEFAULT} for general-purpose number formatting
|
||||
* for most annotated types, except for money types which default to currency
|
||||
* formatting. Set this attribute when you wish to format your field in
|
||||
* accordance with a common style other than the default style.
|
||||
* 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.
|
||||
*/
|
||||
Style style() default Style.DEFAULT;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
String pattern() default "";
|
||||
|
||||
|
||||
-1
@@ -87,7 +87,6 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
|
||||
private Class<? extends Annotation> asyncAnnotationType;
|
||||
|
||||
|
||||
|
||||
public AsyncAnnotationBeanPostProcessor() {
|
||||
setBeforeExistingAdvisors(true);
|
||||
}
|
||||
|
||||
+11
-6
@@ -22,13 +22,18 @@ import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* <p>See @{@link EnableAsync} for usage examples.
|
||||
* <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.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
|
||||
+1
-1
@@ -383,7 +383,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
try {
|
||||
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
|
||||
this.registrar::getObservationRegistry,
|
||||
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
|
||||
this.reactiveSubscriptions.computeIfAbsent(bean, key -> new CopyOnWriteArrayList<>()));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
|
||||
|
||||
@@ -50,7 +50,9 @@ public class Task {
|
||||
|
||||
|
||||
/**
|
||||
* Return the underlying task.
|
||||
* Return a {@link Runnable} that executes the underlying task.
|
||||
* <p>Note, this does not necessarily return the {@link Task#Task(Runnable) original runnable}
|
||||
* as it can be wrapped by the Framework for additional support.
|
||||
*/
|
||||
public Runnable getRunnable() {
|
||||
return this.runnable;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user