mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 502b31a7f2 | |||
| f0e7b42704 | |||
| e86dc9ad95 | |||
| 05c3f56ec7 | |||
| dee80ab1da | |||
| 07fd835ea9 | |||
| 0d6c6eb2d5 | |||
| 4d2cc4ae97 | |||
| c04902fefb | |||
| 3c265e1044 | |||
| 222702f750 | |||
| 7bb19fcde8 | |||
| 18d6a55e3e | |||
| 99890b6147 | |||
| 0afdb08099 | |||
| 40058ef875 | |||
| c769f43b3e | |||
| 4782c697b8 | |||
| de52090959 | |||
| 4d862b871d | |||
| fa781c8390 | |||
| 88cd7addda | |||
| 332c7f770c | |||
| 9f76ea13e3 | |||
| d9c103b7ff | |||
| f1ddd051a3 | |||
| a266e1b403 | |||
| 4d09eb569b | |||
| a439e9030f | |||
| aa5c0dcd72 | |||
| 4a46d957f3 | |||
| 4df93a825b | |||
| be17315f63 | |||
| 31903a9d92 | |||
| 06f69915cb | |||
| d9459bdc74 | |||
| 3f0892b42c | |||
| 182d654fa8 | |||
| bbae625850 | |||
| f376d1b525 | |||
| 707b7698ce | |||
| 5b9cb8291e | |||
| 659472f9e3 | |||
| c2d678879f | |||
| f207c0ed5c | |||
| bbf61c74ab | |||
| aea66265f7 | |||
| 016294b067 | |||
| 8201193650 | |||
| 562157d2c0 | |||
| 63db3d06ab | |||
| dc52042b85 | |||
| cff6e02162 | |||
| 983af78352 | |||
| 67ed64a41d | |||
| d777b70889 | |||
| 472c4012ca | |||
| 90be94a4a5 | |||
| d9e261aecd | |||
| 15d1455acb | |||
| 53844b068c | |||
| d78264756e | |||
| d0efc2230c | |||
| 6a6abac003 | |||
| 66f8eb02bd | |||
| 274b97ca14 | |||
| e4b4512bcb | |||
| db5809f968 | |||
| d890a38f3c | |||
| 98cef503fb | |||
| 362fe12704 | |||
| 2af0323c21 | |||
| fdab8fabd2 | |||
| ae8b45a290 |
+2
-3
@@ -91,18 +91,17 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.12.2/api/",
|
||||
// "https://junit.org/junit5/docs/5.13.1/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.23.1");
|
||||
checkstyle.setToolVersion("10.25.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -166,7 +166,7 @@ following sections to make this pattern much easier to implement.
|
||||
== MockMvc and WebDriver Setup
|
||||
|
||||
To use Selenium WebDriver with `MockMvc`, make sure that your project includes a test
|
||||
dependency on `org.seleniumhq.selenium:selenium-htmlunit3-driver`.
|
||||
dependency on `org.seleniumhq.selenium:htmlunit3-driver`.
|
||||
|
||||
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
|
||||
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
[[spring-mvc-test-client]]
|
||||
= Testing Client Applications
|
||||
|
||||
You can use client-side tests to test code that internally uses the `RestTemplate`. The
|
||||
idea is to declare expected requests and to provide "`stub`" responses so that you can
|
||||
focus on testing the code in isolation (that is, without running a server). The following
|
||||
example shows how to do so:
|
||||
To test code that uses the `RestClient` or `RestTemplate`, you can use a mock web server, such as
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
|
||||
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
|
||||
server, and that means you can test with the same HTTP client that is also configured in
|
||||
the same way as in production, which is important because there are often subtle
|
||||
differences in the way different clients handle network I/O. Another advantage of mock
|
||||
web servers is the ability to simulate specific network issues and conditions at the
|
||||
transport level, in combination with the client used in production.
|
||||
|
||||
In addition to dedicated mock web servers, historically the Spring Framework has provided
|
||||
a built-in option to test `RestClient` or `RestTemplate` through `MockRestServiceServer`.
|
||||
This relies on configuring the client under test with a custom `ClientHttpRequestFactory`
|
||||
backed by the mock server that is in turn set up to expect requests and send "`stub`"
|
||||
responses so that you can focus on testing the code in isolation, without running a server.
|
||||
|
||||
TIP: `MockRestServiceServer` predates the existence of mock web servers. At present, we
|
||||
recommend using mock web servers for more complete testing of the transport layer and
|
||||
network conditions.
|
||||
|
||||
The following example shows an example of using `MockRestServiceServer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -250,7 +250,7 @@ Java::
|
||||
@SqlGroup({
|
||||
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
|
||||
@Sql("/test-user-data.sql")
|
||||
)}
|
||||
})
|
||||
void userTest() {
|
||||
// run code that uses the test schema and test data
|
||||
}
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
= Testing
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
To test code that uses the `WebClient`, you can use a mock web server, such as the
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see an example
|
||||
of its use, check out
|
||||
To test code that uses the `WebClient`, you can use a mock web server, such as
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
|
||||
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
|
||||
server, and that means you can test with the same HTTP client that is also configured in
|
||||
the same way as in production, which is important because there are often subtle
|
||||
differences in the way different clients handle network I/O. Another advantage of mock
|
||||
web servers is the ability to simulate specific network issues and conditions at the
|
||||
transport level, in combination with the client used in production.
|
||||
|
||||
For example use of MockWebServer, see
|
||||
{spring-framework-code}/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[`WebClientIntegrationTests`]
|
||||
in the Spring Framework test suite or the
|
||||
https://github.com/square/okhttp/tree/master/samples/static-server[`static-server`]
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ Java::
|
||||
|
||||
private String name;
|
||||
|
||||
private MultipartFile file;
|
||||
private FilePart file;
|
||||
|
||||
// ...
|
||||
|
||||
@@ -42,7 +42,7 @@ Kotlin::
|
||||
----
|
||||
class MyForm(
|
||||
val name: String,
|
||||
val file: MultipartFile)
|
||||
val file: FilePart)
|
||||
|
||||
@Controller
|
||||
class FileUploadController {
|
||||
|
||||
+1
-5
@@ -34,11 +34,7 @@ Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated oper
|
||||
| `HttpHeaders`
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| `ErrorResponse`, `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
Spring MVC has an extensive integration with Servlet asynchronous request
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-processing[processing]:
|
||||
|
||||
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`]
|
||||
return values in controller methods provide basic support for a single asynchronous
|
||||
return value.
|
||||
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`],
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`], and
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-webasynctask[`WebAsyncTask`] return values
|
||||
in controller methods provide support for a single asynchronous return value.
|
||||
* Controllers can xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[stream] multiple values, including
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
|
||||
* Controllers can use reactive clients and return
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types] for response handling.
|
||||
|
||||
@@ -96,6 +98,47 @@ xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[config
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-async-webasynctask]]
|
||||
== `WebAsyncTask`
|
||||
|
||||
`WebAsyncTask` is comparable to using xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[Callable]
|
||||
but allows customizing additional settings such a request timeout value, and the
|
||||
`AsyncTaskExecutor` to execute the `java.util.concurrent.Callable` with instead
|
||||
of the defaults set up globally for Spring MVC. Below is an example of using `WebAsyncTask`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@GetMapping("/callable")
|
||||
WebAsyncTask<String> handle() {
|
||||
return new WebAsyncTask<String>(20000L,()->{
|
||||
Thread.sleep(10000); //simulate long-running task
|
||||
return "asynchronous request completed";
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@GetMapping("/callable")
|
||||
fun handle(): WebAsyncTask<String> {
|
||||
return WebAsyncTask(20000L) {
|
||||
Thread.sleep(10000) // simulate long-running task
|
||||
"asynchronous request completed"
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-async-processing]]
|
||||
== Processing
|
||||
|
||||
@@ -390,7 +433,7 @@ reactive types from the controller method.
|
||||
Reactive return values are handled as follows:
|
||||
|
||||
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
|
||||
include `Mono` (Reactor) or `Single` (RxJava).
|
||||
include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
|
||||
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
|
||||
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
|
||||
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
|
||||
|
||||
@@ -10,18 +10,20 @@ to any controller. Moreover, as of 5.3, `@ExceptionHandler` methods in `@Control
|
||||
can be used to handle exceptions from any `@Controller` or any other handler.
|
||||
|
||||
`@ControllerAdvice` is meta-annotated with `@Component` and therefore can be registered as
|
||||
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning]
|
||||
. `@RestControllerAdvice` is meta-annotated with `@ControllerAdvice`
|
||||
and `@ResponseBody`, and that means `@ExceptionHandler` methods will have their return
|
||||
value rendered via response body message conversion, rather than via HTML views.
|
||||
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning].
|
||||
|
||||
`@RestControllerAdvice` is a shortcut annotation that combines `@ControllerAdvice`
|
||||
with `@ResponseBody`, in effect simply an `@ControllerAdvice` whose exception handler
|
||||
methods render to the response body.
|
||||
|
||||
On startup, `RequestMappingHandlerMapping` and `ExceptionHandlerExceptionResolver` detect
|
||||
controller advice beans and apply them at runtime. Global `@ExceptionHandler` methods,
|
||||
from an `@ControllerAdvice`, are applied _after_ local ones, from the `@Controller`.
|
||||
By contrast, global `@ModelAttribute` and `@InitBinder` methods are applied _before_ local ones.
|
||||
|
||||
The `@ControllerAdvice` annotation has attributes that let you narrow the set of controllers
|
||||
and handlers that they apply to. For example:
|
||||
By default, both `@ControllerAdvice` and `@RestControllerAdvice` apply to any controller,
|
||||
including `@Controller` and `@RestController`. Use attributes of the annotation to narrow
|
||||
the set of controllers and handlers that they apply to. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+2
-6
@@ -177,13 +177,9 @@ the content negotiation during the error handling phase will decide which conten
|
||||
be converted through `HttpMessageConverter` instances and written to the response.
|
||||
See xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[ResponseEntity].
|
||||
|
||||
| `ErrorResponse`
|
||||
| `ErrorResponse`, `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
| A view name to be resolved with `ViewResolver` implementations and used together with the
|
||||
|
||||
+1
-1
@@ -243,7 +243,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
If there is no `BindingResult` parameter after the `@ModelAttribute`, then
|
||||
`MethodArgumentNotValueException` is raised with the validation errors. However, if method
|
||||
a `MethodArgumentNotValidException` is raised with the validation errors. However, if method
|
||||
validation applies because other parameters have `@jakarta.validation.Constraint` annotations,
|
||||
then `HandlerMethodValidationException` is raised instead. For more details, see the section
|
||||
xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
||||
+1
-5
@@ -22,11 +22,7 @@ supported for all return values.
|
||||
| `HttpHeaders`
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| `ErrorResponse`, `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ to inform the server that the original port was `443`.
|
||||
==== X-Forwarded-Proto
|
||||
|
||||
While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto[`X-Forwarded-Proto: (https|http)`]
|
||||
is a de-facto standard header that is used to communicate the original protocol (for example, https / https)
|
||||
is a de-facto standard header that is used to communicate the original protocol (for example, https / http)
|
||||
to a downstream server. For example, if a request of `https://example.com/resource` is sent to
|
||||
a proxy which forwards the request to `http://localhost:8080/resource`, then a header of
|
||||
`X-Forwarded-Proto: https` can be sent to inform the server that the original protocol was `https`.
|
||||
@@ -119,4 +119,4 @@ https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}
|
||||
|
||||
In this case, the proxy has a prefix of `/api/app1` and the server has a prefix of
|
||||
`/app1`. The proxy can send `X-Forwarded-Prefix: /api/app1` to have the original prefix
|
||||
`/api/app1` override the server prefix `/app1`.
|
||||
`/api/app1` override the server prefix `/app1`.
|
||||
|
||||
@@ -8,20 +8,20 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.7"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.8"))
|
||||
api(platform("io.netty:netty-bom:4.1.121.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.6"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.7"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.27"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.21"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.21"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.12.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.17.0"))
|
||||
api(platform("org.junit:junit-bom:5.13.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.18.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
@@ -47,7 +47,6 @@ dependencies {
|
||||
api("com.thoughtworks.xstream:xstream:1.4.21")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.micrometer:context-propagation:1.1.1")
|
||||
api("io.mockk:mockk:1.13.4")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
|
||||
@@ -100,7 +99,7 @@ dependencies {
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.4")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.5")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.7-SNAPSHOT
|
||||
version=6.2.8
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
plugins {
|
||||
id "com.gradle.develocity" version "3.19"
|
||||
id "io.spring.ge.conventions" version "0.0.17"
|
||||
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
|
||||
}
|
||||
|
||||
include "spring-aop"
|
||||
|
||||
+65
-50
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -112,12 +112,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Nullable
|
||||
private transient ClassLoader pointcutClassLoader;
|
||||
private transient volatile ClassLoader pointcutClassLoader;
|
||||
|
||||
@Nullable
|
||||
private transient PointcutExpression pointcutExpression;
|
||||
private transient volatile PointcutExpression pointcutExpression;
|
||||
|
||||
private transient boolean pointcutParsingFailed = false;
|
||||
private transient volatile boolean pointcutParsingFailed;
|
||||
|
||||
|
||||
/**
|
||||
@@ -197,11 +197,14 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
* Lazily build the underlying AspectJ pointcut expression.
|
||||
*/
|
||||
private PointcutExpression obtainPointcutExpression() {
|
||||
if (this.pointcutExpression == null) {
|
||||
this.pointcutClassLoader = determinePointcutClassLoader();
|
||||
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
|
||||
PointcutExpression pointcutExpression = this.pointcutExpression;
|
||||
if (pointcutExpression == null) {
|
||||
ClassLoader pointcutClassLoader = determinePointcutClassLoader();
|
||||
pointcutExpression = buildPointcutExpression(pointcutClassLoader);
|
||||
this.pointcutClassLoader = pointcutClassLoader;
|
||||
this.pointcutExpression = pointcutExpression;
|
||||
}
|
||||
return this.pointcutExpression;
|
||||
return pointcutExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -467,40 +470,24 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
}
|
||||
|
||||
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
|
||||
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(this, targetMethod);
|
||||
ShadowMatchKey key = new ShadowMatchKey(this, targetMethod);
|
||||
ShadowMatch shadowMatch = ShadowMatchUtils.getShadowMatch(key);
|
||||
if (shadowMatch == null) {
|
||||
PointcutExpression fallbackExpression = null;
|
||||
Method methodToMatch = targetMethod;
|
||||
try {
|
||||
PointcutExpression pointcutExpression = obtainPointcutExpression();
|
||||
synchronized (pointcutExpression) {
|
||||
shadowMatch = ShadowMatchUtils.getShadowMatch(key);
|
||||
if (shadowMatch != null) {
|
||||
return shadowMatch;
|
||||
}
|
||||
PointcutExpression fallbackExpression = null;
|
||||
Method methodToMatch = targetMethod;
|
||||
try {
|
||||
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
// Failed to introspect target method, probably because it has been loaded
|
||||
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
|
||||
try {
|
||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
||||
if (fallbackExpression != null) {
|
||||
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
}
|
||||
catch (ReflectionWorldException ex2) {
|
||||
fallbackExpression = null;
|
||||
}
|
||||
}
|
||||
if (targetMethod != originalMethod && (shadowMatch == null ||
|
||||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
|
||||
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
|
||||
// Fall back to the plain original method in case of no resolvable match or a
|
||||
// negative match on a proxy class (which doesn't carry any annotations on its
|
||||
// redeclared methods), as well as for annotation pointcuts.
|
||||
methodToMatch = originalMethod;
|
||||
try {
|
||||
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
|
||||
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
// Could neither introspect the target class nor the proxy class ->
|
||||
// let's try the original method's declaring class before we give up...
|
||||
// Failed to introspect target method, probably because it has been loaded
|
||||
// in a special ClassLoader. Let's try the declaring ClassLoader instead...
|
||||
try {
|
||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
||||
if (fallbackExpression != null) {
|
||||
@@ -511,21 +498,45 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
fallbackExpression = null;
|
||||
}
|
||||
}
|
||||
if (targetMethod != originalMethod && (shadowMatch == null ||
|
||||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
|
||||
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
|
||||
// Fall back to the plain original method in case of no resolvable match or a
|
||||
// negative match on a proxy class (which doesn't carry any annotations on its
|
||||
// redeclared methods), as well as for annotation pointcuts.
|
||||
methodToMatch = originalMethod;
|
||||
try {
|
||||
shadowMatch = pointcutExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
// Could neither introspect the target class nor the proxy class ->
|
||||
// let's try the original method's declaring class before we give up...
|
||||
try {
|
||||
fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
|
||||
if (fallbackExpression != null) {
|
||||
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
|
||||
}
|
||||
}
|
||||
catch (ReflectionWorldException ex2) {
|
||||
fallbackExpression = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Possibly AspectJ 1.8.10 encountering an invalid signature
|
||||
logger.debug("PointcutExpression matching rejected target method", ex);
|
||||
fallbackExpression = null;
|
||||
}
|
||||
if (shadowMatch == null) {
|
||||
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
|
||||
}
|
||||
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
|
||||
shadowMatch = new DefensiveShadowMatch(shadowMatch,
|
||||
fallbackExpression.matchesMethodExecution(methodToMatch));
|
||||
}
|
||||
shadowMatch = ShadowMatchUtils.setShadowMatch(key, shadowMatch);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Possibly AspectJ 1.8.10 encountering an invalid signature
|
||||
logger.debug("PointcutExpression matching rejected target method", ex);
|
||||
fallbackExpression = null;
|
||||
}
|
||||
if (shadowMatch == null) {
|
||||
shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
|
||||
}
|
||||
else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
|
||||
shadowMatch = new DefensiveShadowMatch(shadowMatch,
|
||||
fallbackExpression.matchesMethodExecution(methodToMatch));
|
||||
}
|
||||
shadowMatch = ShadowMatchUtils.setShadowMatch(this, targetMethod, shadowMatch);
|
||||
}
|
||||
return shadowMatch;
|
||||
}
|
||||
@@ -720,4 +731,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private record ShadowMatchKey(AspectJExpressionPointcut expression, Method method) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -16,24 +16,48 @@
|
||||
|
||||
package org.springframework.aop.aspectj;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.aspectj.weaver.tools.ShadowMatch;
|
||||
|
||||
import org.springframework.aop.support.ExpressionPointcut;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Internal {@link ShadowMatch} utilities.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.2
|
||||
*/
|
||||
public abstract class ShadowMatchUtils {
|
||||
|
||||
private static final Map<Key, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
|
||||
private static final Map<Object, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(256);
|
||||
|
||||
|
||||
/**
|
||||
* Find a {@link ShadowMatch} for the specified key.
|
||||
* @param key the key to use
|
||||
* @return the {@code ShadowMatch} to use for the specified key,
|
||||
* or {@code null} if none found
|
||||
*/
|
||||
@Nullable
|
||||
static ShadowMatch getShadowMatch(Object key) {
|
||||
return shadowMatchCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the {@link ShadowMatch} with the specified key.
|
||||
* If an entry already exists, the given {@code shadowMatch} is ignored.
|
||||
* @param key the key to use
|
||||
* @param shadowMatch the shadow match to use for this key
|
||||
* if none already exists
|
||||
* @return the shadow match to use for the specified key
|
||||
*/
|
||||
static ShadowMatch setShadowMatch(Object key, ShadowMatch shadowMatch) {
|
||||
ShadowMatch existing = shadowMatchCache.putIfAbsent(key, shadowMatch);
|
||||
return (existing != null ? existing : shadowMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache of computed {@link ShadowMatch} instances.
|
||||
@@ -42,34 +66,4 @@ public abstract class ShadowMatchUtils {
|
||||
shadowMatchCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ShadowMatch} for the specified {@link ExpressionPointcut}
|
||||
* and {@link Method} or {@code null} if none is found.
|
||||
* @param expression the expression
|
||||
* @param method the method
|
||||
* @return the {@code ShadowMatch} to use for the specified expression and method
|
||||
*/
|
||||
@Nullable
|
||||
static ShadowMatch getShadowMatch(ExpressionPointcut expression, Method method) {
|
||||
return shadowMatchCache.get(new Key(expression, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the {@link ShadowMatch} to the specified {@link ExpressionPointcut}
|
||||
* and method. If an entry already exists, the given {@code shadowMatch} is
|
||||
* ignored.
|
||||
* @param expression the expression
|
||||
* @param method the method
|
||||
* @param shadowMatch the shadow match to use for this expression and method
|
||||
* if none already exists
|
||||
* @return the shadow match to use for the specified expression and method
|
||||
*/
|
||||
static ShadowMatch setShadowMatch(ExpressionPointcut expression, Method method, ShadowMatch shadowMatch) {
|
||||
ShadowMatch existing = shadowMatchCache.putIfAbsent(new Key(expression, method), shadowMatch);
|
||||
return (existing != null ? existing : shadowMatch);
|
||||
}
|
||||
|
||||
|
||||
private record Key(ExpressionPointcut expression, Method method) {}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -24,7 +24,7 @@ import java.util.Map;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract superclass for counting advices etc.
|
||||
* Abstract superclass for counting advice, etc.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
@@ -62,7 +62,7 @@ public class MethodCounter implements Serializable {
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (other != null && other.getClass() == this.getClass());
|
||||
return (other != null && getClass() == other.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -33,6 +33,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.jvm.internal.DefaultConstructorMarker;
|
||||
import kotlin.reflect.KClass;
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
@@ -669,7 +670,9 @@ public abstract class BeanUtils {
|
||||
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
|
||||
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
|
||||
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
|
||||
Assert.state(paramNames.length == ctor.getParameterCount(),
|
||||
int parameterCount = (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasDefaultConstructorMarker(ctor) ?
|
||||
ctor.getParameterCount() - 1 : ctor.getParameterCount());
|
||||
Assert.state(paramNames.length == parameterCount,
|
||||
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
|
||||
return paramNames;
|
||||
}
|
||||
@@ -939,6 +942,11 @@ public abstract class BeanUtils {
|
||||
}
|
||||
return kotlinConstructor.callBy(argParameters);
|
||||
}
|
||||
|
||||
public static boolean hasDefaultConstructorMarker(Constructor<?> ctor) {
|
||||
int parameterCount = ctor.getParameterCount();
|
||||
return parameterCount > 0 && ctor.getParameters()[parameterCount -1].getType() == DefaultConstructorMarker.class;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -68,10 +68,10 @@ public class AutowiredArgumentsCodeGenerator {
|
||||
for (int i = startIndex; i < parameterTypes.length; i++) {
|
||||
code.add(i > startIndex ? ", " : "");
|
||||
if (!ambiguous) {
|
||||
code.add("$L.get($L)", variableName, i - startIndex);
|
||||
code.add("$L.get($L)", variableName, i);
|
||||
}
|
||||
else {
|
||||
code.add("$L.get($L, $T.class)", variableName, i - startIndex, parameterTypes[i]);
|
||||
code.add("$L.get($L, $T.class)", variableName, i, parameterTypes[i]);
|
||||
}
|
||||
}
|
||||
return code.build();
|
||||
|
||||
+1
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -29,10 +29,6 @@ import org.springframework.lang.Nullable;
|
||||
* load and register methods for bean definitions, specific to
|
||||
* their bean definition format.
|
||||
*
|
||||
* <p>Note that a bean definition reader does not have to implement
|
||||
* this interface. It only serves as a suggestion for bean definition
|
||||
* readers that want to follow standard naming conventions.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see org.springframework.core.io.Resource
|
||||
|
||||
+6
-2
@@ -1679,7 +1679,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
if (autowiredBeanNames != null) {
|
||||
autowiredBeanNames.add(dependencyName);
|
||||
}
|
||||
boolean preExisting = containsSingleton(dependencyName);
|
||||
Object dependencyBean = getBean(dependencyName);
|
||||
if (preExisting && dependencyBean instanceof NullBean) {
|
||||
// for backwards compatibility with addCandidateEntry in the regular code path
|
||||
dependencyBean = null;
|
||||
}
|
||||
return resolveInstance(dependencyBean, descriptor, type, dependencyName);
|
||||
}
|
||||
}
|
||||
@@ -1760,7 +1765,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
throw new BeanNotOfRequiredTypeException(name, type, candidate.getClass());
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -2245,7 +2249,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
* i.e. whether the candidate points back to the original bean or to a factory method
|
||||
* on the original bean.
|
||||
*/
|
||||
@Contract("null, _ -> false;_, null -> false;")
|
||||
@Contract("null, _ -> false; _, null -> false;")
|
||||
private boolean isSelfReference(@Nullable String beanName, @Nullable String candidateName) {
|
||||
return (beanName != null && candidateName != null &&
|
||||
(beanName.equals(candidateName) || (containsBeanDefinition(candidateName) &&
|
||||
|
||||
+6
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -33,13 +33,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Chris Beams
|
||||
*/
|
||||
class FactoryBeanLookupTests {
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private final BeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
beanFactory = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory).loadBeanDefinitions(
|
||||
new ClassPathResource("FactoryBeanLookupTests-context.xml", this.getClass()));
|
||||
new ClassPathResource("FactoryBeanLookupTests-context.xml", getClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -71,6 +72,7 @@ class FactoryBeanLookupTests {
|
||||
Foo foo = beanFactory.getBean("fooFactory", Foo.class);
|
||||
assertThat(foo).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FooFactoryBean extends AbstractFactoryBean<Foo> {
|
||||
|
||||
+26
-3
@@ -960,11 +960,34 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
@Test
|
||||
void constructorResourceInjectionWithNoCandidatesAndNoFallback() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class));
|
||||
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
|
||||
.isThrownBy(() -> bf.getBean("annotatedBean"))
|
||||
.satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorResourceInjectionWithCandidateAndNoFallback() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class));
|
||||
RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class);
|
||||
tb.setFactoryMethodName("createTestBean");
|
||||
bf.registerBeanDefinition("testBean", tb);
|
||||
|
||||
bf.getBean("testBean");
|
||||
assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorResourceInjectionWithNameMatchingCandidateAndNoFallback() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class));
|
||||
RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class);
|
||||
tb.setFactoryMethodName("createTestBean");
|
||||
bf.registerBeanDefinition("testBean3", tb);
|
||||
|
||||
bf.getBean("testBean3");
|
||||
assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorResourceInjectionWithSometimesNullBeanEarly() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
|
||||
@@ -1193,6 +1216,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
@Test
|
||||
void singleConstructorInjectionWithMissingDependency() {
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorOptionalCollectionBean.class));
|
||||
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
|
||||
.isThrownBy(() -> bf.getBean("annotatedBean"));
|
||||
}
|
||||
@@ -1203,6 +1227,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class);
|
||||
tb.setFactoryMethodName("createTestBean");
|
||||
bf.registerBeanDefinition("testBean", tb);
|
||||
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
|
||||
.isThrownBy(() -> bf.getBean("annotatedBean"));
|
||||
}
|
||||
@@ -2428,7 +2453,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void genericsBasedConstructorInjectionWithNonTypedTarget() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
@@ -3060,7 +3085,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
|
||||
protected ITestBean testBean3;
|
||||
|
||||
@Autowired(required = false)
|
||||
public ConstructorWithoutFallbackBean(ITestBean testBean3) {
|
||||
this.testBean3 = testBean3;
|
||||
}
|
||||
@@ -3075,7 +3099,6 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
|
||||
protected ITestBean testBean3;
|
||||
|
||||
@Autowired(required = false)
|
||||
public ConstructorWithNullableArgument(@Nullable ITestBean testBean3) {
|
||||
this.testBean3 = testBean3;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -67,7 +67,7 @@ class AutowiredArgumentsCodeGeneratorTests {
|
||||
AutowiredArgumentsCodeGenerator generator = new AutowiredArgumentsCodeGenerator(
|
||||
Outer.Nested.class, constructor);
|
||||
assertThat(generator.generateCode(constructor.getParameterTypes(), 1))
|
||||
.hasToString("args.get(0), args.get(1)");
|
||||
.hasToString("args.get(1), args.get(2)");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -36,6 +36,7 @@ import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
import org.junit.jupiter.params.support.AnnotationConsumer;
|
||||
import org.junit.jupiter.params.support.ParameterDeclarations;
|
||||
|
||||
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
@@ -691,7 +692,7 @@ class BeanInstanceSupplierTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||
public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
|
||||
return this.source.provideArguments(context);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -133,11 +133,13 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
@Test
|
||||
void generateWhenHasConstructorWithInnerClassAndParameter() {
|
||||
BeanDefinition beanDefinition = new RootBeanDefinition(EnvironmentAwareComponent.class);
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
|
||||
this.beanFactory.registerSingleton("environment", new StandardEnvironment());
|
||||
this.beanFactory.registerSingleton("environment", environment);
|
||||
compile(beanDefinition, (instanceSupplier, compiled) -> {
|
||||
Object bean = getBean(beanDefinition, instanceSupplier);
|
||||
assertThat(bean).isInstanceOf(EnvironmentAwareComponent.class);
|
||||
assertThat(bean).hasFieldOrPropertyWithValue("environment", environment);
|
||||
assertThat(compiled.getSourceFile()).contains(
|
||||
"getBeanFactory().getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent(");
|
||||
});
|
||||
@@ -162,11 +164,13 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
@Test
|
||||
void generateWhenHasNonPublicConstructorWithInnerClassAndParameter() {
|
||||
BeanDefinition beanDefinition = new RootBeanDefinition(EnvironmentAwareComponentWithoutPublicConstructor.class);
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
|
||||
this.beanFactory.registerSingleton("environment", new StandardEnvironment());
|
||||
this.beanFactory.registerSingleton("environment", environment);
|
||||
compile(beanDefinition, (instanceSupplier, compiled) -> {
|
||||
Object bean = getBean(beanDefinition, instanceSupplier);
|
||||
assertThat(bean).isInstanceOf(EnvironmentAwareComponentWithoutPublicConstructor.class);
|
||||
assertThat(bean).hasFieldOrPropertyWithValue("environment", environment);
|
||||
assertThat(compiled.getSourceFile()).doesNotContain(
|
||||
"getBeanFactory().getBean(InnerComponentConfiguration.class)");
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -84,7 +84,7 @@ class PropertyPlaceholderConfigurerTests {
|
||||
.getBeanDefinition());
|
||||
|
||||
PropertyPlaceholderConfigurer pc = new PropertyPlaceholderConfigurer();
|
||||
Resource resource = new ClassPathResource("PropertyPlaceholderConfigurerTests.properties", this.getClass());
|
||||
Resource resource = new ClassPathResource("PropertyPlaceholderConfigurerTests.properties", getClass());
|
||||
pc.setLocation(resource);
|
||||
pc.postProcessBeanFactory(bf);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -46,7 +46,7 @@ class DuplicateBeanIdTests {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
|
||||
assertThatException().as("duplicate ids in same nesting level").isThrownBy(() ->
|
||||
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-sameLevel-context.xml", this.getClass())));
|
||||
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-sameLevel-context.xml", getClass())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -54,7 +54,7 @@ class DuplicateBeanIdTests {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.setAllowBeanDefinitionOverriding(true);
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
|
||||
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-multiLevel-context.xml", this.getClass()));
|
||||
reader.loadBeanDefinitions(new ClassPathResource("DuplicateBeanIdTests-multiLevel-context.xml", getClass()));
|
||||
TestBean testBean = bf.getBean(TestBean.class); // there should be only one
|
||||
assertThat(testBean.getName()).isEqualTo("nested");
|
||||
}
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -36,7 +36,7 @@ class NestedBeansElementAttributeRecursionTests {
|
||||
void defaultLazyInit() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", this.getClass()));
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", getClass()));
|
||||
|
||||
assertLazyInits(bf);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class NestedBeansElementAttributeRecursionTests {
|
||||
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(bf);
|
||||
xmlBeanDefinitionReader.setValidating(false);
|
||||
xmlBeanDefinitionReader.loadBeanDefinitions(
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", this.getClass()));
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-lazy-context.xml", getClass()));
|
||||
|
||||
assertLazyInits(bf);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class NestedBeansElementAttributeRecursionTests {
|
||||
void defaultMerge() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", this.getClass()));
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", getClass()));
|
||||
|
||||
assertMerge(bf);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ class NestedBeansElementAttributeRecursionTests {
|
||||
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(bf);
|
||||
xmlBeanDefinitionReader.setValidating(false);
|
||||
xmlBeanDefinitionReader.loadBeanDefinitions(
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", this.getClass()));
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-merge-context.xml", getClass()));
|
||||
|
||||
assertMerge(bf);
|
||||
}
|
||||
@@ -109,7 +109,7 @@ class NestedBeansElementAttributeRecursionTests {
|
||||
void defaultAutowireCandidates() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", this.getClass()));
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", getClass()));
|
||||
|
||||
assertAutowireCandidates(bf);
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class NestedBeansElementAttributeRecursionTests {
|
||||
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(bf);
|
||||
xmlBeanDefinitionReader.setValidating(false);
|
||||
xmlBeanDefinitionReader.loadBeanDefinitions(
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", this.getClass()));
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-autowire-candidates-context.xml", getClass()));
|
||||
|
||||
assertAutowireCandidates(bf);
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class NestedBeansElementAttributeRecursionTests {
|
||||
void initMethod() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-init-destroy-context.xml", this.getClass()));
|
||||
new ClassPathResource("NestedBeansElementAttributeRecursionTests-init-destroy-context.xml", getClass()));
|
||||
|
||||
InitDestroyBean beanA = bf.getBean("beanA", InitDestroyBean.class);
|
||||
InitDestroyBean beanB = bf.getBean("beanB", InitDestroyBean.class);
|
||||
|
||||
+3
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -26,15 +26,14 @@ import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for new nested beans element support in Spring XML
|
||||
*
|
||||
* @author Chris Beams
|
||||
*/
|
||||
class NestedBeansElementTests {
|
||||
private final Resource XML =
|
||||
new ClassPathResource("NestedBeansElementTests-context.xml", this.getClass());
|
||||
|
||||
private final Resource XML = new ClassPathResource("NestedBeansElementTests-context.xml", getClass());
|
||||
|
||||
@Test
|
||||
void getBean_withoutActiveProfile() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -93,7 +93,6 @@ class BeanUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Instantiate value class`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = "Hello value class!"
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ValueClass(value))
|
||||
@@ -102,7 +101,6 @@ class BeanUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Instantiate value class with multiple constructors`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ValueClassWithMultipleConstructors::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = "Hello value class!"
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ValueClassWithMultipleConstructors(value))
|
||||
@@ -111,7 +109,6 @@ class BeanUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Instantiate class with value class parameter`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = ValueClass("Hello value class!")
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ConstructorWithValueClass(value))
|
||||
@@ -120,7 +117,6 @@ class BeanUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Instantiate class with nullable value class parameter`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithNullableValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = ValueClass("Hello value class!")
|
||||
var instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ConstructorWithNullableValueClass(value))
|
||||
@@ -131,7 +127,6 @@ class BeanUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Instantiate primitive value class`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(PrimitiveValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = 0
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(PrimitiveValueClass(value))
|
||||
@@ -140,7 +135,6 @@ class BeanUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Instantiate class with primitive value class parameter`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithPrimitiveValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = PrimitiveValueClass(0)
|
||||
val instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ConstructorWithPrimitiveValueClass(value))
|
||||
@@ -149,7 +143,6 @@ class BeanUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Instantiate class with nullable primitive value class parameter`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(ConstructorWithNullablePrimitiveValueClass::class.java)!!
|
||||
assertThat(constructor).isNotNull()
|
||||
val value = PrimitiveValueClass(0)
|
||||
var instance = BeanUtils.instantiateClass(constructor, value)
|
||||
assertThat(instance).isEqualTo(ConstructorWithNullablePrimitiveValueClass(value))
|
||||
@@ -157,6 +150,48 @@ class BeanUtilsKotlinTests {
|
||||
assertThat(instance).isEqualTo(ConstructorWithNullablePrimitiveValueClass(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get parameter names with Foo`() {
|
||||
val ctor = BeanUtils.findPrimaryConstructor(Foo::class.java)!!
|
||||
val names = BeanUtils.getParameterNames(ctor)
|
||||
assertThat(names).containsExactly("param1", "param2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get parameter names filters out DefaultConstructorMarker with ConstructorWithValueClass`() {
|
||||
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithValueClass::class.java)!!
|
||||
val names = BeanUtils.getParameterNames(ctor)
|
||||
assertThat(names).containsExactly("value")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithNullableValueClass`() {
|
||||
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithNullableValueClass::class.java)!!
|
||||
val names = BeanUtils.getParameterNames(ctor)
|
||||
assertThat(names).containsExactly("value")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithPrimitiveValueClass`() {
|
||||
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithPrimitiveValueClass::class.java)!!
|
||||
val names = BeanUtils.getParameterNames(ctor)
|
||||
assertThat(names).containsExactly("value")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithNullablePrimitiveValueClass`() {
|
||||
val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithNullablePrimitiveValueClass::class.java)!!
|
||||
val names = BeanUtils.getParameterNames(ctor)
|
||||
assertThat(names).containsExactly("value")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getParameterNames with ClassWithZeroParameterCtor`() {
|
||||
val ctor = BeanUtils.findPrimaryConstructor(ClassWithZeroParameterCtor::class.java)!!
|
||||
val names = BeanUtils.getParameterNames(ctor)
|
||||
assertThat(names).isEmpty()
|
||||
}
|
||||
|
||||
|
||||
class Foo(val param1: String, val param2: Int)
|
||||
|
||||
@@ -216,4 +251,6 @@ class BeanUtilsKotlinTests {
|
||||
|
||||
data class ConstructorWithNullablePrimitiveValueClass(val value: PrimitiveValueClass?)
|
||||
|
||||
class ClassWithZeroParameterCtor()
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -28,7 +28,10 @@ public class InnerComponentConfiguration {
|
||||
|
||||
public class EnvironmentAwareComponent {
|
||||
|
||||
final Environment environment;
|
||||
|
||||
public EnvironmentAwareComponent(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +43,10 @@ public class InnerComponentConfiguration {
|
||||
|
||||
public class EnvironmentAwareComponentWithoutPublicConstructor {
|
||||
|
||||
final Environment environment;
|
||||
|
||||
EnvironmentAwareComponentWithoutPublicConstructor(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -27,6 +28,7 @@ import java.util.Set;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
@@ -286,8 +288,8 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Registering bean definition for @Bean method %s.%s()",
|
||||
configClass.getMetadata().getClassName(), beanName));
|
||||
logger.trace("Registering bean definition for @Bean method %s.%s()"
|
||||
.formatted(configClass.getMetadata().getClassName(), beanName));
|
||||
}
|
||||
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
|
||||
}
|
||||
@@ -344,9 +346,8 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
"@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " +
|
||||
"already exists. This top-level bean definition is considered as an override.",
|
||||
beanMethod, beanName));
|
||||
logger.debug("Skipping bean definition for %s: a definition for bean '%s' already exists. " +
|
||||
"This top-level bean definition is considered as an override.".formatted(beanMethod, beanName));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -372,9 +373,11 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
|
||||
if (reader == null) {
|
||||
try {
|
||||
Constructor<? extends BeanDefinitionReader> constructor =
|
||||
readerClass.getDeclaredConstructor(BeanDefinitionRegistry.class);
|
||||
// Instantiate the specified BeanDefinitionReader
|
||||
reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
|
||||
// Delegate the current ResourceLoader to it if possible
|
||||
reader = BeanUtils.instantiateClass(constructor, this.registry);
|
||||
// Delegate the current ResourceLoader and Environment to it if possible
|
||||
if (reader instanceof AbstractBeanDefinitionReader abdr) {
|
||||
abdr.setResourceLoader(this.resourceLoader);
|
||||
abdr.setEnvironment(this.environment);
|
||||
@@ -383,11 +386,9 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException(
|
||||
"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
|
||||
"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
|
||||
reader.loadBeanDefinitions(resource);
|
||||
});
|
||||
}
|
||||
|
||||
+9
-2
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -139,14 +140,20 @@ class ConfigurationClassEnhancer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given config class relies on package visibility,
|
||||
* either for the class itself or for any of its {@code @Bean} methods.
|
||||
* Checks whether the given config class relies on package visibility, either for
|
||||
* the class and any of its constructors or for any of its {@code @Bean} methods.
|
||||
*/
|
||||
private boolean reliesOnPackageVisibility(Class<?> configSuperClass) {
|
||||
int mod = configSuperClass.getModifiers();
|
||||
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
|
||||
return true;
|
||||
}
|
||||
for (Constructor<?> ctor : configSuperClass.getDeclaredConstructors()) {
|
||||
mod = ctor.getModifiers();
|
||||
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Method method : ReflectionUtils.getDeclaredMethods(configSuperClass)) {
|
||||
if (BeanAnnotationHelper.isBeanAnnotated(method)) {
|
||||
mod = method.getModifiers();
|
||||
|
||||
+19
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -29,14 +29,15 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* Indicates one or more resources containing bean definitions to import.
|
||||
*
|
||||
* <p>Like {@link Import @Import}, this annotation provides functionality similar to
|
||||
* the {@code <import/>} element in Spring XML. It is typically used when designing
|
||||
* {@link Configuration @Configuration} classes to be bootstrapped by an
|
||||
* {@link AnnotationConfigApplicationContext}, but where some XML functionality such
|
||||
* as namespaces is still necessary.
|
||||
* the {@code <import/>} element in Spring XML configuration. It is typically used
|
||||
* when designing {@link Configuration @Configuration} classes to be bootstrapped by
|
||||
* an {@link AnnotationConfigApplicationContext}, but where some XML functionality
|
||||
* such as namespaces is still necessary.
|
||||
*
|
||||
* <p>By default, arguments to the {@link #value} attribute will be processed using a
|
||||
* <p>By default, arguments to the {@link #locations() locations} or {@link #value() value}
|
||||
* attribute will be processed using a
|
||||
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader}
|
||||
* if ending in {@code ".groovy"}; otherwise, an
|
||||
* for resource locations ending in {@code ".groovy"}; otherwise, an
|
||||
* {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}
|
||||
* will be used to parse Spring {@code <beans/>} XML files. Optionally, the {@link #reader}
|
||||
* attribute may be declared, allowing the user to choose a custom {@link BeanDefinitionReader}
|
||||
@@ -77,12 +78,19 @@ public @interface ImportResource {
|
||||
|
||||
/**
|
||||
* {@link BeanDefinitionReader} implementation to use when processing
|
||||
* resources specified via the {@link #value} attribute.
|
||||
* resources specified via the {@link #locations() locations} or
|
||||
* {@link #value() value} attribute.
|
||||
* <p>The configured {@code BeanDefinitionReader} type must declare a
|
||||
* constructor that accepts a single
|
||||
* {@link org.springframework.beans.factory.support.BeanDefinitionRegistry
|
||||
* BeanDefinitionRegistry} argument.
|
||||
* <p>By default, the reader will be adapted to the resource path specified:
|
||||
* {@code ".groovy"} files will be processed with a
|
||||
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader GroovyBeanDefinitionReader};
|
||||
* whereas, all other resources will be processed with an
|
||||
* {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader XmlBeanDefinitionReader}.
|
||||
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader
|
||||
* GroovyBeanDefinitionReader}; whereas, all other resources will be processed
|
||||
* with an {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader
|
||||
* XmlBeanDefinitionReader}.
|
||||
* @see #locations
|
||||
* @see #value
|
||||
*/
|
||||
Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
|
||||
|
||||
+23
-3
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.ConfigurablePropertyResolver;
|
||||
import org.springframework.core.env.Environment;
|
||||
@@ -243,16 +244,34 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getProperty(String name) {
|
||||
// Declare String as covariant return type, since a String is actually required.
|
||||
public String getProperty(String name) {
|
||||
for (PropertySource<?> propertySource : super.source.getPropertySources()) {
|
||||
Object candidate = propertySource.getProperty(name);
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
return convertToString(candidate);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the supplied value to a {@link String} using the {@link ConversionService}
|
||||
* from the {@link Environment}.
|
||||
* <p>This is a modified version of
|
||||
* {@link org.springframework.core.env.AbstractPropertyResolver#convertValueIfNecessary(Object, Class)}.
|
||||
* @param value the value to convert
|
||||
* @return the converted value, or the original value if no conversion is necessary
|
||||
* @since 6.2.8
|
||||
*/
|
||||
@Nullable
|
||||
private String convertToString(Object value) {
|
||||
if (value instanceof String string) {
|
||||
return string;
|
||||
}
|
||||
return super.source.getConversionService().convert(value, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigurableEnvironmentPropertySource {propertySources=" + super.source.getPropertySources() + "}";
|
||||
@@ -279,7 +298,8 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getProperty(String name) {
|
||||
// Declare String as covariant return type, since a String is actually required.
|
||||
public String getProperty(String name) {
|
||||
return super.source.getProperty(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -250,7 +250,7 @@ public abstract class ValidationUtils {
|
||||
|
||||
Assert.notNull(errors, "Errors object must not be null");
|
||||
Object value = errors.getFieldValue(field);
|
||||
if (value == null ||!StringUtils.hasText(value.toString())) {
|
||||
if (value == null || !StringUtils.hasText(value.toString())) {
|
||||
errors.rejectValue(field, errorCode, errorArgs, defaultMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -130,7 +130,7 @@ class SimpleKeyGeneratorTests {
|
||||
|
||||
|
||||
private Object generateKey(Object[] arguments) {
|
||||
Method method = ReflectionUtils.findMethod(this.getClass(), "generateKey", Object[].class);
|
||||
Method method = ReflectionUtils.findMethod(getClass(), "generateKey", Object[].class);
|
||||
return this.generator.generate(this, method, arguments);
|
||||
}
|
||||
|
||||
|
||||
+38
@@ -104,6 +104,31 @@ class ConfigurationClassEnhancerTests {
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNonPublicConstructor() {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
|
||||
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new OverridingClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNonPublicMethod() {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
@@ -160,6 +185,19 @@ class ConfigurationClassEnhancerTests {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
public static class MyConfigWithNonPublicConstructor {
|
||||
|
||||
MyConfigWithNonPublicConstructor() {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String myBean() {
|
||||
return "bean";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
public static class MyConfigWithNonPublicMethod {
|
||||
|
||||
|
||||
+78
-53
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.context.annotation.configuration;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -25,18 +23,18 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.testfixture.env.MockPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ImportResource} support.
|
||||
* Integration tests for {@link ImportResource @ImportResource} support.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
@@ -45,81 +43,88 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class ImportResourceTests {
|
||||
|
||||
@Test
|
||||
void importXml() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlConfig.class);
|
||||
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
|
||||
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
|
||||
assertThat(tb.getName()).isEqualTo("myName");
|
||||
ctx.close();
|
||||
void importResource() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlConfig.class)) {
|
||||
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
|
||||
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
|
||||
assertThat(tb.getName()).isEqualTo("myName");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importXmlIsInheritedFromSuperclassDeclarations() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FirstLevelSubConfig.class);
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).isTrue();
|
||||
ctx.close();
|
||||
void importResourceIsInheritedFromSuperclassDeclarations() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FirstLevelSubConfig.class)) {
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importXmlIsMergedFromSuperclassDeclarations() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SecondLevelSubConfig.class);
|
||||
assertThat(ctx.containsBean("secondLevelXmlDeclaredBean")).as("failed to pick up second-level-declared XML bean").isTrue();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("failed to pick up parent-declared XML bean").isTrue();
|
||||
ctx.close();
|
||||
void importResourceIsMergedFromSuperclassDeclarations() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SecondLevelSubConfig.class)) {
|
||||
assertThat(ctx.containsBean("secondLevelXmlDeclaredBean")).as("failed to pick up second-level-declared XML bean").isTrue();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("failed to pick up parent-declared XML bean").isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importXmlWithNamespaceConfig() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithAopNamespaceConfig.class);
|
||||
Object bean = ctx.getBean("proxiedXmlBean");
|
||||
assertThat(AopUtils.isAopProxy(bean)).isTrue();
|
||||
ctx.close();
|
||||
void importResourceWithNamespaceConfig() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithAopNamespaceConfig.class)) {
|
||||
Object bean = ctx.getBean("proxiedXmlBean");
|
||||
assertThat(AopUtils.isAopProxy(bean)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importXmlWithOtherConfigurationClass() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithConfigurationClass.class);
|
||||
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
|
||||
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
|
||||
assertThat(tb.getName()).isEqualTo("myName");
|
||||
ctx.close();
|
||||
void importResourceWithOtherConfigurationClass() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlWithConfigurationClass.class)) {
|
||||
assertThat(ctx.containsBean("javaDeclaredBean")).as("did not contain java-declared bean").isTrue();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
|
||||
TestBean tb = ctx.getBean("javaDeclaredBean", TestBean.class);
|
||||
assertThat(tb.getName()).isEqualTo("myName");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importWithPlaceholder() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
PropertySource<?> propertySource = new MapPropertySource("test",
|
||||
Collections.<String, Object> singletonMap("test", "springframework"));
|
||||
ctx.getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
ctx.register(ImportXmlConfig.class);
|
||||
ctx.refresh();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
|
||||
ctx.close();
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()) {
|
||||
ctx.getEnvironment().getPropertySources().addFirst(new MockPropertySource("test").withProperty("test", "springframework"));
|
||||
ctx.register(ImportXmlConfig.class);
|
||||
ctx.refresh();
|
||||
assertThat(ctx.containsBean("xmlDeclaredBean")).as("did not contain xml-declared bean").isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importXmlWithAutowiredConfig() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlAutowiredConfig.class);
|
||||
String name = ctx.getBean("xmlBeanName", String.class);
|
||||
assertThat(name).isEqualTo("xml.declared");
|
||||
ctx.close();
|
||||
void importResourceWithAutowiredConfig() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlAutowiredConfig.class)) {
|
||||
String name = ctx.getBean("xmlBeanName", String.class);
|
||||
assertThat(name).isEqualTo("xml.declared");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importNonXmlResource() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportNonXmlResourceConfig.class);
|
||||
assertThat(ctx.containsBean("propertiesDeclaredBean")).isTrue();
|
||||
ctx.close();
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportNonXmlResourceConfig.class)) {
|
||||
assertThat(ctx.containsBean("propertiesDeclaredBean")).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importResourceWithPrivateReader() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportWithPrivateReaderConfig.class)) {
|
||||
assertThat(ctx.containsBean("propertiesDeclaredBean")).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
|
||||
static class ImportXmlConfig {
|
||||
|
||||
@Value("${name}")
|
||||
private String name;
|
||||
|
||||
@Bean public TestBean javaDeclaredBean() {
|
||||
return new TestBean(this.name);
|
||||
}
|
||||
@@ -146,6 +151,7 @@ class ImportResourceTests {
|
||||
|
||||
@Aspect
|
||||
static class AnAspect {
|
||||
|
||||
@Before("execution(* org.springframework.beans.testfixture.beans.TestBean.*(..))")
|
||||
public void advice() { }
|
||||
}
|
||||
@@ -158,18 +164,37 @@ class ImportResourceTests {
|
||||
@Configuration
|
||||
@ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
|
||||
static class ImportXmlAutowiredConfig {
|
||||
@Autowired TestBean xmlDeclaredBean;
|
||||
|
||||
@Bean public String xmlBeanName() {
|
||||
@Autowired
|
||||
TestBean xmlDeclaredBean;
|
||||
|
||||
@Bean
|
||||
public String xmlBeanName() {
|
||||
return xmlDeclaredBean.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Configuration
|
||||
@ImportResource(locations = "classpath:org/springframework/context/annotation/configuration/ImportNonXmlResourceConfig-context.properties",
|
||||
@ImportResource(locations = "org/springframework/context/annotation/configuration/ImportNonXmlResourceConfig.properties",
|
||||
reader = org.springframework.beans.factory.support.PropertiesBeanDefinitionReader.class)
|
||||
static class ImportNonXmlResourceConfig {
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Configuration
|
||||
@ImportResource(locations = "org/springframework/context/annotation/configuration/ImportNonXmlResourceConfig.properties",
|
||||
reader = PrivatePropertiesBeanDefinitionReader.class)
|
||||
static class ImportWithPrivateReaderConfig {
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static class PrivatePropertiesBeanDefinitionReader
|
||||
extends org.springframework.beans.factory.support.PropertiesBeanDefinitionReader {
|
||||
|
||||
PrivatePropertiesBeanDefinitionReader(BeanDefinitionRegistry registry) {
|
||||
super(registry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
-1
@@ -41,6 +41,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.AbstractPropertyResolver;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
@@ -90,6 +92,38 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
assertThat(ppc.getAppliedPropertySources()).isNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that a {@link Converter} registered in the {@link ConversionService}
|
||||
* used by the {@code Environment} is applied during placeholder resolution
|
||||
* against a {@link PropertySource} registered in the {@code Environment}.
|
||||
*/
|
||||
@Test // gh-34936
|
||||
void replacementFromEnvironmentPropertiesWithConversion() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("testBean",
|
||||
genericBeanDefinition(TestBean.class)
|
||||
.addPropertyValue("name", "${my.name}")
|
||||
.getBeanDefinition());
|
||||
|
||||
record Point(int x, int y) {
|
||||
}
|
||||
|
||||
Converter<Point, String> pointToStringConverter =
|
||||
point -> "(%d,%d)".formatted(point.x, point.y);
|
||||
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
conversionService.addConverter(Point.class, String.class, pointToStringConverter);
|
||||
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setConversionService(conversionService);
|
||||
env.setProperty("my.name", new Point(4,5));
|
||||
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("(4,5)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that a {@link PropertySource} added to the {@code Environment} after context
|
||||
* refresh (i.e., after {@link PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()}
|
||||
@@ -136,7 +170,7 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
.getBeanDefinition());
|
||||
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
Resource resource = new ClassPathResource("PropertySourcesPlaceholderConfigurerTests.properties", this.getClass());
|
||||
Resource resource = new ClassPathResource("PropertySourcesPlaceholderConfigurerTests.properties", getClass());
|
||||
ppc.setLocation(resource);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("foo");
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -32,6 +32,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
import org.junit.jupiter.params.support.ParameterDeclarations;
|
||||
|
||||
import static java.time.Instant.MAX;
|
||||
import static java.time.Instant.MIN;
|
||||
@@ -91,7 +92,7 @@ class InstantFormatterTests {
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public final Stream<Arguments> provideArguments(ExtensionContext context) {
|
||||
public final Stream<Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
|
||||
return provideArguments().map(Arguments::of).limit(DATA_SET_SIZE);
|
||||
}
|
||||
|
||||
@@ -137,7 +138,7 @@ class InstantFormatterTests {
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public Stream<Arguments> provideArguments(ExtensionContext context) {
|
||||
public Stream<Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
|
||||
return random.longs(DATA_SET_SIZE, Long.MIN_VALUE, Long.MAX_VALUE)
|
||||
.mapToObj(Instant::ofEpochMilli)
|
||||
.map(instant -> instant.truncatedTo(ChronoUnit.MILLIS))
|
||||
|
||||
+13
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -27,7 +27,7 @@ import org.springframework.core.testfixture.env.MockPropertySource;
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 3.2
|
||||
* @see org.springframework.core.testfixture.env.MockPropertySource
|
||||
* @see MockPropertySource
|
||||
*/
|
||||
public class MockEnvironment extends AbstractEnvironment {
|
||||
|
||||
@@ -44,19 +44,23 @@ public class MockEnvironment extends AbstractEnvironment {
|
||||
|
||||
/**
|
||||
* Set a property on the underlying {@link MockPropertySource} for this environment.
|
||||
* @since 6.2.8
|
||||
* @see MockPropertySource#setProperty(String, Object)
|
||||
*/
|
||||
public void setProperty(String key, String value) {
|
||||
this.propertySource.setProperty(key, value);
|
||||
public void setProperty(String name, Object value) {
|
||||
this.propertySource.setProperty(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty} that returns the current instance.
|
||||
* Useful for method chaining and fluent-style use.
|
||||
* Convenient synonym for {@link #setProperty(String, Object)} that returns
|
||||
* the current instance.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockEnvironment} instance
|
||||
* @see MockPropertySource#withProperty
|
||||
* @since 6.2.8
|
||||
* @see MockPropertySource#withProperty(String, Object)
|
||||
*/
|
||||
public MockEnvironment withProperty(String key, String value) {
|
||||
setProperty(key, value);
|
||||
public MockEnvironment withProperty(String name, Object value) {
|
||||
setProperty(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -81,7 +81,7 @@ public abstract class AbstractTypeReference implements TypeReference {
|
||||
|
||||
@Override
|
||||
public int compareTo(TypeReference other) {
|
||||
return this.getCanonicalName().compareToIgnoreCase(other.getCanonicalName());
|
||||
return getCanonicalName().compareToIgnoreCase(other.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -150,7 +150,8 @@ public class BindingReflectionHintsRegistrar {
|
||||
String companionClassName = clazz.getCanonicalName() + KOTLIN_COMPANION_SUFFIX;
|
||||
if (ClassUtils.isPresent(companionClassName, null)) {
|
||||
Class<?> companionClass = ClassUtils.resolveClassName(companionClassName, null);
|
||||
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer");
|
||||
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer",
|
||||
(Class<?>[]) null);
|
||||
if (serializerMethod != null) {
|
||||
hints.registerMethod(serializerMethod, ExecutableMode.INVOKE);
|
||||
}
|
||||
|
||||
+1
-1
@@ -198,7 +198,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
||||
public void validateRequiredProperties() {
|
||||
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
|
||||
for (String key : this.requiredProperties) {
|
||||
if (this.getProperty(key) == null) {
|
||||
if (getProperty(key) == null) {
|
||||
ex.addMissingRequiredProperty(key);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -45,7 +45,6 @@ import org.springframework.util.ResourceUtils;
|
||||
*/
|
||||
public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
|
||||
@SuppressWarnings("try")
|
||||
@Override
|
||||
public boolean exists() {
|
||||
try {
|
||||
@@ -90,9 +89,15 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
// existence of the entry (or the jar root in case of no entryName).
|
||||
// getJarFile() called for enforced presence check of the jar file,
|
||||
// throwing a NoSuchFileException otherwise (turned to false below).
|
||||
try (JarFile jarFile = jarCon.getJarFile()) {
|
||||
JarFile jarFile = jarCon.getJarFile();
|
||||
try {
|
||||
return (jarCon.getEntryName() == null || jarCon.getJarEntry() != null);
|
||||
}
|
||||
finally {
|
||||
if (!jarCon.getUseCaches()) {
|
||||
jarFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (con.getContentLengthLong() > 0) {
|
||||
return true;
|
||||
|
||||
+38
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -18,6 +18,8 @@ package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -103,10 +105,44 @@ final class DataBufferInputStream extends InputStream {
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readNBytes(int len) throws IOException {
|
||||
if (len < 0) {
|
||||
throw new IllegalArgumentException("len < 0");
|
||||
}
|
||||
checkClosed();
|
||||
int size = Math.min(available(), len);
|
||||
byte[] out = new byte[size];
|
||||
this.dataBuffer.read(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
checkClosed();
|
||||
if (n <= 0) {
|
||||
return 0L;
|
||||
}
|
||||
int skipped = Math.min(available(), n > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) n);
|
||||
this.dataBuffer.readPosition(Math.min(this.end, this.dataBuffer.readPosition() + skipped));
|
||||
return skipped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long transferTo(OutputStream out) throws IOException {
|
||||
Objects.requireNonNull(out, "out");
|
||||
checkClosed();
|
||||
if (available() == 0) {
|
||||
return 0L;
|
||||
}
|
||||
byte[] buf = readAllBytes();
|
||||
out.write(buf);
|
||||
return buf.length;
|
||||
}
|
||||
|
||||
private void checkClosed() throws IOException {
|
||||
if (this.closed) {
|
||||
throw new IOException("DataBufferInputStream is closed");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.lang;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
@@ -77,7 +75,6 @@ import java.lang.annotation.Target;
|
||||
* NullAway custom contract annotations</a>
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Contract {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -25,13 +25,14 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Simple utility methods for dealing with streams. The copy methods of this class are
|
||||
* similar to those defined in {@link FileCopyUtils} except that all affected streams are
|
||||
* left open when done. All copy methods use a block size of 8192 bytes.
|
||||
* Simple utility methods for dealing with streams.
|
||||
*
|
||||
* <p>The copy methods of this class are similar to those defined in
|
||||
* {@link FileCopyUtils} except that all affected streams are left open when done.
|
||||
* All copy methods use a block size of {@value #BUFFER_SIZE} bytes.
|
||||
*
|
||||
* <p>Mainly for use within the framework, but also useful for application code.
|
||||
*
|
||||
@@ -190,14 +191,14 @@ public abstract class StreamUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Drain the remaining content of the given InputStream.
|
||||
* <p>Leaves the InputStream open when done.
|
||||
* @param in the InputStream to drain
|
||||
* @return the number of bytes read
|
||||
* Drain the remaining content of the given {@link InputStream}.
|
||||
* <p>Leaves the {@code InputStream} open when done.
|
||||
* @param in the {@code InputStream} to drain
|
||||
* @return the number of bytes read, or {@code 0} if the supplied
|
||||
* {@code InputStream} is {@code null} or empty
|
||||
* @throws IOException in case of I/O errors
|
||||
* @since 4.3
|
||||
*/
|
||||
@Contract("null -> fail")
|
||||
public static int drain(@Nullable InputStream in) throws IOException {
|
||||
if (in == null) {
|
||||
return 0;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -32,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class AnnotationBackCompatibilityTests {
|
||||
|
||||
@Test
|
||||
void multiplRoutesToMetaAnnotation() {
|
||||
void multipleRoutesToMetaAnnotation() {
|
||||
Class<?> source = WithMetaMetaTestAnnotation1AndMetaTestAnnotation2.class;
|
||||
// Merged annotation chooses lowest depth
|
||||
MergedAnnotation<TestAnnotation> mergedAnnotation = MergedAnnotations.from(source).get(TestAnnotation.class);
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -983,7 +983,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDirectFromClassgetDirectFromClassMetaMetaAnnotatedClass() {
|
||||
void getDirectFromClassWithMetaMetaAnnotatedClass() {
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(
|
||||
MetaMetaAnnotatedClass.class, SearchStrategy.TYPE_HIERARCHY).get(Component.class);
|
||||
assertThat(annotation.getString("value")).isEqualTo("meta2");
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -46,7 +46,7 @@ class ConverterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void andThenCanConvertfromDifferentSourceType() {
|
||||
void andThenCanConvertFromDifferentSourceType() {
|
||||
Converter<String, Integer> length = String::length;
|
||||
assertThat(length.andThen(this.moduloTwo).convert("example")).isEqualTo(1);
|
||||
assertThat(length.andThen(this.addOne).convert("example")).isEqualTo(8);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -342,6 +343,48 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
assertThat(len).isEqualTo(3);
|
||||
assertThat(bytes).containsExactly('c', 'd', 'e');
|
||||
|
||||
buffer.readPosition(0);
|
||||
inputStream = buffer.asInputStream();
|
||||
assertThat(inputStream.readAllBytes()).asString().isEqualTo("abcde");
|
||||
assertThat(inputStream.available()).isEqualTo(0);
|
||||
assertThat(inputStream.readAllBytes()).isEmpty();
|
||||
|
||||
buffer.readPosition(0);
|
||||
inputStream = buffer.asInputStream();
|
||||
inputStream.mark(5);
|
||||
assertThat(inputStream.readNBytes(0)).isEmpty();
|
||||
assertThat(inputStream.readNBytes(1000)).asString().isEqualTo("abcde");
|
||||
inputStream.reset();
|
||||
assertThat(inputStream.readNBytes(3)).asString().isEqualTo("abc");
|
||||
assertThat(inputStream.readNBytes(2)).asString().isEqualTo("de");
|
||||
assertThat(inputStream.readNBytes(10)).isEmpty();
|
||||
|
||||
buffer.readPosition(0);
|
||||
inputStream = buffer.asInputStream();
|
||||
inputStream.mark(5);
|
||||
assertThat(inputStream.skip(1)).isEqualTo(1);
|
||||
assertThat(inputStream.readAllBytes()).asString().isEqualTo("bcde");
|
||||
assertThat(inputStream.skip(10)).isEqualTo(0);
|
||||
assertThat(inputStream.available()).isEqualTo(0);
|
||||
inputStream.reset();
|
||||
assertThat(inputStream.skip(100)).isEqualTo(5);
|
||||
assertThat(inputStream.available()).isEqualTo(0);
|
||||
|
||||
buffer.readPosition(0);
|
||||
inputStream = buffer.asInputStream();
|
||||
inputStream.mark(5);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
assertThat(inputStream.transferTo(out)).isEqualTo(5);
|
||||
assertThat(out.toByteArray()).asString().isEqualTo("abcde");
|
||||
assertThat(inputStream.available()).isEqualTo(0);
|
||||
out.reset();
|
||||
inputStream.reset();
|
||||
assertThat(inputStream.read()).isEqualTo('a');
|
||||
assertThat(inputStream.transferTo(out)).isEqualTo(4);
|
||||
assertThat(out.toByteArray()).asString().isEqualTo("bcde");
|
||||
assertThat(inputStream.available()).isEqualTo(0);
|
||||
assertThat(inputStream.transferTo(OutputStream.nullOutputStream())).isEqualTo(0);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
|
||||
+23
-2
@@ -44,6 +44,8 @@ import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -298,8 +300,8 @@ class PathMatchingResourcePatternResolverTests {
|
||||
@Test
|
||||
void rootPatternRetrievalInJarFiles() throws IOException {
|
||||
assertThat(resolver.getResources("classpath*:aspectj*.dtd")).extracting(Resource::getFilename)
|
||||
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
|
||||
.containsExactly("aspectj_1_5_0.dtd");
|
||||
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
|
||||
.containsExactly("aspectj_1_5_0.dtd");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +312,16 @@ class PathMatchingResourcePatternResolverTests {
|
||||
@TempDir
|
||||
Path temp;
|
||||
|
||||
@BeforeAll
|
||||
static void suppressJarCaches() {
|
||||
URLConnection.setDefaultUseCaches("jar", false);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void restoreJarCaches() {
|
||||
URLConnection.setDefaultUseCaches("jar", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaDashJarFindsClassPathManifestEntries() throws Exception {
|
||||
Path lib = this.temp.resolve("lib");
|
||||
@@ -333,6 +345,7 @@ class PathMatchingResourcePatternResolverTests {
|
||||
StreamUtils.copy("test", StandardCharsets.UTF_8, jar);
|
||||
jar.closeEntry();
|
||||
}
|
||||
|
||||
assertThat(new FileSystemResource(path).exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isTrue();
|
||||
@@ -340,6 +353,14 @@ class PathMatchingResourcePatternResolverTests {
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isFalse();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt").exists()).isFalse();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + "X" + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/none.txt").exists()).isFalse();
|
||||
|
||||
Resource resource = new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR + "assets/file.txt");
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
assertThat(resource.exists()).isTrue();
|
||||
assertThat(resource.createRelative("file.txt").exists()).isTrue();
|
||||
assertThat(new UrlResource(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR).exists()).isTrue();
|
||||
is.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeApplicationJar(Path path) throws Exception {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -491,8 +491,8 @@ class AntPathMatcherTests {
|
||||
assertThat(comparator.compare("/hotels/{hotel}/bookings/{booking}", "/hotels/{hotel}/booking")).isEqualTo(1);
|
||||
|
||||
// SPR-10550
|
||||
assertThat(comparator.compare("/hotels/{hotel}/bookings/{booking}/cutomers/{customer}", "/**")).isEqualTo(-1);
|
||||
assertThat(comparator.compare("/**", "/hotels/{hotel}/bookings/{booking}/cutomers/{customer}")).isEqualTo(1);
|
||||
assertThat(comparator.compare("/hotels/{hotel}/bookings/{booking}/customers/{customer}", "/**")).isEqualTo(-1);
|
||||
assertThat(comparator.compare("/**", "/hotels/{hotel}/bookings/{booking}/customers/{customer}")).isEqualTo(1);
|
||||
assertThat(comparator.compare("/**", "/**")).isEqualTo(0);
|
||||
|
||||
assertThat(comparator.compare("/hotels/{hotel}", "/hotels/*")).isEqualTo(-1);
|
||||
@@ -505,8 +505,8 @@ class AntPathMatcherTests {
|
||||
assertThat(comparator.compare("/hotels/{hotel}", "/hotels/{hotel}.*")).isEqualTo(2);
|
||||
|
||||
// SPR-6741
|
||||
assertThat(comparator.compare("/hotels/{hotel}/bookings/{booking}/cutomers/{customer}", "/hotels/**")).isEqualTo(-1);
|
||||
assertThat(comparator.compare("/hotels/**", "/hotels/{hotel}/bookings/{booking}/cutomers/{customer}")).isEqualTo(1);
|
||||
assertThat(comparator.compare("/hotels/{hotel}/bookings/{booking}/customers/{customer}", "/hotels/**")).isEqualTo(-1);
|
||||
assertThat(comparator.compare("/hotels/**", "/hotels/{hotel}/bookings/{booking}/customers/{customer}")).isEqualTo(1);
|
||||
assertThat(comparator.compare("/hotels/foo/bar/**", "/hotels/{hotel}")).isEqualTo(1);
|
||||
assertThat(comparator.compare("/hotels/{hotel}", "/hotels/foo/bar/**")).isEqualTo(-1);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -228,7 +228,7 @@ class ClassUtilsTests {
|
||||
|
||||
@Test
|
||||
void getShortNameAsProperty() {
|
||||
String shortName = ClassUtils.getShortNameAsProperty(this.getClass());
|
||||
String shortName = ClassUtils.getShortNameAsProperty(getClass());
|
||||
assertThat(shortName).as("Class name did not match").isEqualTo("classUtilsTests");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -382,7 +382,7 @@ class CollectionUtilsTests {
|
||||
if (this == rhs) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == null || this.getClass() != rhs.getClass()) {
|
||||
if (rhs == null || getClass() != rhs.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Instance instance = (Instance) rhs;
|
||||
|
||||
+21
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -81,6 +81,12 @@ class BindingReflectionHintsRegistrarKotlinTests {
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(SampleClass::class.java)
|
||||
.withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS)).accepts(hints)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Register reflection hints on serializer function with parameter`() {
|
||||
bindingRegistrar.registerReflectionHints(hints.reflection(), SampleResult::class.java)
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleResult.Companion::class.java, "serializer")).accepts(hints)
|
||||
}
|
||||
}
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
@@ -89,3 +95,17 @@ class SampleSerializableClass(val name: String)
|
||||
data class SampleDataClass(val name: String, val isNonNullable: Boolean, val isNullable: Boolean?)
|
||||
|
||||
class SampleClass(val name: String)
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
data class SampleResult <T>(
|
||||
val code: Int,
|
||||
val message: String,
|
||||
val data: T,
|
||||
) {
|
||||
companion object {
|
||||
private const val SUCCESS: Int = 200
|
||||
private const val FAILURE: Int = 500
|
||||
fun <T> success(message: String, data: T) = SampleResult<T>(code = SUCCESS, message = message, data = data)
|
||||
fun <T> failure(message: String, data: T) = SampleResult<T>(code = FAILURE, message = message, data = data)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test
|
||||
/**
|
||||
* Kotlin tests for [BridgeMethodResolver].
|
||||
*
|
||||
* @author Sebastien Deleuzes
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class BridgeMethodResolverKotlinTests {
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -26,7 +26,7 @@ import org.springframework.core.env.PropertySource;
|
||||
* a user-provided {@link Properties} object, or if omitted during construction,
|
||||
* the implementation will initialize its own.
|
||||
*
|
||||
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* <p>The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* convenience, for example:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
@@ -95,7 +95,7 @@ public class MockPropertySource extends PropertiesPropertySource {
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty} that returns the current instance.
|
||||
* Useful for method chaining and fluent-style use.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockPropertySource} instance
|
||||
*/
|
||||
public MockPropertySource withProperty(String name, Object value) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -691,9 +691,8 @@ public class CodeFlow implements Opcodes {
|
||||
* Determine whether the given number is to be considered as an integer
|
||||
* for the purposes of a numeric operation at the bytecode level.
|
||||
* @param number the number to check
|
||||
* @return {@code true} if it is an {@link Integer}, {@link Short} or {@link Byte}
|
||||
* @return {@code true} if it is an {@link Integer}, {@link Short}, or {@link Byte}
|
||||
*/
|
||||
@Contract("null -> false")
|
||||
public static boolean isIntegerForNumericOp(Number number) {
|
||||
return (number instanceof Integer || number instanceof Short || number instanceof Byte);
|
||||
}
|
||||
|
||||
+1
-1
@@ -276,7 +276,7 @@ class VariableAndFunctionTests extends AbstractExpressionTests {
|
||||
|
||||
@Test
|
||||
void functionMethodMustBeStatic() throws Exception {
|
||||
context.registerFunction("nonStatic", this.getClass().getMethod("nonStatic"));
|
||||
context.registerFunction("nonStatic", getClass().getMethod("nonStatic"));
|
||||
SpelExpression expression = parser.parseRaw("#nonStatic()");
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> expression.getValue(context))
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -174,7 +174,7 @@ class MappingJackson2MessageConverterTests {
|
||||
|
||||
@Test
|
||||
void toTextMessageWithReturnType() throws JMSException, NoSuchMethodException {
|
||||
Method method = this.getClass().getDeclaredMethod("summary");
|
||||
Method method = getClass().getDeclaredMethod("summary");
|
||||
MethodParameter returnType = new MethodParameter(method, -1);
|
||||
testToTextMessageWithReturnType(returnType);
|
||||
verify(sessionMock).createTextMessage("{\"name\":\"test\"}");
|
||||
@@ -188,7 +188,7 @@ class MappingJackson2MessageConverterTests {
|
||||
|
||||
@Test
|
||||
void toTextMessageWithReturnTypeAndNoJsonView() throws JMSException, NoSuchMethodException {
|
||||
Method method = this.getClass().getDeclaredMethod("none");
|
||||
Method method = getClass().getDeclaredMethod("none");
|
||||
MethodParameter returnType = new MethodParameter(method, -1);
|
||||
|
||||
testToTextMessageWithReturnType(returnType);
|
||||
@@ -197,7 +197,7 @@ class MappingJackson2MessageConverterTests {
|
||||
|
||||
@Test
|
||||
void toTextMessageWithReturnTypeAndMultipleJsonViews() throws NoSuchMethodException {
|
||||
Method method = this.getClass().getDeclaredMethod("invalid");
|
||||
Method method = getClass().getDeclaredMethod("invalid");
|
||||
MethodParameter returnType = new MethodParameter(method, -1);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -332,7 +332,7 @@ public class MessageHeaderAccessor {
|
||||
protected void verifyType(@Nullable String headerName, @Nullable Object headerValue) {
|
||||
if (headerName != null && headerValue != null) {
|
||||
if (MessageHeaders.ERROR_CHANNEL.equals(headerName) ||
|
||||
MessageHeaders.REPLY_CHANNEL.endsWith(headerName)) {
|
||||
MessageHeaders.REPLY_CHANNEL.equals(headerName)) {
|
||||
if (!(headerValue instanceof MessageChannel || headerValue instanceof String)) {
|
||||
throw new IllegalArgumentException(
|
||||
"'" + headerName + "' header value must be a MessageChannel or String");
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -59,9 +59,9 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SubscriptionMethodReturnValueHandlerTests {
|
||||
class SubscriptionMethodReturnValueHandlerTests {
|
||||
|
||||
public static final MimeType MIME_TYPE = new MimeType("text", "plain", StandardCharsets.UTF_8);
|
||||
private static final MimeType MIME_TYPE = new MimeType("text", "plain", StandardCharsets.UTF_8);
|
||||
|
||||
private static final String PAYLOAD = "payload";
|
||||
|
||||
@@ -95,16 +95,16 @@ public class SubscriptionMethodReturnValueHandlerTests {
|
||||
jsonMessagingTemplate.setMessageConverter(new MappingJackson2MessageConverter());
|
||||
this.jsonHandler = new SubscriptionMethodReturnValueHandler(jsonMessagingTemplate);
|
||||
|
||||
Method method = this.getClass().getDeclaredMethod("getData");
|
||||
Method method = getClass().getDeclaredMethod("getData");
|
||||
this.subscribeEventReturnType = new MethodParameter(method, -1);
|
||||
|
||||
method = this.getClass().getDeclaredMethod("getDataAndSendTo");
|
||||
method = getClass().getDeclaredMethod("getDataAndSendTo");
|
||||
this.subscribeEventSendToReturnType = new MethodParameter(method, -1);
|
||||
|
||||
method = this.getClass().getDeclaredMethod("handle");
|
||||
method = getClass().getDeclaredMethod("handle");
|
||||
this.messageMappingReturnType = new MethodParameter(method, -1);
|
||||
|
||||
method = this.getClass().getDeclaredMethod("getJsonView");
|
||||
method = getClass().getDeclaredMethod("getJsonView");
|
||||
this.subscribeEventJsonViewReturnType = new MethodParameter(method, -1);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public class SubscriptionMethodReturnValueHandlerTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void testHeadersPassedToMessagingTemplate() throws Exception {
|
||||
void testHeadersPassedToMessagingTemplate() throws Exception {
|
||||
String sessionId = "sess1";
|
||||
String subscriptionId = "subs1";
|
||||
String destination = "/dest";
|
||||
|
||||
+20
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -34,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Mengqi Xu
|
||||
*/
|
||||
class MessageBuilderTests {
|
||||
|
||||
@@ -238,4 +239,22 @@ class MessageBuilderTests {
|
||||
assertThat(message3.getHeaders().get("foo")).isEqualTo("bar3");
|
||||
}
|
||||
|
||||
@Test // gh-34949
|
||||
void buildMessageWithReplyChannelHeader() {
|
||||
MessageHeaderAccessor headerAccessor = new MessageHeaderAccessor();
|
||||
MessageBuilder<?> messageBuilder = MessageBuilder.withPayload("payload").setHeaders(headerAccessor);
|
||||
|
||||
headerAccessor.setHeader(MessageHeaders.REPLY_CHANNEL, "foo");
|
||||
Message<?> message1 = messageBuilder.build();
|
||||
assertThat(message1.getHeaders().get(MessageHeaders.REPLY_CHANNEL)).isEqualTo("foo");
|
||||
|
||||
headerAccessor.setHeader("hannel", 0);
|
||||
Message<?> message2 = messageBuilder.build();
|
||||
assertThat(message2.getHeaders().get("hannel")).isEqualTo(0);
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> headerAccessor.setHeader(MessageHeaders.REPLY_CHANNEL, 0))
|
||||
.withMessage("'%s' header value must be a MessageChannel or String", MessageHeaders.REPLY_CHANNEL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+34
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -26,7 +26,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 3.2
|
||||
* @see org.springframework.mock.env.MockPropertySource
|
||||
* @see MockPropertySource
|
||||
*/
|
||||
public class MockEnvironment extends AbstractEnvironment {
|
||||
|
||||
@@ -43,19 +43,43 @@ public class MockEnvironment extends AbstractEnvironment {
|
||||
|
||||
/**
|
||||
* Set a property on the underlying {@link MockPropertySource} for this environment.
|
||||
* @since 6.2.8
|
||||
* @see MockPropertySource#setProperty(String, Object)
|
||||
*/
|
||||
public void setProperty(String key, String value) {
|
||||
this.propertySource.setProperty(key, value);
|
||||
public void setProperty(String name, Object value) {
|
||||
this.propertySource.setProperty(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty} that returns the current instance.
|
||||
* Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockEnvironment} instance
|
||||
* @see MockPropertySource#withProperty
|
||||
* Set a property on the underlying {@link MockPropertySource} for this environment.
|
||||
* @see #setProperty(String, Object)
|
||||
*/
|
||||
public MockEnvironment withProperty(String key, String value) {
|
||||
setProperty(key, value);
|
||||
public void setProperty(String name, String value) {
|
||||
this.propertySource.setProperty(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty(String, Object)} that returns
|
||||
* the current instance.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockEnvironment} instance
|
||||
* @since 6.2.8
|
||||
* @see MockPropertySource#withProperty(String, Object)
|
||||
*/
|
||||
public MockEnvironment withProperty(String name, Object value) {
|
||||
setProperty(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty(String, String)} that returns
|
||||
* the current instance.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockEnvironment} instance
|
||||
* @see #withProperty(String, Object)
|
||||
*/
|
||||
public MockEnvironment withProperty(String name, String value) {
|
||||
setProperty(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -26,7 +26,7 @@ import org.springframework.core.env.PropertySource;
|
||||
* a user-provided {@link Properties} object, or if omitted during construction,
|
||||
* the implementation will initialize its own.
|
||||
*
|
||||
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* <p>The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* convenience, for example:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
@@ -36,7 +36,7 @@ import org.springframework.core.env.PropertySource;
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
* @see org.springframework.mock.env.MockEnvironment
|
||||
* @see MockEnvironment
|
||||
*/
|
||||
public class MockPropertySource extends PropertiesPropertySource {
|
||||
|
||||
@@ -95,7 +95,7 @@ public class MockPropertySource extends PropertiesPropertySource {
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty} that returns the current instance.
|
||||
* Useful for method chaining and fluent-style use.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockPropertySource} instance
|
||||
*/
|
||||
public MockPropertySource withProperty(String name, Object value) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -105,7 +105,7 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie
|
||||
|
||||
/**
|
||||
* Set the {@link ClientHttpResponse} to be used as the result of executing
|
||||
* the this request.
|
||||
* this request.
|
||||
* @see #execute()
|
||||
*/
|
||||
public void setResponse(ClientHttpResponse clientHttpResponse) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -1064,8 +1064,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||
* @see #getDateHeader
|
||||
*/
|
||||
public void addHeader(String name, Object value) {
|
||||
if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name) &&
|
||||
!this.headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
|
||||
if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) {
|
||||
setContentType(value.toString());
|
||||
}
|
||||
else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(name) &&
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -226,6 +226,15 @@ class MockHttpServletRequestTests {
|
||||
assertThat(request.getCharacterEncoding()).isEqualTo("UTF-8");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contentTypeMultipleCalls() {
|
||||
String contentType = "text/html";
|
||||
request.addHeader(HttpHeaders.CONTENT_TYPE, "text/plain");
|
||||
request.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||
assertThat(request.getContentType()).isEqualTo(contentType);
|
||||
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo(contentType);
|
||||
}
|
||||
|
||||
@Test // SPR-12677
|
||||
void setContentTypeHeaderWithMoreComplexCharsetSyntax() {
|
||||
String contentType = "test/plain;charset=\"utf-8\";foo=\"charset=bar\";foocharset=bar;foo=bar";
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -110,7 +110,7 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
public String toString() {
|
||||
// @formatter:off
|
||||
return new ToStringCreator(this)
|
||||
.append("id", this.getId())
|
||||
.append("id", getId())
|
||||
.append("name", this.name)
|
||||
.append("age", this.age)
|
||||
.append("eyeColor", this.eyeColor)
|
||||
|
||||
+15
-27
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -19,7 +19,6 @@ package org.springframework.test.web.client.samples;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
@@ -46,27 +45,22 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
|
||||
|
||||
/**
|
||||
* Examples to demonstrate writing client-side REST tests with Spring MVC Test.
|
||||
* While the tests in this class invoke the RestTemplate directly, in actual
|
||||
*
|
||||
* <p>While the tests in this class invoke the RestTemplate directly, in actual
|
||||
* tests the RestTemplate may likely be invoked indirectly, i.e. through client
|
||||
* code.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class SampleTests {
|
||||
class SampleTests {
|
||||
|
||||
private MockRestServiceServer mockServer;
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
private final MockRestServiceServer mockServer = MockRestServiceServer.bindTo(this.restTemplate).ignoreExpectOrder(true).build();
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
this.mockServer = MockRestServiceServer.bindTo(this.restTemplate).ignoreExpectOrder(true).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performGet() {
|
||||
|
||||
void performGet() {
|
||||
String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
|
||||
|
||||
this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
|
||||
@@ -83,8 +77,7 @@ public class SampleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performGetManyTimes() {
|
||||
|
||||
void performGetManyTimes() {
|
||||
String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
|
||||
|
||||
this.mockServer.expect(manyTimes(), requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
|
||||
@@ -105,8 +98,7 @@ public class SampleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expectNever() {
|
||||
|
||||
void expectNever() {
|
||||
String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
|
||||
|
||||
this.mockServer.expect(once(), requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
|
||||
@@ -120,8 +112,7 @@ public class SampleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expectNeverViolated() {
|
||||
|
||||
void expectNeverViolated() {
|
||||
String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
|
||||
|
||||
this.mockServer.expect(once(), requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
|
||||
@@ -135,9 +126,8 @@ public class SampleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performGetWithResponseBodyFromFile() {
|
||||
|
||||
Resource responseBody = new ClassPathResource("ludwig.json", this.getClass());
|
||||
void performGetWithResponseBodyFromFile() {
|
||||
Resource responseBody = new ClassPathResource("ludwig.json", getClass());
|
||||
|
||||
this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
|
||||
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
|
||||
@@ -152,8 +142,7 @@ public class SampleTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verify() {
|
||||
|
||||
void verify() {
|
||||
this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET))
|
||||
.andRespond(withSuccess("1", MediaType.TEXT_PLAIN));
|
||||
|
||||
@@ -183,9 +172,8 @@ public class SampleTests {
|
||||
}
|
||||
|
||||
@Test // SPR-14694
|
||||
public void repeatedAccessToResponseViaResource() {
|
||||
|
||||
Resource resource = new ClassPathResource("ludwig.json", this.getClass());
|
||||
void repeatedAccessToResponseViaResource() {
|
||||
Resource resource = new ClassPathResource("ludwig.json", getClass());
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setInterceptors(Collections.singletonList(new ContentInterceptor(resource)));
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -471,12 +471,12 @@ class MockHttpServletRequestBuilderTests {
|
||||
assertThat(request.getContentType()).isEqualTo("yaml");
|
||||
}
|
||||
|
||||
@Test // SPR-11308
|
||||
@Test
|
||||
void contentTypeViaMultipleHeaderValues() {
|
||||
this.builder.header("Content-Type", MediaType.TEXT_HTML_VALUE, MediaType.ALL_VALUE);
|
||||
this.builder.header("Content-Type", MediaType.TEXT_HTML_VALUE, MediaType.TEXT_PLAIN_VALUE);
|
||||
MockHttpServletRequest request = this.builder.buildRequest(this.servletContext);
|
||||
|
||||
assertThat(request.getContentType()).isEqualTo("text/html");
|
||||
assertThat(request.getContentType()).isEqualTo("text/plain");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -257,7 +257,7 @@ class PrintingResultHandlerTests {
|
||||
this.mvcResult.setHandler(handlerMethod);
|
||||
this.handler.handle(mvcResult);
|
||||
|
||||
assertValue("Handler", "Type", this.getClass().getName());
|
||||
assertValue("Handler", "Type", getClass().getName());
|
||||
assertValue("Handler", "Method", handlerMethod);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ public final class ContentDisposition {
|
||||
for (int i=33; i<= 126; i++) {
|
||||
PRINTABLE.set(i);
|
||||
}
|
||||
PRINTABLE.set(34, false); // "
|
||||
PRINTABLE.set(61, false); // =
|
||||
PRINTABLE.set(63, false); // ?
|
||||
PRINTABLE.set(95, false); // _
|
||||
|
||||
@@ -149,7 +149,16 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
|
||||
}
|
||||
});
|
||||
|
||||
builder.method(this.method.name(), bodyPublisher(headers, body));
|
||||
switch (this.method.name()) {
|
||||
case "GET" :
|
||||
builder.GET();
|
||||
break;
|
||||
case "DELETE" :
|
||||
builder.DELETE();
|
||||
break;
|
||||
default :
|
||||
builder.method(this.method.name(), bodyPublisher(headers, body));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.http.client;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
@@ -103,6 +104,7 @@ final class SimpleClientHttpResponse implements ClientHttpResponse {
|
||||
if (this.responseStream == null) {
|
||||
getBody();
|
||||
}
|
||||
Objects.requireNonNull(this.responseStream);
|
||||
StreamUtils.drain(this.responseStream);
|
||||
this.responseStream.close();
|
||||
}
|
||||
|
||||
+6
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -76,6 +77,11 @@ public abstract class AbstractKotlinSerializationHttpMessageConverter<T extends
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
|
||||
return getSupportedMediaTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return serializer(ResolvableType.forClass(clazz)) != null;
|
||||
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@@ -24,7 +25,10 @@ import jakarta.xml.bind.JAXBException;
|
||||
import jakarta.xml.bind.Marshaller;
|
||||
import jakarta.xml.bind.Unmarshaller;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters}
|
||||
@@ -116,4 +120,21 @@ public abstract class AbstractJaxb2HttpMessageConverter<T> extends AbstractXmlHt
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the charset from the given {@link HttpHeaders#getContentType()}.
|
||||
* @param httpHeaders the current HTTP headers
|
||||
* @return the charset defined in the content type header, or {@code null} if not found
|
||||
* @since 6.2.8
|
||||
*/
|
||||
@Nullable
|
||||
protected Charset detectCharset(HttpHeaders httpHeaders) {
|
||||
MediaType contentType = httpHeaders.getContentType();
|
||||
if (contentType != null && contentType.getCharset() != null) {
|
||||
return contentType.getCharset();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.http.converter.xml;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -148,7 +149,10 @@ public class Jaxb2CollectionHttpMessageConverter<T extends Collection>
|
||||
|
||||
try {
|
||||
Unmarshaller unmarshaller = createUnmarshaller(elementClass);
|
||||
XMLStreamReader streamReader = this.inputFactory.createXMLStreamReader(inputMessage.getBody());
|
||||
Charset detectedCharset = detectCharset(inputMessage.getHeaders());
|
||||
XMLStreamReader streamReader = (detectedCharset != null) ?
|
||||
this.inputFactory.createXMLStreamReader(inputMessage.getBody(), detectedCharset.name()) :
|
||||
this.inputFactory.createXMLStreamReader(inputMessage.getBody());
|
||||
int event = moveToFirstChildOfRootElement(streamReader);
|
||||
|
||||
while (event != XMLStreamReader.END_DOCUMENT) {
|
||||
|
||||
+14
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
@@ -135,7 +136,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
|
||||
@Override
|
||||
protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws Exception {
|
||||
try {
|
||||
source = processSource(source);
|
||||
source = processSource(source, detectCharset(headers));
|
||||
Unmarshaller unmarshaller = createUnmarshaller(clazz);
|
||||
if (clazz.isAnnotationPresent(XmlRootElement.class)) {
|
||||
return unmarshaller.unmarshal(source);
|
||||
@@ -160,9 +161,19 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
|
||||
}
|
||||
}
|
||||
|
||||
protected Source processSource(Source source) {
|
||||
/**
|
||||
* Process {@code source} with {@code charset}.
|
||||
* @param source source to process
|
||||
* @param charset charset to use
|
||||
* @return source
|
||||
* @since 6.2.8
|
||||
*/
|
||||
protected Source processSource(Source source, @Nullable Charset charset) {
|
||||
if (source instanceof StreamSource streamSource) {
|
||||
InputSource inputSource = new InputSource(streamSource.getInputStream());
|
||||
if (charset != null) {
|
||||
inputSource.setEncoding(charset.name());
|
||||
}
|
||||
try {
|
||||
// By default, Spring will prevent the processing of external entities.
|
||||
// This is a mitigation against XXE attacks.
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -69,7 +69,7 @@ public class HttpMediaTypeNotAcceptableException extends HttpMediaTypeException
|
||||
return HttpHeaders.EMPTY;
|
||||
}
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(this.getSupportedMediaTypes());
|
||||
headers.setAccept(getSupportedMediaTypes());
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -71,23 +71,25 @@ import org.springframework.core.annotation.AliasFor;
|
||||
*
|
||||
* <p>The following return types are supported for handler methods:
|
||||
* <ul>
|
||||
* <li>A {@code ModelAndView} object (from Servlet MVC).
|
||||
* <li>A {@link org.springframework.ui.Model} object, with the view name implicitly
|
||||
* <li>{@code ModelAndView} object (from Servlet MVC).
|
||||
* <li>{@link org.springframework.ui.Model} object, with the view name implicitly
|
||||
* determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}.
|
||||
* <li>A {@link java.util.Map} object for exposing a model,
|
||||
* <li>{@link java.util.Map} object for exposing a model,
|
||||
* with the view name implicitly determined through a
|
||||
* {@link org.springframework.web.servlet.RequestToViewNameTranslator}.
|
||||
* <li>A {@link org.springframework.web.servlet.View} object.
|
||||
* <li>A {@link String} value which is interpreted as view name.
|
||||
* <li>{@link org.springframework.web.servlet.View} object.
|
||||
* <li>{@link String} value which is interpreted as view name.
|
||||
* <li>{@link ResponseBody @ResponseBody} annotated methods (Servlet-only)
|
||||
* to set the response content. The return value will be converted to the
|
||||
* response stream using
|
||||
* {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}.
|
||||
* <li>An {@link org.springframework.http.HttpEntity HttpEntity<?>} or
|
||||
* <li>{@link org.springframework.http.HttpEntity HttpEntity<?>} or
|
||||
* {@link org.springframework.http.ResponseEntity ResponseEntity<?>} object
|
||||
* (Servlet-only) to set response headers and content. The ResponseEntity body
|
||||
* will be converted and written to the response stream using
|
||||
* {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}.
|
||||
* <li>{@link org.springframework.http.ProblemDetail} or {@link org.springframework.web.ErrorResponse}
|
||||
* object to render an RFC 9457 error response with details in the body.
|
||||
* <li>{@code void} if the method handles the response itself (by
|
||||
* writing the response content directly, declaring an argument of type
|
||||
* {@link jakarta.servlet.ServletResponse} / {@link jakarta.servlet.http.HttpServletResponse}
|
||||
|
||||
+9
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -26,18 +26,16 @@ import java.lang.annotation.Target;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* A convenience annotation that is itself annotated with
|
||||
* {@link ControllerAdvice @ControllerAdvice}
|
||||
* and {@link ResponseBody @ResponseBody}.
|
||||
* A shortcut annotation that combines {@link ControllerAdvice @ControllerAdvice}
|
||||
* with {@link ResponseBody @ResponseBody}, in effect simply an
|
||||
* {@code @ControllerAdvice} whose exception handler methods render to the
|
||||
* response body.
|
||||
*
|
||||
* <p>Types that carry this annotation are treated as controller advice where
|
||||
* {@link ExceptionHandler @ExceptionHandler} methods assume
|
||||
* {@link ResponseBody @ResponseBody} semantics by default.
|
||||
* <p>By default, {@code @RestControllerAdvice} applies to any controller,
|
||||
* including {@code @Controller} and {@code @RestController}. Use attributes of
|
||||
* the annotation to apply more specific filtering criteria.
|
||||
*
|
||||
* <p><b>NOTE:</b> {@code @RestControllerAdvice} is processed if an appropriate
|
||||
* {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
|
||||
* {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter} pair
|
||||
* which are the default in the MVC Java config and the MVC namespace.
|
||||
* <p>See {@link ControllerAdvice} for more details.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -107,12 +107,12 @@ public interface ServerWebExchange {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the web session for the current request. Always guaranteed to
|
||||
* return an instance either matching to the session id requested by the
|
||||
* client, or with a new session id either because the client did not
|
||||
* specify one or because the underlying session had expired. Use of this
|
||||
* method does not automatically create a session. See {@link WebSession}
|
||||
* for more details.
|
||||
* Return the web session for the current request.
|
||||
* <p>Always guaranteed to return either an instance matching the session id
|
||||
* requested by the client, or a new session either because the client did not
|
||||
* specify a session id or because the underlying session expired.
|
||||
* <p>Use of this method does not automatically create a session. See
|
||||
* {@link WebSession} for more details.
|
||||
*/
|
||||
Mono<WebSession> getSession();
|
||||
|
||||
|
||||
+18
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -79,9 +79,9 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link Clock} to use to set lastAccessTime on every created
|
||||
* session and to calculate if it is expired.
|
||||
* <p>This may be useful to align to different timezone or to set the clock
|
||||
* Configure the {@link Clock} to use to set the {@code lastAccessTime} on
|
||||
* every created session and to calculate if the session has expired.
|
||||
* <p>This may be useful to align to different time zones or to set the clock
|
||||
* back in a test, for example, {@code Clock.offset(clock, Duration.ofMinutes(-31))}
|
||||
* in order to simulate session expiration.
|
||||
* <p>By default this is {@code Clock.system(ZoneId.of("GMT"))}.
|
||||
@@ -94,16 +94,17 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured clock for session lastAccessTime calculations.
|
||||
* Return the configured clock for session {@code lastAccessTime} calculations.
|
||||
*/
|
||||
public Clock getClock() {
|
||||
return this.clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of sessions with an {@link Collections#unmodifiableMap
|
||||
* unmodifiable} wrapper. This could be used for management purposes, to
|
||||
* list active sessions, invalidate expired ones, etc.
|
||||
* Return an {@linkplain Collections#unmodifiableMap unmodifiable} copy of the
|
||||
* map of sessions.
|
||||
* <p>This could be used for management purposes, to list active sessions,
|
||||
* to invalidate expired sessions, etc.
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public Map<String, WebSession> getSessions() {
|
||||
@@ -157,10 +158,11 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for expired sessions and remove them. Typically such checks are
|
||||
* kicked off lazily during calls to {@link #createWebSession() create} or
|
||||
* {@link #retrieveSession retrieve}, no less than 60 seconds apart.
|
||||
* This method can be called to force a check at a specific time.
|
||||
* Check for expired sessions and remove them.
|
||||
* <p>Typically such checks are kicked off lazily during calls to
|
||||
* {@link #createWebSession()} or {@link #retrieveSession}, no less than 60
|
||||
* seconds apart.
|
||||
* <p>This method can be called to force a check at a specific time.
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public void removeExpiredSessions() {
|
||||
@@ -237,7 +239,7 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
InMemoryWebSessionStore.this.sessions.remove(currentId);
|
||||
String newId = String.valueOf(idGenerator.generateId());
|
||||
this.id.set(newId);
|
||||
InMemoryWebSessionStore.this.sessions.put(this.getId(), this);
|
||||
InMemoryWebSessionStore.this.sessions.put(this.id.get(), this);
|
||||
return Mono.empty();
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
@@ -266,11 +268,11 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
|
||||
if (isStarted()) {
|
||||
// Save
|
||||
InMemoryWebSessionStore.this.sessions.put(this.getId(), this);
|
||||
InMemoryWebSessionStore.this.sessions.put(this.id.get(), this);
|
||||
|
||||
// Unless it was invalidated
|
||||
if (this.state.get().equals(State.EXPIRED)) {
|
||||
InMemoryWebSessionStore.this.sessions.remove(this.getId());
|
||||
InMemoryWebSessionStore.this.sessions.remove(this.id.get());
|
||||
return Mono.error(new IllegalStateException("Session was invalidated"));
|
||||
}
|
||||
}
|
||||
@@ -281,7 +283,7 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
private void checkMaxSessionsLimit() {
|
||||
if (sessions.size() >= maxSessions) {
|
||||
expiredSessionChecker.removeExpiredSessions(clock.instant());
|
||||
if (sessions.size() >= maxSessions) {
|
||||
if (sessions.size() >= maxSessions && !sessions.containsKey(this.id.get())) {
|
||||
throw new IllegalStateException("Max sessions limit reached: " + sessions.size());
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -32,10 +32,10 @@ import org.springframework.web.server.WebSession;
|
||||
public interface WebSessionManager {
|
||||
|
||||
/**
|
||||
* Return the {@link WebSession} for the given exchange. Always guaranteed
|
||||
* to return an instance either matching to the session id requested by the
|
||||
* client, or a new session either because the client did not specify one
|
||||
* or because the underlying session expired.
|
||||
* Return the {@link WebSession} for the given exchange.
|
||||
* <p>Always guaranteed to return either an instance matching the session id
|
||||
* requested by the client, or a new session either because the client did not
|
||||
* specify a session id or because the underlying session expired.
|
||||
* @param exchange the current exchange
|
||||
* @return promise for the WebSession
|
||||
*/
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -43,7 +43,7 @@ public interface WebSessionStore {
|
||||
* Return the WebSession for the given id.
|
||||
* <p><strong>Note:</strong> This method should perform an expiration check,
|
||||
* and if it has expired remove the session and return empty. This method
|
||||
* should also update the lastAccessTime of retrieved sessions.
|
||||
* should also update the {@code lastAccessTime} of retrieved sessions.
|
||||
* @param sessionId the session to load
|
||||
* @return the session, or an empty {@code Mono}
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -2040,7 +2040,7 @@ final class WhatWgUrlParser {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || obj.getClass() != this.getClass()) {
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
UrlRecord that = (UrlRecord) obj;
|
||||
@@ -2322,7 +2322,7 @@ final class WhatWgUrlParser {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this || obj != null && obj.getClass() == this.getClass();
|
||||
return obj == this || obj != null && getClass() == obj.getClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -374,19 +374,49 @@ public class PathPattern implements Comparable<PathPattern> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine this pattern with another.
|
||||
* Combine this pattern with the one given as parameter, returning a new
|
||||
* {@code PathPattern} instance that concatenates or merges both.
|
||||
* This operation is not commutative, meaning {@code pattern1.combine(pattern2)}
|
||||
* is not equal to {@code pattern2.combine(pattern1)}.
|
||||
*
|
||||
* <p>Combining two "fixed" patterns effectively concatenates them:
|
||||
* <ul>
|
||||
* <li><code> "/projects" + "/spring-framework" => "/projects/spring-framework"</code>
|
||||
* </ul>
|
||||
* Combining a "fixed" pattern with a "matching" pattern concatenates them:
|
||||
* <ul>
|
||||
* <li><code> "/projects" + "/{project}" => "/projects/{project}"</code>
|
||||
* </ul>
|
||||
* Combining a "matching" pattern with a "fixed" pattern merges them:
|
||||
* <ul>
|
||||
* <li><code> "/projects/*" + "/spring-framework" => "/projects/spring-framework"</code>
|
||||
* <li><code> "/projects/*.html" + "/spring-framework.html" => "/projects/spring-framework.html"</code>
|
||||
* </ul>
|
||||
* Combining two "matching" patterns merges them:
|
||||
* <ul>
|
||||
* <li><code> "/projects/**" + "/*.html" => "/projects/*.html"</code>
|
||||
* <li><code> "/projects/*" + "/{project}" => "/projects/{project}"</code>
|
||||
* <li><code> "/projects/*.html" + "/spring-framework.*" => "/projects/spring-framework.html"</code>
|
||||
* </ul>
|
||||
* Note, if a pattern does not end with a "matching" segment, it is considered as a "fixed" one:
|
||||
* <ul>
|
||||
* <li><code> "/projects/*/releases" + "/{id}" => "/projects/*/releases/{id}"</code>
|
||||
* </ul>
|
||||
* @param otherPattern the pattern to be combined with the current one
|
||||
* @return the new {@code PathPattern} that combines both patterns
|
||||
* @throws IllegalArgumentException if the combination is not allowed
|
||||
*/
|
||||
public PathPattern combine(PathPattern pattern2string) {
|
||||
// If one of them is empty the result is the other. If both empty the result is ""
|
||||
public PathPattern combine(PathPattern otherPattern) {
|
||||
// If one of them is empty, the result is the other. If both are empty, the result is ""
|
||||
if (!StringUtils.hasLength(this.patternString)) {
|
||||
if (!StringUtils.hasLength(pattern2string.patternString)) {
|
||||
if (!StringUtils.hasLength(otherPattern.patternString)) {
|
||||
return this.parser.parse("");
|
||||
}
|
||||
else {
|
||||
return pattern2string;
|
||||
return otherPattern;
|
||||
}
|
||||
}
|
||||
else if (!StringUtils.hasLength(pattern2string.patternString)) {
|
||||
else if (!StringUtils.hasLength(otherPattern.patternString)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -395,40 +425,55 @@ public class PathPattern implements Comparable<PathPattern> {
|
||||
// However:
|
||||
// /usr + /user => /usr/user
|
||||
// /{foo} + /bar => /{foo}/bar
|
||||
if (!this.patternString.equals(pattern2string.patternString) && this.capturedVariableCount == 0 &&
|
||||
matches(PathContainer.parsePath(pattern2string.patternString))) {
|
||||
return pattern2string;
|
||||
if (!this.patternString.equals(otherPattern.patternString) && this.capturedVariableCount == 0 &&
|
||||
matches(PathContainer.parsePath(otherPattern.patternString))) {
|
||||
return otherPattern;
|
||||
}
|
||||
|
||||
// /hotels/* + /booking => /hotels/booking
|
||||
// /hotels/* + booking => /hotels/booking
|
||||
if (this.endsWithSeparatorWildcard) {
|
||||
return this.parser.parse(concat(
|
||||
this.patternString.substring(0, this.patternString.length() - 2),
|
||||
pattern2string.patternString));
|
||||
String prefix = this.patternString.length() > 2 ?
|
||||
this.patternString.substring(0, this.patternString.length() - 2) :
|
||||
String.valueOf(getSeparator());
|
||||
return this.parser.parse(concat(prefix, otherPattern.patternString));
|
||||
}
|
||||
|
||||
// /hotels/** + "/booking/rooms => /hotels/booking/rooms
|
||||
if (this.catchAll) {
|
||||
return this.parser.parse(concat(this.patternString.substring(0, this.patternString.length() - 3),
|
||||
otherPattern.patternString));
|
||||
}
|
||||
|
||||
// /hotels + /booking => /hotels/booking
|
||||
// /hotels + booking => /hotels/booking
|
||||
int starDotPos1 = this.patternString.indexOf("*."); // Are there any file prefix/suffix things to consider?
|
||||
if (this.capturedVariableCount != 0 || starDotPos1 == -1 || getSeparator() == '.') {
|
||||
return this.parser.parse(concat(this.patternString, pattern2string.patternString));
|
||||
int firstStarDotPos = this.patternString.indexOf("*."); // Are there any file prefix/suffix things to consider?
|
||||
if (this.capturedVariableCount != 0 || firstStarDotPos == -1 || getSeparator() == '.') {
|
||||
return this.parser.parse(concat(this.patternString, otherPattern.patternString));
|
||||
}
|
||||
|
||||
// /*.html + /hotel => /hotel.html
|
||||
// /*.html + /hotel.* => /hotel.html
|
||||
String firstExtension = this.patternString.substring(starDotPos1 + 1); // looking for the first extension
|
||||
String p2string = pattern2string.patternString;
|
||||
int dotPos2 = p2string.indexOf('.');
|
||||
String file2 = (dotPos2 == -1 ? p2string : p2string.substring(0, dotPos2));
|
||||
String secondExtension = (dotPos2 == -1 ? "" : p2string.substring(dotPos2));
|
||||
boolean firstExtensionWild = (firstExtension.equals(".*") || firstExtension.isEmpty());
|
||||
boolean secondExtensionWild = (secondExtension.equals(".*") || secondExtension.isEmpty());
|
||||
if (!firstExtensionWild && !secondExtensionWild) {
|
||||
int secondDotPos = otherPattern.patternString.indexOf('.');
|
||||
|
||||
String firstExtension = this.patternString.substring(firstStarDotPos + 1); // looking for the first extension
|
||||
String secondExtension = (secondDotPos == -1 ? "" : otherPattern.patternString.substring(secondDotPos));
|
||||
boolean isFirstExtensionWildcard = (firstExtension.equals(".*") || firstExtension.isEmpty());
|
||||
boolean isSecondExtensionWildcard = (secondExtension.equals(".*") || secondExtension.isEmpty());
|
||||
if (!isFirstExtensionWildcard && !isSecondExtensionWildcard) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot combine patterns: " + this.patternString + " and " + pattern2string);
|
||||
"Cannot combine patterns: " + this.patternString + " and " + otherPattern);
|
||||
}
|
||||
return this.parser.parse(file2 + (firstExtensionWild ? secondExtension : firstExtension));
|
||||
|
||||
String firstPath = this.patternString.substring(0, this.patternString.lastIndexOf(getSeparator()));
|
||||
String secondPath = otherPattern.patternString.substring(0, otherPattern.patternString.lastIndexOf(getSeparator()));
|
||||
if (!this.parser.parse(firstPath).matches(PathContainer.parsePath(secondPath))) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot combine patterns: " + this.patternString + " and " + otherPattern);
|
||||
}
|
||||
|
||||
String secondFile = (secondDotPos == -1 ? otherPattern.patternString : otherPattern.patternString.substring(0, secondDotPos));
|
||||
return this.parser.parse(secondFile + (isFirstExtensionWildcard ? secondExtension : firstExtension));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -313,6 +313,13 @@ class ContentDispositionTests {
|
||||
tester.accept("foo.txt\\\\\\", "foo.txt\\\\\\\\\\\\");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithUtf8FilenameWithQuotes() {
|
||||
String filename = "\"中文.txt";
|
||||
assertThat(ContentDisposition.formData().filename(filename, StandardCharsets.UTF_8).build().toString())
|
||||
.isEqualTo("form-data; filename=\"=?UTF-8?Q?=22=E4=B8=AD=E6=96=87.txt?=\"; filename*=UTF-8''%22%E4%B8%AD%E6%96%87.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithEncodedFilenameUsingInvalidCharset() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
|
||||
+23
@@ -18,15 +18,19 @@ package org.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -34,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* Tests for {@link JdkClientHttpRequestFactory}.
|
||||
*
|
||||
* @author Marten Deinum
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class JdkClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests {
|
||||
|
||||
@@ -88,4 +93,22 @@ class JdkClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test // gh-34971
|
||||
@EnabledForJreRange(min = JRE.JAVA_19) // behavior fixed in Java 19
|
||||
void requestContentLengthHeader() throws Exception {
|
||||
URI uri = URI.create(baseUrl + "/header/Content-Length");
|
||||
assertNoContentLength(uri, HttpMethod.GET);
|
||||
assertNoContentLength(uri, HttpMethod.DELETE);
|
||||
}
|
||||
|
||||
protected void assertNoContentLength(URI uri, HttpMethod method) throws Exception {
|
||||
ClientHttpRequest request = factory.createRequest(uri, method);
|
||||
try (ClientHttpResponse response = request.execute()) {
|
||||
assertThat(response.getStatusCode()).as("Invalid response status").isEqualTo(HttpStatus.OK);
|
||||
assertThat(StreamUtils.copyToString(response.getBody(), StandardCharsets.ISO_8859_1))
|
||||
.as("Invalid Content-Length request header").isEqualTo("Content-Length:null");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -615,7 +615,7 @@ class Jackson2ObjectMapperBuilderTests {
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return this.getClass().getSimpleName();
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user