mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
199 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a471c95c1 | |||
| 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 | |||
| d17601e01c | |||
| 5b387615c6 | |||
| 0e3e34bee0 | |||
| 4745c7cf3c | |||
| 275fb52ad6 | |||
| 7f9aa39748 | |||
| c788554b1d | |||
| 9e8c64011d | |||
| ebb8e34570 | |||
| 9ba954c330 | |||
| ba52164373 | |||
| 977582fced | |||
| 64721b3bc0 | |||
| 02f0f92a72 | |||
| db9e938ec4 | |||
| 3a4315bf16 | |||
| 33fe8d29c1 | |||
| b741632e99 | |||
| cd208797e2 | |||
| 764336f0f2 | |||
| 4903fee939 | |||
| 6978f0a398 | |||
| f62519bb55 | |||
| 0e2af5d113 | |||
| ed7c3d737c | |||
| 01b24f2644 | |||
| 4a4cf8a787 | |||
| 55181fa1c9 | |||
| 300ae841ce | |||
| c248f94e5a | |||
| 19d5ec6781 | |||
| c0b71f8999 | |||
| 5d214c2624 | |||
| 6d710d482a | |||
| ed28390d24 | |||
| e2085063f6 | |||
| edda4731e1 | |||
| 9fa2d7d190 | |||
| c30427fd4e | |||
| 1d908f1847 | |||
| 37b076be51 | |||
| a9453a5959 | |||
| 3781ba223e | |||
| f11a1e6f82 | |||
| ffc785471b | |||
| 6e2fbfe108 | |||
| 968e037503 | |||
| f0a9f649c1 | |||
| 600d6c6fc0 | |||
| 7a55ce48a9 | |||
| 5df9fd4eff | |||
| da13a24604 | |||
| 67e88f3c20 | |||
| 24e66b63d1 | |||
| f3832c7262 | |||
| 16e99f289c | |||
| 642e554c52 | |||
| 4f6304707d | |||
| 3c112703d9 | |||
| 8c44a61033 | |||
| 5e338ef1b8 | |||
| 445da24631 | |||
| 6d97791f5e |
@@ -21,7 +21,7 @@ jobs:
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 23
|
||||
- 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.3/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/",
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
[[beans-classpath-scanning]]
|
||||
= Classpath Scanning and Managed Components
|
||||
|
||||
Most examples in this chapter use XML to specify the configuration metadata that produces
|
||||
each `BeanDefinition` within the Spring container. The previous section
|
||||
(xref:core/beans/annotation-config.adoc[Annotation-based Container Configuration]) demonstrates how to provide a lot of the configuration
|
||||
metadata through source-level annotations. Even in those examples, however, the "base"
|
||||
bean definitions are explicitly defined in the XML file, while the annotations drive only
|
||||
the dependency injection. This section describes an option for implicitly detecting the
|
||||
candidate components by scanning the classpath. Candidate components are classes that
|
||||
match against a filter criteria and have a corresponding bean definition registered with
|
||||
the container. This removes the need to use XML to perform bean registration. Instead, you
|
||||
can use annotations (for example, `@Component`), AspectJ type expressions, or your own
|
||||
Most examples in this chapter use XML to specify the configuration metadata that
|
||||
produces each `BeanDefinition` within the Spring container. The previous section
|
||||
(xref:core/beans/annotation-config.adoc[Annotation-based Container Configuration])
|
||||
demonstrates how to provide a lot of the configuration metadata through source-level
|
||||
annotations. Even in those examples, however, the "base" bean definitions are explicitly
|
||||
defined in the XML file, while the annotations drive only the dependency injection.
|
||||
|
||||
This section describes an option for implicitly detecting the candidate components by
|
||||
scanning the classpath. Candidate components are classes that match against a filter
|
||||
criteria and have a corresponding bean definition registered with the container.
|
||||
This removes the need to use XML to perform bean registration. Instead, you can use
|
||||
annotations (for example, `@Component`), AspectJ type expressions, or your own
|
||||
custom filter criteria to select which classes have bean definitions registered with
|
||||
the container.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
You can define beans using Java rather than using XML files. Take a look at the
|
||||
`@Configuration`, `@Bean`, `@Import`, and `@DependsOn` annotations for examples of how to
|
||||
use these features.
|
||||
`@Configuration`, `@Bean`, `@Import`, and `@DependsOn` annotations for examples
|
||||
of how to use these features.
|
||||
====
|
||||
|
||||
|
||||
@@ -830,10 +832,10 @@ definitions, there is no notion of bean definition inheritance, and inheritance
|
||||
hierarchies at the class level are irrelevant for metadata purposes.
|
||||
|
||||
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes]. As with the pre-built annotations for those scopes,
|
||||
you may also compose your own scoping annotations by using Spring's meta-annotation
|
||||
approach: for example, a custom annotation meta-annotated with `@Scope("prototype")`,
|
||||
possibly also declaring a custom scoped-proxy mode.
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes].
|
||||
As with the pre-built annotations for those scopes, you may also compose your own scoping
|
||||
annotations by using Spring's meta-annotation approach: for example, a custom annotation
|
||||
meta-annotated with `@Scope("prototype")`, possibly also declaring a custom scoped-proxy mode.
|
||||
|
||||
NOTE: To provide a custom strategy for scope resolution rather than relying on the
|
||||
annotation-based approach, you can implement the
|
||||
@@ -875,7 +877,8 @@ Kotlin::
|
||||
----
|
||||
|
||||
When using certain non-singleton scopes, it may be necessary to generate proxies for the
|
||||
scoped objects. The reasoning is described in xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
scoped objects. The reasoning is described in
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
For this purpose, a scoped-proxy attribute is available on the component-scan
|
||||
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
|
||||
the following configuration results in standard JDK dynamic proxies:
|
||||
|
||||
+14
-18
@@ -85,11 +85,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 +99,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.
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
----
|
||||
|
||||
|
||||
+11
@@ -77,6 +77,17 @@ exactly one candidate bean exists.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
As stated in the documentation for Mockito, there are times when using `Mockito.when()` is
|
||||
inappropriate for stubbing a spy – for example, if calling a real method on a spy results
|
||||
in undesired side effects.
|
||||
|
||||
To avoid such undesired side effects, consider using
|
||||
`Mockito.doReturn(...).when(spy)...`, `Mockito.doThrow(...).when(spy)...`,
|
||||
`Mockito.doNothing().when(spy)...`, and similar methods.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
|
||||
will result in an exception.
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ Kotlin::
|
||||
|
||||
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by leveraging the Page Object Pattern. As we mentioned in
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
|
||||
with HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
|
||||
+23
-28
@@ -2,10 +2,10 @@
|
||||
= Application Events
|
||||
|
||||
The TestContext framework provides support for recording
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-events[application events] published in the
|
||||
`ApplicationContext` so that assertions can be performed against those events within
|
||||
tests. All events published during the execution of a single test are made available via
|
||||
the `ApplicationEvents` API which allows you to process the events as a
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-events[application events]
|
||||
published in the `ApplicationContext` so that assertions can be performed against those
|
||||
events within tests. All events published during the execution of a single test are made
|
||||
available via the `ApplicationEvents` API which allows you to process the events as a
|
||||
`java.util.Stream`.
|
||||
|
||||
To use `ApplicationEvents` in your tests, do the following.
|
||||
@@ -16,16 +16,23 @@ To use `ApplicationEvents` in your tests, do the following.
|
||||
that `ApplicationEventsTestExecutionListener` is registered by default and only needs
|
||||
to be manually registered if you have custom configuration via
|
||||
`@TestExecutionListeners` that does not include the default listeners.
|
||||
* Annotate a field of type `ApplicationEvents` with `@Autowired` and use that instance of
|
||||
`ApplicationEvents` in your test and lifecycle methods (such as `@BeforeEach` and
|
||||
`@AfterEach` methods in JUnit Jupiter).
|
||||
** When using the xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[SpringExtension for JUnit Jupiter], you may declare a method
|
||||
parameter of type `ApplicationEvents` in a test or lifecycle method as an alternative
|
||||
to an `@Autowired` field in the test class.
|
||||
* When using the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[SpringExtension for JUnit Jupiter],
|
||||
declare a method parameter of type `ApplicationEvents` in a `@Test`, `@BeforeEach`, or
|
||||
`@AfterEach` method.
|
||||
** Since `ApplicationEvents` is scoped to the lifecycle of the current test method, this
|
||||
is the recommended approach.
|
||||
* Alternatively, you can annotate a field of type `ApplicationEvents` with `@Autowired`
|
||||
and use that instance of `ApplicationEvents` in your test and lifecycle methods.
|
||||
|
||||
NOTE: `ApplicationEvents` is registered with the `ApplicationContext` as a _resolvable
|
||||
dependency_ which is scoped to the lifecycle of the current test method. Consequently,
|
||||
`ApplicationEvents` cannot be accessed outside the lifecycle of a test method and cannot be
|
||||
`@Autowired` into the constructor of a test class.
|
||||
|
||||
The following test class uses the `SpringExtension` for JUnit Jupiter and
|
||||
{assertj-docs}[AssertJ] to assert the types of application events
|
||||
published while invoking a method in a Spring-managed component:
|
||||
{assertj-docs}[AssertJ] to assert the types of application events published while
|
||||
invoking a method in a Spring-managed component:
|
||||
|
||||
// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */
|
||||
[tabs]
|
||||
@@ -38,16 +45,10 @@ Java::
|
||||
@RecordApplicationEvents // <1>
|
||||
class OrderServiceTests {
|
||||
|
||||
@Autowired
|
||||
OrderService orderService;
|
||||
|
||||
@Autowired
|
||||
ApplicationEvents events; // <2>
|
||||
|
||||
@Test
|
||||
void submitOrder() {
|
||||
void submitOrder(@Autowired OrderService service, ApplicationEvents events) { // <2>
|
||||
// Invoke method in OrderService that publishes an event
|
||||
orderService.submitOrder(new Order(/* ... */));
|
||||
service.submitOrder(new Order(/* ... */));
|
||||
// Verify that an OrderSubmitted event was published
|
||||
long numEvents = events.stream(OrderSubmitted.class).count(); // <3>
|
||||
assertThat(numEvents).isEqualTo(1);
|
||||
@@ -66,16 +67,10 @@ Kotlin::
|
||||
@RecordApplicationEvents // <1>
|
||||
class OrderServiceTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var orderService: OrderService
|
||||
|
||||
@Autowired
|
||||
lateinit var events: ApplicationEvents // <2>
|
||||
|
||||
@Test
|
||||
fun submitOrder() {
|
||||
fun submitOrder(@Autowired service: OrderService, events: ApplicationEvents) { // <2>
|
||||
// Invoke method in OrderService that publishes an event
|
||||
orderService.submitOrder(Order(/* ... */))
|
||||
service.submitOrder(Order(/* ... */))
|
||||
// Verify that an OrderSubmitted event was published
|
||||
val numEvents = events.stream(OrderSubmitted::class).count() // <3>
|
||||
assertThat(numEvents).isEqualTo(1)
|
||||
|
||||
@@ -294,7 +294,28 @@ allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
----
|
||||
======
|
||||
|
||||
Note that the body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
NOTE: The body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
The following shows how to bind request parameters, including an optional `DataBinder` customization:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val pet = request.bind(Pet::class.java, {dataBinder -> dataBinder.setAllowedFields("name")})
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-response]]
|
||||
=== ServerResponse
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
= WebClient
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring WebFlux includes a client to perform HTTP requests with. `WebClient` has a
|
||||
functional, fluent API based on Reactor, see xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries],
|
||||
Spring WebFlux includes a client to perform HTTP requests. `WebClient` has a
|
||||
functional, fluent API based on Reactor (see xref:web/webflux-reactive-libraries.adoc[Reactive Libraries])
|
||||
which enables declarative composition of asynchronous logic without the need to deal with
|
||||
threads or concurrency. It is fully non-blocking, it supports streaming, and relies on
|
||||
threads or concurrency. It is fully non-blocking, supports streaming, and relies on
|
||||
the same xref:web/webflux/reactive-spring.adoc#webflux-codecs[codecs] that are also used to encode and
|
||||
decode request and response content on the server side.
|
||||
|
||||
`WebClient` needs an HTTP client library to perform requests with. There is built-in
|
||||
`WebClient` needs an HTTP client library to perform requests. There is built-in
|
||||
support for the following:
|
||||
|
||||
* {reactor-github-org}/reactor-netty[Reactor Netty]
|
||||
* {java-api}/java.net.http/java/net/http/HttpClient.html[JDK HttpClient]
|
||||
* https://github.com/jetty-project/jetty-reactive-httpclient[Jetty Reactive HttpClient]
|
||||
* https://hc.apache.org/index.html[Apache HttpComponents]
|
||||
* Others can be plugged via `ClientHttpConnector`.
|
||||
* Others can be plugged in via `ClientHttpConnector`.
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -234,10 +202,13 @@ Kotlin::
|
||||
======
|
||||
--
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this, for example, to parameterize a base URL based on
|
||||
some external configuration.
|
||||
URI path patterns can also have:
|
||||
|
||||
- Embedded `${...}` placeholders that are resolved on startup via
|
||||
`PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. This is useful, for example, to parameterize a base URL based on
|
||||
external configuration.
|
||||
- SpEL expressions `#{...}`.
|
||||
|
||||
NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support.
|
||||
Both classes are located in `spring-web` and are expressly designed for use with HTTP URL
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,27 +15,27 @@ See xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] for more de
|
||||
[[webmvc-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
`WebClient` is a reactive client to perform HTTP requests with a fluent API.
|
||||
`WebClient` is a reactive client for making HTTP requests with a fluent API.
|
||||
|
||||
See xref:web/webflux-webclient.adoc[WebClient] for more details.
|
||||
See xref:web/webflux-webclient.adoc[`WebClient`] for more details.
|
||||
|
||||
|
||||
[[webmvc-resttemplate]]
|
||||
== `RestTemplate`
|
||||
|
||||
`RestTemplate` is a synchronous client to perform HTTP requests. It is the original
|
||||
`RestTemplate` is a synchronous client for making HTTP requests. It is the original
|
||||
Spring REST client and exposes a simple, template-method API over underlying HTTP client
|
||||
libraries.
|
||||
|
||||
See xref:integration/rest-clients.adoc[REST Endpoints] for details.
|
||||
See xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] for details.
|
||||
|
||||
|
||||
[[webmvc-http-interface]]
|
||||
== HTTP Interface
|
||||
|
||||
The Spring Frameworks lets you define an HTTP service as a Java interface with HTTP
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with HTTP
|
||||
exchange methods. You can then generate a proxy that implements this interface and
|
||||
performs the exchanges. This helps to simplify HTTP remote access and provides additional
|
||||
flexibility for to choose an API style such as synchronous or reactive.
|
||||
flexibility for choosing an API style such as synchronous or reactive.
|
||||
|
||||
See xref:integration/rest-clients.adoc#rest-http-interface[REST Endpoints] for details.
|
||||
See xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] for details.
|
||||
|
||||
@@ -184,6 +184,26 @@ val map = request.params()
|
||||
----
|
||||
======
|
||||
|
||||
The following shows how to bind request parameters, including an optional `DataBinder` customization:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val pet = request.bind(Pet::class.java, {dataBinder -> dataBinder.setAllowedFields("name")})
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-response]]
|
||||
=== ServerResponse
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
+9
-19
@@ -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:
|
||||
|
||||
@@ -217,10 +204,13 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this, for example, to parameterize a base URL based on
|
||||
some external configuration.
|
||||
URI path patterns can also have:
|
||||
|
||||
- Embedded `${...}` placeholders that are resolved on startup via
|
||||
`PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. This is useful, for example, to parameterize a base URL based on
|
||||
external configuration.
|
||||
- SpEL expression `#{...}`.
|
||||
|
||||
|
||||
[[mvc-ann-requestmapping-pattern-comparison]]
|
||||
|
||||
@@ -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.
|
||||
|
||||
|===
|
||||
@@ -7,31 +7,31 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.9"))
|
||||
api(platform("io.netty:netty-bom:4.1.123.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.13"))
|
||||
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.8"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.12"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.27"))
|
||||
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.23"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.23"))
|
||||
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.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.18.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.2")
|
||||
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.1")
|
||||
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.30.2")
|
||||
api("com.google.code.gson:gson:2.13.2")
|
||||
api("com.google.protobuf:protobuf-java-util:4.32.1")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
@@ -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.10")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.12")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.18.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.18.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.18.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,17 +90,17 @@ 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")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.5")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.5")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
|
||||
@@ -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.9")
|
||||
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,24 +129,24 @@ 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.13.0")
|
||||
api("org.htmlunit:htmlunit:4.18.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.12.0")
|
||||
api("org.jruby:jruby:9.4.13.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.15")
|
||||
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.33.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.34.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.9
|
||||
version=6.2.13
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -121,12 +121,12 @@ public abstract class AopConfigUtils {
|
||||
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
|
||||
|
||||
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
|
||||
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
|
||||
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
|
||||
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
|
||||
BeanDefinition beanDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
|
||||
if (!cls.getName().equals(beanDefinition.getBeanClassName())) {
|
||||
int currentPriority = findPriorityForClass(beanDefinition.getBeanClassName());
|
||||
int requiredPriority = findPriorityForClass(cls);
|
||||
if (currentPriority < requiredPriority) {
|
||||
apcDefinition.setBeanClassName(cls.getName());
|
||||
beanDefinition.setBeanClassName(cls.getName());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -134,8 +134,8 @@ public abstract class AopConfigUtils {
|
||||
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
|
||||
beanDefinition.setSource(source);
|
||||
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
|
||||
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
|
||||
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
+1
@@ -117,6 +117,7 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
|
||||
}
|
||||
proxyFactory.addAdvisor(this.advisor);
|
||||
customizeProxyFactory(proxyFactory);
|
||||
proxyFactory.setPreFiltered(true);
|
||||
|
||||
// Use original ClassLoader if bean class not locally loaded in overriding class loader
|
||||
ClassLoader classLoader = getProxyClassLoader();
|
||||
|
||||
@@ -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);
|
||||
|
||||
+3
-3
@@ -24,9 +24,9 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Extension of {@link AbstractAutoProxyCreator} which implements {@link BeanFactoryAware},
|
||||
* adds exposure of the original target class for each proxied bean
|
||||
* ({@link AutoProxyUtils#ORIGINAL_TARGET_CLASS_ATTRIBUTE}),
|
||||
* Extension of {@link AbstractAdvisingBeanPostProcessor} which implements
|
||||
* {@link BeanFactoryAware}, adds exposure of the original target class for each
|
||||
* proxied bean ({@link AutoProxyUtils#ORIGINAL_TARGET_CLASS_ATTRIBUTE}),
|
||||
* and participates in an externally enforced target-class mode for any given bean
|
||||
* ({@link AutoProxyUtils#PRESERVE_TARGET_CLASS_ATTRIBUTE}).
|
||||
* This post-processor is therefore aligned with {@link AbstractAutoProxyCreator}.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
+8
-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,14 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
return true;
|
||||
}
|
||||
}
|
||||
MethodParameter methodParam = descriptor.getMethodParameter();
|
||||
if (methodParam != null) {
|
||||
for (Annotation annotation : methodParam.getMethodAnnotations()) {
|
||||
if (isQualifier(annotation.annotationType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+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
-2
@@ -520,8 +520,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
* to check whether the bean with the given name matches the specified type. Allow
|
||||
* additional constraints to be applied to ensure that beans are not created early.
|
||||
* @param name the name of the bean to query
|
||||
* @param typeToMatch the type to match against (as a
|
||||
* {@code ResolvableType})
|
||||
* @param typeToMatch the type to match against (as a {@code ResolvableType})
|
||||
* @return {@code true} if the bean type matches, {@code false} if it
|
||||
* doesn't match or cannot be determined yet
|
||||
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
|
||||
|
||||
+59
-28
@@ -58,6 +58,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
|
||||
import org.springframework.beans.factory.CannotLoadBeanClassException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
@@ -196,8 +197,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
/** Map from bean name to merged BeanDefinitionHolder. */
|
||||
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
|
||||
|
||||
/** Set of bean definition names with a primary marker. */
|
||||
private final Set<String> primaryBeanNames = ConcurrentHashMap.newKeySet(16);
|
||||
/** Map of bean definition names with a primary marker plus corresponding type. */
|
||||
private final Map<String, Class<?>> primaryBeanNamesWithType = new ConcurrentHashMap<>(16);
|
||||
|
||||
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
|
||||
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);
|
||||
@@ -1037,7 +1038,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
|
||||
super.cacheMergedBeanDefinition(mbd, beanName);
|
||||
if (mbd.isPrimary()) {
|
||||
this.primaryBeanNames.add(beanName);
|
||||
this.primaryBeanNamesWithType.put(beanName, Void.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1101,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()) {
|
||||
@@ -1112,11 +1118,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
|
||||
|
||||
// Trigger initialization of all non-lazy singleton beans...
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
|
||||
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) {
|
||||
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
|
||||
if (!mbd.isAbstract() && mbd.isSingleton()) {
|
||||
@@ -1126,21 +1133,20 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!futures.isEmpty()) {
|
||||
try {
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
|
||||
}
|
||||
catch (CompletionException ex) {
|
||||
ReflectionUtils.rethrowRuntimeException(ex.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.mainThreadPrefix = null;
|
||||
this.preInstantiationThread.remove();
|
||||
}
|
||||
|
||||
if (!futures.isEmpty()) {
|
||||
try {
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
|
||||
}
|
||||
catch (CompletionException ex) {
|
||||
ReflectionUtils.rethrowRuntimeException(ex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger post-initialization callback for all applicable beans...
|
||||
for (String beanName : beanNames) {
|
||||
Object singletonInstance = getSingleton(beanName, false);
|
||||
@@ -1313,7 +1319,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
|
||||
// Cache a primary marker for the given bean.
|
||||
if (beanDefinition.isPrimary()) {
|
||||
this.primaryBeanNames.add(beanName);
|
||||
this.primaryBeanNamesWithType.put(beanName, Void.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1405,7 +1411,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
destroySingleton(beanName);
|
||||
|
||||
// Remove a cached primary marker for the given bean.
|
||||
this.primaryBeanNames.remove(beanName);
|
||||
this.primaryBeanNamesWithType.remove(beanName);
|
||||
|
||||
// Notify all post-processors that the specified bean definition has been reset.
|
||||
for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
|
||||
@@ -1455,11 +1461,30 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addSingleton(String beanName, Object singletonObject) {
|
||||
super.addSingleton(beanName, singletonObject);
|
||||
|
||||
Predicate<Class<?>> filter = (beanType -> beanType != Object.class && beanType.isInstance(singletonObject));
|
||||
this.allBeanNamesByType.keySet().removeIf(filter);
|
||||
this.singletonBeanNamesByType.keySet().removeIf(filter);
|
||||
|
||||
if (this.primaryBeanNamesWithType.containsKey(beanName) && singletonObject.getClass() != NullBean.class) {
|
||||
Class<?> beanType = (singletonObject instanceof FactoryBean<?> fb ?
|
||||
getTypeForFactoryBean(fb) : singletonObject.getClass());
|
||||
if (beanType != null) {
|
||||
this.primaryBeanNamesWithType.put(beanName, beanType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
|
||||
super.registerSingleton(beanName, singletonObject);
|
||||
|
||||
updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
|
||||
clearByTypeCache();
|
||||
this.allBeanNamesByType.remove(Object.class);
|
||||
this.singletonBeanNamesByType.remove(Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1628,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 {
|
||||
|
||||
@@ -1966,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);
|
||||
}
|
||||
}
|
||||
@@ -2083,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;
|
||||
@@ -2236,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)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2261,8 +2288,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
* not matching the given bean name.
|
||||
*/
|
||||
private boolean hasPrimaryConflict(String beanName, Class<?> dependencyType) {
|
||||
for (String candidate : this.primaryBeanNames) {
|
||||
if (isTypeMatch(candidate, dependencyType) && !candidate.equals(beanName)) {
|
||||
for (Map.Entry<String, Class<?>> candidate : this.primaryBeanNamesWithType.entrySet()) {
|
||||
String candidateName = candidate.getKey();
|
||||
Class<?> candidateType = candidate.getValue();
|
||||
if (!candidateName.equals(beanName) && (candidateType != Void.class ?
|
||||
dependencyType.isAssignableFrom(candidateType) : // cached singleton class for primary bean
|
||||
isTypeMatch(candidateName, dependencyType))) { // not instantiated yet or not a singleton
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-3
@@ -23,6 +23,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -418,14 +419,29 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
|
||||
if (destroyMethodName == null) {
|
||||
destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
boolean autoCloseable = (AutoCloseable.class.isAssignableFrom(target));
|
||||
boolean autoCloseable = AutoCloseable.class.isAssignableFrom(target);
|
||||
boolean executorService = ExecutorService.class.isAssignableFrom(target);
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
|
||||
(destroyMethodName == null && autoCloseable)) {
|
||||
(destroyMethodName == null && (autoCloseable || executorService))) {
|
||||
// Only perform destroy method inference in case of the bean
|
||||
// not explicitly implementing the DisposableBean interface
|
||||
destroyMethodName = null;
|
||||
if (!(DisposableBean.class.isAssignableFrom(target))) {
|
||||
if (autoCloseable) {
|
||||
if (executorService) {
|
||||
destroyMethodName = SHUTDOWN_METHOD_NAME;
|
||||
try {
|
||||
// On JDK 19+, avoid the ExecutorService-level AutoCloseable default implementation
|
||||
// which awaits task termination for 1 day, even for delayed tasks such as cron jobs.
|
||||
// Custom close() implementations in ExecutorService subclasses are still accepted.
|
||||
if (target.getMethod(CLOSE_METHOD_NAME).getDeclaringClass() != ExecutorService.class) {
|
||||
destroyMethodName = CLOSE_METHOD_NAME;
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// Ignore - stick with shutdown()
|
||||
}
|
||||
}
|
||||
else if (autoCloseable) {
|
||||
destroyMethodName = CLOSE_METHOD_NAME;
|
||||
}
|
||||
else {
|
||||
|
||||
+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) {
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
if (resource == null) {
|
||||
setValue(null);
|
||||
}
|
||||
else if (nioPathCandidate && !resource.exists()) {
|
||||
else if (nioPathCandidate && (!resource.isFile() || !resource.exists())) {
|
||||
setValue(Paths.get(text).normalize());
|
||||
}
|
||||
else {
|
||||
|
||||
+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());
|
||||
}
|
||||
|
||||
+33
-5
@@ -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
|
||||
@@ -3202,6 +3203,33 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(holder.getNonPublicEnum()).isEqualTo(NonPublicEnum.VALUE_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mostSpecificCacheEntryForTypeMatching() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition();
|
||||
bd1.setFactoryBeanName("config");
|
||||
bd1.setFactoryMethodName("create");
|
||||
lbf.registerBeanDefinition("config", new RootBeanDefinition(BeanWithFactoryMethod.class));
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", new RootBeanDefinition(NestedTestBean.class));
|
||||
lbf.freezeConfiguration();
|
||||
|
||||
String[] allBeanNames = lbf.getBeanNamesForType(Object.class);
|
||||
String[] nestedBeanNames = lbf.getBeanNamesForType(NestedTestBean.class);
|
||||
assertThat(lbf.getType("bd1")).isEqualTo(TestBean.class);
|
||||
assertThat(lbf.getBeanNamesForType(TestBean.class)).containsExactly("bd1");
|
||||
assertThat(lbf.getBeanNamesForType(DerivedTestBean.class)).isEmpty();
|
||||
lbf.getBean("bd1");
|
||||
assertThat(lbf.getType("bd1")).isEqualTo(DerivedTestBean.class);
|
||||
assertThat(lbf.getBeanNamesForType(TestBean.class)).containsExactly("bd1");
|
||||
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"));
|
||||
}
|
||||
|
||||
|
||||
private int registerBeanDefinitions(Properties p) {
|
||||
return registerBeanDefinitions(p, null);
|
||||
@@ -3418,7 +3446,7 @@ class DefaultListableBeanFactoryTests {
|
||||
}
|
||||
|
||||
public TestBean create() {
|
||||
TestBean tb = new TestBean();
|
||||
DerivedTestBean tb = new DerivedTestBean();
|
||||
tb.setName(this.name);
|
||||
return tb;
|
||||
}
|
||||
@@ -3646,11 +3674,11 @@ class DefaultListableBeanFactoryTests {
|
||||
|
||||
private FactoryBean<?> factoryBean;
|
||||
|
||||
public final FactoryBean<?> getFactoryBean() {
|
||||
public FactoryBean<?> getFactoryBean() {
|
||||
return this.factoryBean;
|
||||
}
|
||||
|
||||
public final void setFactoryBean(final FactoryBean<?> factoryBean) {
|
||||
public void setFactoryBean(FactoryBean<?> factoryBean) {
|
||||
this.factoryBean = factoryBean;
|
||||
}
|
||||
}
|
||||
|
||||
+167
-82
@@ -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));
|
||||
@@ -3258,6 +3297,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 +3411,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 {
|
||||
|
||||
|
||||
+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) -> {})));
|
||||
|
||||
+46
-1
@@ -17,6 +17,7 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -59,13 +60,38 @@ class RootBeanDefinitionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedInferredVaue() {
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForCloseMethod() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithCloseMethod.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("close");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForShutdownMethod() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithShutdownMethod.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("shutdown");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForExecutorService() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanImplementingExecutorService.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("shutdown");
|
||||
// even on JDK 19+ where the ExecutorService interface declares a default AutoCloseable implementation
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForAutoCloseableExecutorService() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanImplementingExecutorServiceAndAutoCloseable.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("close");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithNoCandidateSetDestroyMethodNameToNull() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithNoDestroyMethod.class);
|
||||
@@ -90,6 +116,25 @@ class RootBeanDefinitionTests {
|
||||
}
|
||||
|
||||
|
||||
static class BeanWithShutdownMethod {
|
||||
|
||||
public void shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract static class BeanImplementingExecutorService implements ExecutorService {
|
||||
}
|
||||
|
||||
|
||||
abstract static class BeanImplementingExecutorServiceAndAutoCloseable implements ExecutorService, AutoCloseable {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class BeanWithNoDestroyMethod {
|
||||
}
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+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.Aware;
|
||||
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 methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
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.
|
||||
|
||||
+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
|
||||
|
||||
+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.Aware;
|
||||
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 methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
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.
|
||||
|
||||
@@ -55,7 +55,7 @@ public interface MessageSource {
|
||||
* @see java.text.MessageFormat
|
||||
*/
|
||||
@Nullable
|
||||
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
|
||||
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, @Nullable Locale locale);
|
||||
|
||||
/**
|
||||
* Try to resolve the message. Treat as an error if the message can't be found.
|
||||
@@ -71,7 +71,7 @@ public interface MessageSource {
|
||||
* @see #getMessage(MessageSourceResolvable, Locale)
|
||||
* @see java.text.MessageFormat
|
||||
*/
|
||||
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
|
||||
String getMessage(String code, @Nullable Object[] args, @Nullable Locale locale) throws NoSuchMessageException;
|
||||
|
||||
/**
|
||||
* Try to resolve the message using all the attributes contained within the
|
||||
@@ -91,6 +91,6 @@ public interface MessageSource {
|
||||
* @see MessageSourceResolvable#getDefaultMessage()
|
||||
* @see java.text.MessageFormat
|
||||
*/
|
||||
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
|
||||
String getMessage(MessageSourceResolvable resolvable, @Nullable Locale locale) throws NoSuchMessageException;
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
+1
-1
@@ -51,9 +51,9 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* ignored.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
* @see Reflective @Reflective
|
||||
* @see RegisterReflection @RegisterReflection
|
||||
* @since 6.2
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
|
||||
+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 {
|
||||
|
||||
/**
|
||||
|
||||
+6
-3
@@ -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)) {
|
||||
@@ -1502,17 +1505,17 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
|
||||
public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, @Nullable Locale locale) {
|
||||
return getMessageSource().getMessage(code, args, defaultMessage, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
|
||||
public String getMessage(String code, @Nullable Object[] args, @Nullable Locale locale) throws NoSuchMessageException {
|
||||
return getMessageSource().getMessage(code, args, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
|
||||
public String getMessage(MessageSourceResolvable resolvable, @Nullable Locale locale) throws NoSuchMessageException {
|
||||
return getMessageSource().getMessage(resolvable, locale);
|
||||
}
|
||||
|
||||
|
||||
+18
-7
@@ -138,7 +138,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public final String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
|
||||
public final String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, @Nullable Locale locale) {
|
||||
String msg = getMessageInternal(code, args, locale);
|
||||
if (msg != null) {
|
||||
return msg;
|
||||
@@ -150,7 +150,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
|
||||
public final String getMessage(String code, @Nullable Object[] args, @Nullable Locale locale) throws NoSuchMessageException {
|
||||
String msg = getMessageInternal(code, args, locale);
|
||||
if (msg != null) {
|
||||
return msg;
|
||||
@@ -159,11 +159,16 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
|
||||
if (fallback != null) {
|
||||
return fallback;
|
||||
}
|
||||
throw new NoSuchMessageException(code, locale);
|
||||
if (locale == null ) {
|
||||
throw new NoSuchMessageException(code);
|
||||
}
|
||||
else {
|
||||
throw new NoSuchMessageException(code, locale);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
|
||||
public final String getMessage(MessageSourceResolvable resolvable, @Nullable Locale locale) throws NoSuchMessageException {
|
||||
String[] codes = resolvable.getCodes();
|
||||
if (codes != null) {
|
||||
for (String code : codes) {
|
||||
@@ -177,7 +182,13 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
|
||||
if (defaultMessage != null) {
|
||||
return defaultMessage;
|
||||
}
|
||||
throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : "", locale);
|
||||
String code = !ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : "";
|
||||
if (locale == null ) {
|
||||
throw new NoSuchMessageException(code);
|
||||
}
|
||||
else {
|
||||
throw new NoSuchMessageException(code, locale);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +295,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
|
||||
* @see #getDefaultMessage(String)
|
||||
*/
|
||||
@Nullable
|
||||
protected String getDefaultMessage(MessageSourceResolvable resolvable, Locale locale) {
|
||||
protected String getDefaultMessage(MessageSourceResolvable resolvable, @Nullable Locale locale) {
|
||||
String defaultMessage = resolvable.getDefaultMessage();
|
||||
String[] codes = resolvable.getCodes();
|
||||
if (defaultMessage != null) {
|
||||
@@ -331,7 +342,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
|
||||
* @return an array of arguments with any MessageSourceResolvables resolved
|
||||
*/
|
||||
@Override
|
||||
protected Object[] resolveArguments(@Nullable Object[] args, Locale locale) {
|
||||
protected Object[] resolveArguments(@Nullable Object[] args, @Nullable Locale locale) {
|
||||
if (ObjectUtils.isEmpty(args)) {
|
||||
return super.resolveArguments(args, locale);
|
||||
}
|
||||
|
||||
+15
-5
@@ -55,7 +55,7 @@ public class DelegatingMessageSource extends MessageSourceSupport implements Hie
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
|
||||
public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, @Nullable Locale locale) {
|
||||
if (this.parentMessageSource != null) {
|
||||
return this.parentMessageSource.getMessage(code, args, defaultMessage, locale);
|
||||
}
|
||||
@@ -68,17 +68,22 @@ public class DelegatingMessageSource extends MessageSourceSupport implements Hie
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
|
||||
public String getMessage(String code, @Nullable Object[] args, @Nullable Locale locale) throws NoSuchMessageException {
|
||||
if (this.parentMessageSource != null) {
|
||||
return this.parentMessageSource.getMessage(code, args, locale);
|
||||
}
|
||||
else {
|
||||
throw new NoSuchMessageException(code, locale);
|
||||
if (locale == null) {
|
||||
throw new NoSuchMessageException(code);
|
||||
}
|
||||
else {
|
||||
throw new NoSuchMessageException(code, locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
|
||||
public String getMessage(MessageSourceResolvable resolvable, @Nullable Locale locale) throws NoSuchMessageException {
|
||||
if (this.parentMessageSource != null) {
|
||||
return this.parentMessageSource.getMessage(resolvable, locale);
|
||||
}
|
||||
@@ -88,7 +93,12 @@ public class DelegatingMessageSource extends MessageSourceSupport implements Hie
|
||||
}
|
||||
String[] codes = resolvable.getCodes();
|
||||
String code = (codes != null && codes.length > 0 ? codes[0] : "");
|
||||
throw new NoSuchMessageException(code, locale);
|
||||
if (locale == null) {
|
||||
throw new NoSuchMessageException(code);
|
||||
}
|
||||
else {
|
||||
throw new NoSuchMessageException(code, locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -98,7 +98,7 @@ public abstract class MessageSourceSupport {
|
||||
* @return the rendered default message (with resolved arguments)
|
||||
* @see #formatMessage(String, Object[], java.util.Locale)
|
||||
*/
|
||||
protected String renderDefaultMessage(String defaultMessage, @Nullable Object[] args, Locale locale) {
|
||||
protected String renderDefaultMessage(String defaultMessage, @Nullable Object[] args, @Nullable Locale locale) {
|
||||
return formatMessage(defaultMessage, args, locale);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public abstract class MessageSourceSupport {
|
||||
* @param locale the Locale used for formatting
|
||||
* @return the formatted message (with resolved arguments)
|
||||
*/
|
||||
protected String formatMessage(String msg, @Nullable Object[] args, Locale locale) {
|
||||
protected String formatMessage(String msg, @Nullable Object[] args, @Nullable Locale locale) {
|
||||
if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
|
||||
return msg;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ public abstract class MessageSourceSupport {
|
||||
* @param locale the Locale to create a {@code MessageFormat} for
|
||||
* @return the {@code MessageFormat} instance
|
||||
*/
|
||||
protected MessageFormat createMessageFormat(String msg, Locale locale) {
|
||||
protected MessageFormat createMessageFormat(String msg, @Nullable Locale locale) {
|
||||
return new MessageFormat(msg, locale);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public abstract class MessageSourceSupport {
|
||||
* @param locale the Locale to resolve against
|
||||
* @return the resolved argument array
|
||||
*/
|
||||
protected Object[] resolveArguments(@Nullable Object[] args, Locale locale) {
|
||||
protected Object[] resolveArguments(@Nullable Object[] args, @Nullable Locale locale) {
|
||||
return (args != null ? args : new Object[0]);
|
||||
}
|
||||
|
||||
|
||||
+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
@@ -376,7 +376,7 @@ public class SimpleAsyncTaskScheduler extends SimpleAsyncTaskExecutor implements
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.triggerLifecycle.isRunning();
|
||||
return (this.triggerLifecycle.isRunning() || this.fixedDelayLifecycle.isRunning());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.validation;
|
||||
|
||||
import java.util.HexFormat;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -126,8 +128,18 @@ public class FieldError extends ObjectError {
|
||||
// We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but
|
||||
// keep including the full nullSafeToString representation for backwards compatibility.
|
||||
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
|
||||
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
|
||||
"': rejected value [" + formatRejectedValue() + "]; " +
|
||||
resolvableToString();
|
||||
}
|
||||
|
||||
private String formatRejectedValue() {
|
||||
|
||||
// Special handling of byte[], to be moved into ObjectUtils in 7.0
|
||||
if (this.rejectedValue instanceof byte[] bytes && bytes.length != 0) {
|
||||
return "{" + HexFormat.of().formatHex(bytes) + "}";
|
||||
}
|
||||
|
||||
return ObjectUtils.nullSafeToString(this.rejectedValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -69,8 +69,6 @@ import org.springframework.validation.method.ParameterValidationResult;
|
||||
* at the type level of the containing target class, applying to all public service methods
|
||||
* of that class. By default, JSR-303 will validate against its default group only.
|
||||
*
|
||||
* <p>This functionality requires a Bean Validation 1.1+ provider.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
|
||||
-2
@@ -60,8 +60,6 @@ import org.springframework.validation.method.MethodValidationResult;
|
||||
* inline constraint annotations. Validation groups can be specified through {@code @Validated}
|
||||
* as well. By default, JSR-303 will validate against its default group only.
|
||||
*
|
||||
* <p>This functionality requires a Bean Validation 1.1+ provider.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see MethodValidationInterceptor
|
||||
|
||||
-1
@@ -56,7 +56,6 @@ public class SpringConstraintValidatorFactory implements ConstraintValidatorFact
|
||||
return this.beanFactory.createBean(key);
|
||||
}
|
||||
|
||||
// Bean Validation 1.1 releaseInstance method
|
||||
@Override
|
||||
public void releaseInstance(ConstraintValidator<?, ?> instance) {
|
||||
this.beanFactory.destroyBean(instance);
|
||||
|
||||
-2
@@ -55,8 +55,6 @@ import org.springframework.validation.SmartValidator;
|
||||
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean},
|
||||
* and as the primary implementation of the {@link SmartValidator} interface.
|
||||
*
|
||||
* <p>This adapter is fully compatible with Bean Validation 1.1 as well as 2.0.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
|
||||
+6
-6
@@ -60,12 +60,6 @@ class CacheOperationExpressionEvaluatorTests {
|
||||
private final AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
|
||||
|
||||
|
||||
private Collection<CacheOperation> getOps(String name) {
|
||||
Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class);
|
||||
return this.source.getCacheOperations(method, AnnotatedClass.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMultipleCachingSource() {
|
||||
Collection<CacheOperation> ops = getOps("multipleCaching");
|
||||
@@ -144,6 +138,12 @@ class CacheOperationExpressionEvaluatorTests {
|
||||
assertThat(value).isEqualTo(String.class.getName());
|
||||
}
|
||||
|
||||
|
||||
private Collection<CacheOperation> getOps(String name) {
|
||||
Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class);
|
||||
return this.source.getCacheOperations(method, AnnotatedClass.class);
|
||||
}
|
||||
|
||||
private EvaluationContext createEvaluationContext(Object result) {
|
||||
return createEvaluationContext(result, null);
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -104,6 +104,7 @@ class CachePutEvaluationTests {
|
||||
assertThat(this.cache.get(anotherValue + 100).get()).as("Wrong value for @CachePut key").isEqualTo(anotherValue);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
static class Config implements CachingConfigurer {
|
||||
@@ -121,8 +122,10 @@ class CachePutEvaluationTests {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@CacheConfig("test")
|
||||
public static class SimpleService {
|
||||
|
||||
private AtomicLong counter = new AtomicLong();
|
||||
|
||||
/**
|
||||
@@ -144,4 +147,5 @@ class CachePutEvaluationTests {
|
||||
return this.counter.getAndIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+55
@@ -31,6 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.weaving.LoadTimeWeaverAware;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.testfixture.EnabledForTestGroups;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
@@ -68,6 +69,16 @@ class BackgroundBootstrapTests {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(10)
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
void bootstrapWithLoadTimeWeaverAware() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(LoadTimeWeaverAwareBeanConfig.class);
|
||||
ctx.getBean("testBean1", TestBean.class);
|
||||
ctx.getBean("testBean2", TestBean.class);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(10)
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
@@ -266,6 +277,50 @@ class BackgroundBootstrapTests {
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class LoadTimeWeaverAwareBeanConfig {
|
||||
|
||||
@Bean
|
||||
LoadTimeWeaverAware loadTimeWeaverAware(ObjectProvider<TestBean> testBean1) {
|
||||
Thread thread = new Thread(testBean1::getObject);
|
||||
thread.start();
|
||||
try {
|
||||
thread.join();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return (loadTimeWeaver -> {});
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestBean testBean1(TestBean testBean2) {
|
||||
return new TestBean(testBean2);
|
||||
}
|
||||
|
||||
@Bean @Lazy
|
||||
public FactoryBean<TestBean> testBean2() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
TestBean testBean = new TestBean();
|
||||
return new FactoryBean<>() {
|
||||
@Override
|
||||
public TestBean getObject() {
|
||||
return testBean;
|
||||
}
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return testBean.getClass();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class StrictLockingBeanConfig {
|
||||
|
||||
|
||||
+15
-9
@@ -61,6 +61,7 @@ class ClassPathBeanDefinitionScannerTests {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(12);
|
||||
assertThat(context.containsBean("serviceInvocationCounter")).isTrue();
|
||||
assertThat(context.containsBean("fooServiceImpl")).isTrue();
|
||||
@@ -73,8 +74,8 @@ class ClassPathBeanDefinitionScannerTests {
|
||||
assertThat(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)).isTrue();
|
||||
assertThat(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME)).isTrue();
|
||||
assertThat(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME)).isTrue();
|
||||
context.refresh();
|
||||
|
||||
context.refresh();
|
||||
FooServiceImpl fooService = context.getBean("fooServiceImpl", FooServiceImpl.class);
|
||||
assertThat(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent")).isTrue();
|
||||
assertThat(fooService.foo(123)).isEqualTo("bar");
|
||||
@@ -157,6 +158,7 @@ class ClassPathBeanDefinitionScannerTests {
|
||||
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(12);
|
||||
|
||||
ClassPathBeanDefinitionScanner scanner2 = new ClassPathBeanDefinitionScanner(context) {
|
||||
@@ -182,8 +184,8 @@ class ClassPathBeanDefinitionScannerTests {
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
scanner.setIncludeAnnotationConfig(false);
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(7);
|
||||
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(7);
|
||||
assertThat(context.containsBean("serviceInvocationCounter")).isTrue();
|
||||
assertThat(context.containsBean("fooServiceImpl")).isTrue();
|
||||
assertThat(context.containsBean("stubFooDao")).isTrue();
|
||||
@@ -482,12 +484,14 @@ class ClassPathBeanDefinitionScannerTests {
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(12);
|
||||
context.refresh();
|
||||
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(12);
|
||||
|
||||
context.refresh();
|
||||
FooServiceImpl fooService = context.getBean("fooService", FooServiceImpl.class);
|
||||
StaticListableBeanFactory myBf = (StaticListableBeanFactory) context.getBean("myBf");
|
||||
MessageSource ms = (MessageSource) context.getBean("messageSource");
|
||||
|
||||
assertThat(fooService.isInitCalled()).isTrue();
|
||||
assertThat(fooService.foo(123)).isEqualTo("bar");
|
||||
assertThat(fooService.lookupFoo(123)).isEqualTo("bar");
|
||||
@@ -509,9 +513,10 @@ class ClassPathBeanDefinitionScannerTests {
|
||||
scanner.setIncludeAnnotationConfig(false);
|
||||
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(7);
|
||||
context.refresh();
|
||||
|
||||
assertThat(beanCount).isGreaterThanOrEqualTo(7);
|
||||
|
||||
context.refresh();
|
||||
try {
|
||||
context.getBean("fooService");
|
||||
}
|
||||
@@ -545,9 +550,10 @@ class ClassPathBeanDefinitionScannerTests {
|
||||
scanner.setAutowireCandidatePatterns("*NoSuchDao");
|
||||
scanner.scan(BASE_PACKAGE);
|
||||
context.refresh();
|
||||
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
|
||||
context.getBean("fooService"))
|
||||
.satisfies(ex -> assertThat(ex.getMostSpecificCause()).isInstanceOf(NoSuchBeanDefinitionException.class));
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> context.getBean("fooService"))
|
||||
.satisfies(ex ->
|
||||
assertThat(ex.getMostSpecificCause()).isInstanceOf(NoSuchBeanDefinitionException.class));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+34
-28
@@ -409,11 +409,14 @@ class ConfigurationClassPostProcessorTests {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
|
||||
beanFactory.setAllowBeanDefinitionOverriding(false);
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() -> pp.postProcessBeanFactory(beanFactory))
|
||||
.withMessageContaining("bar")
|
||||
.withMessageContaining("SingletonBeanConfig")
|
||||
.withMessageContaining(TestBean.class.getName());
|
||||
.withMessageContainingAll(
|
||||
"bar",
|
||||
"SingletonBeanConfig",
|
||||
TestBean.class.getName()
|
||||
);
|
||||
}
|
||||
|
||||
@Test // gh-25430
|
||||
@@ -422,10 +425,13 @@ class ConfigurationClassPostProcessorTests {
|
||||
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
|
||||
beanFactory.setAllowBeanDefinitionOverriding(false);
|
||||
context.register(FirstConfiguration.class, SecondConfiguration.class);
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(context::refresh)
|
||||
.withMessageContaining("alias 'taskExecutor'")
|
||||
.withMessageContaining("name 'applicationTaskExecutor'")
|
||||
.withMessageContaining("bean definition 'taskExecutor'");
|
||||
.withMessageContainingAll(
|
||||
"alias 'taskExecutor'",
|
||||
"name 'applicationTaskExecutor'",
|
||||
"bean definition 'taskExecutor'"
|
||||
);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -978,7 +984,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSelfReferenceExclusionForFactoryMethodOnSameBean() {
|
||||
void selfReferenceExclusionForFactoryMethodOnSameBean() {
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(beanFactory);
|
||||
beanFactory.addBeanPostProcessor(bpp);
|
||||
@@ -992,7 +998,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigWithDefaultMethods() {
|
||||
void configWithDefaultMethods() {
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(beanFactory);
|
||||
beanFactory.addBeanPostProcessor(bpp);
|
||||
@@ -1006,7 +1012,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigWithDefaultMethodsUsingAsm() {
|
||||
void configWithDefaultMethodsUsingAsm() {
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(beanFactory);
|
||||
beanFactory.addBeanPostProcessor(bpp);
|
||||
@@ -1020,7 +1026,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigWithFailingInit() { // gh-23343
|
||||
void configWithFailingInit() { // gh-23343
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(beanFactory);
|
||||
beanFactory.addBeanPostProcessor(bpp);
|
||||
@@ -1034,7 +1040,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCircularDependency() {
|
||||
void circularDependency() {
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(beanFactory);
|
||||
beanFactory.addBeanPostProcessor(bpp);
|
||||
@@ -1048,42 +1054,42 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCircularDependencyWithApplicationContext() {
|
||||
void circularDependencyWithApplicationContext() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(A.class, AStrich.class))
|
||||
.withMessageContaining("Circular reference");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrototypeArgumentThroughBeanMethodCall() {
|
||||
void prototypeArgumentThroughBeanMethodCall() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithPrototype.class);
|
||||
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSingletonArgumentThroughBeanMethodCall() {
|
||||
void singletonArgumentThroughBeanMethodCall() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithSingleton.class);
|
||||
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullArgumentThroughBeanMethodCall() {
|
||||
void nullArgumentThroughBeanMethodCall() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithNull.class);
|
||||
ctx.getBean("aFoo");
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInjectionPointMatchForNarrowTargetReturnType() {
|
||||
void injectionPointMatchForNarrowTargetReturnType() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(FooBarConfiguration.class);
|
||||
assertThat(ctx.getBean(FooImpl.class).bar).isSameAs(ctx.getBean(BarImpl.class));
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVarargOnBeanMethod() {
|
||||
void varargOnBeanMethod() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class, TestBean.class);
|
||||
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
|
||||
assertThat(bean.testBeans).isNotNull();
|
||||
@@ -1093,7 +1099,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyVarargOnBeanMethod() {
|
||||
void emptyVarargOnBeanMethod() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class);
|
||||
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
|
||||
assertThat(bean.testBeans).isNotNull();
|
||||
@@ -1102,7 +1108,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionArgumentOnBeanMethod() {
|
||||
void collectionArgumentOnBeanMethod() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class, TestBean.class);
|
||||
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
|
||||
assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class));
|
||||
@@ -1110,7 +1116,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyCollectionArgumentOnBeanMethod() {
|
||||
void emptyCollectionArgumentOnBeanMethod() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class);
|
||||
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
|
||||
assertThat(bean.testBeans).isEmpty();
|
||||
@@ -1118,7 +1124,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMapArgumentOnBeanMethod() {
|
||||
void mapArgumentOnBeanMethod() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class, DummyRunnable.class);
|
||||
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
|
||||
assertThat(bean.testBeans).hasSize(1).containsValue(ctx.getBean(Runnable.class));
|
||||
@@ -1126,7 +1132,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyMapArgumentOnBeanMethod() {
|
||||
void emptyMapArgumentOnBeanMethod() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class);
|
||||
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
|
||||
assertThat(bean.testBeans).isEmpty();
|
||||
@@ -1134,7 +1140,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionInjectionFromSameConfigurationClass() {
|
||||
void collectionInjectionFromSameConfigurationClass() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class);
|
||||
CollectionInjectionConfiguration bean = ctx.getBean(CollectionInjectionConfiguration.class);
|
||||
assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class));
|
||||
@@ -1142,7 +1148,7 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMapInjectionFromSameConfigurationClass() {
|
||||
void mapInjectionFromSameConfigurationClass() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapInjectionConfiguration.class);
|
||||
MapInjectionConfiguration bean = ctx.getBean(MapInjectionConfiguration.class);
|
||||
assertThat(bean.testBeans).containsOnly(Map.entry("testBean", ctx.getBean(Runnable.class)));
|
||||
@@ -1150,20 +1156,20 @@ class ConfigurationClassPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeanLookupFromSameConfigurationClass() {
|
||||
void beanLookupFromSameConfigurationClass() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanLookupConfiguration.class);
|
||||
assertThat(ctx.getBean(BeanLookupConfiguration.class).getTestBean()).isSameAs(ctx.getBean(TestBean.class));
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameClashBetweenConfigurationClassAndBean() {
|
||||
void nameClashBetweenConfigurationClassAndBean() {
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class).getBean("myTestBean", TestBean.class));
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeanDefinitionRegistryPostProcessorConfig() {
|
||||
void beanDefinitionRegistryPostProcessorConfig() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanDefinitionRegistryPostProcessorConfig.class);
|
||||
assertThat(ctx.getBean("myTestBean")).isInstanceOf(TestBean.class);
|
||||
ctx.close();
|
||||
|
||||
+17
-2
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.event;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -105,8 +106,9 @@ class AnnotationDrivenEventListenerTests {
|
||||
this.eventCollector.assertTotalEventsCount(1);
|
||||
|
||||
this.eventCollector.clear();
|
||||
this.context.publishEvent(event);
|
||||
this.eventCollector.assertEvent(listener, event);
|
||||
TestEvent otherEvent = new TestEvent(this, Integer.valueOf(1));
|
||||
this.context.publishEvent(otherEvent);
|
||||
this.eventCollector.assertEvent(listener, otherEvent);
|
||||
this.eventCollector.assertTotalEventsCount(1);
|
||||
|
||||
context.getBean(ApplicationEventMulticaster.class).removeApplicationListeners(l ->
|
||||
@@ -742,6 +744,11 @@ class AnnotationDrivenEventListenerTests {
|
||||
public void handleString(String content) {
|
||||
collectEvent(content);
|
||||
}
|
||||
|
||||
@EventListener({Boolean.class, Integer.class})
|
||||
public void handleBooleanOrInteger(Serializable content) {
|
||||
collectEvent(content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1009,6 +1016,8 @@ class AnnotationDrivenEventListenerTests {
|
||||
|
||||
void handleString(String payload);
|
||||
|
||||
void handleBooleanOrInteger(Serializable content);
|
||||
|
||||
void handleTimestamp(Long timestamp);
|
||||
|
||||
void handleRatio(Double ratio);
|
||||
@@ -1031,6 +1040,12 @@ class AnnotationDrivenEventListenerTests {
|
||||
super.handleString(payload);
|
||||
}
|
||||
|
||||
@EventListener({Boolean.class, Integer.class})
|
||||
@Override
|
||||
public void handleBooleanOrInteger(Serializable content) {
|
||||
super.handleBooleanOrInteger(content);
|
||||
}
|
||||
|
||||
@ConditionalEvent("#root.event.timestamp > #p0")
|
||||
@Override
|
||||
public void handleTimestamp(Long timestamp) {
|
||||
|
||||
@@ -18,11 +18,12 @@ package org.springframework.context.event.test;
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class TestEvent extends IdentifiableApplicationEvent {
|
||||
|
||||
public final String msg;
|
||||
public final Object msg;
|
||||
|
||||
public TestEvent(Object source, String id, String msg) {
|
||||
super(source, id);
|
||||
@@ -34,6 +35,11 @@ public class TestEvent extends IdentifiableApplicationEvent {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public TestEvent(Object source, Integer msg) {
|
||||
super(source);
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public TestEvent(Object source) {
|
||||
this(source, "test");
|
||||
}
|
||||
|
||||
-2
@@ -32,8 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
public class CandidateComponentsIndexLoaderTests {
|
||||
|
||||
@Test
|
||||
|
||||
-2
@@ -30,8 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
public class CandidateComponentsIndexTests {
|
||||
|
||||
@Test
|
||||
|
||||
-4
@@ -503,10 +503,6 @@ class SpringValidatorAdapterTests {
|
||||
|
||||
private static final String ID = "id";
|
||||
|
||||
@Override
|
||||
public void initialize(AnythingValid constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Object value, ConstraintValidatorContext context) {
|
||||
List<Field> fieldsErrors = new ArrayList<>();
|
||||
|
||||
-12
@@ -425,10 +425,6 @@ class ValidatorFactoryTests {
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void initialize(NameAddressValid constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
|
||||
if (value.expectsAutowiredValidator) {
|
||||
@@ -495,10 +491,6 @@ class ValidatorFactoryTests {
|
||||
|
||||
public static class InnerValidator implements ConstraintValidator<InnerValid, InnerBean> {
|
||||
|
||||
@Override
|
||||
public void initialize(InnerValid constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
@@ -542,10 +534,6 @@ class ValidatorFactoryTests {
|
||||
|
||||
public static class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {
|
||||
|
||||
@Override
|
||||
public void initialize(NotXList constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(List<String> list, ConstraintValidatorContext context) {
|
||||
context.disableDefaultConstraintViolation();
|
||||
|
||||
@@ -193,12 +193,16 @@ public class ClassReader {
|
||||
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
|
||||
this.classFileBuffer = classFileBuffer;
|
||||
this.b = classFileBuffer;
|
||||
|
||||
// Check the class' major_version. This field is after the magic and minor_version fields, which
|
||||
// use 4 and 2 bytes respectively.
|
||||
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V25) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported class file major version " + readShort(classFileOffset + 6));
|
||||
}
|
||||
// SPRING PATCH: leniently try to parse newer class files as well
|
||||
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
|
||||
// throw new IllegalArgumentException(
|
||||
// "Unsupported class file major version " + readShort(classFileOffset + 6));
|
||||
// }
|
||||
// END OF PATCH
|
||||
|
||||
// Create the constant pool arrays. The constant_pool_count field is after the magic,
|
||||
// minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
|
||||
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
|
||||
|
||||
@@ -290,6 +290,7 @@ public interface Opcodes {
|
||||
int V23 = 0 << 16 | 67;
|
||||
int V24 = 0 << 16 | 68;
|
||||
int V25 = 0 << 16 | 69;
|
||||
int V26 = 0 << 16 | 70;
|
||||
|
||||
/**
|
||||
* Version flag indicating that the class is using 'preview' features.
|
||||
|
||||
@@ -1473,7 +1473,7 @@ final class SymbolTable {
|
||||
|
||||
/**
|
||||
* Another entry (and so on recursively) having the same hash code (modulo the size of {@link
|
||||
* SymbolTable#labelEntries}}) as this one.
|
||||
* SymbolTable#labelEntries}) as this one.
|
||||
*/
|
||||
LabelEntry next;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -110,7 +110,7 @@ class BridgeMethodResolver {
|
||||
&& currentMethod != null) {
|
||||
Signature target = new Signature(name, desc);
|
||||
// If the target signature is the same as the current,
|
||||
// we shouldn't change our bridge becaues invokespecial
|
||||
// we shouldn't change our bridge because invokespecial
|
||||
// is the only way to make progress (otherwise we'll
|
||||
// get infinite recursion). This would typically
|
||||
// only happen when a bridge method is created to widen
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user