Compare commits

..

1 Commits

Author SHA1 Message Date
Stéphane Nicoll 8f64480c9f Release v6.2.10 2025-08-14 09:15:43 +02:00
284 changed files with 4370 additions and 8992 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
toolchain: false
- version: 21
toolchain: true
- version: 25
- version: 24
toolchain: true
exclude:
- os:
+1 -1
View File
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
## Continuous Integration Builds
CI builds are defined with [GitHub Actions workflows](.github/workflows).
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
## Stay in Touch
+2 -2
View File
@@ -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.hibernate.org/orm/5.6/javadocs/",
"https://docs.jboss.org/hibernate/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.14.1/api/",
// "https://junit.org/junit5/docs/5.13.4/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/",
@@ -85,11 +85,11 @@ element. The following example shows how to use it:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="collaborator" class="..." />
<bean id="theTargetBean" class="..."/>
<bean id="client" class="...">
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="collaborator" />
<idref bean="theTargetBean"/>
</property>
</bean>
----
@@ -99,24 +99,28 @@ following snippet:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="collaborator" class="..." />
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="collaborator" />
<bean id="theClientBean" class="...">
<property name="targetName" ref="theTargetBean"/>
</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 therefore 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 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: 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: 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
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();
----
@@ -77,17 +77,6 @@ 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#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-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:
@@ -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,23 +16,16 @@ 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.
* 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.
* 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.
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]
@@ -45,10 +38,16 @@ Java::
@RecordApplicationEvents // <1>
class OrderServiceTests {
@Autowired
OrderService orderService;
@Autowired
ApplicationEvents events; // <2>
@Test
void submitOrder(@Autowired OrderService service, ApplicationEvents events) { // <2>
void submitOrder() {
// Invoke method in OrderService that publishes an event
service.submitOrder(new Order(/* ... */));
orderService.submitOrder(new Order(/* ... */));
// Verify that an OrderSubmitted event was published
long numEvents = events.stream(OrderSubmitted.class).count(); // <3>
assertThat(numEvents).isEqualTo(1);
@@ -67,10 +66,16 @@ Kotlin::
@RecordApplicationEvents // <1>
class OrderServiceTests {
@Autowired
lateinit var orderService: OrderService
@Autowired
lateinit var events: ApplicationEvents // <2>
@Test
fun submitOrder(@Autowired service: OrderService, events: ApplicationEvents) { // <2>
fun submitOrder() {
// Invoke method in OrderService that publishes an event
service.submitOrder(Order(/* ... */))
orderService.submitOrder(Order(/* ... */))
// Verify that an OrderSubmitted event was published
val numEvents = events.stream(OrderSubmitted::class).count() // <3>
assertThat(numEvents).isEqualTo(1)
@@ -294,28 +294,7 @@ allPartsEvents.windowUntil(PartEvent::isLast)
----
======
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")})
----
======
Note that the body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
[[webflux-fn-response]]
=== ServerResponse
@@ -448,7 +448,7 @@ Java::
----
@GetMapping
FragmentsRendering handle() {
return FragmentsRendering.fragment("posts").fragment("comments").build();
return FragmentsRendering.with("posts").fragment("comments").build();
}
----
@@ -458,7 +458,7 @@ Kotlin::
----
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.fragment("posts").fragment("comments").build()
return FragmentsRendering.with("posts").fragment("comments").build()
}
----
======
@@ -2,18 +2,18 @@
= WebClient
:page-section-summary-toc: 1
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])
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],
which enables declarative composition of asynchronous logic without the need to deal with
threads or concurrency. It is fully non-blocking, supports streaming, and relies on
threads or concurrency. It is fully non-blocking, it 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. There is built-in
`WebClient` needs an HTTP client library to perform requests with. 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 in via `ClientHttpConnector`.
* Others can be plugged via `ClientHttpConnector`.
@@ -310,7 +310,7 @@ Java::
Flux<String> source = ... ;
Mono<Void> output = session.send(source.map(session::textMessage)); <2>
return input.and(output); <3>
return Mono.zip(input, output).then(); <3>
}
}
----
@@ -338,7 +338,7 @@ Kotlin::
val source: Flux<String> = ...
val output = session.send(source.map(session::textMessage)) // <2>
return input.and(output) // <3>
return Mono.zip(input, output).then() // <3>
}
}
----
@@ -89,7 +89,39 @@ Kotlin::
You can map requests by using glob patterns and wildcards:
include::partial$web/uri-patterns.adoc[leveloffset=+1]
[cols="2,3,5"]
|===
|Pattern |Description |Example
| `+?+`
| Matches one character
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
| `+*+`
| Matches zero or more characters within a path segment
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`
| `+**+`
| Matches zero or more path segments until the end of the path
| `+"/resources/**"+` matches `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
`+"/resources/**/file.png"+` is invalid as `+**+` is only allowed at the end of the path.
| `+{name}+`
| Matches a path segment and captures it as a variable named "name"
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
| `+{name:[a-z]+}+`
| Matches the regexp `+"[a-z]+"+` as a path variable named "name"
| `+"/projects/{project:[a-z]+}/versions"+` matches `+"/projects/spring/versions"+` but not `+"/projects/spring1/versions"+`
| `+{*path}+`
| Matches zero or more path segments until the end of the path and captures it as a variable named "path"
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
|===
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
@@ -7,23 +7,19 @@ Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
1. Java Bean Validation is applied individually to an
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
it is a command object rather than a container such as `Map` or `Collection`, it does not
have `Errors` or `BindingResult` immediately after in the method signature, and does not
otherwise require method validation (see next). `WebExchangeBindException` is the
exception raised when validating a method parameter individually.
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
resolvers validate a method argument individually if the method parameter is annotated
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
discussed next). The exception raised in this case is `WebExchangeBindException`.
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
method for the return value, and it supersedes any validation that would be applied
otherwise to a method parameter individually because method validation covers both
method parameter constraints and nested constraints via `@Valid`.
`HandlerMethodValidationException` is the exception raised validation is applied
to the method.
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
directly on method parameters, or on the method (for the return value), then method
validation must be applied, and that supersedes validation at the method argument level
because method validation covers both method parameter constraints and nested constraints
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
Applications must handle both `WebExchangeBindException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
@@ -15,27 +15,27 @@ See xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] for more de
[[webmvc-webclient]]
== `WebClient`
`WebClient` is a reactive client for making HTTP requests with a fluent API.
`WebClient` is a reactive client to perform 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 for making HTTP requests. It is the original
`RestTemplate` is a synchronous client to perform 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-resttemplate[`RestTemplate`] for details.
See xref:integration/rest-clients.adoc[REST Endpoints] for details.
[[webmvc-http-interface]]
== HTTP Interface
The Spring Framework lets you define an HTTP service as a Java interface with HTTP
The Spring Frameworks 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 choosing an API style such as synchronous or reactive.
flexibility for to choose an API style such as synchronous or reactive.
See xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] for details.
See xref:integration/rest-clients.adoc#rest-http-interface[REST Endpoints] for details.
@@ -184,26 +184,6 @@ 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.fragment("posts").fragment("comments").build();
return FragmentsRendering.with("posts").fragment("comments").build();
}
----
@@ -58,7 +58,7 @@ Kotlin::
----
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.fragment("posts").fragment("comments").build()
return FragmentsRendering.with("posts").fragment("comments").build()
}
----
======
@@ -3,10 +3,7 @@
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-filters[See equivalent in the Reactive stack]#
In the Servlet API, you can add a `jakarta.servlet.Filter` to apply interception-style logic
before and after the rest of the processing chain of filters and the target `Servlet`.
The `spring-web` module has a number of built-in `Filter` implementations:
The `spring-web` module provides some useful filters:
* xref:web/webmvc/filters.adoc#filters-http-put[Form Data]
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
@@ -14,19 +11,9 @@ The `spring-web` module has a number of built-in `Filter` implementations:
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
There are also base class implementations for use in Spring applications:
* `GenericFilterBean` -- base class for a `Filter` configured as a Spring bean;
integrates with the Spring `ApplicationContext` lifecycle.
* `OncePerRequestFilter` -- extension of `GenericFilterBean` that supports a single
invocation at the start of a request, i.e. during the `REQUEST` dispatch phase, and
ignoring further handling via `FORWARD` dispatches. The filter also provides control
over whether the `Filter` gets involved in `ASYNC` and `ERROR` dispatches.
Servlet filters can be configured in `web.xml` or via Servlet annotations.
In a Spring Boot application , you can
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare Filter's as beans]
and Boot will have them configured.
Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations.
If you are using Spring Boot, you can
{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare them as beans and configure them as part of your application].
[[filters-http-put]]
@@ -422,7 +422,7 @@ reactive types from the controller method.
Reactive return values are handled as follows:
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
include `CompletionStage` (JDK), `Mono` (Reactor), and `Single` (RxJava).
include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
@@ -103,9 +103,22 @@ Spring WebFlux. It was enabled for use in Spring MVC from version 5.3 and is ena
default from version 6.0. See xref:web/webmvc/mvc-config/path-matching.adoc[MVC config] for
customizations of path matching options.
You can map requests by using glob patterns and wildcards:
`PathPattern` supports the same pattern syntax as `AntPathMatcher`. In addition, it also
supports the capturing pattern, for example, `+{*spring}+`, for matching 0 or more path segments
at the end of a path. `PathPattern` also restricts the use of `+**+` for matching multiple
path segments such that it's only allowed at the end of a pattern. This eliminates many
cases of ambiguity when choosing the best matching pattern for a given request.
For full pattern syntax please refer to
{spring-framework-api}/web/util/pattern/PathPattern.html[PathPattern] and
{spring-framework-api}/util/AntPathMatcher.html[AntPathMatcher].
include::partial$web/uri-patterns.adoc[leveloffset=+1]
Some example patterns:
* `+"/resources/ima?e.png"+` - match one character in a path segment
* `+"/resources/*.png"+` - match zero or more characters in a path segment
* `+"/resources/**"+` - match multiple path segments
* `+"/projects/{project}/versions"+` - match a path segment and capture it as a variable
* `++"/projects/{project:[a-z]+}/versions"++` - match and capture a variable with a regex
Captured URI variables can be accessed with `@PathVariable`. For example:
@@ -7,26 +7,22 @@ Spring MVC has built-in xref:core/validation/validator.adoc[validation] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
1. Java Bean Validation is applied individually to an
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method parameter
annotated with `@jakarta.validation.Valid` or Spring's `@Validated` so long as
it is a command object rather than a container such as `Map` or `Collection`, it does not
have `Errors` or `BindingResult` immediately after in the method signature, and does not
otherwise require method validation (see next). `MethodArgumentNotValidException` is the
exception raised when validating a method parameter individually.
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
resolvers validate a method argument individually if the method parameter is annotated
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
2. Java Bean Validation is applied to the method when `@Constraint` annotations such as
`@Min`, `@NotBlank` and others are declared directly on method parameters, or on the
method for the return value, and it supersedes any validation that would be applied
otherwise to a method parameter individually because method validation covers both
method parameter constraints and nested constraints via `@Valid`.
`HandlerMethodValidationException` is the exception raised validation is applied
to the method.
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
directly on method parameters, or on the method (for the return value), then method
validation must be applied, and that supersedes validation at the method argument level
because method validation covers both method parameter constraints and nested constraints
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
Applications should handle both `MethodArgumentNotValidException` and
`HandlerMethodValidationException` since either may be raised depending on the controller
Applications must handle both `MethodArgumentNotValidException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
method signature. The two exceptions, however are designed to be very similar, and can be
handled with almost identical code. The main difference is that the former is for a single
object while the latter is for a list of method parameters.
@@ -1,51 +0,0 @@
[cols="2,3,5"]
|===
|Pattern |Description |Example
| `spring`
| Literal pattern
| `+"/spring"+` matches `+"/spring"+`
| `+?+`
| Matches one character
| `+"/pages/t?st.html"+` matches `+"/pages/test.html"+` and `+"/pages/t3st.html"+`
| `+*+`
| Matches zero or more characters within a path segment
| `+"/resources/*.png"+` matches `+"/resources/file.png"+`
`+"/projects/*/versions"+` matches `+"/projects/spring/versions"+` but does not match `+"/projects/spring/boot/versions"+`.
`+"/projects/*"+` matches `+"/projects/spring"+` but does not match `+"/projects"+` as the path segment is not present.
| `+**+`
| Matches zero or more path segments
| `+"/resources/**"+` matches `+"/resources"+`, `+"/resources/file.png"+` and `+"/resources/images/file.png"+`
`+"/**/info"+` matches `+"/info"+`, `+"/spring/info"+` and `+"/spring/framework/info"+`
`+"/resources/**/file.png"+` is invalid as `+**+` is not allowed in the middle of the path.
`+"/**/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
| `+{name}+`
| Similar to `+*+`, but also captures the path segment as a variable named "name"
| `+"/projects/{project}/versions"+` matches `+"/projects/spring/versions"+` and captures `+project=spring+`
`+"/projects/{project}/versions"+` does not match `+"/projects/spring/framework/versions"+` as it captures a single path segment.
| `{name:[a-z]+}`
| Matches the regexp `"[a-z]+"` as a path variable named "name"
| `"/projects/{project:[a-z]+}/versions"` matches `"/projects/spring/versions"` but not `"/projects/spring1/versions"`
| `+{*path}+`
| Similar to `+**+`, but also captures the path segments as a variable named "path"
| `+"/resources/{*file}"+` matches `+"/resources/images/file.png"+` and captures `+file=/images/file.png+`
`+"{*path}/resources"+` matches `+"/spring/framework/resources"+` and captures `+path=/spring/framework+`
`+"/resources/{*path}/file.png"+` is invalid as `{*path}` is not allowed in the middle of the path.
`+"/{*path}/spring/**"+` is not allowed, as only a single `+**+`/`+{*path}+` instance is allowed per pattern.
|===
+33 -33
View File
@@ -7,31 +7,31 @@ javaPlatform {
}
dependencies {
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("com.fasterxml.jackson:jackson-bom:2.18.4.1"))
api(platform("io.micrometer:micrometer-bom:1.14.10"))
api(platform("io.netty:netty-bom:4.1.124.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.12"))
api(platform("io.projectreactor:reactor-bom:2024.0.9"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
api(platform("org.apache.groovy:groovy-bom:4.0.28"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.27.6"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.30"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.30"))
api(platform("org.assertj:assertj-bom:3.27.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.25"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.25"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.14.1"))
api(platform("org.mockito:mockito-bom:5.20.0"))
api(platform("org.junit:junit-bom:5.13.4"))
api(platform("org.mockito:mockito-bom:5.18.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.4")
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.2.3")
api("com.github.ben-manes.caffeine:caffeine:3.2.2")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.13.2")
api("com.google.protobuf:protobuf-java-util:4.32.1")
api("com.google.code.gson:gson:2.13.1")
api("com.google.protobuf:protobuf-java-util:4.31.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.12")
api("io.reactivex.rxjava3:rxjava:3.1.10")
api("io.smallrye.reactive:mutiny:1.10.0")
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.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.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.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.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.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.5")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
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.2.0")
api("org.easymock:easymock:5.6.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.12")
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.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
@@ -129,7 +129,7 @@ dependencies {
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.18.0")
api("org.htmlunit:htmlunit:4.14.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.13.0")
api("org.junit.support:testng-engine:1.0.5")
@@ -137,16 +137,16 @@ dependencies {
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
api("org.seleniumhq.selenium:selenium-java:4.38.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.34.0")
api("org.seleniumhq.selenium:selenium-java:4.35.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.4")
api("org.xmlunit:xmlunit-matchers:2.10.4")
api("org.yaml:snakeyaml:2.5")
api("org.xmlunit:xmlunit-assertj:2.10.3")
api("org.xmlunit:xmlunit-matchers:2.10.3")
api("org.yaml:snakeyaml:2.4")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.13
version=6.2.10
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
@@ -62,7 +62,7 @@ public interface ProxyMethodInvocation extends MethodInvocation {
MethodInvocation invocableClone(Object... arguments);
/**
* Set the arguments to be used on subsequent invocations in any advice
* Set the arguments to be used on subsequent invocations in the any advice
* in this chain.
* @param arguments the argument array
*/
@@ -41,8 +41,8 @@ abstract class CoroutinesUtils {
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
@SuppressWarnings({"unchecked", "rawtypes"})
static Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
(Continuation<Object>) continuation);
@@ -81,19 +81,13 @@ 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);
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.verify;
* Tests for {@link AsyncExecutionInterceptor}.
*
* @author Bao Ngo
* @since 7.0
*/
class AsyncExecutionInterceptorTests {
@@ -61,13 +62,11 @@ class AsyncExecutionInterceptorTests {
O run();
}
static class FutureRunner implements GenericRunner<Future<Void>> {
@Override
public Future<Void> run() {
return CompletableFuture.runAsync(() -> {});
return CompletableFuture.runAsync(() -> {
});
}
}
}
@@ -18,15 +18,6 @@ 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;
@@ -34,7 +25,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Tests for {@link ScopedProxyUtils}.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 5.1.10
*/
class ScopedProxyUtilsTests {
@@ -63,79 +53,15 @@ 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");
}
@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();
.isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName("myBean"))
.withMessage("bean name 'myBean' does not refer to the target of a scoped proxy");
}
}
@@ -19,7 +19,6 @@ 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].
@@ -49,13 +48,6 @@ 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 {
@@ -71,24 +63,4 @@ 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,
)
}
@@ -566,7 +566,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = ClassUtils.getUserClass(clazz);
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
@@ -586,11 +586,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (method.isBridge()) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(method);
if (ann != null && method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
@@ -608,7 +609,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
methodElements.add(new AutowiredMethodElement(method, required, pd));
}
});
@@ -180,6 +180,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
* {@code true} if a qualifier has been found and matched,
* {@code null} if no qualifier has been found at all
*/
@Nullable
protected Boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
boolean qualifierFound = false;
@@ -374,14 +375,6 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
return true;
}
}
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
for (Annotation annotation : methodParam.getMethodAnnotations()) {
if (isQualifier(annotation.annotationType())) {
return true;
}
}
}
return false;
}
@@ -29,7 +29,6 @@ import org.springframework.aot.generate.ValueCodeGenerator.Delegate;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.CollectionDelegate;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates.MapDelegate;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
@@ -58,7 +57,6 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
* <li>{@link LinkedHashMap}</li>
* <li>{@link BeanReference}</li>
* <li>{@link TypedStringValue}</li>
* <li>{@link AutowiredPropertyMarker}</li>
* </ul>
* When combined with {@linkplain ValueCodeGeneratorDelegates#INSTANCES the
* delegates for common value types}, this should be added first as they have
@@ -70,8 +68,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
new ManagedMapDelegate(),
new LinkedHashMapDelegate(),
new BeanReferenceDelegate(),
new TypedStringValueDelegate(),
new AutowiredPropertyMarkerDelegate()
new TypedStringValueDelegate()
);
@@ -219,20 +216,4 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
return valueCodeGenerator.generateCode(value);
}
}
/**
* {@link Delegate} for {@link AutowiredPropertyMarker} types.
*/
private static class AutowiredPropertyMarkerDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
if (value instanceof AutowiredPropertyMarker) {
return CodeBlock.of("$T.INSTANCE", AutowiredPropertyMarker.class);
}
return null;
}
}
}
@@ -294,12 +294,9 @@ 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()));
@@ -152,18 +152,6 @@ 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}.
@@ -171,7 +159,6 @@ 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;
@@ -520,7 +520,8 @@ 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
@@ -58,7 +58,6 @@ 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;
@@ -197,8 +196,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
/** Map of bean definition names with a primary marker plus corresponding type. */
private final Map<String, Class<?>> primaryBeanNamesWithType = new ConcurrentHashMap<>(16);
/** Set of bean definition names with a primary marker. */
private final Set<String> primaryBeanNames = ConcurrentHashMap.newKeySet(16);
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);
@@ -1038,7 +1037,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
super.cacheMergedBeanDefinition(mbd, beanName);
if (mbd.isPrimary()) {
this.primaryBeanNamesWithType.put(beanName, Void.class);
this.primaryBeanNames.add(beanName);
}
}
@@ -1102,11 +1101,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return null;
}
@Override
public void prepareSingletonBootstrap() {
this.mainThreadPrefix = getThreadNamePrefix();
}
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
@@ -1118,12 +1112,11 @@ 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);
if (this.mainThreadPrefix == null) {
this.mainThreadPrefix = getThreadNamePrefix();
}
this.mainThreadPrefix = getThreadNamePrefix();
try {
List<CompletableFuture<?>> futures = new ArrayList<>();
for (String beanName : beanNames) {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (!mbd.isAbstract() && mbd.isSingleton()) {
@@ -1133,20 +1126,21 @@ 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);
@@ -1319,7 +1313,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
// Cache a primary marker for the given bean.
if (beanDefinition.isPrimary()) {
this.primaryBeanNamesWithType.put(beanName, Void.class);
this.primaryBeanNames.add(beanName);
}
}
@@ -1411,7 +1405,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
destroySingleton(beanName);
// Remove a cached primary marker for the given bean.
this.primaryBeanNamesWithType.remove(beanName);
this.primaryBeanNames.remove(beanName);
// Notify all post-processors that the specified bean definition has been reset.
for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
@@ -1464,27 +1458,15 @@ 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));
this.allBeanNamesByType.remove(Object.class);
this.singletonBeanNamesByType.remove(Object.class);
}
@Override
@@ -1653,8 +1635,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
@SuppressWarnings("NullAway") // Dataflow analysis limitation
@Nullable
@SuppressWarnings("NullAway")
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
@@ -1991,8 +1973,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
for (String candidate : candidateNames) {
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
(!multiple || matchesBeanName(candidate, descriptor.getDependencyName()) ||
getAutowireCandidateResolver().hasQualifier(descriptor))) {
(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
@@ -2109,9 +2090,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
boolean candidateLocal = containsBeanDefinition(candidateBeanName);
boolean primaryLocal = containsBeanDefinition(primaryBeanName);
if (candidateLocal == primaryLocal) {
String message = "more than one 'primary' bean found among candidates: " + candidates.keySet();
logger.trace(message);
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), message);
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"more than one 'primary' bean found among candidates: " + candidates.keySet());
}
else if (candidateLocal) {
primaryBeanName = candidateBeanName;
@@ -2263,12 +2243,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
/**
* Determine whether the given dependency name matches the bean name or the aliases
* Determine whether the given candidate name matches the bean name or the aliases
* stored in this bean definition.
*/
protected boolean matchesBeanName(String beanName, @Nullable String dependencyName) {
return (dependencyName != null &&
(dependencyName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), dependencyName)));
protected boolean matchesBeanName(String beanName, @Nullable String candidateName) {
return (candidateName != null &&
(candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName)));
}
/**
@@ -2288,12 +2268,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* not matching the given bean name.
*/
private boolean hasPrimaryConflict(String beanName, Class<?> dependencyType) {
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
for (String candidate : this.primaryBeanNames) {
if (isTypeMatch(candidate, dependencyType) && !candidate.equals(beanName)) {
return true;
}
}
@@ -23,7 +23,6 @@ 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;
@@ -419,29 +418,14 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName();
boolean autoCloseable = AutoCloseable.class.isAssignableFrom(target);
boolean executorService = ExecutorService.class.isAssignableFrom(target);
boolean autoCloseable = (AutoCloseable.class.isAssignableFrom(target));
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && (autoCloseable || executorService))) {
(destroyMethodName == null && autoCloseable)) {
// Only perform destroy method inference in case of the bean
// not explicitly implementing the DisposableBean interface
destroyMethodName = null;
if (!(DisposableBean.class.isAssignableFrom(target))) {
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) {
if (autoCloseable) {
destroyMethodName = CLOSE_METHOD_NAME;
}
else {
@@ -128,48 +128,43 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
locked = (lockFlag && this.singletonLock.tryLock());
}
try {
// 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) {
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 {
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 {
if (locked) {
afterSingletonCreation(beanName);
}
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) {
@@ -472,7 +472,7 @@ abstract class AbstractPropertyAccessorTests {
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
assertThat(name).as("name is OK").isEqualTo(target.getName());
accessor.setPropertyValues(new MutablePropertyValues());
// Check it's unchanged
// Check its unchanged
assertThat(target.getAge()).as("age is OK").isEqualTo(age);
assertThat(name).as("name is OK").isEqualTo(target.getName());
}
@@ -87,7 +87,6 @@ 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;
@@ -1799,7 +1798,7 @@ class DefaultListableBeanFactoryTests {
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(TestBean.class))
.withMessageEndingWith("more than one 'primary' bean found among candidates: [bd1, bd2]");
.withMessageContaining("more than one 'primary'");
}
@Test
@@ -2123,7 +2122,7 @@ class DefaultListableBeanFactoryTests {
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(ConstructorDependency.class, 42))
.withMessageEndingWith("more than one 'primary' bean found among candidates: [bd1, bd2]");
.withMessageContaining("more than one 'primary'");
}
@Test
@@ -3224,10 +3223,6 @@ class DefaultListableBeanFactoryTests {
assertThat(lbf.getBeanNamesForType(DerivedTestBean.class)).containsExactly("bd1");
assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames);
assertThat(lbf.getBeanNamesForType(Object.class)).isSameAs(allBeanNames);
lbf.registerSingleton("bd3", new Object());
assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames);
assertThat(lbf.getBeanNamesForType(Object.class)).containsExactly(StringUtils.addStringToArray(allBeanNames, "bd3"));
}
@@ -1275,6 +1275,88 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean().get("testBean2")).isNull();
}
@Test
void fieldInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb1 = new TestBean("tb1");
TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
bf.registerSingleton("testBean2", tb2);
bf.registerAlias("testBean1", "testBean");
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
}
@Test
void methodInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndMultipleMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
.isThrownBy(() -> bf.getBean("annotatedBean"))
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
}
@Test
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
rbd2.setAutowireCandidate(false);
bf.registerBeanDefinition("testBean2", rbd2);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
TestBean tb = bf.getBean("testBean1", TestBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndNoMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isNull();
assertThat(bean.getTestBean()).isNull();
}
@Test
void constructorInjectionWithTypedMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
@@ -1327,19 +1409,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
@Test
void constructorInjectionWithPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(NamedMapConstructorInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
NamedMapConstructorInjectionBean bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
}
@Test
void constructorInjectionWithQualifiedPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapConstructorInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
@@ -1428,114 +1497,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBeanSet()).contains(tb1, tb2);
}
@Test
void fieldInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb1 = new TestBean("tb1");
TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
bf.registerSingleton("testBean2", tb2);
bf.registerAlias("testBean1", "testBean");
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
assertThat(bean.getTestBeanMap()).containsValue(tb1);
assertThat(bean.getTestBeanMap()).containsValue(tb2);
}
@Test
void methodInjectionWithMap() {
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndMultipleMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
.isThrownBy(() -> bf.getBean("annotatedBean"))
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
}
@Test
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
rbd2.setAutowireCandidate(false);
bf.registerBeanDefinition("testBean2", rbd2);
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
TestBean tb = bf.getBean("testBean1", TestBean.class);
assertThat(bean.getTestBeanMap()).hasSize(1);
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
assertThat(bean.getTestBeanMap()).containsValue(tb);
assertThat(bean.getTestBean()).isSameAs(tb);
}
@Test
void methodInjectionWithMapAndNoMatches() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isNull();
assertThat(bean.getTestBean()).isNull();
}
@Test
void methodInjectionWithPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(NamedMapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
NamedMapMethodInjectionBean bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
}
@Test
void methodInjectionWithQualifiedPlainHashMapAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapMethodInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
bf.registerBeanDefinition("myTestBeanMap", new RootBeanDefinition(HashMap.class));
QualifiedMapMethodInjectionBean bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
}
@Test
void selfReference() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class));
@@ -3297,21 +3258,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
}
public static class NamedMapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
public NamedMapConstructorInjectionBean(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
public static class QualifiedMapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap;
@@ -3411,37 +3357,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
}
public static class NamedMapMethodInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
public static class QualifiedMapMethodInjectionBean {
private Map<String, TestBean> testBeanMap;
@Autowired
@Qualifier("myTestBeanMap")
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public Map<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
@SuppressWarnings("serial")
public static class ObjectFactoryFieldInjectionBean implements Serializable {
@@ -16,7 +16,6 @@
package org.springframework.beans.factory.aot;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -35,7 +34,6 @@ import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -628,22 +626,6 @@ class BeanDefinitionMethodGeneratorTests {
-> assertThat(customPropertyValue.value()).isEqualTo("test")));
}
@Test
void generateBeanDefinitionMethodWhenHasAutowiredPropertyGeneratesMethod() {
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(CustomBean.class).addAutowiredProperty("innerBean")
.getBeanDefinition();
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) ->
assertThat(actual.getPropertyValues().get("innerBean"))
.isSameAs(AutowiredPropertyMarker.INSTANCE));
}
@Test
void generateBeanDefinitionMethodWhenHasAotContributionsAppliesContributions() {
RegisteredBean registeredBean = registerBean(
@@ -39,7 +39,6 @@ import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.ValueCodeGenerator;
import org.springframework.aot.generate.ValueCodeGeneratorDelegates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.AutowiredPropertyMarker;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -487,16 +486,4 @@ class BeanDefinitionPropertyValueCodeGeneratorDelegatesTests {
}
@Nested
class AutowiredPropertyMarkerTests {
@Test
void generateWhenAutowiredPropertyMarker() {
compile(AutowiredPropertyMarker.INSTANCE, (instance, compiler) ->
assertThat(instance).isInstanceOf(AutowiredPropertyMarker.class)
.isSameAs(AutowiredPropertyMarker.INSTANCE));
}
}
}
@@ -418,16 +418,6 @@ 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) -> {})));
@@ -474,26 +464,6 @@ 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) -> {})));
@@ -17,7 +17,6 @@
package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutorService;
import org.junit.jupiter.api.Test;
@@ -60,38 +59,13 @@ class RootBeanDefinitionTests {
}
@Test
void resolveDestroyMethodWithMatchingCandidateReplacedForCloseMethod() {
void resolveDestroyMethodWithMatchingCandidateReplacedInferredVaue() {
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);
@@ -116,25 +90,6 @@ 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 {
}
@@ -28,7 +28,6 @@ import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.FieldRetrievingFactoryBean;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.parsing.ComponentDefinition;
@@ -47,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Juergen Hoeller
* @author Mark Fisher
*/
@SuppressWarnings("rawtypes")
class UtilNamespaceHandlerTests {
private DefaultListableBeanFactory beanFactory;
@@ -55,7 +55,7 @@ class UtilNamespaceHandlerTests {
@BeforeEach
void setup() {
void setUp() {
this.beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory);
reader.setEventListener(this.listener);
@@ -109,17 +109,17 @@ class UtilNamespaceHandlerTests {
@Test
void testSimpleMap() {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
Map<?, ?> map = (Map) this.beanFactory.getBean("simpleMap");
assertThat(map.get("foo")).isEqualTo("bar");
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("simpleMap");
Map<?, ?> map2 = (Map) this.beanFactory.getBean("simpleMap");
assertThat(map).isSameAs(map2);
}
@Test
void testScopedMap() {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
Map<?, ?> map = (Map) this.beanFactory.getBean("scopedMap");
assertThat(map.get("foo")).isEqualTo("bar");
Map<?, ?> map2 = (Map<?, ?>) this.beanFactory.getBean("scopedMap");
Map<?, ?> map2 = (Map) this.beanFactory.getBean("scopedMap");
assertThat(map2.get("foo")).isEqualTo("bar");
assertThat(map).isNotSameAs(map2);
}
@@ -164,23 +164,17 @@ class UtilNamespaceHandlerTests {
}
@Test
void testMapWithRef() throws Exception {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithRef");
void testMapWithRef() {
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithRef");
assertThat(map).isInstanceOf(TreeMap.class);
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
assertThat(this.beanFactory.resolveDependency(
new DependencyDescriptor(getClass().getDeclaredField("mapWithRef"), true), null))
.isSameAs(map);
}
@Test
void testMapWithTypes() throws Exception {
Map<?, ?> map = (Map<?, ?>) this.beanFactory.getBean("mapWithTypes");
void testMapWithTypes() {
Map<?, ?> map = (Map) this.beanFactory.getBean("mapWithTypes");
assertThat(map).isInstanceOf(LinkedCaseInsensitiveMap.class);
assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean"));
assertThat(this.beanFactory.resolveDependency(
new DependencyDescriptor(getClass().getDeclaredField("mapWithTypes"), true), null))
.isSameAs(map);
}
@Test
@@ -246,11 +240,11 @@ class UtilNamespaceHandlerTests {
void testCircularCollections() {
TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionsBean");
assertThat(bean.getSomeList()).singleElement().isSameAs(bean);
assertThat(bean.getSomeSet()).singleElement().isSameAs(bean);
assertThat(bean.getSomeList()).singleElement().isEqualTo(bean);
assertThat(bean.getSomeSet()).singleElement().isEqualTo(bean);
assertThat(bean.getSomeMap()).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -261,17 +255,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isTrue();
assertThat(list).singleElement().isSameAs(bean);
assertThat(list).singleElement().isEqualTo(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
assertThat(set).singleElement().isSameAs(bean);
assertThat(set).singleElement().isEqualTo(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -282,17 +276,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
assertThat(list).singleElement().isSameAs(bean);
assertThat(list).singleElement().isEqualTo(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isTrue();
assertThat(set).singleElement().isSameAs(bean);
assertThat(set).singleElement().isEqualTo(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isFalse();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -303,17 +297,17 @@ class UtilNamespaceHandlerTests {
List<?> list = bean.getSomeList();
assertThat(Proxy.isProxyClass(list.getClass())).isFalse();
assertThat(list).singleElement().isSameAs(bean);
assertThat(list).singleElement().isEqualTo(bean);
Set<?> set = bean.getSomeSet();
assertThat(Proxy.isProxyClass(set.getClass())).isFalse();
assertThat(set).singleElement().isSameAs(bean);
assertThat(set).singleElement().isEqualTo(bean);
Map<?, ?> map = bean.getSomeMap();
assertThat(Proxy.isProxyClass(map.getClass())).isTrue();
assertThat(map).hasSize(1).allSatisfy((key, value) -> {
assertThat(key).isEqualTo("foo");
assertThat(value).isSameAs(bean);
assertThat(value).isEqualTo(bean);
});
}
@@ -378,9 +372,4 @@ class UtilNamespaceHandlerTests {
assertThat(props).as("Incorrect property value").containsEntry("foo2", "local2");
}
// For DependencyDescriptor resolution
private Map<String, TestBean> mapWithRef;
private Map<String, TestBean> mapWithTypes;
}
@@ -59,8 +59,6 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
private boolean jedi;
private String favoriteCafé;
private ITestBean spouse;
private String touchy;
@@ -211,14 +209,6 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
this.jedi = jedi;
}
public String getFavoriteCafé() {
return this.favoriteCafé;
}
public void setFavoriteCafé(String favoriteCafé) {
this.favoriteCafé = favoriteCafé;
}
@Override
public ITestBean getSpouse() {
return this.spouse;
@@ -33,14 +33,4 @@ public class DeprecatedForRemovalMemberConfiguration {
return bean.toString();
}
@SuppressWarnings("removal")
public DeprecatedForRemovalBean deprecatedReturnType() {
return new DeprecatedForRemovalBean();
}
@SuppressWarnings("removal")
DeprecatedForRemovalBean deprecatedReturnTypeProtected() {
return new DeprecatedForRemovalBean();
}
}
@@ -38,9 +38,4 @@ public class DeprecatedMemberConfiguration {
return new DeprecatedBean();
}
@SuppressWarnings("deprecation")
DeprecatedBean deprecatedReturnTypeProtected() {
return new DeprecatedBean();
}
}
@@ -37,13 +37,12 @@ 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 and the forthcoming
* support for an AOT-generated Spring components index
* @deprecated as of 6.1, in favor of the AOT engine.
*/
@Deprecated(since = "6.1", forRemoval = true)
public class CandidateComponentsIndexer implements Processor {
@@ -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
@@ -25,7 +25,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
@@ -98,10 +97,6 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip 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.
@@ -23,14 +23,14 @@ import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented for explicitly specifying how caches are resolved
* and how keys are generated for annotation-driven cache management.
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableCaching} that wish or need to specify
* explicitly how caches are resolved and how keys are generated for annotation-driven
* cache management.
*
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableCaching}.
* See @{@link EnableCaching} for general examples and context; see
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()},
* and {@link #errorHandler()} for detailed instructions.
* <p>See @{@link EnableCaching} for general examples and context; see
* {@link #cacheManager()}, {@link #cacheResolver()}, {@link #keyGenerator()}, and
* {@link #errorHandler()} for detailed instructions.
*
* <p><b>NOTE: A {@code CachingConfigurer} will get initialized early.</b>
* Do not inject common dependencies into autowired fields directly; instead, consider
@@ -27,7 +27,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -140,10 +139,6 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Skip 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.
@@ -17,18 +17,19 @@
package org.springframework.context.annotation;
/**
* Enumeration used to determine whether JDK/CGLIB proxy-based or
* Enumeration used to determine whether JDK proxy-based or
* AspectJ weaving-based advice should be applied.
*
* @author Chris Beams
* @since 3.1
* @see org.springframework.scheduling.annotation.AsyncConfigurationSelector#selectImports
* @see org.springframework.scheduling.annotation.EnableAsync#mode()
* @see org.springframework.scheduling.annotation.AsyncConfigurationSelector#selectImports
* @see org.springframework.transaction.annotation.EnableTransactionManagement#mode()
*/
public enum AdviceMode {
/**
* JDK/CGLIB proxy-based advice.
* JDK proxy-based advice.
*/
PROXY,
@@ -90,6 +90,7 @@ 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";
@@ -451,9 +452,9 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPattern);
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
@@ -536,13 +537,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter filter : this.excludeFilters) {
if (filter.match(metadataReader, getMetadataReaderFactory())) {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter filter : this.includeFilters) {
if (filter.match(metadataReader, getMetadataReaderFactory())) {
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
@@ -149,6 +149,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private static final boolean jndiPresent = ClassUtils.isPresent(
"javax.naming.InitialContext", CommonAnnotationBeanPostProcessor.class.getClassLoader());
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
@Nullable
private static final Class<? extends Annotation> jakartaResourceType;
@@ -158,8 +160,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
@Nullable
private static final Class<? extends Annotation> ejbAnnotationType;
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(3);
static {
jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
if (jakartaResourceType != null) {
@@ -424,7 +424,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = ClassUtils.getUserClass(clazz);
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
@@ -455,23 +455,24 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (method.isBridge()) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
if (ejbAnnotationType != null && method.isAnnotationPresent(ejbAnnotationType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
if (method.getParameterCount() != 1) {
throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
}
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new EjbRefElement(method, method, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
}
}
else if (jakartaResourceType != null && method.isAnnotationPresent(jakartaResourceType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -480,13 +481,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new ResourceElement(method, method, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new ResourceElement(method, bridgedMethod, pd));
}
}
}
else if (javaxResourceType != null && method.isAnnotationPresent(javaxResourceType)) {
if (method.equals(BridgeMethodResolver.getMostSpecificMethod(method, clazz))) {
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -495,8 +496,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz);
currElements.add(new LegacyResourceElement(method, method, pd));
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
}
}
}
@@ -157,17 +157,9 @@ class ConfigurationClassBeanDefinitionReader {
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
configBeanDef.setScope(scopeMetadata.getScopeName());
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);
}
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
@@ -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,9 +101,10 @@ public @interface EventListener {
/**
* The event classes that this listener handles.
* <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.
* <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.
*/
@AliasFor("value")
Class<?>[] classes() default {};
@@ -45,7 +45,9 @@ 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(".");
@@ -81,7 +83,7 @@ public class CandidateComponentsIndex {
public Set<String> getCandidateTypes(String basePackage, String stereotype) {
List<Entry> candidates = this.index.get(stereotype);
if (candidates != null) {
return candidates.stream()
return candidates.parallelStream()
.filter(t -> t.match(basePackage))
.map(t -> t.type)
.collect(Collectors.toSet());
@@ -92,7 +94,7 @@ public class CandidateComponentsIndex {
private static class Entry {
final String type;
private final String type;
private final String packageName;
@@ -38,7 +38,10 @@ 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 {
/**
@@ -936,9 +936,6 @@ 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)) {
@@ -25,18 +25,18 @@ import java.lang.annotation.Target;
/**
* Declares that a field or method parameter should be formatted as a number.
*
* <p>Supports formatting by style or custom pattern string. Can be applied to
* any JDK {@code Number} types such as {@code Double} and {@code Long}.
* <p>Supports formatting by style or custom pattern string. Can be applied
* to any JDK {@code Number} type such as {@code Double} and {@code Long}.
*
* <p>For style-based formatting, set the {@link #style} attribute to the desired
* {@link Style}. For custom formatting, set the {@link #pattern} attribute to the
* desired number pattern, such as {@code "#,###.##"}.
* <p>For style-based formatting, set the {@link #style} attribute to be the
* desired {@link Style}. For custom formatting, set the {@link #pattern}
* attribute to be the number pattern, such as {@code #, ###.##}.
*
* <p>Each attribute is mutually exclusive, so only set one attribute per
* annotation (the one most convenient for your formatting needs). When the
* {@link #pattern} attribute is specified, it takes precedence over the
* {@link #style} attribute. When no annotation attributes are specified, the
* default format applied is style-based for either number or currency,
* annotation instance (the one most convenient one for your formatting needs).
* When the {@link #pattern} attribute is specified, it takes precedence over
* the {@link #style} attribute. When no annotation attributes are specified,
* the default format applied is style-based for either number of currency,
* depending on the annotated field or method parameter type.
*
* @author Keith Donald
@@ -50,21 +50,19 @@ import java.lang.annotation.Target;
public @interface NumberFormat {
/**
* The style pattern to use to format the field or method parameter.
* The style pattern to use to format the field.
* <p>Defaults to {@link Style#DEFAULT} for general-purpose number formatting
* for most annotated types, except for money types which default to currency
* formatting.
* <p>Set this attribute when you wish to format your field or method parameter
* in accordance with a common style other than the default style.
* formatting. Set this attribute when you wish to format your field in
* accordance with a common style other than the default style.
*/
Style style() default Style.DEFAULT;
/**
* The custom pattern to use to format the field or method parameter.
* <p>Defaults to an empty String, indicating no custom pattern has been
* specified.
* <p>Set this attribute when you wish to format your field or method parameter
* in accordance with a custom number pattern not represented by a style.
* The custom pattern to use to format the field.
* <p>Defaults to empty String, indicating no custom pattern String has been specified.
* Set this attribute when you wish to format your field in accordance with a
* custom number pattern not represented by a style.
*/
String pattern() default "";
@@ -87,6 +87,7 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
private Class<? extends Annotation> asyncAnnotationType;
public AsyncAnnotationBeanPostProcessor() {
setBeforeExistingAdvisors(true);
}
@@ -22,18 +22,13 @@ import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented for customizing the {@link Executor} instance used when
* processing async method invocations or the {@link AsyncUncaughtExceptionHandler}
* instance used to process exceptions thrown from async methods with a {@code void}
* return type.
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableAsync} that wish to customize the
* {@link Executor} instance used when processing async method invocations or the
* {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from
* async method with {@code void} return type.
*
* <p>Typically implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableAsync}.
* See the @{@link EnableAsync} javadoc for usage examples.
*
* <p><b>NOTE: An {@code AsyncConfigurer} will get initialized early.</b>
* Do not inject common dependencies into autowired fields directly; instead, consider
* declaring a lazy {@link org.springframework.beans.factory.ObjectProvider} for those.
* <p>See @{@link EnableAsync} for usage examples.
*
* @author Chris Beams
* @author Stephane Nicoll
@@ -50,9 +50,7 @@ public class 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.
* Return the underlying task.
*/
public Runnable getRunnable() {
return this.runnable;
@@ -16,8 +16,6 @@
package org.springframework.validation;
import java.util.HexFormat;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -128,18 +126,8 @@ public class FieldError extends ObjectError {
// We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but
// keep including the full nullSafeToString representation for backwards compatibility.
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
"': rejected value [" + formatRejectedValue() + "]; " +
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
resolvableToString();
}
private String formatRejectedValue() {
// Special handling of byte[], to be moved into ObjectUtils in 7.0
if (this.rejectedValue instanceof byte[] bytes && bytes.length != 0) {
return "{" + HexFormat.of().formatHex(bytes) + "}";
}
return ObjectUtils.nullSafeToString(this.rejectedValue);
}
}
@@ -69,6 +69,8 @@ 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
@@ -60,6 +60,8 @@ 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
@@ -56,6 +56,7 @@ 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);
@@ -55,6 +55,8 @@ 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
@@ -60,6 +60,12 @@ 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");
@@ -138,12 +144,6 @@ 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);
}
@@ -104,7 +104,6 @@ class CachePutEvaluationTests {
assertThat(this.cache.get(anotherValue + 100).get()).as("Wrong value for @CachePut key").isEqualTo(anotherValue);
}
@Configuration
@EnableCaching
static class Config implements CachingConfigurer {
@@ -122,10 +121,8 @@ class CachePutEvaluationTests {
}
@CacheConfig("test")
public static class SimpleService {
private AtomicLong counter = new AtomicLong();
/**
@@ -147,5 +144,4 @@ class CachePutEvaluationTests {
return this.counter.getAndIncrement();
}
}
}
@@ -31,7 +31,6 @@ 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;
@@ -69,16 +68,6 @@ 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)
@@ -277,50 +266,6 @@ 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 {
@@ -61,7 +61,6 @@ 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();
@@ -74,8 +73,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();
FooServiceImpl fooService = context.getBean("fooServiceImpl", FooServiceImpl.class);
assertThat(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent")).isTrue();
assertThat(fooService.foo(123)).isEqualTo("bar");
@@ -158,7 +157,6 @@ class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(12);
ClassPathBeanDefinitionScanner scanner2 = new ClassPathBeanDefinitionScanner(context) {
@@ -184,8 +182,8 @@ class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(7);
assertThat(context.containsBean("serviceInvocationCounter")).isTrue();
assertThat(context.containsBean("fooServiceImpl")).isTrue();
assertThat(context.containsBean("stubFooDao")).isTrue();
@@ -484,14 +482,12 @@ class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
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");
@@ -513,10 +509,9 @@ class ClassPathBeanDefinitionScannerTests {
scanner.setIncludeAnnotationConfig(false);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
assertThat(beanCount).isGreaterThanOrEqualTo(7);
context.refresh();
try {
context.getBean("fooService");
}
@@ -550,10 +545,9 @@ 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));
}
@@ -409,14 +409,11 @@ class ConfigurationClassPostProcessorTests {
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
beanFactory.setAllowBeanDefinitionOverriding(false);
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> pp.postProcessBeanFactory(beanFactory))
.withMessageContainingAll(
"bar",
"SingletonBeanConfig",
TestBean.class.getName()
);
.withMessageContaining("bar")
.withMessageContaining("SingletonBeanConfig")
.withMessageContaining(TestBean.class.getName());
}
@Test // gh-25430
@@ -425,13 +422,10 @@ class ConfigurationClassPostProcessorTests {
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
beanFactory.setAllowBeanDefinitionOverriding(false);
context.register(FirstConfiguration.class, SecondConfiguration.class);
assertThatIllegalStateException().isThrownBy(context::refresh)
.withMessageContainingAll(
"alias 'taskExecutor'",
"name 'applicationTaskExecutor'",
"bean definition 'taskExecutor'"
);
.withMessageContaining("alias 'taskExecutor'")
.withMessageContaining("name 'applicationTaskExecutor'")
.withMessageContaining("bean definition 'taskExecutor'");
context.close();
}
@@ -984,7 +978,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void selfReferenceExclusionForFactoryMethodOnSameBean() {
void testSelfReferenceExclusionForFactoryMethodOnSameBean() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -998,7 +992,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void configWithDefaultMethods() {
void testConfigWithDefaultMethods() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1012,7 +1006,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void configWithDefaultMethodsUsingAsm() {
void testConfigWithDefaultMethodsUsingAsm() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1026,7 +1020,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void configWithFailingInit() { // gh-23343
void testConfigWithFailingInit() { // gh-23343
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1040,7 +1034,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void circularDependency() {
void testCircularDependency() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
@@ -1054,42 +1048,42 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void circularDependencyWithApplicationContext() {
void testCircularDependencyWithApplicationContext() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(A.class, AStrich.class))
.withMessageContaining("Circular reference");
}
@Test
void prototypeArgumentThroughBeanMethodCall() {
void testPrototypeArgumentThroughBeanMethodCall() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithPrototype.class);
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
ctx.close();
}
@Test
void singletonArgumentThroughBeanMethodCall() {
void testSingletonArgumentThroughBeanMethodCall() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithSingleton.class);
ctx.getBean(FooFactory.class).createFoo(new BarArgument());
ctx.close();
}
@Test
void nullArgumentThroughBeanMethodCall() {
void testNullArgumentThroughBeanMethodCall() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithNull.class);
ctx.getBean("aFoo");
ctx.close();
}
@Test
void injectionPointMatchForNarrowTargetReturnType() {
void testInjectionPointMatchForNarrowTargetReturnType() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(FooBarConfiguration.class);
assertThat(ctx.getBean(FooImpl.class).bar).isSameAs(ctx.getBean(BarImpl.class));
ctx.close();
}
@Test
void varargOnBeanMethod() {
void testVarargOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class, TestBean.class);
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1099,7 +1093,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void emptyVarargOnBeanMethod() {
void testEmptyVarargOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class);
VarargConfiguration bean = ctx.getBean(VarargConfiguration.class);
assertThat(bean.testBeans).isNotNull();
@@ -1108,7 +1102,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void collectionArgumentOnBeanMethod() {
void testCollectionArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class, TestBean.class);
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class));
@@ -1116,7 +1110,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void emptyCollectionArgumentOnBeanMethod() {
void testEmptyCollectionArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class);
CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class);
assertThat(bean.testBeans).isEmpty();
@@ -1124,7 +1118,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void mapArgumentOnBeanMethod() {
void testMapArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class, DummyRunnable.class);
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
assertThat(bean.testBeans).hasSize(1).containsValue(ctx.getBean(Runnable.class));
@@ -1132,7 +1126,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void emptyMapArgumentOnBeanMethod() {
void testEmptyMapArgumentOnBeanMethod() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class);
MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class);
assertThat(bean.testBeans).isEmpty();
@@ -1140,7 +1134,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void collectionInjectionFromSameConfigurationClass() {
void testCollectionInjectionFromSameConfigurationClass() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class);
CollectionInjectionConfiguration bean = ctx.getBean(CollectionInjectionConfiguration.class);
assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class));
@@ -1148,7 +1142,7 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void mapInjectionFromSameConfigurationClass() {
void testMapInjectionFromSameConfigurationClass() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapInjectionConfiguration.class);
MapInjectionConfiguration bean = ctx.getBean(MapInjectionConfiguration.class);
assertThat(bean.testBeans).containsOnly(Map.entry("testBean", ctx.getBean(Runnable.class)));
@@ -1156,20 +1150,20 @@ class ConfigurationClassPostProcessorTests {
}
@Test
void beanLookupFromSameConfigurationClass() {
void testBeanLookupFromSameConfigurationClass() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanLookupConfiguration.class);
assertThat(ctx.getBean(BeanLookupConfiguration.class).getTestBean()).isSameAs(ctx.getBean(TestBean.class));
ctx.close();
}
@Test
void nameClashBetweenConfigurationClassAndBean() {
void testNameClashBetweenConfigurationClassAndBean() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class));
.isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class).getBean("myTestBean", TestBean.class));
}
@Test
void beanDefinitionRegistryPostProcessorConfig() {
void testBeanDefinitionRegistryPostProcessorConfig() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanDefinitionRegistryPostProcessorConfig.class);
assertThat(ctx.getBean("myTestBean")).isInstanceOf(TestBean.class);
ctx.close();
@@ -16,7 +16,6 @@
package org.springframework.context.event;
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -106,9 +105,8 @@ class AnnotationDrivenEventListenerTests {
this.eventCollector.assertTotalEventsCount(1);
this.eventCollector.clear();
TestEvent otherEvent = new TestEvent(this, Integer.valueOf(1));
this.context.publishEvent(otherEvent);
this.eventCollector.assertEvent(listener, otherEvent);
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
context.getBean(ApplicationEventMulticaster.class).removeApplicationListeners(l ->
@@ -744,11 +742,6 @@ class AnnotationDrivenEventListenerTests {
public void handleString(String content) {
collectEvent(content);
}
@EventListener({Boolean.class, Integer.class})
public void handleBooleanOrInteger(Serializable content) {
collectEvent(content);
}
}
@@ -1016,8 +1009,6 @@ class AnnotationDrivenEventListenerTests {
void handleString(String payload);
void handleBooleanOrInteger(Serializable content);
void handleTimestamp(Long timestamp);
void handleRatio(Double ratio);
@@ -1040,12 +1031,6 @@ 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,12 +18,11 @@ package org.springframework.context.event.test;
/**
* @author Stephane Nicoll
* @author Juergen Hoeller
*/
@SuppressWarnings("serial")
public class TestEvent extends IdentifiableApplicationEvent {
public final Object msg;
public final String msg;
public TestEvent(Object source, String id, String msg) {
super(source, id);
@@ -35,11 +34,6 @@ 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");
}
@@ -32,6 +32,8 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*
* @author Stephane Nicoll
*/
@Deprecated
@SuppressWarnings("removal")
public class CandidateComponentsIndexLoaderTests {
@Test
@@ -30,6 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Stephane Nicoll
*/
@Deprecated
@SuppressWarnings("removal")
public class CandidateComponentsIndexTests {
@Test
@@ -503,6 +503,10 @@ class SpringValidatorAdapterTests {
private static final String ID = "id";
@Override
public void initialize(AnythingValid constraintAnnotation) {
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
List<Field> fieldsErrors = new ArrayList<>();
@@ -425,6 +425,10 @@ class ValidatorFactoryTests {
@Autowired
private Environment environment;
@Override
public void initialize(NameAddressValid constraintAnnotation) {
}
@Override
public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
if (value.expectsAutowiredValidator) {
@@ -491,6 +495,10 @@ class ValidatorFactoryTests {
public static class InnerValidator implements ConstraintValidator<InnerValid, InnerBean> {
@Override
public void initialize(InnerValid constraintAnnotation) {
}
@Override
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
@@ -534,6 +542,10 @@ class ValidatorFactoryTests {
public static class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {
@Override
public void initialize(NotXList constraintAnnotation) {
}
@Override
public boolean isValid(List<String> list, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
@@ -193,16 +193,12 @@ public class ClassReader {
final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
this.classFileBuffer = classFileBuffer;
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
// SPRING PATCH: leniently try to parse newer class files as well
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
// throw new IllegalArgumentException(
// "Unsupported class file major version " + readShort(classFileOffset + 6));
// }
// END OF PATCH
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V25) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
// Create the constant pool arrays. The constant_pool_count field is after the magic,
// minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
int constantPoolCount = readUnsignedShort(classFileOffset + 8);
@@ -290,7 +290,6 @@ public interface Opcodes {
int V23 = 0 << 16 | 67;
int V24 = 0 << 16 | 68;
int V25 = 0 << 16 | 69;
int V26 = 0 << 16 | 70;
/**
* Version flag indicating that the class is using 'preview' features.
@@ -1473,7 +1473,7 @@ final class SymbolTable {
/**
* Another entry (and so on recursively) having the same hash code (modulo the size of {@link
* SymbolTable#labelEntries}) as this one.
* SymbolTable#labelEntries}}) as this one.
*/
LabelEntry next;
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 because invokespecial
// we shouldn't change our bridge becaues invokespecial
// is the only way to make progress (otherwise we'll
// get infinite recursion). This would typically
// only happen when a bridge method is created to widen
@@ -1278,32 +1278,29 @@ public class Enhancer extends AbstractClassGenerator {
Signature bridgeTarget = (Signature) bridgeToTarget.get(method.getSignature());
if (bridgeTarget != null) {
// checkcast each argument against the target's argument types
Type[] argTypes = method.getSignature().getArgumentTypes();
Type[] targetTypes = bridgeTarget.getArgumentTypes();
for (int i = 0; i < targetTypes.length; i++) {
for (int i = 0; i < bridgeTarget.getArgumentTypes().length; i++) {
e.load_arg(i);
Type argType = argTypes[i];
Type target = targetTypes[i];
if (!target.equals(argType)) {
if (!TypeUtils.isPrimitive(target)) {
e.box(argType);
}
Type target = bridgeTarget.getArgumentTypes()[i];
if (!target.equals(method.getSignature().getArgumentTypes()[i])) {
e.checkcast(target);
}
}
e.invoke_virtual_this(bridgeTarget);
// Not necessary to cast if the target & bridge have the same return type.
Type retType = method.getSignature().getReturnType();
Type target = bridgeTarget.getReturnType();
if (!target.equals(retType)) {
if (!TypeUtils.isPrimitive(target)) {
e.unbox(retType);
}
else {
e.checkcast(retType);
}
// Not necessary to cast if the target & bridge have
// the same return type.
// (This conveniently includes void and primitive types,
// which would fail if casted. It's not possible to
// covariant from boxed to unbox (or vice versa), so no having
// to box/unbox for bridges).
// TODO: It also isn't necessary to checkcast if the return is
// assignable from the target. (This would happen if a subclass
// used covariant returns to narrow the return type within a bridge
// method.)
if (!retType.equals(bridgeTarget.getReturnType())) {
e.checkcast(retType);
}
}
else {
@@ -100,15 +100,15 @@ public final class BridgeMethodResolver {
}
private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
Class<?> userClass = targetClass;
if (!bridgeMethod.isBridge()) {
if (!bridgeMethod.isBridge() && localBridge) {
userClass = ClassUtils.getUserClass(targetClass);
if (userClass == targetClass) {
return bridgeMethod;
}
}
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass));
Method bridgedMethod = cache.get(cacheKey);
if (bridgedMethod == null) {
@@ -118,7 +118,7 @@ public final class BridgeMethodResolver {
ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) {
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod, targetClass));
searchCandidates(candidateMethods, bridgeMethod));
}
if (bridgedMethod == null) {
// A bridge method was passed in but we couldn't find the bridged method.
@@ -149,16 +149,14 @@ public final class BridgeMethodResolver {
* @return the bridged method, or {@code null} if none found
*/
@Nullable
private static Method searchCandidates(
List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
if (candidateMethods.isEmpty()) {
return null;
}
Method previousMethod = null;
boolean sameSig = true;
for (Method candidateMethod : candidateMethods) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, targetClass)) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
return candidateMethod;
}
else if (previousMethod != null) {
@@ -174,12 +172,12 @@ public final class BridgeMethodResolver {
* Determines whether the bridge {@link Method} is the bridge for the
* supplied candidate {@link Method}.
*/
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> targetClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, targetClass)) {
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
return true;
}
Method method = findGenericDeclaration(bridgeMethod);
return (method != null && isResolvedTypeMatch(method, candidateMethod, targetClass));
return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
}
/**
@@ -188,25 +186,14 @@ public final class BridgeMethodResolver {
* are equal after resolving all types against the declaringType, otherwise
* returns {@code false}.
*/
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> targetClass) {
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
Type[] genericParameters = genericMethod.getGenericParameterTypes();
if (genericParameters.length != candidateMethod.getParameterCount()) {
return false;
}
Class<?> clazz = targetClass;
while (clazz != null && clazz != Object.class && clazz != genericMethod.getDeclaringClass()) {
if (checkResolvedTypeMatch(genericMethod, candidateMethod, clazz)) {
return true;
}
clazz = clazz.getSuperclass();
}
return false;
}
private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> clazz) {
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
for (int i = 0; i < candidateParameters.length; i++) {
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz);
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
Class<?> candidateParameter = candidateParameters[i];
if (candidateParameter.isArray()) {
// An array type: compare the component type.
@@ -286,9 +273,7 @@ public final class BridgeMethodResolver {
* introduced in Java 6 to fix <a href="https://bugs.openjdk.org/browse/JDK-6342411">
* JDK-6342411</a>.
* @return whether signatures match as described
* @deprecated as of 6.2.13: not necessary anymore due to {@link #getMostSpecificMethod}
*/
@Deprecated(since = "6.2.13", forRemoval = true)
public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
if (bridgeMethod == bridgedMethod) {
// Same method: for common purposes, return true to proceed as if it was a visibility bridge.
@@ -18,7 +18,6 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -39,7 +38,6 @@ import org.springframework.util.StringUtils;
* interface-declared parameter annotations from the concrete target method.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 6.1
* @see #getMethodAnnotation(Class)
* @see #getMethodParameters()
@@ -183,7 +181,7 @@ public class AnnotatedMethod {
clazz = null;
}
if (clazz != null) {
for (Method candidate : clazz.getDeclaredMethods()) {
for (Method candidate : clazz.getMethods()) {
if (isOverrideFor(candidate)) {
parameterAnnotations.add(candidate.getParameterAnnotations());
}
@@ -196,9 +194,8 @@ public class AnnotatedMethod {
}
private boolean isOverrideFor(Method candidate) {
if (Modifier.isPrivate(candidate.getModifiers()) ||
!candidate.getName().equals(this.method.getName()) ||
(candidate.getParameterCount() != this.method.getParameterCount())) {
if (!candidate.getName().equals(this.method.getName()) ||
candidate.getParameterCount() != this.method.getParameterCount()) {
return false;
}
Class<?>[] paramTypes = this.method.getParameterTypes();
@@ -207,7 +204,7 @@ public class AnnotatedMethod {
}
for (int i = 0; i < paramTypes.length; i++) {
if (paramTypes[i] !=
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).toClass()) {
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
return false;
}
}
@@ -372,14 +372,14 @@ abstract class AnnotationsScanner {
private static boolean hasSameGenericTypeParameters(
Method rootMethod, Method candidateMethod, Class<?>[] rootParameterTypes) {
Class<?> rootDeclaringClass = rootMethod.getDeclaringClass();
Class<?> sourceDeclaringClass = rootMethod.getDeclaringClass();
Class<?> candidateDeclaringClass = candidateMethod.getDeclaringClass();
if (!candidateDeclaringClass.isAssignableFrom(rootDeclaringClass)) {
if (!candidateDeclaringClass.isAssignableFrom(sourceDeclaringClass)) {
return false;
}
for (int i = 0; i < rootParameterTypes.length; i++) {
Class<?> resolvedParameterType = ResolvableType.forMethodParameter(
candidateMethod, i, rootDeclaringClass).toClass();
candidateMethod, i, sourceDeclaringClass).resolve();
if (rootParameterTypes[i] != resolvedParameterType) {
return false;
}
@@ -175,18 +175,14 @@ public abstract class AbstractCharSequenceDecoder<T extends CharSequence> extend
public final T decode(DataBuffer dataBuffer, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
try {
Charset charset = getCharset(mimeType);
T value = decodeInternal(dataBuffer, charset);
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(value, !traceOn);
return Hints.getLogPrefix(hints) + "Decoded " + formatted;
});
return value;
}
finally {
DataBufferUtils.release(dataBuffer);
}
Charset charset = getCharset(mimeType);
T value = decodeInternal(dataBuffer, charset);
DataBufferUtils.release(dataBuffer);
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(value, !traceOn);
return Hints.getLogPrefix(hints) + "Decoded " + formatted;
});
return value;
}
private Charset getCharset(@Nullable MimeType mimeType) {
@@ -50,7 +50,7 @@ public abstract class Hints {
/**
* Create a map with a single hint via {@link Collections#singletonMap}.
* Create a map wit a single hint via {@link Collections#singletonMap}.
* @param hintName the hint name
* @param value the hint value
* @return the created map
@@ -117,10 +117,8 @@ public interface Resource extends InputStreamSource {
/**
* Return a File handle for this resource.
* <p>Note: This only works for files in the default file system.
* @throws UnsupportedOperationException if the resource is a file but cannot be
* exposed as a {@code java.io.File}; an alternative to {@code FileNotFoundException}
* @throws java.io.FileNotFoundException if the resource cannot be resolved as a file
* @throws java.io.FileNotFoundException if the resource cannot be resolved as
* absolute file path, i.e. if the resource is not available in a file system
* @throws IOException in case of general resolution/reading failures
* @see #getInputStream()
*/
@@ -36,9 +36,8 @@ import org.springframework.util.Assert;
* <p>{@code DataBuffer}s has a separate {@linkplain #readPosition() read} and
* {@linkplain #writePosition() write} position, as opposed to {@code ByteBuffer}'s
* single {@linkplain ByteBuffer#position() position}. As such, the {@code DataBuffer}
* does not require a {@linkplain ByteBuffer#flip() flip} to read after writing.
* In general, the following invariant holds for the read and write positions,
* and the capacity:
* does not require a {@linkplain ByteBuffer#flip() flip} to read after writing. In general,
* the following invariant holds for the read and write positions, and the capacity:
*
* <blockquote>
* {@code 0} {@code <=}
@@ -47,13 +46,12 @@ import org.springframework.util.Assert;
* <i>capacity</i>
* </blockquote>
*
* <p>The {@linkplain #capacity() capacity} of a {@code DataBuffer} is expanded on
* demand, similar to {@code StringBuilder}.
* <p>The {@linkplain #capacity() capacity} of a {@code DataBuffer} is expanded on demand,
* similar to {@code StringBuilder}.
*
* <p>The main purpose of the {@code DataBuffer} abstraction is to provide a
* convenient wrapper around {@link ByteBuffer} which is similar to Netty's
* {@link io.netty.buffer.ByteBuf} but can also be used on non-Netty platforms
* (i.e. Servlet containers).
* <p>The main purpose of the {@code DataBuffer} abstraction is to provide a convenient wrapper
* around {@link ByteBuffer} which is similar to Netty's {@link io.netty.buffer.ByteBuf} but
* can also be used on non-Netty platforms (i.e. Servlet containers).
*
* @author Arjen Poutsma
* @author Brian Clozel
@@ -115,8 +113,8 @@ public interface DataBuffer {
* the current capacity, it will be expanded.
* @param capacity the new capacity
* @return this buffer
* @deprecated as of 6.0, in favor of {@link #ensureWritable(int)}
* which has different semantics
* @deprecated as of 6.0, in favor of {@link #ensureWritable(int)}, which
* has different semantics
*/
@Deprecated(since = "6.0")
DataBuffer capacity(int capacity);
@@ -188,28 +186,6 @@ public interface DataBuffer {
*/
byte getByte(int index);
/**
* Process a range of bytes from the current buffer using a
* {@link ByteProcessor}.
* @param index the index at which the processing will start
* @param length the maximum number of bytes to be processed
* @param processor the processor that consumes bytes
* @return the position that was reached when processing was stopped,
* or {@code -1} if the entire byte range was processed.
* @throws IndexOutOfBoundsException when {@code index} is out of bounds
* @since 6.2.12
*/
default int forEachByte(int index, int length, ByteProcessor processor) {
Assert.isTrue(length >= 0, "Length must be >= 0");
for (int position = index; position < index + length; position++) {
byte b = getByte(position);
if(!processor.process(b)) {
return position;
}
}
return -1;
}
/**
* Read a single byte from the current reading position from this data buffer.
* @return the byte at this buffer's current reading position
@@ -242,16 +218,16 @@ public interface DataBuffer {
DataBuffer write(byte b);
/**
* Write the given source into this buffer, starting at the current writing
* position of this buffer.
* Write the given source into this buffer, starting at the current writing position
* of this buffer.
* @param source the bytes to be written into this buffer
* @return this buffer
*/
DataBuffer write(byte[] source);
/**
* Write at most {@code length} bytes of the given source into this buffer,
* starting at the current writing position of this buffer.
* Write at most {@code length} bytes of the given source into this buffer, starting
* at the current writing position of this buffer.
* @param source the bytes to be written into this buffer
* @param offset the index within {@code source} to start writing from
* @param length the maximum number of bytes to be written from {@code source}
@@ -260,8 +236,8 @@ public interface DataBuffer {
DataBuffer write(byte[] source, int offset, int length);
/**
* Write one or more {@code DataBuffer}s to this buffer, starting at the
* current writing position. It is the responsibility of the caller to
* Write one or more {@code DataBuffer}s to this buffer, starting at the current
* writing position. It is the responsibility of the caller to
* {@linkplain DataBufferUtils#release(DataBuffer) release} the given data buffers.
* @param buffers the byte buffers to write into this buffer
* @return this buffer
@@ -269,8 +245,8 @@ public interface DataBuffer {
DataBuffer write(DataBuffer... buffers);
/**
* Write one or more {@link ByteBuffer} to this buffer, starting at the
* current writing position.
* Write one or more {@link ByteBuffer} to this buffer, starting at the current
* writing position.
* @param buffers the byte buffers to write into this buffer
* @return this buffer
*/
@@ -323,37 +299,36 @@ public interface DataBuffer {
}
/**
* Create a new {@code DataBuffer} whose contents is a shared subsequence
* of this data buffer's content. Data between this data buffer and the
* returned buffer is shared; though changes in the returned buffer's
* position will not be reflected in the reading nor writing position
* of this data buffer.
* Create a new {@code DataBuffer} whose contents is a shared subsequence of this
* data buffer's content. Data between this data buffer and the returned buffer is
* shared; though changes in the returned buffer's position will not be reflected
* in the reading nor writing position of this data buffer.
* <p><strong>Note</strong> that this method will <strong>not</strong> call
* {@link DataBufferUtils#retain(DataBuffer)} on the resulting slice:
* the reference count will not be increased.
* {@link DataBufferUtils#retain(DataBuffer)} on the resulting slice: the reference
* count will not be increased.
* @param index the index at which to start the slice
* @param length the length of the slice
* @return the specified slice of this data buffer
* @deprecated as of 6.0, in favor of {@link #split(int)}
* which has different semantics
* @deprecated as of 6.0, in favor of {@link #split(int)}, which
* has different semantics
*/
@Deprecated(since = "6.0")
DataBuffer slice(int index, int length);
/**
* Create a new {@code DataBuffer} whose contents is a shared, retained subsequence
* of this data buffer's content. Data between this data buffer and the returned
* buffer is shared; though changes in the returned buffer's position will not be
* reflected in the reading nor writing position of this data buffer.
* Create a new {@code DataBuffer} whose contents is a shared, retained subsequence of this
* data buffer's content. Data between this data buffer and the returned buffer is
* shared; though changes in the returned buffer's position will not be reflected
* in the reading nor writing position of this data buffer.
* <p><strong>Note</strong> that unlike {@link #slice(int, int)}, this method
* <strong>will</strong> call {@link DataBufferUtils#retain(DataBuffer)}
* (or equivalent) on the resulting slice.
* <strong>will</strong> call {@link DataBufferUtils#retain(DataBuffer)} (or equivalent) on the
* resulting slice.
* @param index the index at which to start the slice
* @param length the length of the slice
* @return the specified, retained slice of this data buffer
* @since 5.2
* @deprecated as of 6.0, in favor of {@link #split(int)}
* which has different semantics
* @deprecated as of 6.0, in favor of {@link #split(int)}, which
* has different semantics
*/
@Deprecated(since = "6.0")
default DataBuffer retainedSlice(int index, int length) {
@@ -362,10 +337,12 @@ public interface DataBuffer {
/**
* Splits this data buffer into two at the given index.
*
* <p>Data that precedes the {@code index} will be returned in a new buffer,
* while this buffer will contain data that follows after {@code index}.
* Memory between the two buffers is shared, but independent and cannot
* overlap (unlike {@link #slice(int, int) slice}).
*
* <p>The {@linkplain #readPosition() read} and
* {@linkplain #writePosition() write} position of the returned buffer are
* truncated to fit within the buffers {@linkplain #capacity() capacity} if
@@ -385,22 +362,22 @@ public interface DataBuffer {
* will not be reflected in the reading nor writing position of this data buffer.
* @return this data buffer as a byte buffer
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(ByteBuffer)},
* {@link #readableByteBuffers()} or {@link #writableByteBuffers()}
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
*/
@Deprecated(since = "6.0")
ByteBuffer asByteBuffer();
/**
* Expose a subsequence of this buffer's bytes as a {@link ByteBuffer}. Data
* between this {@code DataBuffer} and the returned {@code ByteBuffer} is shared;
* though changes in the returned buffer's {@linkplain ByteBuffer#position() position}
* Expose a subsequence of this buffer's bytes as a {@link ByteBuffer}. Data between
* this {@code DataBuffer} and the returned {@code ByteBuffer} is shared; though
* changes in the returned buffer's {@linkplain ByteBuffer#position() position}
* will not be reflected in the reading nor writing position of this data buffer.
* @param index the index at which to start the byte buffer
* @param length the length of the returned byte buffer
* @return this data buffer as a byte buffer
* @since 5.0.1
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(int, ByteBuffer, int, int)},
* {@link #readableByteBuffers()} or {@link #writableByteBuffers()}
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
*/
@Deprecated(since = "6.0")
ByteBuffer asByteBuffer(int index, int length);
@@ -554,22 +531,4 @@ public interface DataBuffer {
void close();
}
/**
* Process a range of bytes one by one.
* @since 6.2.12
*/
@FunctionalInterface
interface ByteProcessor {
/**
* Process the given {@code byte} and indicate whether processing
* should continue further.
* @param b a byte from the {@link DataBuffer}
* @return {@code true} if processing should continue,
* or {@code false} if processing should stop at this element
*/
boolean process(byte b);
}
}
@@ -31,19 +31,8 @@ package org.springframework.core.io.buffer;
public class DataBufferLimitException extends IllegalStateException {
/**
* Create an instance with the given message.
*/
public DataBufferLimitException(String message) {
super(message);
}
/**
* Create an instance with a message and a cause, e.g. {@link OutOfMemoryError}.
* @since 6.2.12
*/
public DataBufferLimitException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -16,6 +16,7 @@
package org.springframework.core.io.buffer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -222,17 +223,17 @@ public abstract class DataBufferUtils {
try {
if (resource.isFile()) {
Path filePath = resource.getFile().toPath();
File file = resource.getFile();
return readAsynchronousFileChannel(
() -> AsynchronousFileChannel.open(filePath, StandardOpenOption.READ),
() -> AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ),
position, bufferFactory, bufferSize);
}
}
catch (IOException | UnsupportedOperationException ignore) {
catch (IOException ignore) {
// fallback to resource.readableChannel(), below
}
Flux<DataBuffer> result = readByteChannel(resource::readableChannel, bufferFactory, bufferSize);
return (position == 0 ? result : skipUntilByteCount(result, position));
return position == 0 ? result : skipUntilByteCount(result, position);
}
@@ -832,9 +833,13 @@ public abstract class DataBufferUtils {
@Override
public int match(DataBuffer dataBuffer) {
int start = dataBuffer.readPosition();
int end = dataBuffer.writePosition();
return dataBuffer.forEachByte(start, end - start, b -> !this.match(b));
for (int pos = dataBuffer.readPosition(); pos < dataBuffer.writePosition(); pos++) {
byte b = dataBuffer.getByte(pos);
if (match(b)) {
return pos;
}
}
return -1;
}
@Override
@@ -877,13 +882,14 @@ public abstract class DataBufferUtils {
@Override
public int match(DataBuffer dataBuffer) {
int start = dataBuffer.readPosition();
int end = dataBuffer.writePosition();
int matchPosition = dataBuffer.forEachByte(start, end - start, b -> !this.match(b));
if (matchPosition != -1) {
reset();
for (int pos = dataBuffer.readPosition(); pos < dataBuffer.writePosition(); pos++) {
byte b = dataBuffer.getByte(pos);
if (match(b)) {
reset();
return pos;
}
}
return matchPosition;
return -1;
}
@Override
@@ -55,6 +55,7 @@ public final class JettyDataBuffer implements PooledDataBuffer {
this.bufferFactory = bufferFactory;
this.delegate = delegate;
this.chunk = chunk;
this.chunk.retain();
}
JettyDataBuffer(JettyDataBufferFactory bufferFactory, DefaultDataBuffer delegate) {

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