Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel 9e8cea3ef8 Release v7.0.8 2026-06-08 19:20:41 +02:00
336 changed files with 2411 additions and 9535 deletions
@@ -2,7 +2,7 @@ name: Build and Deploy Snapshot
on:
push:
branches:
- main
- 7.0.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
@@ -27,7 +27,7 @@ jobs:
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
build-name: 'spring-framework-7.1.x'
build-name: 'spring-framework-7.0.x'
folder: 'deployment-repository'
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-snapshot-local'
+2 -2
View File
@@ -2,8 +2,8 @@ name: Release Milestone
on:
push:
tags:
- v7.1.0-M[1-9]
- v7.1.0-RC[1-9]
- v7.0.0-M[1-9]
- v7.0.0-RC[1-9]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
+1 -1
View File
@@ -2,7 +2,7 @@ name: Release
on:
push:
tags:
- v7.1.[0-9]+
- v7.0.[0-9]+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
+1 -1
View File
@@ -1,4 +1,4 @@
# Contributing to the Spring Framework
# Contributing to the Spring Framework
First off, thank you for taking the time to contribute! :+1: :tada:
+1 -1
View File
@@ -78,7 +78,7 @@ configure([rootProject] + javaProjects) { project ->
"https://projectreactor.io/docs/core/release/api/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
"https://docs.junit.org/6.1.0/api/",
"https://docs.junit.org/6.0.3/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
"https://jspecify.dev/docs/api/"
+1 -1
View File
@@ -20,7 +20,7 @@ ext {
dependencies {
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.2.0"
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.1.0"
implementation "com.tngtech.archunit:archunit:1.4.1"
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
+1 -1
View File
@@ -31,7 +31,7 @@ asciidoc:
spring-org: 'spring-projects'
spring-github-org: "https://github.com/{spring-org}"
spring-framework-github: "https://github.com/{spring-org}/spring-framework"
spring-framework-code: '{spring-framework-github}/tree/main'
spring-framework-code: '{spring-framework-github}/tree/7.0.x'
spring-framework-issues: '{spring-framework-github}/issues'
spring-framework-wiki: '{spring-framework-github}/wiki'
# Docs
@@ -541,6 +541,7 @@ following kinds of expressions cannot be compiled.
* Expressions relying on the conversion service
* Expressions using custom resolvers
* Expressions using overloaded operators
* Expressions using `Optional` with the null-safe or Elvis operator
* Expressions using array construction syntax
* Expressions using selection or projection
* Expressions using bean references
@@ -402,27 +402,6 @@ To serialize only a subset of the object properties, you can specify a {baeldung
.toBodilessEntity();
----
==== URL encoded Forms
URL encoded forms, using the `"application/x-www-form-urlencoded"` media type, are useful for sending String key/values over the wire.
This is supported by the `FormHttpMessageConverter`, if the application uses a `MultiValueMap<String, String>` as source instance
or a target type.
For example:
[source,java,indent=0,subs="verbatim"]
----
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("project", "Spring Framework");
form.add("module", "spring-web");
ResponseEntity<Void> response = this.restClient.post()
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(form)
.retrieve()
.toBodilessEntity();
----
==== Multipart
To send multipart data, you need to provide a `MultiValueMap<String, Object>` whose values may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for part content with headers.
@@ -440,70 +419,18 @@ For example:
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
ResponseEntity<Void> response = this.restClient.post()
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(parts)
.retrieve()
.toBodilessEntity();
// send using RestClient.post or RestTemplate.postForEntity
----
In most cases, you do not have to specify the `Content-Type` for each part.
The content type is determined automatically based on the `HttpMessageConverter` chosen to serialize it or, in the case of a `Resource`, based on the file extension.
If necessary, you can explicitly provide the `MediaType` with an `HttpEntity` wrapper.
The `Content-Type` is set to `multipart/form-data` by the `MultipartHttpMessageConverter`.
As seen in the previous section, `MultiValueMap` types can also be used for URL encoded forms.
It is preferable to explicitly set the media type in the `Content-Type` or `Accept` HTTP request headers to ensure that the expected
message converter is used.
`RestClient` can also receive multipart responses.
To decode a multipart response body, use a `ParameterizedTypeReference<MultiValueMap<String, Part>>`.
The decoded map contains `Part` instances where `FormFieldPart` represents form field values
and `FilePart` represents file parts with a `filename()` and a `transferTo()` method.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim"]
----
MultiValueMap<String, Part> result = this.restClient.get()
.uri("https://example.com/upload")
.accept(MediaType.MULTIPART_FORM_DATA)
.retrieve()
.body(new ParameterizedTypeReference<>() {});
Part field = result.getFirst("fieldPart");
if (field instanceof FormFieldPart formField) {
String fieldValue = formField.value();
}
Part file = result.getFirst("filePart");
if (file instanceof FilePart filePart) {
filePart.transferTo(Path.of("/tmp/" + filePart.filename()));
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
----
val result = this.restClient.get()
.uri("https://example.com/upload")
.accept(MediaType.MULTIPART_FORM_DATA)
.retrieve()
.body(object : ParameterizedTypeReference<MultiValueMap<String, Part>>() {})
val field = result?.getFirst("fieldPart")
if (field is FormFieldPart) {
val fieldValue = field.value()
}
val file = result?.getFirst("filePart")
if (file is FilePart) {
file.transferTo(Path.of("/tmp/" + file.filename()))
}
----
======
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set to `multipart/form-data` by the `FormHttpMessageConverter`.
If the `MultiValueMap` has `String` values, the `Content-Type` defaults to `application/x-www-form-urlencoded`.
If necessary the `Content-Type` may also be set explicitly.
[[rest-request-factories]]
=== Client Request Factories
@@ -12,20 +12,18 @@ The annotations can be applied in the following ways.
* On a non-static field in a test class or any of its superclasses.
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
* On a parameter in the constructor for a test class.
* At the type level on a test class or any superclass or implemented interface in the
type hierarchy above the test class.
* At the type level on an enclosing class for a `@Nested` test class or on any class or
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
class.
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field or constructor parameter,
the bean to mock or spy is inferred from the type of the annotated field or parameter. If
multiple candidates exist in the `ApplicationContext`, a `@Qualifier` annotation can be
declared on the field or parameter to help disambiguate. In the absence of a `@Qualifier`
annotation, the name of the annotated field or parameter will be used as a _fallback
qualifier_. Alternatively, you can explicitly specify a bean name to mock or spy by
setting the `value` or `name` attribute in the annotation.
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
is inferred from the type of the annotated field. If multiple candidates exist in the
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation
@@ -203,82 +201,6 @@ Kotlin::
<1> Replace the bean named `service` with a Mockito mock.
======
The following example shows how to use `@MockitoBean` on a constructor parameter for a
by-type lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoBean CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
the constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoBean val customService: CustomService) { // <1>
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
the constructor.
======
The following example shows how to use `@MockitoBean` on a constructor parameter for a
by-name lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoBean("service") CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Replace the bean named `service` with a Mockito mock and inject it into the
constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoBean("service") val customService: CustomService) { // <1>
// tests...
}
----
<1> Replace the bean named `service` with a Mockito mock and inject it into the
constructor.
======
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
[tabs]
@@ -453,80 +375,6 @@ Kotlin::
<1> Wrap the bean named `service` with a Mockito spy.
======
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
a by-type lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoSpyBean CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoSpyBean val customService: CustomService) { // <1>
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
constructor.
======
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
a by-name lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoSpyBean("service") CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoSpyBean("service") val customService: CustomService) { // <1>
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
======
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
[tabs]
@@ -142,9 +142,6 @@ provides two alternative ways to verify the response:
1. xref:resttestclient-workflow[Built-in Assertions] extend the request workflow with a chain of expectations
2. xref:resttestclient-assertj[AssertJ Integration] to verify the response via `assertThat()` statements
TIP: See the xref:integration/rest-clients.adoc#rest-message-conversion[HTTP Message Conversion]
section for examples on how to prepare a request with any content, including form data and multipart data.
[[resttestclient.workflow]]
@@ -216,16 +213,6 @@ To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
include-code::./JsonTests[tag=jsonPath,indent=0]
[[resttestclient.multipart]]
==== Multipart Content
When testing endpoints that return multipart responses, you can decode the body to a
`MultiValueMap<String, Part>` and assert individual parts using the `FormFieldPart`
and `FilePart` subtypes.
include-code::./MultipartTests[tag=multipart,indent=0]
[[resttestclient.assertj]]
=== AssertJ Integration
@@ -2,9 +2,8 @@
= Bean Overriding in Tests
Bean overriding in tests refers to the ability to override specific beans in the
`ApplicationContext` for a test class, by annotating the test class, one or more
non-static fields in the test class, or one or more parameters in the constructor for the
test class.
`ApplicationContext` for a test class, by annotating the test class or one or more
non-static fields in the test class.
NOTE: This feature is intended as a less risky alternative to the practice of registering
a bean via `@Bean` with the `DefaultListableBeanFactory`
@@ -43,9 +42,9 @@ The `spring-test` module registers implementations of the latter two
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
properties file].
The bean overriding infrastructure searches for annotations on test classes, non-static
fields in test classes, and parameters in test class constructors that are meta-annotated
with `@BeanOverride`, and instantiates the corresponding `BeanOverrideProcessor` which is
The bean overriding infrastructure searches for annotations on test classes as well as
annotations on non-static fields in test classes that are meta-annotated with
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
responsible for creating an appropriate `BeanOverrideHandler`.
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
@@ -179,10 +179,6 @@ If a specific parameter in a constructor for a JUnit Jupiter test class is of ty
`ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with
`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific
parameter with the corresponding bean or value from the test's `ApplicationContext`.
Similarly, if a specific parameter is annotated with `@MockitoBean` or `@MockitoSpyBean`,
Spring will inject a Mockito mock or spy, respectively &mdash; see
xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
for details.
Spring can also be configured to autowire all arguments for a test class constructor if
the constructor is considered to be _autowirable_. A constructor is considered to be
@@ -580,8 +580,8 @@ Kotlin::
[[webtestclient-stream]]
==== Streaming Responses
To test potentially infinite streams such as `"text/event-stream"`,
`"application/jsonl"` or `"application/x-ndjson"`, start by verifying the response status and headers, and then
To test potentially infinite streams such as `"text/event-stream"` or
`"application/x-ndjson"`, start by verifying the response status and headers, and then
obtain a `FluxExchangeResult`:
[tabs]
@@ -485,8 +485,8 @@ The `JacksonJsonEncoder` works as follows:
* For a multi-value publisher with `application/json`, by default collect the values with
`Flux#collectToList()` and then serialize the resulting collection.
* For a multi-value publisher with a streaming media type such as
`application/jsonl`, `application/x-ndjson` or `application/stream+x-jackson-smile`,
encode, write, and flush each value individually using a
`application/x-ndjson` or `application/stream+x-jackson-smile`, encode, write, and
flush each value individually using a
https://en.wikipedia.org/wiki/JSON_streaming[line-delimited JSON] format. Other
streaming media types may be registered with the encoder.
* For SSE the `JacksonJsonEncoder` is invoked per event and the output is flushed to ensure
@@ -598,7 +598,7 @@ To configure all three in WebFlux, you'll need to supply a pre-configured instan
[.small]#xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[See equivalent in the Servlet stack]#
When streaming to the HTTP response (for example, `text/event-stream`,
`application/jsonl`, `application/x-ndjson`), it is important to send data periodically, in order to
`application/x-ndjson`), it is important to send data periodically, in order to
reliably detect a disconnected client sooner rather than later. Such a send could be a
comment-only, empty SSE event or any other "no-op" data that would effectively serve as
a heartbeat.
@@ -23,17 +23,13 @@ For all converters, a default media type is used, but you can override it by set
By default, this converter supports all text media types(`text/{asterisk}`) and writes with a `Content-Type` of `text/plain`.
| `FormHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write URL encoded forms.
| An `HttpMessageConverter` implementation that can read and write form data from the HTTP request and response.
By default, this converter reads and writes the `application/x-www-form-urlencoded` media type.
Form data is read from and written into a `MultiValueMap<String, String>`.
`Map<String, String>` is also supported, but multiple values under the same key will be ignored.
| `MultipartHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write multipart messages.
`MultiValueMap<String, Object>` can be written to multipart messages, converting each part independently using
the configured message converters. Multipart messages can be read into `MultiValueMap<String, Part>`, each value
being a `Part` or one of its subtypes (`FormFieldPart` and `FilePart`).
By default, `multipart/form-data` is supported. Additional multipart subtypes can be supported for writing form data.
The converter can also write (but not read) multipart data read from a `MultiValueMap<String, Object>`.
By default, `multipart/form-data` is supported.
Additional multipart subtypes can be supported for writing form data.
Consult the javadoc for `FormHttpMessageConverter` for further details.
| `ByteArrayHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write byte arrays from the HTTP request and response.
@@ -423,8 +423,8 @@ 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).
* A multi-value stream with a streaming media type (such as `application/jsonl`,
`application/x-ndjson` or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
* 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).
Applications can also return `Flux<ServerSentEvent>` or `Observable<ServerSentEvent>`.
* A multi-value stream with any other media type (such as `application/json`) is adapted
@@ -1,54 +0,0 @@
/*
* Copyright 2025-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.testing.resttestclient.multipart;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.converter.multipart.FilePart;
import org.springframework.http.converter.multipart.FormFieldPart;
import org.springframework.http.converter.multipart.Part;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
public class MultipartTests {
RestTestClient client;
@Test
void multipart() {
// tag::multipart[]
client.get().uri("/upload")
.accept(MediaType.MULTIPART_FORM_DATA)
.exchange()
.expectStatus().isOk()
.expectBody(new ParameterizedTypeReference<MultiValueMap<String, Part>>() {})
.value(result -> {
Part field = result.getFirst("fieldPart");
assertThat(field).isInstanceOfSatisfying(FormFieldPart.class,
formField -> assertThat(formField.value()).isEqualTo("fieldValue"));
Part file = result.getFirst("filePart");
assertThat(file).isInstanceOfSatisfying(FilePart.class,
filePart -> assertThat(filePart.filename()).isEqualTo("logo.png"));
});
// end::multipart[]
}
}
+6 -6
View File
@@ -7,7 +7,7 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.21.2"))
api(platform("com.fasterxml.jackson:jackson-bom:2.20.2"))
api(platform("io.micrometer:micrometer-bom:1.16.6"))
api(platform("io.netty:netty-bom:4.2.15.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.6"))
@@ -18,14 +18,14 @@ dependencies {
api(platform("org.eclipse.jetty:jetty-bom:12.1.9"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.9"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.11.0"))
api(platform("org.junit:junit-bom:6.1.0"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0"))
api(platform("org.junit:junit-bom:6.0.3"))
api(platform("org.mockito:mockito-bom:5.23.0"))
api(platform("tools.jackson:jackson-bom:3.1.1"))
api(platform("tools.jackson:jackson-bom:3.0.4"))
constraints {
api("com.fasterxml:aalto-xml:1.3.4")
api("com.fasterxml.woodstox:woodstox-core:7.1.1")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.2.3")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
@@ -120,7 +120,7 @@ dependencies {
api("org.glassfish:jakarta.el:4.0.2")
api("org.graalvm.sdk:graal-sdk:22.3.1")
api("org.hamcrest:hamcrest:3.0")
api("org.hibernate.orm:hibernate-core:7.4.0.Final")
api("org.hibernate.orm:hibernate-core:7.2.17.Final")
api("org.hibernate.validator:hibernate-validator:9.1.0.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.21.0")
+2 -2
View File
@@ -1,10 +1,10 @@
version=7.1.0-SNAPSHOT
version=7.0.8
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
org.gradle.parallel=true
kotlinVersion=2.3.20
kotlinVersion=2.2.21
byteBuddyVersion=1.17.6
kotlin.jvm.target.validation.mode=ignore
@@ -76,6 +76,8 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
*/
private static final Log logger = LogFactory.getLog(AbstractNestablePropertyAccessor.class);
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
@Nullable Object wrappedObject;
private String nestedPath = "";
@@ -154,6 +156,21 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
/**
* Specify a limit for array and collection auto-growing.
* <p>Default is unlimited on a plain accessor.
*/
public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
/**
* Return the limit for array and collection auto-growing.
*/
public int getAutoGrowCollectionLimit() {
return this.autoGrowCollectionLimit;
}
/**
* Switch the target object, replacing the cached introspection results only
* if the class of the new object is different to that of the replaced object.
@@ -281,7 +298,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
componentType, ph.nested(tokens.keys.length));
int length = Array.getLength(propValue);
if (arrayIndex >= length && arrayIndex < getAutoGrowCollectionLimit()) {
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
System.arraycopy(propValue, 0, newArray, 0, length);
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
@@ -307,7 +324,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
requiredType.getResolvableType().resolve(), requiredType);
int size = list.size();
if (index >= size && index < getAutoGrowCollectionLimit()) {
if (index >= size && index < this.autoGrowCollectionLimit) {
for (int i = size; i < index; i++) {
try {
list.add(null);
@@ -744,7 +761,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
return array;
}
int length = Array.getLength(array);
if (index >= length && index < getAutoGrowCollectionLimit()) {
if (index >= length && index < this.autoGrowCollectionLimit) {
Class<?> componentType = array.getClass().componentType();
Object newArray = Array.newInstance(componentType, index + 1);
System.arraycopy(array, 0, newArray, 0, length);
@@ -768,7 +785,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
return;
}
int size = collection.size();
if (index >= size && index < getAutoGrowCollectionLimit()) {
if (index >= size && index < this.autoGrowCollectionLimit) {
Class<?> elementType = ph.getResolvableType().getNested(nestingLevel).asCollection().resolveGeneric();
if (elementType != null) {
for (int i = collection.size(); i < index + 1; i++) {
@@ -40,8 +40,6 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
private boolean autoGrowNestedPaths = false;
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
boolean suppressNotWritablePropertyException = false;
@@ -65,16 +63,6 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
return this.autoGrowNestedPaths;
}
@Override
public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
@Override
public int getAutoGrowCollectionLimit() {
return this.autoGrowCollectionLimit;
}
@Override
public void setPropertyValue(PropertyValue pv) throws BeansException {
@@ -74,17 +74,4 @@ public interface ConfigurablePropertyAccessor extends PropertyAccessor, Property
*/
boolean isAutoGrowNestedPaths();
/**
* Specify a limit for array and collection auto-growing.
* <p>Default is unlimited on a plain accessor.
* @since 7.1
*/
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
/**
* Return the limit for array and collection auto-growing.
* @since 7.1
*/
int getAutoGrowCollectionLimit();
}
@@ -100,7 +100,6 @@ import org.springframework.core.ResolvableType;
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @author Yanming Zhou
* @since 13 April 2001
* @see BeanNameAware#setBeanName
* @see BeanClassLoaderAware#setBeanClassLoader
@@ -176,29 +175,6 @@ public interface BeanFactory {
*/
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Behaves the same as {@link #getBean(String)}, but provides a measure of type
* safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the
* required type. This means that ClassCastException can't be thrown on casting
* the result correctly, as can happen with {@link #getBean(String)}.
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to retrieve
* @param typeReference the reference to obtain type the bean must match
* @return an instance of the bean.
* Note that the return value will never be {@code null}. In case of a stub for
* {@code null} from a factory method having been resolved for the requested bean, a
* {@code BeanNotOfRequiredTypeException} against the NullBean stub will be raised.
* Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
* @throws NoSuchBeanDefinitionException if there is no such bean definition
* @throws BeanNotOfRequiredTypeException if the bean is not of the required type
* @throws BeansException if the bean could not be created
* @since 7.1
* @see #getBean(String, Class)
*/
<T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException;
/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
@@ -16,17 +16,14 @@
package org.springframework.beans.factory;
import java.lang.reflect.Type;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.util.ClassUtils;
/**
* Thrown when a bean doesn't match the expected type.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Yanming Zhou
*/
@SuppressWarnings("serial")
public class BeanNotOfRequiredTypeException extends BeansException {
@@ -35,7 +32,7 @@ public class BeanNotOfRequiredTypeException extends BeansException {
private final String beanName;
/** The required type. */
private final Type genericRequiredType;
private final Class<?> requiredType;
/** The offending type. */
private final Class<?> actualType;
@@ -49,22 +46,10 @@ public class BeanNotOfRequiredTypeException extends BeansException {
* the expected type
*/
public BeanNotOfRequiredTypeException(String beanName, Class<?> requiredType, Class<?> actualType) {
this(beanName, (Type) requiredType, actualType);
}
/**
* Create a new BeanNotOfRequiredTypeException.
* @param beanName the name of the bean requested
* @param requiredType the required type
* @param actualType the actual type returned, which did not match
* the expected type
* @since 7.1
*/
public BeanNotOfRequiredTypeException(String beanName, Type requiredType, Class<?> actualType) {
super("Bean named '" + beanName + "' is expected to be of type '" + requiredType.getTypeName() +
"' but was actually of type '" + actualType.getTypeName() + "'");
super("Bean named '" + beanName + "' is expected to be of type '" + ClassUtils.getQualifiedName(requiredType) +
"' but was actually of type '" + ClassUtils.getQualifiedName(actualType) + "'");
this.beanName = beanName;
this.genericRequiredType = requiredType;
this.requiredType = requiredType;
this.actualType = actualType;
}
@@ -80,15 +65,7 @@ public class BeanNotOfRequiredTypeException extends BeansException {
* Return the expected type for the bean.
*/
public Class<?> getRequiredType() {
return (this.genericRequiredType instanceof Class<?> clazz ? clazz : ResolvableType.forType(this.genericRequiredType).toClass());
}
/**
* Return the expected generic type for the bean.
* @since 7.1
*/
public Type getGenericRequiredType() {
return this.genericRequiredType;
return this.requiredType;
}
/**
@@ -19,9 +19,21 @@ package org.springframework.beans.factory;
import org.springframework.core.env.Environment;
/**
* Contract for registering beans programmatically. Implementations use the
* {@link BeanRegistry} and {@link Environment} to register beans:
* Contract for registering beans programmatically, typically imported with an
* {@link org.springframework.context.annotation.Import @Import} annotation on
* a {@link org.springframework.context.annotation.Configuration @Configuration}
* class.
* <pre class="code">
* &#064;Configuration
* &#064;Import(MyBeanRegistrar.class)
* class MyConfiguration {
* }</pre>
* Can also be applied to an application context via
* {@link org.springframework.context.support.GenericApplicationContext#register(BeanRegistrar...)}.
*
*
* <p>Bean registrar implementations use {@link BeanRegistry} and {@link Environment}
* APIs to register beans programmatically in a concise and flexible way.
* <pre class="code">
* class MyBeanRegistrar implements BeanRegistrar {
*
@@ -40,55 +52,9 @@ import org.springframework.core.env.Environment;
* }
* }</pre>
*
* <p>{@code BeanRegistrar} implementations are not Spring components: they must have
* a no-arg constructor and cannot rely on dependency injection or any other
* component-model feature. They can be used in two distinct ways depending on the
* application context setup.
*
* <h3>With the {@code @Configuration} model</h3>
*
* <p>A {@code BeanRegistrar} must be imported via
* {@link org.springframework.context.annotation.Import @Import} on a
* {@link org.springframework.context.annotation.Configuration @Configuration} class:
*
* <pre class="code">
* &#064;Configuration
* &#064;Import(MyBeanRegistrar.class)
* class MyConfiguration {
* }</pre>
*
* <p>This is the only mechanism that triggers bean registration in the annotation-based
* configuration model. Annotating an implementation with {@code @Configuration} or
* {@code @Component}, or returning an instance from a {@code @Bean} method, registers
* it as a bean but does <strong>not</strong> invoke its
* {@link #register(BeanRegistry, Environment) register} method.
*
* <p>When imported, the registrar is invoked in the order it is encountered during
* configuration class processing. It can therefore check for and build on beans that
* have already been defined, but has no visibility into beans that will be registered
* by classes processed later.
*
* <h3>Programmatic usage</h3>
*
* <p>A {@code BeanRegistrar} can also be applied directly to a
* {@link org.springframework.context.support.GenericApplicationContext}:
*
* <pre class="code">
* GenericApplicationContext context = new GenericApplicationContext();
* context.register(new MyBeanRegistrar());
* context.registerBean("myBean", MyBean.class);
* context.refresh();</pre>
*
* <p>This mode is primarily intended for fully programmatic application context setups.
* Registrars applied this way are invoked before any {@code @Configuration} class is
* processed. They can therefore observe beans registered programmatically (e.g., via
* one of the {@code GenericApplicationContext#registerBean} methods), but will
* <strong>not</strong> see any beans defined in {@code @Configuration} classes also
* registered with the context.
*
* <p>A {@code BeanRegistrar} implementing {@link org.springframework.context.annotation.ImportAware}
* can optionally introspect import metadata when used in an import scenario; otherwise
* the {@code setImportMetadata} method is not called.
* can optionally introspect import metadata when used in an import scenario, otherwise the
* {@code setImportMetadata} method is simply not being called.
*
* <p>In Kotlin, it is recommended to use {@code BeanRegistrarDsl} instead of
* implementing {@code BeanRegistrar}.
@@ -33,7 +33,6 @@ import org.springframework.core.env.Environment;
* programmatic bean registration capabilities.
*
* @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 7.0
*/
public interface BeanRegistry {
@@ -141,28 +140,6 @@ public interface BeanRegistry {
*/
<T> void registerBean(String name, ParameterizedTypeReference<T> beanType, Consumer<Spec<T>> customizer);
/**
* Determine whether a bean of the given name is already registered.
* @param name the name of the bean
* @since 7.1
*/
boolean containsBean(String name);
/**
* Determine whether a bean of the given type is already registered.
* @param beanType the type of the bean
* @since 7.1
*/
boolean containsBean(Class<?> beanType);
/**
* Determine whether a bean of the given generics-containing type is
* already registered.
* @param beanType the generics-containing type of the bean
* @since 7.1
*/
<T> boolean containsBean(ParameterizedTypeReference<T> beanType);
/**
* Specification for customizing a bean.
@@ -89,31 +89,6 @@ public final class ParameterResolutionDelegate {
AnnotatedElementUtils.hasAnnotation(annotatedParameter, Value.class));
}
/**
* Resolve the dependency for the supplied {@link Parameter} from the
* supplied {@link AutowireCapableBeanFactory}.
* <p>See {@link #resolveDependency(Parameter, int, String, Class, AutowireCapableBeanFactory)}
* for details.
* @param parameter the parameter whose dependency should be resolved (must not be
* {@code null})
* @param parameterIndex the index of the parameter in the constructor or method
* that declares the parameter
* @param containingClass the concrete class that contains the parameter; this may
* differ from the class that declares the parameter in that it may be a subclass
* thereof, potentially substituting type variables (must not be {@code null})
* @param beanFactory the {@code AutowireCapableBeanFactory} from which to resolve
* the dependency (must not be {@code null})
* @return the resolved object, or {@code null} if none found
* @throws BeansException if dependency resolution failed
* @see #resolveDependency(Parameter, int, String, Class, AutowireCapableBeanFactory)
*/
public static @Nullable Object resolveDependency(
Parameter parameter, int parameterIndex, Class<?> containingClass, AutowireCapableBeanFactory beanFactory)
throws BeansException {
return resolveDependency(parameter, parameterIndex, null, containingClass, beanFactory);
}
/**
* Resolve the dependency for the supplied {@link Parameter} from the
* supplied {@link AutowireCapableBeanFactory}.
@@ -126,13 +101,11 @@ public final class ParameterResolutionDelegate {
* with {@link Autowired @Autowired} with the {@link Autowired#required required}
* flag set to {@code false}.
* <p>If an explicit <em>qualifier</em> is not declared, the name of the parameter
* (or a supplied custom name) will be used as the qualifier for resolving ambiguities.
* will be used as the qualifier for resolving ambiguities.
* @param parameter the parameter whose dependency should be resolved (must not be
* {@code null})
* @param parameterIndex the index of the parameter in the constructor or method
* that declares the parameter
* @param parameterName a custom name for the parameter; or {@code null} to use
* the default parameter name discovery logic
* @param containingClass the concrete class that contains the parameter; this may
* differ from the class that declares the parameter in that it may be a subclass
* thereof, potentially substituting type variables (must not be {@code null})
@@ -140,14 +113,13 @@ public final class ParameterResolutionDelegate {
* the dependency (must not be {@code null})
* @return the resolved object, or {@code null} if none found
* @throws BeansException if dependency resolution failed
* @since 7.1
* @see #isAutowirable
* @see Autowired#required
* @see SynthesizingMethodParameter#forExecutable(Executable, int)
* @see AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor, String)
*/
public static @Nullable Object resolveDependency(Parameter parameter, int parameterIndex,
@Nullable String parameterName, Class<?> containingClass, AutowireCapableBeanFactory beanFactory)
public static @Nullable Object resolveDependency(
Parameter parameter, int parameterIndex, Class<?> containingClass, AutowireCapableBeanFactory beanFactory)
throws BeansException {
Assert.notNull(parameter, "Parameter must not be null");
@@ -160,7 +132,7 @@ public final class ParameterResolutionDelegate {
MethodParameter methodParameter = SynthesizingMethodParameter.forExecutable(
parameter.getDeclaringExecutable(), parameterIndex);
DependencyDescriptor descriptor = new NamedParameterDependencyDescriptor(methodParameter, required, parameterName);
DependencyDescriptor descriptor = new DependencyDescriptor(methodParameter, required);
descriptor.setContainingClass(containingClass);
return beanFactory.resolveDependency(descriptor, null);
}
@@ -199,26 +171,4 @@ public final class ParameterResolutionDelegate {
return parameter;
}
@SuppressWarnings("serial")
private static class NamedParameterDependencyDescriptor extends DependencyDescriptor {
private final @Nullable String parameterName;
NamedParameterDependencyDescriptor(MethodParameter methodParameter, boolean required, @Nullable String parameterName) {
super(methodParameter, required);
this.parameterName = parameterName;
}
@Override
public @Nullable String getDependencyName() {
return (this.parameterName != null ? this.parameterName : super.getDependencyName());
}
@Override
public boolean usesStandardBeanLookup() {
return true;
}
}
}
@@ -45,7 +45,7 @@ public interface AutowiredArguments {
Object value = getObject(index);
if (!ClassUtils.isAssignableValue(requiredType, value)) {
throw new IllegalArgumentException("Argument type mismatch: expected '" +
requiredType.getTypeName() + "' for value [" + value + "]");
ClassUtils.getQualifiedName(requiredType) + "' for value [" + value + "]");
}
return (T) value;
}
@@ -26,7 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
@@ -195,31 +194,30 @@ public abstract class YamlProcessor {
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
AtomicInteger count = new AtomicInteger();
int count = 0;
try {
if (logger.isDebugEnabled()) {
logger.debug("Loading from YAML: " + resource);
}
resource.consumeContent(inputStream -> {
Reader reader = new UnicodeReader(inputStream);
try (Reader reader = new UnicodeReader(resource.getInputStream())) {
for (Object object : yaml.loadAll(reader)) {
if (object != null && process(asMap(object), callback)) {
count.incrementAndGet();
count++;
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " document" + (count.get() > 1 ? "s" : "") +
logger.debug("Loaded " + count + " document" + (count > 1 ? "s" : "") +
" from YAML resource: " + resource);
}
});
}
}
catch (IOException ex) {
handleProcessError(resource, ex);
}
return (count.get() > 0);
return (count > 0);
}
private void handleProcessError(Resource resource, IOException ex) {
@@ -17,7 +17,6 @@
package org.springframework.beans.factory.support;
import java.beans.PropertyEditor;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -67,7 +66,6 @@ import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.core.DecoratingClassLoader;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.log.LogMessage;
@@ -203,17 +201,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
return doGetBean(name, requiredType, null, false);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
Object bean = getBean(name);
Type requiredType = typeReference.getType();
if (!ResolvableType.forType(requiredType).isInstance(bean)) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return (T) bean;
}
@Override
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
return doGetBean(name, null, args, false);
@@ -426,7 +413,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
catch (TypeMismatchException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Failed to convert bean '" + name + "' to required type '" +
requiredType.getTypeName() + "'", ex);
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
@@ -26,7 +26,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.beans.factory.ListableBeanFactory;
@@ -177,22 +176,6 @@ public class BeanRegistryAdapter implements BeanRegistry {
this.beanRegistry.registerBeanDefinition(name, beanDefinition);
}
@Override
public boolean containsBean(String name) {
return this.beanFactory.containsBean(name);
}
@Override
public boolean containsBean(Class<?> beanType) {
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanType).length > 0;
}
@Override
public <T> boolean containsBean(ParameterizedTypeReference<T> beanType) {
ResolvableType resolvableType = ResolvableType.forType(beanType);
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, resolvableType).length > 0;
}
/**
* {@link RootBeanDefinition} subclass for {@code #registerBean} based
@@ -17,6 +17,7 @@
package org.springframework.beans.factory.support;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashMap;
@@ -255,14 +256,14 @@ public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader
Properties props = new Properties();
try {
encodedResource.getResource().consumeContent(is -> {
try (InputStream is = encodedResource.getResource().getInputStream()) {
if (encodedResource.getEncoding() != null) {
getPropertiesPersister().load(props, new InputStreamReader(is, encodedResource.getEncoding()));
}
else {
getPropertiesPersister().load(props, is);
}
});
}
int count = registerBeanDefinitions(props, prefix, encodedResource.getResource().getDescription());
if (logger.isDebugEnabled()) {
@@ -17,7 +17,6 @@
package org.springframework.beans.factory.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -65,7 +64,6 @@ import org.springframework.util.StringUtils;
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @author Yanming Zhou
* @since 06.01.2003
* @see DefaultListableBeanFactory
*/
@@ -151,17 +149,6 @@ public class StaticListableBeanFactory implements ListableBeanFactory {
return (T) bean;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
Object bean = getBean(name);
Type requiredType = typeReference.getType();
if (!ResolvableType.forType(requiredType).isInstance(bean)) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return (T) bean;
}
@Override
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
if (!ObjectUtils.isEmpty(args)) {
@@ -21,7 +21,6 @@ import java.io.InputStream;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.parsers.ParserConfigurationException;
@@ -338,16 +337,12 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
AtomicInteger count = new AtomicInteger();
encodedResource.getResource().consumeContent(inputStream -> {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
count.addAndGet(doLoadBeanDefinitions(inputSource, encodedResource.getResource()));
});
return count.get();
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
@@ -84,8 +84,8 @@ public class ClassArrayEditor extends PropertyEditorSupport {
return "";
}
StringJoiner sj = new StringJoiner(",");
for (Class<?> clazz : classes) {
sj.add(clazz.getTypeName());
for (Class<?> klass : classes) {
sj.add(ClassUtils.getQualifiedName(klass));
}
return sj.toString();
}
@@ -72,7 +72,12 @@ public class ClassEditor extends PropertyEditorSupport {
@Override
public String getAsText() {
Class<?> clazz = (Class<?>) getValue();
return (clazz != null ? clazz.getTypeName() : "");
if (clazz != null) {
return ClassUtils.getQualifiedName(clazz);
}
else {
return "";
}
}
}
@@ -24,7 +24,6 @@ import org.springframework.core.ResolvableType
* This extension is not subject to type erasure and retains actual generic type arguments.
*
* @author Sebastien Deleuze
* @author Yanming Zhou
* @since 5.0
*/
inline fun <reified T : Any> BeanFactory.getBean(): T =
@@ -32,14 +31,14 @@ inline fun <reified T : Any> BeanFactory.getBean(): T =
/**
* Extension for [BeanFactory.getBean] providing a `getBean<Foo>("foo")` variant.
* This extension is not subject to type erasure and retains actual generic type arguments.
* Like the original Java method, this extension is subject to type erasure.
*
* @see BeanFactory.getBean(String, Class<T>)
* @author Sebastien Deleuze
* @since 5.0
*/
inline fun <reified T : Any> BeanFactory.getBean(name: String): T =
getBean(name, (object : ParameterizedTypeReference<T>() {}))
getBean(name, T::class.java)
/**
* Extension for [BeanFactory.getBean] providing a `getBean<Foo>(arg1, arg2)` variant.
@@ -18,8 +18,8 @@ package org.springframework.beans.factory
import org.springframework.beans.factory.BeanRegistry.SupplierContext
import org.springframework.core.ParameterizedTypeReference
import org.springframework.core.ResolvableType
import org.springframework.core.env.Environment
import kotlin.reflect.KClass
/**
* Contract for registering programmatically beans.
@@ -364,28 +364,6 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
return registry.registerBean(object: ParameterizedTypeReference<T>() {}, customizer)
}
/**
* Determine whether a bean of the given name is already registered.
* @param name the name of the bean
* @since 7.1
*/
fun containsBean(name: String): Boolean = registry.containsBean(name)
/**
* Determine whether a bean of the given type is already registered.
* @param beanType the type of the bean
* @since 7.1
*/
fun containsBean(beanType: KClass<*>): Boolean = registry.containsBean(beanType.java)
/**
* Determine whether a bean of the given type is already registered.
* @param T the type of the bean
* @since 7.1
*/
inline fun <reified T : Any> containsBean(): Boolean =
registry.containsBean(object: ParameterizedTypeReference<T>() {})
/**
* Context available from the bean instance supplier designed to give access
@@ -79,7 +79,6 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
@@ -1683,29 +1682,6 @@ class DefaultListableBeanFactoryTests {
lbf.getBean(TestBean.class));
}
@Test
void getBeanByNameWithTypeReference() {
RootBeanDefinition bd1 = new RootBeanDefinition(StringTemplate.class);
RootBeanDefinition bd2 = new RootBeanDefinition(NumberTemplate.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
Template<String> stringTemplate = lbf.getBean("bd1", new ParameterizedTypeReference<>() {});
Template<Number> numberTemplate = lbf.getBean("bd2", new ParameterizedTypeReference<>() {});
assertThat(stringTemplate).isInstanceOf(StringTemplate.class);
assertThat(numberTemplate).isInstanceOf(NumberTemplate.class);
assertThatExceptionOfType(BeanNotOfRequiredTypeException.class)
.isThrownBy(() -> lbf.getBean("bd2", new ParameterizedTypeReference<Template<String>>() {}))
.satisfies(ex -> {
assertThat(ex.getBeanName()).isEqualTo("bd2");
assertThat(ex.getRequiredType()).isEqualTo(Template.class);
assertThat(ex.getActualType()).isEqualTo(NumberTemplate.class);
assertThat(ex.getGenericRequiredType().toString()).endsWith("Template<java.lang.String>");
});
}
@Test
void getBeanByTypeWithPrimary() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
@@ -3896,16 +3872,4 @@ class DefaultListableBeanFactoryTests {
}
}
private static class Template<T> {
}
private static class StringTemplate extends Template<String> {
}
private static class NumberTemplate extends Template<Number> {
}
}
@@ -45,9 +45,9 @@ class ParameterResolutionTests {
@Test
void isAutowirablePreconditions() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ParameterResolutionDelegate.isAutowirable(null, 0))
.withMessageContaining("Parameter must not be null");
assertThatIllegalArgumentException().isThrownBy(() ->
ParameterResolutionDelegate.isAutowirable(null, 0))
.withMessageContaining("Parameter must not be null");
}
@Test
@@ -87,30 +87,29 @@ class ParameterResolutionTests {
Parameter[] parameters = notAutowirableConstructor.getParameters();
for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) {
Parameter parameter = parameters[parameterIndex];
assertThat(ParameterResolutionDelegate.isAutowirable(parameter, parameterIndex))
.as("Parameter " + parameter + " must not be autowirable").isFalse();
assertThat(ParameterResolutionDelegate.isAutowirable(parameter, parameterIndex)).as("Parameter " + parameter + " must not be autowirable").isFalse();
}
}
@Test
void resolveDependencyPreconditionsForParameter() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(null, 0, null, mock()))
.withMessageContaining("Parameter must not be null");
.isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(null, 0, null, mock()))
.withMessageContaining("Parameter must not be null");
}
@Test
void resolveDependencyPreconditionsForContainingClass() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(getParameter(), 0, null, null))
.withMessageContaining("Containing class must not be null");
assertThatIllegalArgumentException().isThrownBy(() ->
ParameterResolutionDelegate.resolveDependency(getParameter(), 0, null, null))
.withMessageContaining("Containing class must not be null");
}
@Test
void resolveDependencyPreconditionsForBeanFactory() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(getParameter(), 0, getClass(), null))
.withMessageContaining("AutowireCapableBeanFactory must not be null");
assertThatIllegalArgumentException().isThrownBy(() ->
ParameterResolutionDelegate.resolveDependency(getParameter(), 0, getClass(), null))
.withMessageContaining("AutowireCapableBeanFactory must not be null");
}
private Parameter getParameter() throws NoSuchMethodException {
@@ -134,64 +133,9 @@ class ParameterResolutionTests {
parameter, parameterIndex, AutowirableClass.class, beanFactory);
assertThat(intermediateDependencyDescriptor.getAnnotatedElement()).isEqualTo(constructor);
assertThat(intermediateDependencyDescriptor.getMethodParameter().getParameter()).isEqualTo(parameter);
assertThat(intermediateDependencyDescriptor.usesStandardBeanLookup()).isTrue();
}
}
@Test
void resolveDependencyWithCustomParameterNamePreconditionsForParameter() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(null, 0, "customName", getClass(), mock()))
.withMessageContaining("Parameter must not be null");
}
@Test
void resolveDependencyWithCustomParameterNamePreconditionsForContainingClass() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(getParameter(), 0, "customName", null, mock()))
.withMessageContaining("Containing class must not be null");
}
@Test
void resolveDependencyWithCustomParameterNamePreconditionsForBeanFactory() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ParameterResolutionDelegate.resolveDependency(getParameter(), 0, "customName", getClass(), null))
.withMessageContaining("AutowireCapableBeanFactory must not be null");
}
@Test
void resolveDependencyWithNullCustomParameterNameFallsBackToDefaultParameterNameDiscovery() throws Exception {
Constructor<?> constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class);
AutowireCapableBeanFactory beanFactory = mock();
given(beanFactory.resolveDependency(any(), isNull())).willAnswer(invocation -> invocation.getArgument(0));
Parameter[] parameters = constructor.getParameters();
for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) {
Parameter parameter = parameters[parameterIndex];
DependencyDescriptor via4ArgMethod = (DependencyDescriptor) ParameterResolutionDelegate.resolveDependency(
parameter, parameterIndex, AutowirableClass.class, beanFactory);
DependencyDescriptor via5ArgMethod = (DependencyDescriptor) ParameterResolutionDelegate.resolveDependency(
parameter, parameterIndex, null, AutowirableClass.class, beanFactory);
assertThat(via5ArgMethod.getDependencyName()).isEqualTo(via4ArgMethod.getDependencyName());
}
}
@Test
void resolveDependencyWithCustomParameterName() throws Exception {
Constructor<?> constructor = AutowirableClass.class.getConstructor(String.class, String.class, String.class, String.class);
AutowireCapableBeanFactory beanFactory = mock();
given(beanFactory.resolveDependency(any(), isNull())).willAnswer(invocation -> invocation.getArgument(0));
Parameter parameter = constructor.getParameters()[0];
DependencyDescriptor descriptor = (DependencyDescriptor) ParameterResolutionDelegate.resolveDependency(
parameter, 0, "customBeanName", AutowirableClass.class, beanFactory);
assertThat(descriptor.getAnnotatedElement()).isEqualTo(constructor);
assertThat(descriptor.getMethodParameter().getParameter()).isEqualTo(parameter);
assertThat(descriptor.getDependencyName()).isEqualTo("customBeanName");
assertThat(descriptor.usesStandardBeanLookup()).isTrue();
}
void autowirableMethod(
@Autowired String firstParameter,
@@ -21,7 +21,6 @@ import io.mockk.mockk
import io.mockk.verify
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.core.ParameterizedTypeReference
import org.springframework.core.ResolvableType
/**
@@ -54,16 +53,7 @@ class BeanFactoryExtensionsTests {
fun `getBean with String and reified type parameters`() {
val name = "foo"
bf.getBean<Foo>(name)
verify { bf.getBean(name, ofType<ParameterizedTypeReference<Foo>>()) }
}
@Test
fun `getBean with String and reified generic type parameters`() {
val name = "foo"
val foo = listOf(Foo())
every { bf.getBean(name, ofType<ParameterizedTypeReference<List<Foo>>>()) } returns foo
assertThat(bf.getBean<List<Foo>>("foo")).isSameAs(foo)
verify { bf.getBean(name, ofType<ParameterizedTypeReference<List<Foo>>>()) }
verify { bf.getBean(name, Foo::class.java) }
}
@Test
@@ -79,7 +79,7 @@ public class SimpleMailMessage implements MailMessage, Serializable {
this.to = copyOrNull(original.getTo());
this.cc = copyOrNull(original.getCc());
this.bcc = copyOrNull(original.getBcc());
this.sentDate = copyOrNull(original.sentDate);
this.sentDate = original.getSentDate();
this.subject = original.getSubject();
this.text = original.getText();
}
@@ -147,11 +147,11 @@ public class SimpleMailMessage implements MailMessage, Serializable {
@Override
public void setSentDate(@Nullable Date sentDate) {
this.sentDate = copyOrNull(sentDate);
this.sentDate = sentDate;
}
public @Nullable Date getSentDate() {
return copyOrNull(this.sentDate);
return this.sentDate;
}
@Override
@@ -194,8 +194,8 @@ public class SimpleMailMessage implements MailMessage, Serializable {
if (getBcc() != null) {
target.setBcc(copy(getBcc()));
}
if (this.sentDate != null) {
target.setSentDate((Date) this.sentDate.clone());
if (getSentDate() != null) {
target.setSentDate(getSentDate());
}
if (getSubject() != null) {
target.setSubject(getSubject());
@@ -247,10 +247,6 @@ public class SimpleMailMessage implements MailMessage, Serializable {
return copy(state);
}
private static @Nullable Date copyOrNull(@Nullable Date date) {
return (date != null ? (Date) date.clone() : null);
}
private static String[] copy(String[] state) {
return state.clone();
}
@@ -21,16 +21,11 @@ import java.util.Date;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SimpleMailMessage}.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
* @author Rick Evans
@@ -103,64 +98,6 @@ class SimpleMailMessageTests {
assertThat(copy.getBcc()[0]).isEqualTo("us@mail.org");
}
@Test // gh-36626
void setSentDateStoresACopy() {
SimpleMailMessage message = new SimpleMailMessage();
Date sentDate = new Date(1234L);
message.setSentDate(sentDate);
sentDate.setTime(0L);
assertThat(message.getSentDate()).isEqualTo(new Date(1234L));
}
@Test // gh-36626
void getSentDateReturnsACopy() {
SimpleMailMessage message = new SimpleMailMessage();
Date sentDate = new Date(1234L);
message.setSentDate(sentDate);
Date exportedDate = message.getSentDate();
exportedDate.setTime(0L);
assertThat(message.getSentDate()).isEqualTo(new Date(1234L));
}
@Test // gh-36626
void copyConstructorCopiesSentDate() {
Date sentDate = new Date(1234L);
SimpleMailMessage original = new SimpleMailMessage();
original.setSentDate(sentDate);
SimpleMailMessage copy = new SimpleMailMessage(original);
sentDate.setTime(0L);
Date copiedDate = copy.getSentDate();
assertThat(copiedDate).isNotNull();
copiedDate.setTime(1L);
assertThat(original.getSentDate()).isEqualTo(new Date(1234L));
assertThat(copy.getSentDate()).isEqualTo(new Date(1234L));
}
@Test // gh-36626
void copyToCopiesSentDate() {
SimpleMailMessage source = new SimpleMailMessage();
source.setSentDate(new Date(1234L));
MailMessage target = mock();
source.copyTo(target);
ArgumentCaptor<Date> dateCaptor = ArgumentCaptor.forClass(Date.class);
verify(target).setSentDate(dateCaptor.capture());
Date copiedDate = dateCaptor.getValue();
assertThat(copiedDate).isNotNull();
copiedDate.setTime(0L);
assertThat(source.getSentDate()).isEqualTo(new Date(1234L));
}
/**
* Tests that two equal SimpleMailMessages have equal hash codes.
*/
@@ -291,6 +291,23 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
this.initialized = true;
}
/**
* Convenience method to return a String representation of this Method
* for use in logging. Can be overridden in subclasses to provide a
* different identifier for the given method.
* @param method the method we're interested in
* @param targetClass class the method is on
* @return log message identifying this method
* @see org.springframework.util.ClassUtils#getQualifiedMethodName
* @deprecated since 6.2.18 with no replacement, for removal in 7.1
*/
@Deprecated(since = "6.2.18", forRemoval = true)
protected String methodIdentification(Method method, Class<?> targetClass) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
return ClassUtils.getQualifiedMethodName(specificMethod);
}
protected Collection<? extends Cache> getCaches(
CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {
@@ -152,12 +152,12 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(2);
static {
JAKARTA_RESOURCE_TYPE = AnnotationUtils.loadAnnotationType("jakarta.annotation.Resource");
JAKARTA_RESOURCE_TYPE = loadAnnotationType("jakarta.annotation.Resource");
if (JAKARTA_RESOURCE_TYPE != null) {
resourceAnnotationTypes.add(JAKARTA_RESOURCE_TYPE);
}
EJB_ANNOTATION_TYPE = AnnotationUtils.loadAnnotationType("jakarta.ejb.EJB");
EJB_ANNOTATION_TYPE = loadAnnotationType("jakarta.ejb.EJB");
if (EJB_ANNOTATION_TYPE != null) {
resourceAnnotationTypes.add(EJB_ANNOTATION_TYPE);
}
@@ -191,8 +191,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
// Jakarta EE 9 set of annotations in jakarta.annotation package
addInitAnnotationType(AnnotationUtils.loadAnnotationType("jakarta.annotation.PostConstruct"));
addDestroyAnnotationType(AnnotationUtils.loadAnnotationType("jakarta.annotation.PreDestroy"));
addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct"));
addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy"));
// java.naming module present on JDK 9+?
if (JNDI_PRESENT) {
@@ -575,6 +575,18 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
@SuppressWarnings("unchecked")
private static @Nullable Class<? extends Annotation> loadAnnotationType(String name) {
try {
return (Class<? extends Annotation>)
ClassUtils.forName(name, CommonAnnotationBeanPostProcessor.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
return null;
}
}
/**
* Class representing generic injection information about an annotated field
* or setter method, supporting @Resource and related annotations.
@@ -423,14 +423,12 @@ class ConfigurationClassBeanDefinitionReader {
}
private void loadBeanDefinitionsFromBeanRegistrars(MultiValueMap<String, BeanRegistrar> registrars) {
registrars.values().forEach(registrarList -> registrarList.forEach(registrar -> {
if (!(this.registry instanceof ListableBeanFactory beanFactory)) {
throw new IllegalStateException("Cannot support bean registrars since " +
this.registry.getClass().getName() + " does not implement ListableBeanFactory");
}
registrar.register(new BeanRegistryAdapter(
this.registry, beanFactory, this.environment, registrar.getClass()), this.environment);
}));
if (!(this.registry instanceof ListableBeanFactory beanFactory)) {
throw new IllegalStateException("Cannot support bean registrars since " +
this.registry.getClass().getName() + " does not implement ListableBeanFactory");
}
registrars.values().forEach(registrarList -> registrarList.forEach(registrar -> registrar.register(new BeanRegistryAdapter(
this.registry, beanFactory, this.environment, registrar.getClass()), this.environment)));
}
@@ -132,7 +132,6 @@ import org.springframework.util.ReflectionUtils;
* @author Sam Brannen
* @author Sebastien Deleuze
* @author Brian Clozel
* @author Yanming Zhou
* @since January 21, 2001
* @see #refreshBeanFactory
* @see #getBeanFactory
@@ -1306,12 +1305,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
return getBeanFactory().getBean(name, requiredType);
}
@Override
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name, typeReference);
}
@Override
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
assertBeanFactoryActive();
@@ -44,8 +44,6 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
@@ -107,10 +105,6 @@ import org.springframework.util.Assert;
*/
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private static final String DEFERRED_REGISTRY_POST_PROCESSOR_BEAN_NAME =
GenericApplicationContext.class.getName() + ".deferredRegistryPostProcessor";
private final DefaultListableBeanFactory beanFactory;
private @Nullable ResourceLoader resourceLoader;
@@ -610,13 +604,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
*/
public void register(BeanRegistrar... registrars) {
for (BeanRegistrar registrar : registrars) {
DeferredRegistryPostProcessor pp = (DeferredRegistryPostProcessor)
this.beanFactory.getSingleton(DEFERRED_REGISTRY_POST_PROCESSOR_BEAN_NAME);
if (pp == null) {
pp = new DeferredRegistryPostProcessor();
this.beanFactory.registerSingleton(DEFERRED_REGISTRY_POST_PROCESSOR_BEAN_NAME, pp);
}
pp.addRegistrar(registrar);
new BeanRegistryAdapter(this.beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
}
}
@@ -660,31 +648,4 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
}
}
/**
* Internal post-processor for invoking DeferredBeanRegistrars at the end
* of the BeanDefinitionRegistryPostProcessor PriorityOrdered phase,
* right before a potential ConfigurationClassPostProcessor.
*/
private class DeferredRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private final List<BeanRegistrar> registrars = new ArrayList<>();
public void addRegistrar(BeanRegistrar registrar) {
this.registrars.add(registrar);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1; // within PriorityOrdered, 1 before ConfigurationClassPostProcessor
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
for (BeanRegistrar registrar : this.registrars) {
new BeanRegistryAdapter(beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
}
}
}
}
@@ -60,13 +60,9 @@ import org.springframework.util.StringUtils;
* are treated in a slightly different fashion than the "basenames" property of
* {@link ResourceBundleMessageSource}. It follows the basic ResourceBundle rule of not
* specifying file extension or language codes, but can refer to any Spring resource
* location (instead of being restricted to classpath resources).
*
* <p>With a "classpath:" prefix, resources can still be loaded from the classpath,
* but "cacheSeconds" values other than "-1" (caching forever) are not expected to
* be effective in this case. As of 7.1, a "classpath*:" prefix is accepted as well,
* loading all classpath resources of the same fully-qualified name: for example,
* "classpath*:/messages.properties" or "classpath*:META-INF/messages.properties".
* location (instead of being restricted to classpath resources). With a "classpath:"
* prefix, resources can still be loaded from the classpath, but "cacheSeconds" values
* other than "-1" (caching forever) might not work reliably in this case.
*
* <p>For a typical web application, message files could be placed in {@code WEB-INF}:
* for example, a "WEB-INF/messages" basename would find a "WEB-INF/messages.properties",
@@ -566,8 +562,8 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
*/
protected Properties loadProperties(Resource resource, String filename) throws IOException {
Properties props = newProperties();
String resourceFilename = resource.getFilename();
resource.consumeContent(inputStream -> {
try (InputStream inputStream = resource.getInputStream()) {
String resourceFilename = resource.getFilename();
if (resourceFilename != null && resourceFilename.endsWith(XML_EXTENSION)) {
if (logger.isDebugEnabled()) {
logger.debug("Loading properties [" + resource.getFilename() + "]");
@@ -598,8 +594,8 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
this.propertiesPersister.load(props, inputStream);
}
}
});
return props;
return props;
}
}
/**
@@ -16,7 +16,6 @@
package org.springframework.jndi.support;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -60,7 +59,6 @@ import org.springframework.jndi.TypeMismatchNamingException;
* in particular if BeanFactory-style type checking is required.
*
* @author Juergen Hoeller
* @author Yanming Zhou
* @since 2.5
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory
* @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
@@ -134,17 +132,6 @@ public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFac
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
Object bean = getBean(name);
Type requiredType = typeReference.getType();
if (!ResolvableType.forType(requiredType).isInstance(bean)) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return (T) bean;
}
@Override
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
if (args != null) {
@@ -273,9 +273,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
* Specify the limit for array and collection auto-growing.
* <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
* <p>Used for setter injection - and as of 7.1 also for field injection -
* via {@link #bind(PropertyValues)}; not applicable to constructor binding
* via {@link #construct}.
* <p>Used for setter injection via {@link #bind(PropertyValues)};
* not applicable to field injection, and not to constructor binding
* via {@link #construct} either.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowCollectionLimit
*/
@@ -342,7 +342,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
*/
protected AbstractPropertyBindingResult createDirectFieldBindingResult() {
DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(),
getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
getObjectName(), isAutoGrowNestedPaths());
if (this.conversionService != null) {
result.initConversion(this.conversionService);
@@ -41,8 +41,6 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult {
private final boolean autoGrowNestedPaths;
private final int autoGrowCollectionLimit;
private transient @Nullable ConfigurablePropertyAccessor directFieldAccessor;
@@ -62,24 +60,9 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult {
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
*/
public DirectFieldBindingResult(@Nullable Object target, String objectName, boolean autoGrowNestedPaths) {
this(target, objectName, autoGrowNestedPaths, Integer.MAX_VALUE);
}
/**
* Create a new {@code DirectFieldBindingResult} for the given target.
* @param target the target object to bind onto
* @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
* @param autoGrowCollectionLimit the limit for array and collection auto-growing
* @since 7.1
*/
public DirectFieldBindingResult(@Nullable Object target, String objectName,
boolean autoGrowNestedPaths, int autoGrowCollectionLimit) {
super(objectName);
this.target = target;
this.autoGrowNestedPaths = autoGrowNestedPaths;
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
@@ -99,7 +82,6 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult {
this.directFieldAccessor = createDirectFieldAccessor();
this.directFieldAccessor.setExtractOldValueForEditor(true);
this.directFieldAccessor.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
this.directFieldAccessor.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
}
return this.directFieldAccessor;
}
@@ -22,10 +22,8 @@ import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.testfixture.beans.factory.BarRegistrar;
import org.springframework.context.testfixture.beans.factory.ConditionalBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.FooRegistrar;
import org.springframework.context.testfixture.beans.factory.GenericBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.ImportAwareBeanRegistrar;
@@ -34,28 +32,17 @@ import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Foo;
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Init;
import org.springframework.context.testfixture.context.annotation.registrar.BeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.ComponentBeanRegistrar;
import org.springframework.context.testfixture.context.annotation.registrar.ComponentBeanRegistrar.IgnoredFromComponent;
import org.springframework.context.testfixture.context.annotation.registrar.ConditionalBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.ConfigurationBeanRegistrar;
import org.springframework.context.testfixture.context.annotation.registrar.ConfigurationBeanRegistrar.BeanBeanRegistrar;
import org.springframework.context.testfixture.context.annotation.registrar.ConfigurationBeanRegistrar.IgnoredFromBean;
import org.springframework.context.testfixture.context.annotation.registrar.ConfigurationBeanRegistrar.IgnoredFromConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.GenericBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.ImportAwareBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.MultipleBeanRegistrarsConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.TestBeanConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link BeanRegistrar} imported by @{@link org.springframework.context.annotation.Configuration}.
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
*/
class BeanRegistrarConfigurationTests {
@@ -72,36 +59,6 @@ class BeanRegistrarConfigurationTests {
assertThat(beanDefinition.getDescription()).isEqualTo("Custom description");
}
@Test
void beanRegistrarIgnoreBeans() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigurationBeanRegistrar.class);
assertThatNoException().isThrownBy(() -> context.getBean(ConfigurationBeanRegistrar.class));
assertThatNoException().isThrownBy(() -> context.getBean(BeanBeanRegistrar.class));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> context.getBean(IgnoredFromConfiguration.class));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> context.getBean(IgnoredFromBean.class));
}
@Test
void beanRegistrarWithClasspathScanningIgnoreBeans() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("org.springframework.context.testfixture.context.annotation.registrar");
context.refresh();
assertThatNoException().isThrownBy(() -> context.getBean(ConfigurationBeanRegistrar.class));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> context.getBean(IgnoredFromConfiguration.class));
assertThatNoException().isThrownBy(() -> context.getBean(BeanBeanRegistrar.class));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> context.getBean(IgnoredFromBean.class));
assertThatNoException().isThrownBy(() -> context.getBean(ComponentBeanRegistrar.class));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> context.getBean(IgnoredFromComponent.class));
}
@Test
void beanRegistrarWithProfile() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@@ -148,42 +105,4 @@ class BeanRegistrarConfigurationTests {
assertThat(context.getBean(BarRegistrar.Bar.class)).isNotNull();
}
@Test
void programmaticBeanRegistrarIsInvokedBeforeConfigurationClassPostProcessor() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestBeanConfiguration.class);
context.register(new ConditionalBeanRegistrar());
context.refresh();
assertThat(context.containsBean("myTestBean")).isFalse();
}
@Test
void programmaticBeanRegistrarHandlesProgrammaticRegisteredBean() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(new ConditionalBeanRegistrar());
context.registerBean("testBean", TestBean.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
@Test
void importedBeanRegistrarWithConditionNotMet() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ConditionalBeanRegistrarConfiguration.class);
context.register(TestBeanConfiguration.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isFalse();
}
@Test
void importedBeanRegistrarWithConditionMet() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestBeanConfiguration.class);
context.register(ConditionalBeanRegistrarConfiguration.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
}
@@ -46,11 +46,9 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.testfixture.beans.factory.CircularBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.ConditionalBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.ImportAwareBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.OverridingBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar;
@@ -655,8 +653,8 @@ class GenericApplicationContextTests {
void beanRegistrarWithDefinitionOverride() {
GenericApplicationContext context = new GenericApplicationContext();
context.setAllowBeanDefinitionOverriding(false);
context.register(new OverridingBeanRegistrar());
assertThatExceptionOfType(BeanDefinitionOverrideException.class).isThrownBy(context::refresh);
assertThatExceptionOfType(BeanDefinitionOverrideException.class).isThrownBy(
() -> context.register(new OverridingBeanRegistrar()));
}
@Test
@@ -667,24 +665,6 @@ class GenericApplicationContextTests {
assertThat(context.getBean(ImportAwareBeanRegistrar.ClassNameHolder.class).className()).isNull();
}
@Test
void beanRegistrarWithConditionNotMet() {
GenericApplicationContext context = new GenericApplicationContext();
context.register(new ConditionalBeanRegistrar());
context.refresh();
assertThat(context.containsBean("myTestBean")).isFalse();
}
@Test
void beanRegistrarWithConditionMet() {
GenericApplicationContext context = new GenericApplicationContext();
context.register(new ConditionalBeanRegistrar());
context.registerBean("testBean", TestBean.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
private MergedBeanDefinitionPostProcessor registerMockMergedBeanDefinitionPostProcessor(GenericApplicationContext context) {
MergedBeanDefinitionPostProcessor bpp = mock();
@@ -17,12 +17,10 @@
package org.springframework.validation;
import java.beans.PropertyEditorSupport;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.NullValueInNestedPathException;
@@ -136,25 +134,6 @@ class DataBinderFieldAccessTests {
binder.bind(pvs));
}
@Test
void directFieldAccessHonorsDefaultAutoGrowCollectionLimit() {
FieldAccessForm target = new FieldAccessForm();
DataBinder binder = new DataBinder(target);
binder.initDirectFieldAccess();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("items[255].name", "value");
binder.bind(pvs);
assertThat(target.items).hasSize(256);
assertThat(target.items.get(255).name).isEqualTo("value");
MutablePropertyValues outOfBounds = new MutablePropertyValues();
outOfBounds.add("items[256].name", "too-far");
assertThatExceptionOfType(InvalidPropertyException.class).isThrownBy(() ->
binder.bind(outOfBounds));
}
@Test
void bindingWithErrorsAndCustomEditors() {
FieldAccessBean rod = new FieldAccessBean();
@@ -197,16 +176,4 @@ class DataBinderFieldAccessTests {
assertThat(tb.getSpouse()).isNotNull();
});
}
static class FieldAccessForm {
public List<FieldAccessItem> items;
}
static class FieldAccessItem {
public String name;
}
}
@@ -84,13 +84,7 @@ class BeanRegistrarDslConfigurationTests {
assertThat(context.getBeanProvider<Bar>().singleOrNull()).isNotNull
}
@Test
fun containsBean() {
AnnotationConfigApplicationContext(ContainsBeanRegistrarKotlinConfiguration::class.java)
}
class Foo
data class Bar(val foo: Foo)
data class Baz(val message: String = "")
class Init : InitializingBean {
@@ -151,18 +145,4 @@ class BeanRegistrarDslConfigurationTests {
private class ChainedBeanRegistrar : BeanRegistrarDsl({
register(SampleBeanRegistrar())
})
@Configuration
@Import(ContainsBeanRegistrar::class)
internal class ContainsBeanRegistrarKotlinConfiguration
private class ContainsBeanRegistrar : BeanRegistrarDsl({
assertThat(containsBean("foo")).isFalse()
assertThat(containsBean(Foo::class)).isFalse()
assertThat(containsBean<Foo>()).isFalse()
registerBean<Foo>("foo")
assertThat(containsBean("foo")).isTrue()
assertThat(containsBean(Foo::class)).isTrue()
assertThat(containsBean<Foo>()).isTrue()
})
}
@@ -1,36 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.beans.factory;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.Environment;
public class ConditionalBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
if (registry.containsBean("testBean") &&
registry.containsBean(TestBean.class) &&
registry.containsBean(new ParameterizedTypeReference<Comparable<Object>>() {
})) {
registry.registerBean("myTestBean", TestBean.class);
}
}
}
@@ -1,35 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation.registrar;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class ComponentBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean(IgnoredFromComponent.class);
}
public record IgnoredFromComponent() {}
}
@@ -1,26 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation.registrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.testfixture.beans.factory.ConditionalBeanRegistrar;
@Configuration
@Import(ConditionalBeanRegistrar.class)
public class ConditionalBeanRegistrarConfiguration {
}
@@ -1,49 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation.registrar;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class ConfigurationBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean(IgnoredFromConfiguration.class);
}
@Bean
BeanBeanRegistrar beanBeanRegistrar() {
return new BeanBeanRegistrar();
}
public static class BeanBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean(IgnoredFromBean.class);
}
}
public record IgnoredFromConfiguration() {}
public record IgnoredFromBean() {}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation.registrar;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestBeanConfiguration {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
@@ -20,8 +20,6 @@ import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.util.ClassUtils;
/**
* Reference to a Java method, identified by its owner class and the method name.
*
@@ -45,7 +43,7 @@ public final class MethodReference {
}
public static MethodReference of(Class<?> klass, String methodName) {
return new MethodReference(ClassUtils.getCanonicalName(klass), methodName);
return new MethodReference(klass.getCanonicalName(), methodName);
}
/**
@@ -25,7 +25,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Record of an invocation of a method relevant to {@link org.springframework.aot.hint.RuntimeHints}.
@@ -182,7 +181,7 @@ public final class RecordedInvocation {
else {
Class<?> instanceType = (getInstance() instanceof Class<?> clazz) ? clazz : getInstance().getClass();
return "<%s> invocation of <%s> on type <%s> with arguments %s".formatted(
getHintType().hintClassName(), getMethodReference(), ClassUtils.getCanonicalName(instanceType), getArguments());
getHintType().hintClassName(), getMethodReference(), instanceType.getCanonicalName(), getArguments());
}
}
@@ -34,11 +34,9 @@ public class ClassNameReader {
private static class EarlyExitException extends RuntimeException {
}
// SPRING PATCH BEGIN
public static String getClassName(ClassReader r) {
return r.getClassName().replace('/', '.');
return getClassInfo(r)[0];
}
// SPRING PATCH END
public static String[] getClassInfo(ClassReader r) {
final List<String> array = new ArrayList<>();
@@ -160,10 +160,6 @@ public final class GenericTypeResolver {
resolvedTypeVariable = ResolvableType.forVariableBounds(typeVariable);
}
if (resolvedTypeVariable != ResolvableType.NONE) {
Type type = resolvedTypeVariable.getType();
if (type instanceof ParameterizedType) {
return resolveType(type, contextClass);
}
Class<?> resolved = resolvedTypeVariable.resolve();
if (resolved != null) {
return resolved;
@@ -22,7 +22,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
@@ -1153,6 +1152,7 @@ public class ResolvableType implements Serializable {
* @see #forClassWithGenerics(Class, ResolvableType...)
*/
public static ResolvableType forClassWithGenerics(Class<?> clazz, Class<?>... generics) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(generics, "Generics array must not be null");
ResolvableType[] resolvableGenerics = new ResolvableType[generics.length];
for (int i = 0; i < generics.length; i++) {
@@ -1281,18 +1281,6 @@ public class ResolvableType implements Serializable {
return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()).getNested(nestingLevel);
}
/**
* Return a {@code ResolvableType} for the specified {@link Parameter}.
* <p>This is a convenience factory method for scenarios where a {@code Parameter}
* descriptor is already available.
* @param parameter the source parameter
* @return a {@code ResolvableType} for the specified parameter
* @since 7.1
*/
public static ResolvableType forParameter(Parameter parameter) {
return forMethodParameter(MethodParameter.forParameter(parameter));
}
/**
* Return a {@code ResolvableType} for the specified {@link Constructor} parameter.
* @param constructor the source constructor (must not be {@code null})
@@ -1301,6 +1289,7 @@ public class ResolvableType implements Serializable {
* @see #forConstructorParameter(Constructor, int, Class)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex) {
Assert.notNull(constructor, "Constructor must not be null");
return forMethodParameter(new MethodParameter(constructor, parameterIndex));
}
@@ -1318,6 +1307,7 @@ public class ResolvableType implements Serializable {
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex,
Class<?> implementationClass) {
Assert.notNull(constructor, "Constructor must not be null");
MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex, implementationClass);
return forMethodParameter(methodParameter);
}
@@ -1329,6 +1319,7 @@ public class ResolvableType implements Serializable {
* @see #forMethodReturnType(Method, Class)
*/
public static ResolvableType forMethodReturnType(Method method) {
Assert.notNull(method, "Method must not be null");
return forMethodParameter(new MethodParameter(method, -1));
}
@@ -1342,6 +1333,7 @@ public class ResolvableType implements Serializable {
* @see #forMethodReturnType(Method)
*/
public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) {
Assert.notNull(method, "Method must not be null");
MethodParameter methodParameter = new MethodParameter(method, -1, implementationClass);
return forMethodParameter(methodParameter);
}
@@ -1355,6 +1347,7 @@ public class ResolvableType implements Serializable {
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(Method method, int parameterIndex) {
Assert.notNull(method, "Method must not be null");
return forMethodParameter(new MethodParameter(method, parameterIndex));
}
@@ -1370,6 +1363,7 @@ public class ResolvableType implements Serializable {
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(Method method, int parameterIndex, Class<?> implementationClass) {
Assert.notNull(method, "Method must not be null");
MethodParameter methodParameter = new MethodParameter(method, parameterIndex, implementationClass);
return forMethodParameter(methodParameter);
}
@@ -24,7 +24,6 @@ import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Abstract base class for {@link MergedAnnotation} implementations.
@@ -216,7 +215,7 @@ abstract class AbstractMergedAnnotation<A extends Annotation> implements MergedA
T value = getAttributeValue(attributeName, type);
if (value == null) {
throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + ClassUtils.getCanonicalName(getType()));
"' present in merged annotation " + getType().getName());
}
return value;
}
@@ -25,7 +25,6 @@ import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -127,7 +126,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, @Nullable Object
AnnotationAttributes(Class<? extends Annotation> annotationType, boolean validated) {
Assert.notNull(annotationType, "'annotationType' must not be null");
this.annotationType = annotationType;
this.displayName = ClassUtils.getCanonicalName(annotationType);
this.displayName = annotationType.getName();
this.validated = validated;
}
@@ -32,7 +32,6 @@ import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -650,7 +649,7 @@ final class AnnotationTypeMapping {
throw new AnnotationConfigurationException(String.format(
"Different @AliasFor mirror values for annotation [%s]%s; attribute '%s' " +
"and its alias '%s' are declared with values of [%s] and [%s].",
ClassUtils.getCanonicalName(getAnnotationType()), on,
getAnnotationType().getName(), on,
attributes.get(result).getName(),
attribute.getName(),
ObjectUtils.nullSafeToString(lastValue),
@@ -28,7 +28,6 @@ import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.lang.Contract;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
@@ -125,8 +124,7 @@ final class AnnotationTypeMappings {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
if (failureLogger.isEnabled()) {
failureLogger.log("Failed to introspect " + (meta ? "meta-annotation @" : "annotation @") +
ClassUtils.getCanonicalName(annotationType),
(source != null ? ClassUtils.getCanonicalName(source.getAnnotationType()) : null), ex);
annotationType.getName(), (source != null ? source.getAnnotationType() : null), ex);
}
}
}
@@ -36,7 +36,6 @@ import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet;
import org.springframework.core.annotation.MergedAnnotation.Adapt;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
@@ -178,23 +177,6 @@ public abstract class AnnotationUtils {
return true;
}
/**
* Load the specified annotation type, if available.
* @param annotationName the fully-qualified name of the annotation type
* @return the annotation type as a {@code Class}, or {@code null} if not found
* @since 7.1
*/
@SuppressWarnings("unchecked")
public static @Nullable Class<? extends Annotation> loadAnnotationType(String annotationName) {
try {
return (Class<? extends Annotation>)
ClassUtils.forName(annotationName, AnnotationUtils.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
return null;
}
}
/**
* Get a single {@link Annotation} of {@code annotationType} from the supplied
* annotation: either the given annotation itself or a direct meta-annotation
@@ -26,7 +26,6 @@ import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
@@ -144,9 +143,8 @@ final class AttributeMethods {
throw ex;
}
catch (Throwable ex) {
throw new IllegalStateException(
"Could not obtain annotation attribute value for " + get(i).getName() +
" declared on @" + ClassUtils.getCanonicalName(annotation.annotationType()), ex);
throw new IllegalStateException("Could not obtain annotation attribute value for " +
get(i).getName() + " declared on @" + getName(annotation.annotationType()), ex);
}
}
}
@@ -307,8 +305,13 @@ final class AttributeMethods {
if (attributeName == null) {
return "(none)";
}
String in = (annotationType != null ? " in annotation [" + ClassUtils.getCanonicalName(annotationType) + "]" : "");
String in = (annotationType != null ? " in annotation [" + annotationType.getName() + "]" : "");
return "attribute '" + attributeName + "'" + in;
}
private static String getName(Class<?> clazz) {
String canonicalName = clazz.getCanonicalName();
return (canonicalName != null ? canonicalName : clazz.getName());
}
}
@@ -26,7 +26,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
@@ -313,7 +312,7 @@ public abstract class RepeatableContainers {
if (returnType.componentType() != repeatable) {
throw new AnnotationConfigurationException(
"Container type [%s] must declare a 'value' attribute for an array of type [%s]"
.formatted(ClassUtils.getCanonicalName(container), ClassUtils.getCanonicalName(repeatable)));
.formatted(container.getName(), repeatable.getName()));
}
}
catch (AnnotationConfigurationException ex) {
@@ -322,7 +321,7 @@ public abstract class RepeatableContainers {
catch (Throwable ex) {
throw new AnnotationConfigurationException(
"Invalid declaration of container type [%s] for repeatable annotation [%s]"
.formatted(ClassUtils.getCanonicalName(container), ClassUtils.getCanonicalName(repeatable)), ex);
.formatted(container.getName(), repeatable.getName()), ex);
}
this.repeatable = repeatable;
this.container = container;
@@ -332,7 +331,7 @@ public abstract class RepeatableContainers {
private Class<? extends Annotation> deduceContainer(Class<? extends Annotation> repeatable) {
Repeatable annotation = repeatable.getAnnotation(Repeatable.class);
Assert.notNull(annotation, () -> "Annotation type must be a repeatable annotation: " +
"failed to resolve container type for " + ClassUtils.getCanonicalName(repeatable));
"failed to resolve container type for " + repeatable.getName());
return annotation.value();
}
@@ -136,7 +136,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
private String annotationToString() {
String string = this.string;
if (string == null) {
StringBuilder builder = new StringBuilder("@").append(ClassUtils.getCanonicalName(this.type)).append('(');
StringBuilder builder = new StringBuilder("@").append(getName(this.type)).append('(');
if (this.attributes.size() == 1 && this.attributes.get(0).getName().equals(MergedAnnotation.VALUE)) {
// Don't prepend "value=" for an annotation that only declares a "value" attribute.
builder.append(toString(getAttributeValue(this.attributes.get(0))));
@@ -208,7 +208,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
return e.name();
}
if (type == Class.class) {
return ClassUtils.getCanonicalName((Class<?>) value) + ".class";
return getName((Class<?>) value) + ".class";
}
return String.valueOf(value);
}
@@ -218,7 +218,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
return this.annotation.getValue(attributeName, type).orElseThrow(
() -> new NoSuchElementException("No value found for attribute named '" + attributeName +
"' in merged annotation " + ClassUtils.getCanonicalName(this.annotation.getType())));
"' in merged annotation " + getName(this.annotation.getType())));
});
// Clone non-empty arrays so that users cannot alter the contents of values in our cache.
@@ -272,4 +272,9 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
private static String getName(Class<?> clazz) {
String canonicalName = clazz.getCanonicalName();
return (canonicalName != null ? canonicalName : clazz.getName());
}
}
@@ -450,12 +450,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
value = clazz.getName();
}
else if (value instanceof String str && type == Class.class) {
try {
value = ClassUtils.forName(str, getClassLoader());
}
catch (ClassNotFoundException | LinkageError ex) {
throw new TypeNotPresentException(str, ex);
}
value = ClassUtils.resolveClassName(str, getClassLoader());
}
else if (value instanceof Class<?>[] classes && type == String[].class) {
String[] names = new String[classes.length];
@@ -466,14 +461,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
else if (value instanceof String[] names && type == Class[].class) {
Class<?>[] classes = new Class<?>[names.length];
ClassLoader classLoader = getClassLoader();
for (int i = 0; i < names.length; i++) {
try {
classes[i] = ClassUtils.forName(names[i], classLoader);
}
catch (ClassNotFoundException | LinkageError ex) {
throw new TypeNotPresentException(names[i], ex);
}
classes[i] = ClassUtils.resolveClassName(names[i], getClassLoader());
}
value = classes;
}
@@ -490,7 +479,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
if (!type.isInstance(value)) {
throw new IllegalArgumentException("Unable to adapt value of type " +
ClassUtils.getCanonicalName(value.getClass()) + " to " + ClassUtils.getCanonicalName(type));
value.getClass().getName() + " to " + type.getName());
}
return (T) value;
}
@@ -525,8 +514,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
if (!attributeType.isInstance(value)) {
throw new IllegalStateException("Attribute '" + attribute.getName() +
"' in annotation " + ClassUtils.getCanonicalName(getType()) + " should be compatible with " +
ClassUtils.getCanonicalName(attributeType) + " but a " + ClassUtils.getCanonicalName(value.getClass()) +
"' in annotation " + getType().getName() + " should be compatible with " +
attributeType.getName() + " but a " + value.getClass().getName() +
" value was returned");
}
return value;
@@ -583,7 +572,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
int attributeIndex = (isFiltered(attributeName) ? -1 : this.mapping.getAttributes().indexOf(attributeName));
if (attributeIndex == -1 && required) {
throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + ClassUtils.getCanonicalName(getType()));
"' present in merged annotation " + getType().getName());
}
return attributeIndex;
}
@@ -660,10 +649,9 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
catch (Exception ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
if (logger.isEnabled()) {
String type = ClassUtils.getCanonicalName(mapping.getAnnotationType());
String type = mapping.getAnnotationType().getName();
String item = (mapping.getDistance() == 0 ? "annotation " + type :
"meta-annotation " + type + " from " +
ClassUtils.getCanonicalName(mapping.getRoot().getAnnotationType()));
"meta-annotation " + type + " from " + mapping.getRoot().getAnnotationType().getName());
logger.log("Failed to introspect " + item, source, ex);
}
return null;
@@ -541,7 +541,7 @@ public class TypeDescriptor implements Serializable {
public String toString() {
StringBuilder builder = new StringBuilder();
for (Annotation ann : getAnnotations()) {
builder.append('@').append(ClassUtils.getCanonicalName(ann.annotationType())).append(' ');
builder.append('@').append(getName(ann.annotationType())).append(' ');
}
builder.append(getResolvableType());
return builder.toString();
@@ -726,6 +726,11 @@ public class TypeDescriptor implements Serializable {
return new TypeDescriptor(property).nested(nestingLevel);
}
private static String getName(Class<?> clazz) {
String canonicalName = clazz.getCanonicalName();
return (canonicalName != null ? canonicalName : clazz.getName());
}
private interface AnnotatedElementSupplier extends Supplier<AnnotatedElementAdapter>, Serializable {
}
@@ -88,10 +88,13 @@ final class ProfilesParser {
}
case "!" -> elements.add(not(parseTokens(expression, tokens, Context.NEGATE)));
case ")" -> {
Profiles merged = merge(expression, elements, operator);
if (context == Context.PARENTHESIS) {
return merge(expression, elements, operator);
return merged;
}
assertWellFormed(expression, false);
elements.clear();
elements.add(merged);
operator = null;
}
default -> {
Profiles value = equals(token);
@@ -102,7 +105,6 @@ final class ProfilesParser {
}
}
}
assertWellFormed(expression, context != Context.PARENTHESIS);
return merge(expression, elements, operator);
}
@@ -16,19 +16,10 @@
package org.springframework.core.io;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -39,7 +30,6 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.function.IOConsumer;
/**
* Default implementation of the {@link ResourceLoader} interface.
@@ -168,9 +158,6 @@ public class DefaultResourceLoader implements ResourceLoader {
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else if (location.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
return new ClassPathAllResource(location.substring(CLASSPATH_ALL_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
@@ -200,96 +187,6 @@ public class DefaultResourceLoader implements ResourceLoader {
}
/**
* A multi-content ClassPathResource handle that can expose the content
* from all matching resources in the classpath.
* @since 7.1
*/
protected static class ClassPathAllResource extends ClassPathResource {
public ClassPathAllResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
@Override
public boolean isFile() {
return false;
}
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(
getDescription() + " cannot be resolved to single URL or File - use 'classpath:' instead");
}
@Override
public long contentLength() throws IOException {
long combinedLength = 0;
ClassLoader cl = getClassLoader();
Enumeration<URL> urls = (cl != null ? cl.getResources(getPath()) : ClassLoader.getSystemResources(getPath()));
while (urls.hasMoreElements()) {
URLConnection con = urls.nextElement().openConnection();
long length = con.getContentLengthLong();
if (length < 0) {
return -1;
}
combinedLength += length;
}
return combinedLength;
}
@Override
public InputStream getInputStream() throws IOException {
List<InputStream> streams = new ArrayList<>();
ClassLoader cl = getClassLoader();
Enumeration<URL> urls = (cl != null ? cl.getResources(getPath()) : ClassLoader.getSystemResources(getPath()));
while (urls.hasMoreElements()) {
try {
streams.add(urls.nextElement().openStream());
}
catch (IOException ex) {
streams.forEach(stream -> {
try {
stream.close();
}
catch (IOException ex2) {
ex.addSuppressed(ex2);
}
});
throw ex;
}
}
return switch (streams.size()) {
case 0 -> InputStream.nullInputStream();
case 1 -> streams.get(0);
default -> new SequenceInputStream(Collections.enumeration(streams));
};
}
@Override
public void consumeContent(IOConsumer<InputStream> consumer) throws IOException {
ClassLoader cl = getClassLoader();
Enumeration<URL> urls = (cl != null ? cl.getResources(getPath()) : ClassLoader.getSystemResources(getPath()));
while (urls.hasMoreElements()) {
try (InputStream inputStream = urls.nextElement().openStream()) {
consumer.accept(inputStream);
}
}
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassPathAllResource(pathToUse, getClassLoader());
}
@Override
public String getDescription() {
return "'classpath*:' resource [" + getPath() + "]";
}
}
/**
* ClassPathResource that explicitly expresses a context-relative path
* through implementing the ContextResource interface.
@@ -30,7 +30,6 @@ import java.nio.file.Path;
import org.jspecify.annotations.Nullable;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.function.IOConsumer;
/**
* Interface for a resource descriptor that abstracts from the actual
@@ -157,26 +156,6 @@ public interface Resource extends InputStreamSource {
return Channels.newChannel(getInputStream());
}
/**
* Process the contents of this resource through the given consumer callback.
* <p>The given consumer will be invoked a single time by default - but may
* also be invoked multiple times in case of a multi-content resource handle,
* for example returned from a
* {@link ResourceLoader#getResource getResource("classpath*:...")} call.
* While {@link #getInputStream()} returns a merged sequence of content
* in such a case, this method performs one callback per file content.
* @param consumer a consumer for each InputStream
* @throws IOException in case of general resolution/reading failures
* @since 7.1
* @see #getInputStream()
* @see ResourceLoader#CLASSPATH_ALL_URL_PREFIX
*/
default void consumeContent(IOConsumer<InputStream> consumer) throws IOException {
try (InputStream inputStream = getInputStream()) {
consumer.accept(inputStream);
}
}
/**
* Return the contents of this resource as a byte array.
* @return the contents of this resource as byte array
@@ -42,47 +42,18 @@ import org.springframework.util.ResourceUtils;
*/
public interface ResourceLoader {
/**
* Pseudo URL prefix for loading from the class path: {@value}.
* <p>This retrieves the "nearest" matching resource in the classpath.
* @see ClassLoader#getResource
*/
/** Pseudo URL prefix for loading from the class path: "classpath:". */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* Pseudo URL prefix for all matching resources from the class path: {@value}.
* <p>This differs from the common {@link #CLASSPATH_URL_PREFIX "classpath:"} prefix
* in that it retrieves all matching resources for a given path. For example, to
* locate all "messages.properties" files in the root of all deployed JAR files
* you can use the location pattern {@code "classpath*:/messages.properties"}.
* <p>As of Spring Framework 6.0, the semantics for the {@code "classpath*:"}
* prefix have been expanded to include the module path as well as the class path.
* <p>As of Spring Framework 7.1, this prefix is supported for {@link #getResource}
* calls as well (exposing a multi-content resource handle), rather than just for
* {@link org.springframework.core.io.support.ResourcePatternResolver#getResources}.
* @since 7.1 (previously only declared on the
* {@link org.springframework.core.io.support.ResourcePatternResolver} sub-interface)
* @see ClassLoader#getResources
* @see Resource#consumeContent
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* Return a {@code Resource} handle for the specified resource location.
* <p>The handle should always be a reusable resource descriptor,
* allowing for multiple {@link Resource#getInputStream()} calls.
* <ul>
* <li>Must support fully qualified URLs, for example, "file:C:/test.properties".
* <li>Must support classpath pseudo-URLs, for example, "classpath:test.properties".
* (Exposing the "nearest" resource in the classpath; see {@link ClassLoader#getResource}.)
* <li>Should support classpath-all URLs, for example, "classpath*:test.properties".
* (If supported, the returned {@code Resource} needs to expose the entire content of
* all same-named resources in the classpath through {@link Resource#consumeContent};
* see {@link ClassLoader#getResources}.
* For individual access to each such matching resource in the classpath, use
* {@link org.springframework.core.io.support.ResourcePatternResolver#getResources}.)
* <li>Should support relative file paths, for example, "WEB-INF/test.properties".
* <p><ul>
* <li>Must support fully qualified URLs, for example, "file:C:/test.dat".
* <li>Must support classpath pseudo-URLs, for example, "classpath:test.dat".
* <li>Should support relative file paths, for example, "WEB-INF/test.dat".
* (This will be implementation-specific, typically provided by an
* ApplicationContext implementation.)
* </ul>
@@ -91,9 +62,8 @@ public interface ResourceLoader {
* @param location the resource location
* @return a corresponding {@code Resource} handle (never {@code null})
* @see #CLASSPATH_URL_PREFIX
* @see #CLASSPATH_ALL_URL_PREFIX
* @see Resource#exists()
* @see Resource#consumeContent
* @see Resource#getInputStream()
*/
Resource getResource(String location);
@@ -26,10 +26,8 @@ import org.jspecify.annotations.Nullable;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.function.IOConsumer;
/**
* Holder that combines a {@link Resource} descriptor with a specific encoding
@@ -127,6 +125,26 @@ public class EncodedResource implements InputStreamSource {
return (this.encoding != null || this.charset != null);
}
/**
* Open a {@code java.io.Reader} for the specified resource, using the specified
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
* (if any).
* @throws IOException if opening the Reader failed
* @see #requiresReader()
* @see #getInputStream()
*/
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
/**
* Open an {@code InputStream} for the specified resource, ignoring any specified
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}.
@@ -139,47 +157,6 @@ public class EncodedResource implements InputStreamSource {
return this.resource.getInputStream();
}
/**
* Open a {@code java.io.Reader} for the specified resource, using the specified
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
* (if any).
* @throws IOException if opening the Reader failed
* @see #requiresReader()
* @see #getInputStream()
*/
public Reader getReader() throws IOException {
return getReader(this.resource.getInputStream());
}
private Reader getReader(InputStream inputStream) throws IOException {
if (this.charset != null) {
return new InputStreamReader(inputStream, this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(inputStream, this.encoding);
}
else {
return new InputStreamReader(inputStream);
}
}
/**
* Process the contents of this resource through the given consumer callback.
* <p>The given consumer will be invoked a single time by default - but may
* also be invoked multiple times in case of a multi-content resource handle,
* for example returned from a
* {@link ResourceLoader#getResource getResource("classpath*:...")} call.
* While {@link #getReader()} returns a merged sequence of content
* in such a case, this method performs one callback per file content.
* @param consumer a consumer for each Reader
* @throws IOException in case of general resolution/reading failures
* @since 7.1
* @see Resource#consumeContent
*/
public void consumeContent(IOConsumer<Reader> consumer) throws IOException {
this.resource.consumeContent(inputStream -> consumer.accept(getReader(inputStream)));
}
/**
* Returns the contents of the specified resource as a string, using the specified
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding} (if any).
@@ -57,6 +57,18 @@ import org.springframework.core.io.ResourceLoader;
*/
public interface ResourcePatternResolver extends ResourceLoader {
/**
* Pseudo URL prefix for all matching resources from the class path: {@code "classpath*:"}.
* <p>This differs from ResourceLoader's {@code "classpath:"} URL prefix in
* that it retrieves all matching resources for a given path &mdash; for
* example, to locate all "beans.xml" files in the root of all deployed JAR
* files you can use the location pattern {@code "classpath*:/beans.xml"}.
* <p>As of Spring Framework 6.0, the semantics for the {@code "classpath*:"}
* prefix have been expanded to include the module path as well as the class path.
* @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
/**
* Resolve the given location pattern into {@code Resource} objects.
* <p>Overlapping resource entries that point to the same physical
@@ -23,7 +23,6 @@ import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ExceptionTypeFilter;
import org.springframework.util.backoff.BackOff;
@@ -97,7 +96,8 @@ class DefaultRetryPolicy implements RetryPolicy {
private static String names(Set<Class<? extends Throwable>> types) {
StringJoiner result = new StringJoiner(", ", "[", "]");
for (Class<? extends Throwable> type : types) {
result.add(ClassUtils.getCanonicalName(type));
String name = type.getCanonicalName();
result.add(name != null? name : type.getName());
}
return result.toString();
}
@@ -24,8 +24,6 @@ import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.util.ClassUtils;
/**
* {@link ValueStyler} that converts objects to String form &mdash; generally for
* debugging purposes &mdash; using simple styling conventions that mimic the
@@ -47,7 +45,7 @@ public class SimpleValueStyler extends DefaultValueStyler {
/**
* Default {@link Class} styling function: {@link Class#getCanonicalName()}.
*/
public static final Function<Class<?>, String> DEFAULT_CLASS_STYLER = ClassUtils::getCanonicalName;
public static final Function<Class<?>, String> DEFAULT_CLASS_STYLER = Class::getCanonicalName;
/**
* Default {@link Method} styling function: converts the supplied {@link Method}
@@ -54,7 +54,6 @@ public interface MethodMetadata extends AnnotatedTypeMetadata {
/**
* Get the fully-qualified name of the underlying method's declared return type.
* @since 4.2
* @see Class#getTypeName()
*/
String getReturnTypeName();
@@ -103,7 +103,7 @@ public class StandardMethodMetadata implements MethodMetadata {
@Override
public String getReturnTypeName() {
return this.introspectedMethod.getReturnType().getTypeName();
return this.introspectedMethod.getReturnType().getName();
}
@Override
@@ -101,13 +101,7 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
@SuppressWarnings("unchecked")
public <E extends Enum<E>> void visitEnum(String descriptor, String value, Consumer<E> consumer) {
String className = Type.getType(descriptor).getClassName();
Class<E> type = null;
try {
type = (Class<E>) ClassUtils.forName(className, this.classLoader);
}
catch (ClassNotFoundException | LinkageError ex) {
throw new TypeNotPresentException(className, ex);
}
Class<E> type = (Class<E>) ClassUtils.resolveClassName(className, this.classLoader);
consumer.accept(Enum.valueOf(type, value));
}
@@ -119,13 +113,7 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
if (AnnotationFilter.PLAIN.matches(className)) {
return null;
}
Class<T> type = null;
try {
type = (Class<T>) ClassUtils.forName(className, this.classLoader);
}
catch (ClassNotFoundException | LinkageError ex) {
throw new TypeNotPresentException(className, ex);
}
Class<T> type = (Class<T>) ClassUtils.resolveClassName(className, this.classLoader);
return new MergedAnnotationReadingVisitor<>(this.classLoader, this.source, type, consumer);
}
@@ -16,9 +16,7 @@
package org.springframework.core.type.classreading;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.jspecify.annotations.Nullable;
@@ -59,7 +57,7 @@ final class SimpleAnnotationMetadataReadingVisitor extends ClassVisitor {
private final Set<String> memberClassNames = new LinkedHashSet<>(4);
private final List<MergedAnnotation<?>> annotations = new ArrayList<>(4);
private final Set<MergedAnnotation<?>> annotations = new LinkedHashSet<>(4);
private final Set<MethodMetadata> declaredMethods = new LinkedHashSet<>(4);
@@ -1131,26 +1131,11 @@ public abstract class ClassUtils {
return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}
/**
* Return the {@linkplain Class#getCanonicalName() canonical name} of the given
* class, or the {@linkplain Class#getTypeName() type name} of the class if the
* canonical name does not exist.
* @param clazz the class
* @return the canonical name of the class, or the type name as a fallback
* @since 7.1
*/
public static String getCanonicalName(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
String canonicalName = clazz.getCanonicalName();
return (canonicalName != null ? canonicalName : clazz.getTypeName());
}
/**
* Return the qualified name of the given class: usually simply
* the class name, but component type class name + "[]" for arrays.
* @param clazz the class
* @return the qualified name of the class
* @see Class#getTypeName()
*/
public static String getQualifiedName(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
@@ -367,7 +367,7 @@ public final class ConcurrentLruCache<K, V> {
private static final class ReadOperations<K, V> {
private static final int BUFFER_COUNT = 4;
private static final int BUFFER_COUNT = detectNumberOfBuffers();
private static final int BUFFERS_MASK = BUFFER_COUNT - 1;
@@ -450,6 +450,11 @@ public final class ConcurrentLruCache<K, V> {
this.processedCount.lazySet(bufferIndex, writeCount);
}
private static int detectNumberOfBuffers() {
int availableProcessors = Runtime.getRuntime().availableProcessors();
int nextPowerOfTwo = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(availableProcessors - 1));
return Math.min(4, nextPowerOfTwo);
}
}
@@ -252,9 +252,7 @@ public abstract class MimeTypeUtils {
if (eqIndex >= 0) {
String attribute = parameter.substring(0, eqIndex).trim();
String value = parameter.substring(eqIndex + 1).trim();
if (parameters.put(attribute, value) != null) {
throw new InvalidMimeTypeException(mimeType, "duplicate parameter '" + parameter + "'");
}
parameters.put(attribute, value);
}
}
index = nextIndex;
@@ -1,61 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.function;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.function.Consumer;
import org.springframework.core.io.Resource;
/**
* Common functional interface for I/O content consumption, for example
* consuming an {@link java.io.InputStream} or a {@link java.io.Reader}.
*
* @author Juergen Hoeller
* @since 7.1
* @param <C> the type of stream/reader
* @see Resource#consumeContent
* @see org.springframework.core.io.support.EncodedResource#consumeContent
* @see ThrowingConsumer
*/
@FunctionalInterface
public interface IOConsumer<C> extends Consumer<C> {
/**
* Performs this operation on the given argument, possibly throwing
* an {@link IOException}.
* @param content the stream/reader
* @throws IOException on error
*/
void acceptWithException(C content) throws IOException;
/**
* Default {@link Consumer#accept(Object)} that wraps any thrown
* {@link IOException} in an {@link UncheckedIOException}.
* @see java.util.function.Consumer#accept(Object)
*/
default void accept(C input) {
try {
acceptWithException(input);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
@@ -139,12 +139,7 @@ abstract class ClassFileAnnotationDelegate {
private static Class<?> loadEnumClass(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
String className = ClassFileAnnotationMetadata.resolveTypeName(enumValue.classSymbol());
try {
return ClassUtils.forName(className, classLoader);
}
catch (ClassNotFoundException | LinkageError ex) {
throw new TypeNotPresentException(className, ex);
}
return ClassUtils.resolveClassName(className, classLoader);
}
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
@@ -255,7 +255,7 @@ final class ClassFileAnnotationMetadata implements AnnotationMetadata {
private Set<MethodMetadata> declaredMethods = new LinkedHashSet<>(4);
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptyList());
private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet());
public Builder(ClassLoader classLoader) {
this.classLoader = classLoader;
@@ -25,7 +25,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@@ -251,15 +250,6 @@ class GenericTypeResolverTests {
assertThat(resolvedType).isEqualTo(InheritsDefaultMethod.ConcreteType.class);
}
@Test
void resolveTypeFromNestedParameterizedType() {
Type resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyCollectionInterfaceType.class);
assertThat(resolvedType).isEqualTo(method(MyCollectionInterfaceType.class, "get").getGenericReturnType());
resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyOptionalInterfaceType.class);
assertThat(resolvedType).isEqualTo(method(MyOptionalInterfaceType.class, "get").getGenericReturnType());
}
private static Method method(Class<?> target, String methodName, Class<?>... parameterTypes) {
Method method = findMethod(target, methodName, parameterTypes);
assertThat(method).describedAs(target.getName() + "#" + methodName).isNotNull();
@@ -268,29 +258,12 @@ class GenericTypeResolverTests {
public interface MyInterfaceType<T> {
default T get() {
return null;
}
}
public class MySimpleInterfaceType implements MyInterfaceType<String> {
}
public class MyParameterizedInterfaceType<P> implements MyInterfaceType<Collection<P>> {
}
public class MyOptionalInterfaceType extends MyParameterizedInterfaceType<Optional<String>> {
@Override
public Collection<Optional<String>> get() {
return super.get();
}
}
public class MyCollectionInterfaceType implements MyInterfaceType<Collection<String>> {
@Override
public Collection<String> get() {
return MyInterfaceType.super.get();
}
}
public abstract class MyAbstractType<T> implements MyInterfaceType<T> {
@@ -25,7 +25,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
@@ -208,29 +207,6 @@ class ResolvableTypeTests {
.withMessage("Field must not be null");
}
@Test
void forParameterForMethod() throws Exception {
Method method = Methods.class.getMethod("charSequenceParameter", List.class);
Parameter parameter = method.getParameters()[0];
ResolvableType type = ResolvableType.forParameter(parameter);
assertThat(type.getType()).isEqualTo(method.getGenericParameterTypes()[0]);
}
@Test
void forParameterForConstructor() throws Exception {
Constructor<Constructors> constructor = Constructors.class.getConstructor(List.class);
Parameter parameter = constructor.getParameters()[0];
ResolvableType type = ResolvableType.forParameter(parameter);
assertThat(type.getType()).isEqualTo(constructor.getGenericParameterTypes()[0]);
}
@Test
void forParameterMustNotBeNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forParameter(null))
.withMessage("Parameter must not be null");
}
@Test
void forConstructorParameter() throws Exception {
Constructor<Constructors> constructor = Constructors.class.getConstructor(List.class);
@@ -245,13 +221,6 @@ class ResolvableTypeTests {
.withMessage("Constructor must not be null");
}
@Test
void forConstructorParameterWithImplementationClassMustNotBeNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forConstructorParameter(null, 0, TypedConstructors.class))
.withMessage("Executable must not be null");
}
@Test
void forMethodParameterByIndex() throws Exception {
Method method = Methods.class.getMethod("charSequenceParameter", List.class);
@@ -266,13 +235,6 @@ class ResolvableTypeTests {
.withMessage("Method must not be null");
}
@Test
void forMethodParameterByIndexWithImplementationClassMustNotBeNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forMethodParameter(null, 0, TypedMethods.class))
.withMessage("Executable must not be null");
}
@Test
void forMethodParameter() throws Exception {
Method method = Methods.class.getMethod("charSequenceParameter", List.class);
@@ -340,13 +302,6 @@ class ResolvableTypeTests {
.withMessage("Method must not be null");
}
@Test
void forMethodReturnWithImplementationClassMustNotBeNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forMethodReturnType(null, TypedMethods.class))
.withMessage("Executable must not be null");
}
@Test // gh-27748
void genericMatchesReturnType() throws Exception {
Method method = SomeRepository.class.getMethod("someMethod", Class.class, Class.class, Class.class);
@@ -1352,13 +1307,6 @@ class ResolvableTypeTests {
assertThat(type.asMap().toString()).isEqualTo("java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>");
}
@Test
void forClassWithGenericsClassMustNotBeNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ResolvableType.forClassWithGenerics(null, String.class))
.withMessage("Class must not be null");
}
@Test
void forClassWithMismatchedGenerics() {
assertThatIllegalArgumentException()
@@ -102,7 +102,7 @@ class AnnotationTypeMappingsTests {
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForWithBothValueAndAttribute.class))
.withMessage("In @AliasFor declared on attribute 'test' in annotation [%s], attribute 'attribute' " +
"and its alias 'value' are present with values of 'foo' and 'bar', but only one is permitted.",
AliasForWithBothValueAndAttribute.class.getCanonicalName());
AliasForWithBothValueAndAttribute.class.getName());
}
@Test
@@ -111,7 +111,7 @@ class AnnotationTypeMappingsTests {
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToSelfNonExistingAttribute.class))
.withMessage("@AliasFor declaration on attribute 'test' in annotation [%s] " +
"declares an alias for 'missing' which is not present.",
AliasForToSelfNonExistingAttribute.class.getCanonicalName());
AliasForToSelfNonExistingAttribute.class.getName());
}
@Test
@@ -119,8 +119,8 @@ class AnnotationTypeMappingsTests {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToOtherNonExistingAttribute.class))
.withMessage("Attribute 'test' in annotation [%s] is declared as an @AliasFor nonexistent " +
"attribute 'missing' in annotation [%s].", AliasForToOtherNonExistingAttribute.class.getCanonicalName(),
AliasForToOtherNonExistingAttributeTarget.class.getCanonicalName());
"attribute 'missing' in annotation [%s].", AliasForToOtherNonExistingAttribute.class.getName(),
AliasForToOtherNonExistingAttributeTarget.class.getName());
}
@Test
@@ -129,7 +129,7 @@ class AnnotationTypeMappingsTests {
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToSelf.class))
.withMessage("@AliasFor declaration on attribute 'test' in annotation [%s] points to itself. " +
"Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
AliasForToSelf.class.getCanonicalName());
AliasForToSelf.class.getName());
}
@Test
@@ -147,13 +147,13 @@ class AnnotationTypeMappingsTests {
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForWithIncompatibleReturnTypes.class))
.withMessage("Misconfigured aliases: attribute 'test' in annotation [%s] and attribute 'test' " +
"in annotation [%s] must declare the same return type.",
AliasForWithIncompatibleReturnTypes.class.getCanonicalName(),
AliasForWithIncompatibleReturnTypesTarget.class.getCanonicalName());
AliasForWithIncompatibleReturnTypes.class.getName(),
AliasForWithIncompatibleReturnTypesTarget.class.getName());
}
@Test
void forAnnotationTypeWhenAliasForToSelfAnnotatedToOtherAttribute() {
String annotationType = AliasForToSelfAnnotatedToOtherAttribute.class.getCanonicalName();
String annotationType = AliasForToSelfAnnotatedToOtherAttribute.class.getName();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToSelfAnnotatedToOtherAttribute.class))
.withMessage("Attribute 'b' in annotation [%1$s] must be declared as an @AliasFor attribute 'a' in " +
@@ -167,8 +167,8 @@ class AnnotationTypeMappingsTests {
}
private void assertMixedImplicitAndExplicitAliases(Class<? extends Annotation> annotationType, String overriddenAttribute) {
String annotationName = annotationType.getCanonicalName();
String metaAnnotationName = AliasPair.class.getCanonicalName();
String annotationName = annotationType.getName();
String metaAnnotationName = AliasPair.class.getName();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(annotationType))
.withMessage("Attribute 'b' in annotation [" + annotationName +
@@ -180,8 +180,8 @@ class AnnotationTypeMappingsTests {
void forAnnotationTypeWhenAliasForNonMetaAnnotated() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForNonMetaAnnotated.class))
.withMessage("@AliasFor declaration on attribute 'test' in annotation [" + AliasForNonMetaAnnotated.class.getCanonicalName() +
"] declares an alias for attribute 'test' in annotation [" + AliasForNonMetaAnnotatedTarget.class.getCanonicalName() +
.withMessage("@AliasFor declaration on attribute 'test' in annotation [" + AliasForNonMetaAnnotated.class.getName() +
"] declares an alias for attribute 'test' in annotation [" + AliasForNonMetaAnnotatedTarget.class.getName() +
"] which is not meta-present.");
}
@@ -189,8 +189,8 @@ class AnnotationTypeMappingsTests {
void forAnnotationTypeWhenAliasForSelfWithDifferentDefaults() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForSelfWithDifferentDefaults.class))
.withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithDifferentDefaults.class.getCanonicalName() +
"] and attribute 'b' in annotation [" + AliasForSelfWithDifferentDefaults.class.getCanonicalName() +
.withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithDifferentDefaults.class.getName() +
"] and attribute 'b' in annotation [" + AliasForSelfWithDifferentDefaults.class.getName() +
"] must declare the same default value.");
}
@@ -198,8 +198,8 @@ class AnnotationTypeMappingsTests {
void forAnnotationTypeWhenAliasForSelfWithMissingDefault() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForSelfWithMissingDefault.class))
.withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithMissingDefault.class.getCanonicalName() +
"] and attribute 'b' in annotation [" + AliasForSelfWithMissingDefault.class.getCanonicalName() +
.withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithMissingDefault.class.getName() +
"] and attribute 'b' in annotation [" + AliasForSelfWithMissingDefault.class.getName() +
"] must declare default values.");
}
@@ -207,8 +207,8 @@ class AnnotationTypeMappingsTests {
void forAnnotationTypeWhenAliasWithExplicitMirrorAndDifferentDefaults() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasWithExplicitMirrorAndDifferentDefaults.class))
.withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getCanonicalName() +
"] and attribute 'c' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getCanonicalName() +
.withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getName() +
"] and attribute 'c' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getName() +
"] must declare the same default value.");
}
@@ -352,8 +352,8 @@ class AnnotationTypeMappingsTests {
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(AliasPair.class).get(0);
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> resolveMirrorSets(mapping, WithDifferentValueAliasPair.class, AliasPair.class))
.withMessage("Different @AliasFor mirror values for annotation [" + AliasPair.class.getCanonicalName() +
"] declared on " + WithDifferentValueAliasPair.class.getName() +
.withMessage("Different @AliasFor mirror values for annotation [" + AliasPair.class.getName() + "] declared on " +
WithDifferentValueAliasPair.class.getName() +
"; attribute 'a' and its alias 'b' are declared with values of [test1] and [test2].");
}
@@ -718,11 +718,11 @@ class AnnotationUtilsTests {
ImplicitAliasesWithMissingDefaultValuesContextConfig config = clazz.getAnnotation(annotationType);
assertThat(config).isNotNull();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> synthesizeAnnotation(config, clazz))
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
synthesizeAnnotation(config, clazz))
.withMessageStartingWith("Misconfigured aliases:")
.withMessageContaining("attribute 'location1' in annotation [%s]", annotationType.getCanonicalName())
.withMessageContaining("attribute 'location2' in annotation [%s]", annotationType.getCanonicalName())
.withMessageContaining("attribute 'location1' in annotation [" + annotationType.getName() + "]")
.withMessageContaining("attribute 'location2' in annotation [" + annotationType.getName() + "]")
.withMessageContaining("default values");
}
@@ -733,11 +733,11 @@ class AnnotationUtilsTests {
ImplicitAliasesWithDifferentDefaultValuesContextConfig.class;
ImplicitAliasesWithDifferentDefaultValuesContextConfig config = clazz.getAnnotation(annotationType);
assertThat(config).isNotNull();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> synthesizeAnnotation(config, clazz))
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() ->
synthesizeAnnotation(config, clazz))
.withMessageStartingWith("Misconfigured aliases:")
.withMessageContaining("attribute 'location1' in annotation [%s]", annotationType.getCanonicalName())
.withMessageContaining("attribute 'location2' in annotation [%s]", annotationType.getCanonicalName())
.withMessageContaining("attribute 'location1' in annotation [" + annotationType.getName() + "]")
.withMessageContaining("attribute 'location2' in annotation [" + annotationType.getName() + "]")
.withMessageContaining("same default value");
}
@@ -919,18 +919,8 @@ class AnnotationUtilsTests {
Map<String, Object> map = Collections.singletonMap(VALUE, 42L);
assertThatIllegalStateException().isThrownBy(() ->
synthesizeAnnotation(map, Component.class, null).value())
.withMessageContaining("Attribute 'value' in annotation %s should be compatible with " +
"java.lang.String but a java.lang.Long value was returned",
Component.class.getCanonicalName());
}
@Test
void synthesizeAnnotationFromMapWithAttributeOfIncorrectArrayType() {
Map<String, Object> map = Collections.singletonMap(VALUE, new int[] {42});
assertThatIllegalStateException().isThrownBy(() ->
synthesizeAnnotation(map, CharsContainer.class, null).chars())
.withMessageContaining("Attribute 'chars' in annotation %s should be compatible with " +
"char[] but a int[] value was returned", CharsContainer.class.getCanonicalName());
.withMessageContaining("Attribute 'value' in annotation org.springframework.core.testfixture.stereotype.Component " +
"should be compatible with java.lang.String but a java.lang.Long value was returned");
}
@Test
@@ -183,32 +183,32 @@ class ComposedRepeatableAnnotationsTests {
assertThatIllegalArgumentException().isThrownBy(throwingCallable)
.withMessageStartingWith("Annotation type must be a repeatable annotation")
.withMessageContaining("failed to resolve container type for")
.withMessageContaining(NonRepeatable.class.getCanonicalName());
.withMessageContaining(NonRepeatable.class.getName());
}
private void expectContainerMissingValueAttribute(ThrowingCallable throwingCallable) {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(throwingCallable)
.withMessageStartingWith("Invalid declaration of container type")
.withMessageContaining(ContainerMissingValueAttribute.class.getCanonicalName())
.withMessageContaining(ContainerMissingValueAttribute.class.getName())
.withMessageContaining("for repeatable annotation")
.withMessageContaining(InvalidRepeatable.class.getCanonicalName())
.withMessageContaining(InvalidRepeatable.class.getName())
.withCauseExactlyInstanceOf(NoSuchMethodException.class);
}
private void expectContainerWithNonArrayValueAttribute(ThrowingCallable throwingCallable) {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(throwingCallable)
.withMessageStartingWith("Container type")
.withMessageContaining(ContainerWithNonArrayValueAttribute.class.getCanonicalName())
.withMessageContaining(ContainerWithNonArrayValueAttribute.class.getName())
.withMessageContaining("must declare a 'value' attribute for an array of type")
.withMessageContaining(InvalidRepeatable.class.getCanonicalName());
.withMessageContaining(InvalidRepeatable.class.getName());
}
private void expectContainerWithArrayValueAttributeButWrongComponentType(ThrowingCallable throwingCallable) {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(throwingCallable)
.withMessageStartingWith("Container type")
.withMessageContaining(ContainerWithArrayValueAttributeButWrongComponentType.class.getCanonicalName())
.withMessageContaining(ContainerWithArrayValueAttributeButWrongComponentType.class.getName())
.withMessageContaining("must declare a 'value' attribute for an array of type")
.withMessageContaining(InvalidRepeatable.class.getCanonicalName());
.withMessageContaining(InvalidRepeatable.class.getName());
}
private void assertGetRepeatableAnnotations(AnnotatedElement element) {

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