mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
140 Commits
v6.1.0-RC2
...
v6.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| e6585e0250 | |||
| af1b3c72d5 | |||
| 770cbd2fb5 | |||
| d1867f1b73 | |||
| e5f04e5ddf | |||
| 99378fe947 | |||
| b0e29acd23 | |||
| c02f735056 | |||
| c18784678d | |||
| c485e9b74f | |||
| 51cdff591c | |||
| d93114df9a | |||
| 5ac4c3bd76 | |||
| 3a70c71c5e | |||
| 05c3ffb2fb | |||
| 19e8ed130c | |||
| f15b8b95ad | |||
| 183c2f82b2 | |||
| 4464251754 | |||
| 8eebb5e27b | |||
| 0ef3cb9b9e | |||
| 8868fe2ea5 | |||
| ca9634143c | |||
| 0e6bf098cb | |||
| c373f496f3 | |||
| eb1883bdc4 | |||
| 01fc1ee3bb | |||
| 510caad6c3 | |||
| df6f66110f | |||
| 92c3843dae | |||
| e12269ef1a | |||
| ac235a0c43 | |||
| 2613dfce0b | |||
| a4e3af5cbe | |||
| 978e81e72e | |||
| e71117dcdf | |||
| d5874ab99e | |||
| 2b7a9209b3 | |||
| 99327b7db1 | |||
| 3e06441d97 | |||
| 8f21479234 | |||
| dd97dee7fd | |||
| eb3982b6c2 | |||
| 258f99a8bc | |||
| 01f2925048 | |||
| dced3539a3 | |||
| bb66885426 | |||
| 7f615fd8af | |||
| 33b1ff5d27 | |||
| 44a37000ec | |||
| 53fe5fafed | |||
| f5453cc445 | |||
| b9bd98fc5b | |||
| a97ecf7a29 | |||
| 8a7144ba42 | |||
| 6c3a3dc7d9 | |||
| b1a7161218 | |||
| 332de60586 | |||
| 769128a0b2 | |||
| 7714110940 | |||
| 75d1278bde | |||
| d8441fc80c | |||
| d8ed7c7906 | |||
| 620f558547 | |||
| 8841b7c3bb | |||
| 6c00e6162b | |||
| 1e78cc35e5 | |||
| 42874178ce | |||
| 11fdb5ba17 | |||
| 9957bb6918 | |||
| 9414c2ddba | |||
| 38724a1205 | |||
| 55d13a0232 | |||
| a207e5e1b0 | |||
| b7c579954c | |||
| 6a7a0bddb7 | |||
| ba4d459f81 | |||
| 5c012bbb0c | |||
| d34b3c1a71 | |||
| e943058b18 | |||
| cafb38ad1d | |||
| 9759bf5964 | |||
| e778d2e908 | |||
| c5bcfc7682 | |||
| 5752e03d97 | |||
| 5afb00d270 | |||
| dc26d3b0ec | |||
| 486503bd31 | |||
| 0839f5b749 | |||
| 888d8c15d9 | |||
| dbb2d4f1d3 | |||
| 9d6bda2b44 | |||
| c30b3796f2 | |||
| 7f94c64b72 | |||
| 50e55d5219 | |||
| fcd4ba2f1f | |||
| 8c77e398b6 | |||
| 34e5512ff6 | |||
| cd503e3af6 | |||
| 5df6e8825d | |||
| 86bb8a015b | |||
| efb93ca109 | |||
| b3a6dbaab3 | |||
| ed49f86dfb | |||
| 12078e6ec5 | |||
| 654e822676 | |||
| 24aa6163f0 | |||
| 30e4a0a300 | |||
| 6dd93d4d85 | |||
| ab316d9bc8 | |||
| e73107341c | |||
| 27b5813591 | |||
| fe14c85ada | |||
| 4da2440f63 | |||
| 9bbe3aa52a | |||
| f16122d533 | |||
| d54a694f5a | |||
| 9c5dcad0e5 | |||
| b05b5902c0 | |||
| 1d38d649fa | |||
| a5841fede5 | |||
| 2a5653c7d6 | |||
| f8a33cd66e | |||
| 115a46c5e8 | |||
| 199d5e5f37 | |||
| 3d248607dc | |||
| 574c57739e | |||
| 2b750926c3 | |||
| 8a2acbeac1 | |||
| f088d4a05b | |||
| 8770544769 | |||
| fc77172d67 | |||
| de74520286 | |||
| cb9382d986 | |||
| c7bb981db1 | |||
| 7f1beb0140 | |||
| 1762bf4a60 | |||
| d3fba6d49b | |||
| c5333a0a68 | |||
| 44b98c6a8b |
+2
-2
@@ -7,7 +7,7 @@ plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.49.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'me.champeau.jmh' version '0.7.1' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.10.0/api/",
|
||||
// "https://junit.org/junit5/docs/5.10.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.12.4");
|
||||
checkstyle.setToolVersion("10.12.5");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -4,7 +4,7 @@ image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.4.0-SNAPSHOT'
|
||||
tag: '0.4.0'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
|
||||
@@ -27,7 +27,8 @@ javadoc {
|
||||
author = true
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = "framework-docs/src/docs/api/overview.html"
|
||||
overview = "$rootProject.rootDir/framework-docs/src/docs/api/overview.html"
|
||||
destinationDir = file("${project.buildDir}/docs/javadoc-api")
|
||||
splitIndex = true
|
||||
links(rootProject.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
@@ -51,7 +52,8 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.file("$buildDir/docs/kdoc"))
|
||||
outputDirectory.set(project.file("$buildDir/docs/kdoc-api"))
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ plugins {
|
||||
|
||||
description = "Spring Framework Docs"
|
||||
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
antora {
|
||||
|
||||
@@ -182,6 +182,7 @@
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
|
||||
@@ -6,8 +6,8 @@ it until later.
|
||||
|
||||
By default, there is a single instance of each aspect within the application
|
||||
context. AspectJ calls this the singleton instantiation model. It is possible to define
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis` and `pertarget`
|
||||
instantiation models; `percflow`, `percflowbelow`, and `pertypewithin` are not currently
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis`, `pertarget`, and
|
||||
`pertypewithin` instantiation models; `percflow` and `percflowbelow` are not currently
|
||||
supported.
|
||||
|
||||
You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect`
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ Kotlin::
|
||||
|
||||
Used at the class level as above, the annotation indicates a default for all methods of
|
||||
the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[null] for
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
|
||||
further details on which methods Spring considers transactional. Note that a class-level
|
||||
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
|
||||
inherited methods need to be locally redeclared in order to participate in a
|
||||
|
||||
@@ -109,7 +109,8 @@ By default, the following `KeyValues` are created:
|
||||
|Name | Description
|
||||
|`code.function` _(required)_|Name of Java `Method` that is scheduled for execution.
|
||||
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method.
|
||||
|`exception` _(required)_|Name of the exception thrown during the execution, or `"none"` if no exception happened.
|
||||
|`error` _(required)_|Class name of the exception thrown during the execution, or `"none"` if no exception happened.
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|`outcome` _(required)_|Outcome of the method execution. Can be `"SUCCESS"`, `"ERROR"` or `"UNKNOWN"` (if for example the operation was cancelled during execution).
|
||||
|===
|
||||
|
||||
@@ -134,7 +135,8 @@ By default, both observations share the same set of possible `KeyValues`:
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` |Class name of the exception thrown during the messaging operation (or "none").
|
||||
|`error` |Class name of the exception thrown during the messaging operation (or "none").
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|`messaging.destination.temporary` _(required)_|Whether the destination is a `TemporaryQueue` or `TemporaryTopic` (values: `"true"` or `"false"`).
|
||||
|`messaging.operation` _(required)_|Name of JMS operation being performed (values: `"publish"` or `"process"`).
|
||||
|===
|
||||
@@ -199,7 +201,7 @@ include-code::./UserController[]
|
||||
|
||||
NOTE: Because the instrumentation is done at the Servlet Filter level, the observation scope only covers the filters ordered after this one as well as the handling of the request.
|
||||
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
|
||||
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside of the scope of this project.
|
||||
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside the scope of this project.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
@@ -207,8 +209,9 @@ By default, the following `KeyValues` are created:
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
@@ -244,8 +247,9 @@ By default, the following `KeyValues` are created:
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
@@ -278,12 +282,13 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
@@ -310,7 +315,8 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
@@ -333,12 +339,13 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
@@ -354,18 +361,18 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
|
||||
|
||||
Spring Framework does not contribute Observations for xref:core/beans/context-introduction.adoc#context-functionality-events-annotation[`@EventListener` calls],
|
||||
as they don't have the right semantics for such instrumentation.
|
||||
By default, event publication and processing is done synchronously and on the same Thread.
|
||||
By default, event publication and processing are done synchronously and on the same thread.
|
||||
This means that during the execution of that task, the ThreadLocals and logging context will be the same as the event publisher.
|
||||
|
||||
If the application configures globally a custom `ApplicationEventMulticaster` with a strategy that schedules event processing on different threads, this is no longer true.
|
||||
All `@EventListener` methods will be processed on a different thread, outstide of the main event publication thread.
|
||||
In these cases, the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation library] can help propagating such values and better correlate the processing of the events.
|
||||
If the application globally configures a custom `ApplicationEventMulticaster` with a strategy that schedules event processing on different threads, this is no longer true.
|
||||
All `@EventListener` methods will be processed on a different thread, outside the main event publication thread.
|
||||
In these cases, the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation library] can help propagate such values and better correlate the processing of the events.
|
||||
The application can configure the chosen `TaskExecutor` to use a `ContextPropagatingTaskDecorator` that decorates tasks and propagates context.
|
||||
For this to work, the `io.micrometer:context-propagation` library must be present on the classpath:
|
||||
|
||||
include-code::./ApplicationEventsConfiguration[]
|
||||
|
||||
Similarly, if that asynchronous choice is made locally for each `@EventListener` annotated method, by adding an `@Async` method to it,
|
||||
Similarly, if that asynchronous choice is made locally for each `@EventListener` annotated method, by adding `@Async` to it,
|
||||
you can choose a `TaskExecutor` that propagates context by referring to it by its qualifier.
|
||||
Given the following `TaskExecutor` bean definition, configured with the dedicated task decorator:
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ For example, declaring `List<Foo>` in Kotlin is conceptually equivalent to `java
|
||||
`kotlin.collections.List` is declared as
|
||||
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/[`interface List<out E> : kotlin.collections.Collection<E>`].
|
||||
|
||||
This needs to be taken in account by using the `out` Kotlin keyword on generic types when using Java classes,
|
||||
This needs to be taken into account by using the `out` Kotlin keyword on generic types when using Java classes,
|
||||
for example when writing a `org.springframework.core.convert.converter.Converter` from a Kotlin type to a Java type.
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
|
||||
@@ -11,6 +11,7 @@ xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupite
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-enabledif[`@EnabledIf`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-disabledif[`@DisabledIf`]
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
|
||||
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
|
||||
== `@SpringJUnitConfig`
|
||||
|
||||
@@ -27,4 +27,5 @@ Spring's testing annotations include the following:
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[`@SqlConfig`]
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`]
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[`@SqlGroup`]
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
[[spring-testing-annotation-disabledinaotmode]]
|
||||
= `@DisabledInAotMode`
|
||||
|
||||
`@DisabledInAotMode` signals that an annotated test class is disabled in Spring AOT
|
||||
(ahead-of-time) mode, which means that the `ApplicationContext` for the test class will
|
||||
not be processed for AOT optimizations at build time.
|
||||
|
||||
If a test class is annotated with `@DisabledInAotMode`, all other test classes which
|
||||
specify configuration to load the same `ApplicationContext` must also be annotated with
|
||||
`@DisabledInAotMode`. Failure to annotate all such test classes will result in an
|
||||
exception, either at build time or run time.
|
||||
|
||||
When used with JUnit Jupiter based tests, `@DisabledInAotMode` also signals that the
|
||||
annotated test class or test method is disabled when running the test suite in Spring AOT
|
||||
mode. When applied at the class level, all test methods within that class will be
|
||||
disabled. In this sense, `@DisabledInAotMode` has semantics similar to those of JUnit
|
||||
Jupiter's `@DisabledInNativeImage` annotation.
|
||||
|
||||
For details on AOT support specific to integration tests, see
|
||||
xref:testing/testcontext-framework/aot.adoc[Ahead of Time Support for Tests].
|
||||
@@ -19,6 +19,15 @@ following features.
|
||||
use an AOT-optimized `ApplicationContext` that participates transparently with the
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[context cache].
|
||||
|
||||
All tests are enabled in AOT mode by default. However, you can selectively disable an
|
||||
entire test class or individual test method in AOT mode by annotating it with
|
||||
xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`].
|
||||
When using JUnit Jupiter, you may selectively enable or disable tests in a GraalVM native
|
||||
image via Jupiter's `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations.
|
||||
Note that `@DisabledInAotMode` also disables the annotated test class or test method when
|
||||
running within a GraalVM native image, analogous to JUnit Jupiter's
|
||||
`@DisabledInNativeImage` annotation.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
By default, if an error is encountered during build-time AOT processing, an exception
|
||||
|
||||
+100
-28
@@ -95,13 +95,22 @@ In addition to the aforementioned mechanisms for running SQL scripts programmati
|
||||
you can declaratively configure SQL scripts in the Spring TestContext Framework.
|
||||
Specifically, you can declare the `@Sql` annotation on a test class or test method to
|
||||
configure individual SQL statements or the resource paths to SQL scripts that should be
|
||||
run against a given database before or after an integration test method. Support for
|
||||
`@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled by default.
|
||||
run against a given database before or after an integration test class or test method.
|
||||
Support for `@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled
|
||||
by default.
|
||||
|
||||
NOTE: Method-level `@Sql` declarations override class-level declarations by default. As
|
||||
of Spring Framework 5.2, however, this behavior may be configured per test class or per
|
||||
test method via `@SqlMergeMode`. See
|
||||
xref:testing/testcontext-framework/executing-sql.adoc#testcontext-executing-sql-declaratively-script-merging[Merging and Overriding Configuration with `@SqlMergeMode`] for further details.
|
||||
[NOTE]
|
||||
====
|
||||
Method-level `@Sql` declarations override class-level declarations by default, but this
|
||||
behavior may be configured per test class or per test method via `@SqlMergeMode`. See
|
||||
xref:testing/testcontext-framework/executing-sql.adoc#testcontext-executing-sql-declaratively-script-merging[Merging and Overriding Configuration with `@SqlMergeMode`]
|
||||
for further details.
|
||||
|
||||
However, this does not apply to class-level declarations configured for the
|
||||
`BEFORE_TEST_CLASS` or `AFTER_TEST_CLASS` execution phases. Such declarations cannot be
|
||||
overridden, and the corresponding scripts and statements will be executed once per class
|
||||
in addition to any method-level scripts and statements.
|
||||
====
|
||||
|
||||
[[testcontext-executing-sql-declaratively-script-resources]]
|
||||
=== Path Resource Semantics
|
||||
@@ -174,17 +183,25 @@ script, depending on where `@Sql` is declared. If a default cannot be detected,
|
||||
defined in the class `com.example.MyTest`, the corresponding default script is
|
||||
`classpath:com/example/MyTest.testMethod.sql`.
|
||||
|
||||
[[testcontext-executing-sql-declaratively-logging]]
|
||||
=== Logging SQL Scripts and Statements
|
||||
|
||||
If you want to see which SQL scripts are being executed, set the
|
||||
`org.springframework.test.context.jdbc` logging category to `DEBUG`.
|
||||
|
||||
If you want to see which SQL statements are being executed, set the
|
||||
`org.springframework.jdbc.datasource.init` logging category to `DEBUG`.
|
||||
|
||||
[[testcontext-executing-sql-declaratively-multiple-annotations]]
|
||||
=== Declaring Multiple `@Sql` Sets
|
||||
|
||||
If you need to configure multiple sets of SQL scripts for a given test class or test
|
||||
method but with different syntax configuration, different error handling rules, or
|
||||
different execution phases per set, you can declare multiple instances of `@Sql`. With
|
||||
Java 8, you can use `@Sql` as a repeatable annotation. Otherwise, you can use the
|
||||
`@SqlGroup` annotation as an explicit container for declaring multiple instances of
|
||||
`@Sql`.
|
||||
different execution phases per set, you can declare multiple instances of `@Sql`. You can
|
||||
either use `@Sql` as a repeatable annotation, or you can use the `@SqlGroup` annotation
|
||||
as an explicit container for declaring multiple instances of `@Sql`.
|
||||
|
||||
The following example shows how to use `@Sql` as a repeatable annotation with Java 8:
|
||||
The following example shows how to use `@Sql` as a repeatable annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -204,7 +221,12 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin
|
||||
@Test
|
||||
@Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`"))
|
||||
@Sql("/test-user-data.sql")
|
||||
fun userTest() {
|
||||
// run code that uses the test schema and test data
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -212,9 +234,8 @@ In the scenario presented in the preceding example, the `test-schema.sql` script
|
||||
different syntax for single-line comments.
|
||||
|
||||
The following example is identical to the preceding example, except that the `@Sql`
|
||||
declarations are grouped together within `@SqlGroup`. With Java 8 and above, the use of
|
||||
`@SqlGroup` is optional, but you may need to use `@SqlGroup` for compatibility with
|
||||
other JVM languages such as Kotlin.
|
||||
declarations are grouped together within `@SqlGroup`. The use of `@SqlGroup` is optional,
|
||||
but you may need to use `@SqlGroup` for compatibility with other JVM languages.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -239,7 +260,8 @@ Kotlin::
|
||||
@Test
|
||||
@SqlGroup(
|
||||
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
|
||||
Sql("/test-user-data.sql"))
|
||||
Sql("/test-user-data.sql")
|
||||
)
|
||||
fun userTest() {
|
||||
// Run code that uses the test schema and test data
|
||||
}
|
||||
@@ -249,10 +271,10 @@ Kotlin::
|
||||
[[testcontext-executing-sql-declaratively-script-execution-phases]]
|
||||
=== Script Execution Phases
|
||||
|
||||
By default, SQL scripts are run before the corresponding test method. However, if
|
||||
you need to run a particular set of scripts after the test method (for example, to clean
|
||||
up database state), you can use the `executionPhase` attribute in `@Sql`, as the
|
||||
following example shows:
|
||||
By default, SQL scripts are run before the corresponding test method. However, if you
|
||||
need to run a particular set of scripts after the test method (for example, to clean up
|
||||
database state), you can set the `executionPhase` attribute in `@Sql` to
|
||||
`AFTER_TEST_METHOD`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -281,12 +303,11 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@SqlGroup(
|
||||
Sql("create-test-data.sql",
|
||||
config = SqlConfig(transactionMode = ISOLATED)),
|
||||
Sql("delete-test-data.sql",
|
||||
config = SqlConfig(transactionMode = ISOLATED),
|
||||
executionPhase = AFTER_TEST_METHOD))
|
||||
@Sql("create-test-data.sql",
|
||||
config = SqlConfig(transactionMode = ISOLATED))
|
||||
@Sql("delete-test-data.sql",
|
||||
config = SqlConfig(transactionMode = ISOLATED),
|
||||
executionPhase = AFTER_TEST_METHOD)
|
||||
fun userTest() {
|
||||
// run code that needs the test data to be committed
|
||||
// to the database outside of the test's transaction
|
||||
@@ -294,9 +315,60 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
|
||||
NOTE: `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
|
||||
`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively.
|
||||
|
||||
As of Spring Framework 6.1, it is possible to run a particular set of scripts before or
|
||||
after the test class by setting the `executionPhase` attribute in a class-level `@Sql`
|
||||
declaration to `BEFORE_TEST_CLASS` or `AFTER_TEST_CLASS`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@SpringJUnitConfig
|
||||
@Sql(scripts = "/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
|
||||
class DatabaseTests {
|
||||
|
||||
@Test
|
||||
void emptySchemaTest() {
|
||||
// run code that uses the test schema without any test data
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql("/test-user-data.sql")
|
||||
void userTest() {
|
||||
// run code that uses the test schema and test data
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@SpringJUnitConfig
|
||||
@Sql("/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
|
||||
class DatabaseTests {
|
||||
|
||||
@Test
|
||||
fun emptySchemaTest() {
|
||||
// run code that uses the test schema without any test data
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql("/test-user-data.sql")
|
||||
fun userTest() {
|
||||
// run code that uses the test schema and test data
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `BEFORE_TEST_CLASS` is statically imported from `Sql.ExecutionPhase`.
|
||||
|
||||
[[testcontext-executing-sql-declaratively-script-configuration]]
|
||||
=== Script Configuration with `@SqlConfig`
|
||||
|
||||
@@ -324,7 +396,7 @@ individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql
|
||||
{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details.
|
||||
|
||||
[[testcontext-executing-sql-declaratively-tx]]
|
||||
*Transaction management for `@Sql`*
|
||||
==== Transaction management for `@Sql`
|
||||
|
||||
By default, the `SqlScriptsTestExecutionListener` infers the desired transaction
|
||||
semantics for scripts configured by using `@Sql`. Specifically, SQL scripts are run
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# All Modules
|
||||
_See also the <a href="../javadoc-api/" target="_blank">Java API documentation (Javadoc)</a>._
|
||||
@@ -1,7 +1,10 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
This is the public API documentation for the <a href="https://github.com/spring-projects/spring-framework" target="_top">Spring Framework</a>.
|
||||
This is the public Java API documentation (Javadoc) for the <a href="https://github.com/spring-projects/spring-framework" target="_top">Spring Framework</a>.
|
||||
</p>
|
||||
<p><em>
|
||||
See also the <a href="../kdoc-api/" target="_blank">Kotlin API documentation (KDoc)</a>.
|
||||
</em></p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,30 +8,30 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.0-RC1"))
|
||||
api(platform("io.netty:netty-bom:4.1.100.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.0"))
|
||||
api(platform("io.netty:netty-bom:4.1.101.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.0-RC1"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.0"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.2"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.2"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.3"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.6.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.7.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.librepdf:openpdf:1.3.30")
|
||||
api("com.github.librepdf:openpdf:1.3.33")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.10.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.24.4")
|
||||
api("com.h2database:h2:2.2.220")
|
||||
api("com.google.protobuf:protobuf-java-util:3.25.0")
|
||||
api("com.h2database:h2:2.2.224")
|
||||
api("com.jayway.jsonpath:json-path:2.8.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
@@ -43,20 +43,20 @@ dependencies {
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.0.3")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.20")
|
||||
api("commons-io:commons-io:2.11.0")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.micrometer:context-propagation:1.1.0-RC1")
|
||||
api("io.micrometer:context-propagation:1.1.0")
|
||||
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")
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.6")
|
||||
api("io.smallrye.reactive:mutiny:1.9.0")
|
||||
api("io.undertow:undertow-core:2.3.8.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.8.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.8.Final")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.10.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.10.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.10.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -90,22 +90,22 @@ dependencies {
|
||||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.70.0")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.17.4")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.4")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.4")
|
||||
api("org.apache.activemq:artemis-junit-5:2.29.0")
|
||||
api("org.apache.activemq:artemis-jakarta-client:2.29.0")
|
||||
api("org.apache.activemq:activemq-broker:5.17.6")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.6")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.6")
|
||||
api("org.apache.activemq:artemis-jakarta-client:2.31.2")
|
||||
api("org.apache.activemq:artemis-junit-5:2.31.2")
|
||||
api("org.apache.commons:commons-pool2:2.9.0")
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.2")
|
||||
api("org.apache.poi:poi-ooxml:5.2.3")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.14")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.14")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.14")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.14")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.3")
|
||||
api("org.apache.poi:poi-ooxml:5.2.4")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.15")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.15")
|
||||
api("org.aspectj:aspectjrt:1.9.20.1")
|
||||
api("org.aspectj:aspectjtools:1.9.20.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.20.1")
|
||||
@@ -115,24 +115,22 @@ dependencies {
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.1")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.32")
|
||||
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
|
||||
// is not available on Maven Central
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.1")
|
||||
api("org.hamcrest:hamcrest:2.2")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.2")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.4.3.0")
|
||||
api("org.jruby:jruby:9.4.5.0")
|
||||
api("org.junit.support:testng-engine:1.0.4")
|
||||
api("org.mozilla:rhino:1.7.14")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
@@ -144,9 +142,9 @@ dependencies {
|
||||
api("org.slf4j:slf4j-api:2.0.9")
|
||||
api("org.testng:testng:7.8.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.53")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
api("org.xmlunit:xmlunit-assertj:2.9.1")
|
||||
api("org.xmlunit:xmlunit-matchers:2.9.1")
|
||||
api("org.yaml:snakeyaml:2.0")
|
||||
api("org.yaml:snakeyaml:2.2")
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
version=6.1.0-SNAPSHOT
|
||||
version=6.1.0
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=1.9.20-RC2
|
||||
kotlinVersion=1.9.20
|
||||
|
||||
kotlin.jvm.target.validation.mode=ignore
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
+13
-5
@@ -1,3 +1,4 @@
|
||||
import org.gradle.plugins.ide.eclipse.model.Library
|
||||
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
|
||||
import org.gradle.plugins.ide.eclipse.model.SourceFolder
|
||||
|
||||
@@ -6,6 +7,7 @@ apply plugin: 'eclipse'
|
||||
eclipse.jdt {
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
javaRuntimeName = "JavaSE-17"
|
||||
}
|
||||
|
||||
// Replace classpath entries with project dependencies (GRADLE-1116)
|
||||
@@ -60,12 +62,18 @@ eclipse.classpath.file.whenMerged {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that JMH sources and resources are treated as test classpath entries
|
||||
// so that they can see test fixtures.
|
||||
// https://github.com/melix/jmh-gradle-plugin/issues/157
|
||||
// Remove recursive project dependencies
|
||||
eclipse.classpath.file.whenMerged {
|
||||
entries.findAll { it.path =~ /src\/jmh\/(java|kotlin|resources)/ }.each {
|
||||
it.entryAttributes['test'] = 'true'
|
||||
entries.findAll { it instanceof ProjectDependency && it.path == ('/' + project.name) }.each {
|
||||
entries.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove classpath entries for non-existent libraries added by the me.champeau.mrjar
|
||||
// plugin, such as "spring-core/build/classes/kotlin/java21".
|
||||
eclipse.classpath.file.whenMerged {
|
||||
entries.findAll { it instanceof Library && !file(it.path).exists() }.each {
|
||||
entries.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
* We need to detect this as "code-style" AspectJ aspects should not be
|
||||
* interpreted by Spring AOP.
|
||||
*/
|
||||
private boolean compiledByAjc(Class<?> clazz) {
|
||||
static boolean compiledByAjc(Class<?> clazz) {
|
||||
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
|
||||
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
|
||||
// an implementation detail of the AspectJ compiler.
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* An AOT {@link BeanRegistrationAotProcessor} that detects the presence of
|
||||
* classes compiled with AspectJ and adds the related required field hints.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 6.1
|
||||
*/
|
||||
class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||
|
||||
private static final boolean aspectjPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
|
||||
AspectJAdvisorBeanRegistrationAotProcessor.class.getClassLoader());
|
||||
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
if (aspectjPresent) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
if (AbstractAspectJAdvisorFactory.compiledByAjc(beanClass)) {
|
||||
return new AspectJAdvisorContribution(beanClass);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static class AspectJAdvisorContribution implements BeanRegistrationAotContribution {
|
||||
|
||||
private final Class<?> beanClass;
|
||||
|
||||
public AspectJAdvisorContribution(Class<?> beanClass) {
|
||||
this.beanClass = beanClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
|
||||
generationContext.getRuntimeHints().reflection().registerType(this.beanClass, MemberCategory.DECLARED_FIELDS);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+5
-5
@@ -40,11 +40,12 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
private static final boolean aspectJPresent = ClassUtils.isPresent(
|
||||
"org.aspectj.lang.annotation.Pointcut", AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
|
||||
private static final boolean aspectJPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
|
||||
AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
if (aspectJPresent) {
|
||||
return AspectDelegate.processAheadOfTime(beanFactory);
|
||||
@@ -52,6 +53,7 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on AspectJ at runtime.
|
||||
*/
|
||||
@@ -63,7 +65,6 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
|
||||
List<Advisor> advisors = builder.buildAspectJAdvisors();
|
||||
return (advisors.isEmpty() ? null : new AspectContribution(advisors));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +85,6 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+105
-21
@@ -18,6 +18,9 @@ package org.springframework.aop.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.aop.ClassFilter;
|
||||
@@ -25,11 +28,15 @@ import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
|
||||
/**
|
||||
* Pointcut and method matcher for use as a simple <b>cflow</b>-style pointcut.
|
||||
*
|
||||
* <p>Each configured method name pattern can be an exact method name or a
|
||||
* pattern (see {@link #isMatch(String, String)} for details on the supported
|
||||
* pattern styles).
|
||||
*
|
||||
* <p>Note that evaluating such pointcuts is 10-15 times slower than evaluating
|
||||
* normal pointcuts, but they are useful in some cases.
|
||||
*
|
||||
@@ -37,6 +44,8 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @see NameMatchMethodPointcut
|
||||
* @see JdkRegexpMethodPointcut
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable {
|
||||
@@ -48,11 +57,10 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
|
||||
protected final Class<?> clazz;
|
||||
|
||||
/**
|
||||
* The method against which to match, potentially {@code null}.
|
||||
* <p>Available for use in subclasses since 6.1.
|
||||
* An immutable list of distinct method name patterns against which to match.
|
||||
* @since 6.1
|
||||
*/
|
||||
@Nullable
|
||||
protected final String methodName;
|
||||
protected final List<String> methodNamePatterns;
|
||||
|
||||
private final AtomicInteger evaluationCount = new AtomicInteger();
|
||||
|
||||
@@ -62,21 +70,52 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
|
||||
* @param clazz the class
|
||||
*/
|
||||
public ControlFlowPointcut(Class<?> clazz) {
|
||||
this(clazz, null);
|
||||
this(clazz, (String) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new pointcut that matches all calls below the given method
|
||||
* in the given class.
|
||||
* <p>If no method name is given, the pointcut matches all control flows
|
||||
* Construct a new pointcut that matches all calls below a method matching
|
||||
* the given method name pattern in the given class.
|
||||
* <p>If no method name pattern is given, the pointcut matches all control flows
|
||||
* below the given class.
|
||||
* @param clazz the class
|
||||
* @param methodName the name of the method (may be {@code null})
|
||||
* @param methodNamePattern the method name pattern (may be {@code null})
|
||||
*/
|
||||
public ControlFlowPointcut(Class<?> clazz, @Nullable String methodName) {
|
||||
public ControlFlowPointcut(Class<?> clazz, @Nullable String methodNamePattern) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
this.clazz = clazz;
|
||||
this.methodName = methodName;
|
||||
this.methodNamePatterns = (methodNamePattern != null ?
|
||||
Collections.singletonList(methodNamePattern) : Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new pointcut that matches all calls below a method matching
|
||||
* one of the given method name patterns in the given class.
|
||||
* <p>If no method name pattern is given, the pointcut matches all control flows
|
||||
* below the given class.
|
||||
* @param clazz the class
|
||||
* @param methodNamePatterns the method name patterns (potentially empty)
|
||||
* @since 6.1
|
||||
*/
|
||||
public ControlFlowPointcut(Class<?> clazz, String... methodNamePatterns) {
|
||||
this(clazz, Arrays.asList(methodNamePatterns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new pointcut that matches all calls below a method matching
|
||||
* one of the given method name patterns in the given class.
|
||||
* <p>If no method name pattern is given, the pointcut matches all control flows
|
||||
* below the given class.
|
||||
* @param clazz the class
|
||||
* @param methodNamePatterns the method name patterns (potentially empty)
|
||||
* @since 6.1
|
||||
*/
|
||||
public ControlFlowPointcut(Class<?> clazz, List<String> methodNamePatterns) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
Assert.notNull(methodNamePatterns, "List of method name patterns must not be null");
|
||||
Assert.noNullElements(methodNamePatterns, "List of method name patterns must not contain null elements");
|
||||
this.clazz = clazz;
|
||||
this.methodNamePatterns = methodNamePatterns.stream().distinct().toList();
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +147,16 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
|
||||
incrementEvaluationCount();
|
||||
|
||||
for (StackTraceElement element : new Throwable().getStackTrace()) {
|
||||
if (element.getClassName().equals(this.clazz.getName()) &&
|
||||
(this.methodName == null || element.getMethodName().equals(this.methodName))) {
|
||||
return true;
|
||||
if (element.getClassName().equals(this.clazz.getName())) {
|
||||
if (this.methodNamePatterns.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String methodName = element.getMethodName();
|
||||
for (int i = 0; i < this.methodNamePatterns.size(); i++) {
|
||||
if (isMatch(methodName, i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -134,6 +180,47 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
|
||||
this.evaluationCount.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given method name matches the method name pattern at the
|
||||
* specified index.
|
||||
* <p>This method is invoked by {@link #matches(Method, Class, Object...)}.
|
||||
* <p>The default implementation retrieves the method name pattern from
|
||||
* {@link #methodNamePatterns} and delegates to {@link #isMatch(String, String)}.
|
||||
* <p>Can be overridden in subclasses — for example, to support
|
||||
* regular expressions.
|
||||
* @param methodName the method name to check
|
||||
* @param patternIndex the index of the method name pattern
|
||||
* @return {@code true} if the method name matches the pattern at the specified
|
||||
* index
|
||||
* @since 6.1
|
||||
* @see #methodNamePatterns
|
||||
* @see #isMatch(String, String)
|
||||
* @see #matches(Method, Class, Object...)
|
||||
*/
|
||||
protected boolean isMatch(String methodName, int patternIndex) {
|
||||
String methodNamePattern = this.methodNamePatterns.get(patternIndex);
|
||||
return isMatch(methodName, methodNamePattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given method name matches the method name pattern.
|
||||
* <p>This method is invoked by {@link #isMatch(String, int)}.
|
||||
* <p>The default implementation checks for direct equality as well as
|
||||
* {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy} matches.
|
||||
* <p>Can be overridden in subclasses — for example, to support a
|
||||
* different style of simple pattern matching.
|
||||
* @param methodName the method name to check
|
||||
* @param methodNamePattern the method name pattern
|
||||
* @return {@code true} if the method name matches the pattern
|
||||
* @since 6.1
|
||||
* @see #isMatch(String, int)
|
||||
* @see PatternMatchUtils#simpleMatch(String, String)
|
||||
*/
|
||||
protected boolean isMatch(String methodName, String methodNamePattern) {
|
||||
return (methodName.equals(methodNamePattern) ||
|
||||
PatternMatchUtils.simpleMatch(methodNamePattern, methodName));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClassFilter getClassFilter() {
|
||||
@@ -149,22 +236,19 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof ControlFlowPointcut that &&
|
||||
this.clazz.equals(that.clazz)) &&
|
||||
ObjectUtils.nullSafeEquals(this.methodName, that.methodName));
|
||||
this.clazz.equals(that.clazz)) && this.methodNamePatterns.equals(that.methodNamePatterns));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int code = this.clazz.hashCode();
|
||||
if (this.methodName != null) {
|
||||
code = 37 * code + this.methodName.hashCode();
|
||||
}
|
||||
code = 37 * code + this.methodNamePatterns.hashCode();
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + ": class = " + this.clazz.getName() + "; methodName = " + this.methodName;
|
||||
return getClass().getName() + ": class = " + this.clazz.getName() + "; methodNamePatterns = " + this.methodNamePatterns;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
|
||||
org.springframework.aop.scope.ScopedProxyBeanRegistrationAotProcessor
|
||||
org.springframework.aop.scope.ScopedProxyBeanRegistrationAotProcessor,\
|
||||
org.springframework.aop.aspectj.annotation.AspectJAdvisorBeanRegistrationAotProcessor
|
||||
|
||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor= \
|
||||
org.springframework.aop.aspectj.annotation.AspectJBeanFactoryInitializationAotProcessor
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
|
||||
|
||||
/**
|
||||
* Tests for {@link AspectJAdvisorBeanRegistrationAotProcessor}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class AspectJAdvisorBeanRegistrationAotProcessorTests {
|
||||
|
||||
private final GenerationContext generationContext = new TestGenerationContext();
|
||||
|
||||
private final RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
|
||||
|
||||
@Test
|
||||
void shouldProcessesAspectJClass() {
|
||||
process(AspectJClass.class);
|
||||
assertThat(reflection().onType(AspectJClass.class).withMemberCategory(MemberCategory.DECLARED_FIELDS))
|
||||
.accepts(this.runtimeHints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipRegularClass() {
|
||||
process(RegularClass.class);
|
||||
assertThat(this.runtimeHints.reflection().typeHints()).isEmpty();
|
||||
}
|
||||
|
||||
void process(Class<?> beanClass) {
|
||||
BeanRegistrationAotContribution contribution = createContribution(beanClass);
|
||||
if (contribution != null) {
|
||||
contribution.applyTo(this.generationContext, mock());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
|
||||
return new AspectJAdvisorBeanRegistrationAotProcessor()
|
||||
.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.getName()));
|
||||
}
|
||||
|
||||
|
||||
static class AspectJClass {
|
||||
private static java.lang.Throwable ajc$initFailureCause;
|
||||
}
|
||||
|
||||
static class RegularClass {
|
||||
private static java.lang.Throwable initFailureCause;
|
||||
}
|
||||
|
||||
}
|
||||
+187
-92
@@ -17,6 +17,8 @@
|
||||
package org.springframework.aop.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -38,110 +40,88 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class ControlFlowPointcutTests {
|
||||
|
||||
@Test
|
||||
void matches() {
|
||||
TestBean target = new TestBean();
|
||||
target.setAge(27);
|
||||
void matchesExactMethodName() {
|
||||
MyComponent component = new MyComponent();
|
||||
TestBean target = new TestBean("Jane", 27);
|
||||
ControlFlowPointcut cflow = pointcut("getAge");
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
ControlFlowPointcut cflow = new ControlFlowPointcut(One.class, "getAge");
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
ITestBean proxied = (ITestBean) pf.getProxy();
|
||||
pf.addAdvisor(new DefaultPointcutAdvisor(cflow, nop));
|
||||
ITestBean proxy = (ITestBean) pf.getProxy();
|
||||
|
||||
// Not advised, not under One
|
||||
assertThat(proxied.getAge()).isEqualTo(target.getAge());
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
// Will not be advised: not under MyComponent
|
||||
assertThat(proxy.getAge()).isEqualTo(target.getAge());
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(1);
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
|
||||
// Will be advised
|
||||
assertThat(new One().getAge(proxied)).isEqualTo(target.getAge());
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
// Will be advised due to "getAge" pattern: the proxy is invoked under MyComponent#getAge
|
||||
assertThat(component.getAge(proxy)).isEqualTo(target.getAge());
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(2);
|
||||
|
||||
// Won't be advised
|
||||
assertThat(new One().nomatch(proxied)).isEqualTo(target.getAge());
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
|
||||
// Will not be advised: the proxy is invoked under MyComponent, but there is no match for "nomatch"
|
||||
assertThat(component.nomatch(proxy)).isEqualTo(target.getAge());
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(3);
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesMethodNamePatterns() {
|
||||
ControlFlowPointcut cflow = pointcut("set", "getAge");
|
||||
assertMatchesSetAndGetAge(cflow);
|
||||
|
||||
cflow = pointcut("foo", "get*", "bar", "*se*", "baz");
|
||||
assertMatchesSetAndGetAge(cflow);
|
||||
}
|
||||
|
||||
@Test
|
||||
void regExControlFlowPointcut() {
|
||||
ControlFlowPointcut cflow = new RegExControlFlowPointcut(MyComponent.class, "(set.*?|getAge)");
|
||||
assertMatchesSetAndGetAge(cflow);
|
||||
|
||||
cflow = new RegExControlFlowPointcut(MyComponent.class, "set", "^getAge$");
|
||||
assertMatchesSetAndGetAge(cflow);
|
||||
}
|
||||
|
||||
@Test
|
||||
void controlFlowPointcutIsExtensible() {
|
||||
@SuppressWarnings("serial")
|
||||
class CustomControlFlowPointcut extends ControlFlowPointcut {
|
||||
|
||||
CustomControlFlowPointcut(Class<?> clazz, String methodName) {
|
||||
super(clazz, methodName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
super.incrementEvaluationCount();
|
||||
return super.matches(method, targetClass, args);
|
||||
}
|
||||
|
||||
Class<?> trackedClass() {
|
||||
return super.clazz;
|
||||
}
|
||||
|
||||
String trackedMethod() {
|
||||
return super.methodName;
|
||||
}
|
||||
}
|
||||
|
||||
CustomControlFlowPointcut cflow = new CustomControlFlowPointcut(One.class, "getAge");
|
||||
|
||||
assertThat(cflow.trackedClass()).isEqualTo(One.class);
|
||||
assertThat(cflow.trackedMethod()).isEqualTo("getAge");
|
||||
|
||||
TestBean target = new TestBean("Jane", 27);
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
pf.addAdvisor(new DefaultPointcutAdvisor(cflow, nop));
|
||||
ITestBean proxy = (ITestBean) pf.getProxy();
|
||||
|
||||
// Not advised: the proxy is not invoked under One#getAge
|
||||
assertThat(proxy.getAge()).isEqualTo(target.getAge());
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(2); // intentional double increment
|
||||
|
||||
// Will be advised: the proxy is invoked under One#getAge
|
||||
assertThat(new One().getAge(proxy)).isEqualTo(target.getAge());
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(4); // intentional double increment
|
||||
|
||||
// Won't be advised: the proxy is not invoked under One#getAge
|
||||
assertThat(new One().nomatch(proxy)).isEqualTo(target.getAge());
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(6); // intentional double increment
|
||||
CustomControlFlowPointcut cflow = new CustomControlFlowPointcut(MyComponent.class, "set*", "getAge", "set*", "set*");
|
||||
assertMatchesSetAndGetAge(cflow, 2);
|
||||
assertThat(cflow.trackedClass()).isEqualTo(MyComponent.class);
|
||||
assertThat(cflow.trackedMethodNamePatterns()).containsExactly("set*", "getAge");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that we can use a cflow pointcut only in conjunction with
|
||||
* Check that we can use a cflow pointcut in conjunction with
|
||||
* a static pointcut: e.g. all setter methods that are invoked under
|
||||
* a particular class. This greatly reduces the number of calls
|
||||
* to the cflow pointcut, meaning that it's not so prohibitively
|
||||
* expensive.
|
||||
*/
|
||||
@Test
|
||||
void selectiveApplication() {
|
||||
TestBean target = new TestBean();
|
||||
target.setAge(27);
|
||||
void controlFlowPointcutCanBeCombinedWithStaticPointcut() {
|
||||
MyComponent component = new MyComponent();
|
||||
TestBean target = new TestBean("Jane", 27);
|
||||
ControlFlowPointcut cflow = pointcut();
|
||||
Pointcut settersUnderMyComponent = Pointcuts.intersection(Pointcuts.SETTERS, cflow);
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
ControlFlowPointcut cflow = new ControlFlowPointcut(One.class);
|
||||
Pointcut settersUnderOne = Pointcuts.intersection(Pointcuts.SETTERS, cflow);
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
ITestBean proxied = (ITestBean) pf.getProxy();
|
||||
pf.addAdvisor(new DefaultPointcutAdvisor(settersUnderOne, nop));
|
||||
pf.addAdvisor(new DefaultPointcutAdvisor(settersUnderMyComponent, nop));
|
||||
ITestBean proxy = (ITestBean) pf.getProxy();
|
||||
|
||||
// Not advised, not under One
|
||||
// Will not be advised: not under MyComponent
|
||||
target.setAge(16);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(0);
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
|
||||
// Not advised; under One but not a setter
|
||||
assertThat(new One().getAge(proxied)).isEqualTo(16);
|
||||
// Will not be advised: under MyComponent but not a setter
|
||||
assertThat(component.getAge(proxy)).isEqualTo(16);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(0);
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
|
||||
// Won't be advised
|
||||
new One().set(proxied);
|
||||
// Will be advised due to Pointcuts.SETTERS: the proxy is invoked under MyComponent#set
|
||||
component.set(proxy);
|
||||
assertThat(proxy.getAge()).isEqualTo(5);
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
|
||||
// We saved most evaluations
|
||||
@@ -149,34 +129,149 @@ class ControlFlowPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsAndHashCode() throws Exception {
|
||||
assertThat(new ControlFlowPointcut(One.class)).isEqualTo(new ControlFlowPointcut(One.class));
|
||||
assertThat(new ControlFlowPointcut(One.class, "getAge")).isEqualTo(new ControlFlowPointcut(One.class, "getAge"));
|
||||
assertThat(new ControlFlowPointcut(One.class, "getAge")).isNotEqualTo(new ControlFlowPointcut(One.class));
|
||||
void equalsAndHashCode() {
|
||||
assertThat(pointcut()).isEqualTo(pointcut());
|
||||
assertThat(pointcut()).hasSameHashCodeAs(pointcut());
|
||||
|
||||
assertThat(new ControlFlowPointcut(One.class)).hasSameHashCodeAs(new ControlFlowPointcut(One.class));
|
||||
assertThat(new ControlFlowPointcut(One.class, "getAge")).hasSameHashCodeAs(new ControlFlowPointcut(One.class, "getAge"));
|
||||
assertThat(new ControlFlowPointcut(One.class, "getAge")).doesNotHaveSameHashCodeAs(new ControlFlowPointcut(One.class));
|
||||
assertThat(pointcut("getAge")).isEqualTo(pointcut("getAge"));
|
||||
assertThat(pointcut("getAge")).hasSameHashCodeAs(pointcut("getAge"));
|
||||
|
||||
assertThat(pointcut("getAge")).isNotEqualTo(pointcut());
|
||||
assertThat(pointcut("getAge")).doesNotHaveSameHashCodeAs(pointcut());
|
||||
|
||||
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut("get*", "set*"));
|
||||
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut("get*", "set*", "set*", "get*"));
|
||||
assertThat(pointcut("get*", "set*")).hasSameHashCodeAs(pointcut("get*", "get*", "set*"));
|
||||
|
||||
assertThat(pointcut("get*", "set*")).isNotEqualTo(pointcut("set*", "get*"));
|
||||
assertThat(pointcut("get*", "set*")).doesNotHaveSameHashCodeAs(pointcut("set*", "get*"));
|
||||
|
||||
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut(List.of("get*", "set*")));
|
||||
assertThat(pointcut("get*", "set*")).isEqualTo(pointcut(List.of("get*", "set*", "set*", "get*")));
|
||||
assertThat(pointcut("get*", "set*")).hasSameHashCodeAs(pointcut(List.of("get*", "get*", "set*")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
assertThat(new ControlFlowPointcut(One.class)).asString()
|
||||
.isEqualTo(ControlFlowPointcut.class.getName() + ": class = " + One.class.getName() + "; methodName = null");
|
||||
assertThat(new ControlFlowPointcut(One.class, "getAge")).asString()
|
||||
.isEqualTo(ControlFlowPointcut.class.getName() + ": class = " + One.class.getName() + "; methodName = getAge");
|
||||
String pointcutType = ControlFlowPointcut.class.getName();
|
||||
String componentType = MyComponent.class.getName();
|
||||
|
||||
assertThat(pointcut()).asString()
|
||||
.startsWith(pointcutType)
|
||||
.contains(componentType)
|
||||
.endsWith("[]");
|
||||
|
||||
assertThat(pointcut("getAge")).asString()
|
||||
.startsWith(pointcutType)
|
||||
.contains(componentType)
|
||||
.endsWith("[getAge]");
|
||||
|
||||
assertThat(pointcut("get*", "set*", "get*")).asString()
|
||||
.startsWith(pointcutType)
|
||||
.contains(componentType)
|
||||
.endsWith("[get*, set*]");
|
||||
}
|
||||
|
||||
|
||||
private static class One {
|
||||
int getAge(ITestBean proxied) {
|
||||
return proxied.getAge();
|
||||
private static ControlFlowPointcut pointcut() {
|
||||
return new ControlFlowPointcut(MyComponent.class);
|
||||
}
|
||||
|
||||
private static ControlFlowPointcut pointcut(String methodNamePattern) {
|
||||
return new ControlFlowPointcut(MyComponent.class, methodNamePattern);
|
||||
}
|
||||
|
||||
private static ControlFlowPointcut pointcut(String... methodNamePatterns) {
|
||||
return new ControlFlowPointcut(MyComponent.class, methodNamePatterns);
|
||||
}
|
||||
|
||||
private static ControlFlowPointcut pointcut(List<String> methodNamePatterns) {
|
||||
return new ControlFlowPointcut(MyComponent.class, methodNamePatterns);
|
||||
}
|
||||
|
||||
private static void assertMatchesSetAndGetAge(ControlFlowPointcut cflow) {
|
||||
assertMatchesSetAndGetAge(cflow, 1);
|
||||
}
|
||||
|
||||
private static void assertMatchesSetAndGetAge(ControlFlowPointcut cflow, int evaluationFactor) {
|
||||
MyComponent component = new MyComponent();
|
||||
TestBean target = new TestBean("Jane", 27);
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
pf.addAdvisor(new DefaultPointcutAdvisor(cflow, nop));
|
||||
ITestBean proxy = (ITestBean) pf.getProxy();
|
||||
|
||||
// Will not be advised: not under MyComponent
|
||||
assertThat(proxy.getAge()).isEqualTo(target.getAge());
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(1 * evaluationFactor);
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
|
||||
// Will be advised: the proxy is invoked under MyComponent#getAge
|
||||
assertThat(component.getAge(proxy)).isEqualTo(target.getAge());
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(2 * evaluationFactor);
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
|
||||
// Will be advised: the proxy is invoked under MyComponent#set
|
||||
component.set(proxy);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(3 * evaluationFactor);
|
||||
assertThat(proxy.getAge()).isEqualTo(5);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(4 * evaluationFactor);
|
||||
assertThat(nop.getCount()).isEqualTo(2);
|
||||
|
||||
// Will not be advised: the proxy is invoked under MyComponent, but there is no match for "nomatch"
|
||||
assertThat(component.nomatch(proxy)).isEqualTo(target.getAge());
|
||||
assertThat(nop.getCount()).isEqualTo(2);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(5 * evaluationFactor);
|
||||
}
|
||||
|
||||
|
||||
private static class MyComponent {
|
||||
int getAge(ITestBean proxy) {
|
||||
return proxy.getAge();
|
||||
}
|
||||
int nomatch(ITestBean proxied) {
|
||||
return proxied.getAge();
|
||||
int nomatch(ITestBean proxy) {
|
||||
return proxy.getAge();
|
||||
}
|
||||
void set(ITestBean proxied) {
|
||||
proxied.setAge(5);
|
||||
void set(ITestBean proxy) {
|
||||
proxy.setAge(5);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomControlFlowPointcut extends ControlFlowPointcut {
|
||||
|
||||
CustomControlFlowPointcut(Class<?> clazz, String... methodNamePatterns) {
|
||||
super(clazz, methodNamePatterns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
super.incrementEvaluationCount();
|
||||
return super.matches(method, targetClass, args);
|
||||
}
|
||||
|
||||
Class<?> trackedClass() {
|
||||
return super.clazz;
|
||||
}
|
||||
|
||||
List<String> trackedMethodNamePatterns() {
|
||||
return super.methodNamePatterns;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class RegExControlFlowPointcut extends ControlFlowPointcut {
|
||||
|
||||
private final List<Pattern> compiledPatterns;
|
||||
|
||||
RegExControlFlowPointcut(Class<?> clazz, String... methodNamePatterns) {
|
||||
super(clazz, methodNamePatterns);
|
||||
this.compiledPatterns = super.methodNamePatterns.stream().map(Pattern::compile).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMatch(String methodName, int patternIndex) {
|
||||
return this.compiledPatterns.get(patternIndex).matcher(methodName).matches();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-39
@@ -30,34 +30,35 @@ import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link NameMatchMethodPointcut}.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class NameMatchMethodPointcutTests {
|
||||
class NameMatchMethodPointcutTests {
|
||||
|
||||
protected NameMatchMethodPointcut pc;
|
||||
private final NameMatchMethodPointcut pc = new NameMatchMethodPointcut();
|
||||
|
||||
protected Person proxied;
|
||||
private final SerializableNopInterceptor nop = new SerializableNopInterceptor();
|
||||
|
||||
protected SerializableNopInterceptor nop;
|
||||
private Person personProxy;
|
||||
|
||||
|
||||
/**
|
||||
* Create an empty pointcut, populating instance variables.
|
||||
*/
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
void setup() {
|
||||
ProxyFactory pf = new ProxyFactory(new SerializablePerson());
|
||||
nop = new SerializableNopInterceptor();
|
||||
pc = new NameMatchMethodPointcut();
|
||||
pf.addAdvisor(new DefaultPointcutAdvisor(pc, nop));
|
||||
proxied = (Person) pf.getProxy();
|
||||
personProxy = (Person) pf.getProxy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchingOnly() {
|
||||
// Can't do exact matching through isMatch
|
||||
void isMatch() {
|
||||
assertThat(pc.isMatch("echo", "echo")).isTrue();
|
||||
assertThat(pc.isMatch("echo", "ech*")).isTrue();
|
||||
assertThat(pc.isMatch("setName", "setN*")).isTrue();
|
||||
assertThat(pc.isMatch("setName", "set*")).isTrue();
|
||||
@@ -67,73 +68,87 @@ public class NameMatchMethodPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() throws Throwable {
|
||||
void noMappedMethodNamePatterns() throws Throwable {
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
proxied.getName();
|
||||
proxied.setName("");
|
||||
proxied.echo(null);
|
||||
personProxy.getName();
|
||||
personProxy.setName("");
|
||||
personProxy.echo(null);
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchOneMethod() throws Throwable {
|
||||
void methodNamePatternsMappedIndividually() throws Throwable {
|
||||
pc.addMethodName("echo");
|
||||
pc.addMethodName("set*");
|
||||
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
proxied.getName();
|
||||
proxied.getName();
|
||||
|
||||
personProxy.getName();
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
proxied.echo(null);
|
||||
|
||||
personProxy.getName();
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
|
||||
personProxy.echo(null);
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
|
||||
proxied.setName("");
|
||||
personProxy.setName("");
|
||||
assertThat(nop.getCount()).isEqualTo(2);
|
||||
proxied.setAge(25);
|
||||
assertThat(proxied.getAge()).isEqualTo(25);
|
||||
|
||||
personProxy.setAge(25);
|
||||
assertThat(nop.getCount()).isEqualTo(3);
|
||||
assertThat(personProxy.getAge()).isEqualTo(25);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSets() throws Throwable {
|
||||
void methodNamePatternsMappedAsVarargs() throws Throwable {
|
||||
pc.setMappedNames("set*", "echo");
|
||||
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
proxied.getName();
|
||||
proxied.setName("");
|
||||
|
||||
personProxy.getName();
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
|
||||
personProxy.setName("");
|
||||
assertThat(nop.getCount()).isEqualTo(1);
|
||||
proxied.echo(null);
|
||||
|
||||
personProxy.echo(null);
|
||||
assertThat(nop.getCount()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializable() throws Throwable {
|
||||
testSets();
|
||||
// Count is now 2
|
||||
Person p2 = SerializationTestUtils.serializeAndDeserialize(proxied);
|
||||
void serializable() throws Throwable {
|
||||
methodNamePatternsMappedAsVarargs();
|
||||
|
||||
Person p2 = SerializationTestUtils.serializeAndDeserialize(personProxy);
|
||||
NopInterceptor nop2 = (NopInterceptor) ((Advised) p2).getAdvisors()[0].getAdvice();
|
||||
|
||||
// nop.getCount() should still be 2.
|
||||
assertThat(nop2.getCount()).isEqualTo(2);
|
||||
|
||||
p2.getName();
|
||||
assertThat(nop2.getCount()).isEqualTo(2);
|
||||
|
||||
p2.echo(null);
|
||||
assertThat(nop2.getCount()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsAndHashCode() {
|
||||
void equalsAndHashCode() {
|
||||
NameMatchMethodPointcut pc1 = new NameMatchMethodPointcut();
|
||||
NameMatchMethodPointcut pc2 = new NameMatchMethodPointcut();
|
||||
|
||||
String foo = "foo";
|
||||
String mappedNamePattern = "foo";
|
||||
|
||||
assertThat(pc2).isEqualTo(pc1);
|
||||
assertThat(pc2.hashCode()).isEqualTo(pc1.hashCode());
|
||||
assertThat(pc2).hasSameHashCodeAs(pc1);
|
||||
|
||||
pc1.setMappedName(foo);
|
||||
assertThat(pc1.equals(pc2)).isFalse();
|
||||
assertThat(pc1.hashCode()).isNotEqualTo(pc2.hashCode());
|
||||
pc1.setMappedName(mappedNamePattern);
|
||||
assertThat(pc1).isNotEqualTo(pc2);
|
||||
assertThat(pc1).doesNotHaveSameHashCodeAs(pc2);
|
||||
|
||||
pc2.setMappedName(foo);
|
||||
pc2.setMappedName(mappedNamePattern);
|
||||
assertThat(pc2).isEqualTo(pc1);
|
||||
assertThat(pc2.hashCode()).isEqualTo(pc1.hashCode());
|
||||
assertThat(pc2).hasSameHashCodeAs(pc1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-1
@@ -174,7 +174,9 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
Map<Integer, ValueHolder> indexedValues = constructorValues.getIndexedArgumentValues();
|
||||
if (!indexedValues.isEmpty()) {
|
||||
indexedValues.forEach((index, valueHolder) -> {
|
||||
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
|
||||
Object value = valueHolder.getValue();
|
||||
CodeBlock valueCode = castIfNecessary(value == null, Object.class,
|
||||
generateValue(valueHolder.getName(), value));
|
||||
code.addStatement(
|
||||
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
|
||||
BEAN_DEFINITION_VARIABLE, index, valueCode);
|
||||
@@ -346,6 +348,20 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the specified {@code valueCode} to the specified {@code castType} if
|
||||
* the {@code castNecessary} is {@code true}. Otherwise return the valueCode
|
||||
* as is.
|
||||
* @param castNecessary whether a cast is necessary
|
||||
* @param castType the type to cast to
|
||||
* @param valueCode the code for the value
|
||||
* @return the existing value or a form of {@code (CastType) valueCode} if a
|
||||
* cast is necessary
|
||||
*/
|
||||
private CodeBlock castIfNecessary(boolean castNecessary, Class<?> castType, CodeBlock valueCode) {
|
||||
return (castNecessary ? CodeBlock.of("($T) $L", castType, valueCode) : valueCode);
|
||||
}
|
||||
|
||||
static class PropertyNamesStack {
|
||||
|
||||
private static final ThreadLocal<ArrayDeque<String>> threadLocal = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
|
||||
+20
-6
@@ -235,6 +235,18 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorArgumentValuesWhenIndexedNullValue() {
|
||||
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
|
||||
compile((actual, compiled) -> {
|
||||
ConstructorArgumentValues argumentValues = actual.getConstructorArgumentValues();
|
||||
Map<Integer, ValueHolder> values = argumentValues.getIndexedArgumentValues();
|
||||
assertThat(values.get(0)).satisfies(assertValueHolder(null, null, null));
|
||||
assertThat(values).hasSize(1);
|
||||
assertThat(argumentValues.getGenericArgumentValues()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorArgumentValuesWhenGenericValuesWithName() {
|
||||
this.beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(String.class);
|
||||
@@ -255,7 +267,9 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
});
|
||||
}
|
||||
|
||||
private Consumer<ValueHolder> assertValueHolder(Object value, @Nullable Class<?> type, @Nullable String name) {
|
||||
private Consumer<ValueHolder> assertValueHolder(
|
||||
@Nullable Object value, @Nullable Class<?> type, @Nullable String name) {
|
||||
|
||||
return valueHolder -> {
|
||||
assertThat(valueHolder.getValue()).isEqualTo(value);
|
||||
assertThat(valueHolder.getType()).isEqualTo((type != null ? type.getName() : null));
|
||||
@@ -273,7 +287,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
|
||||
});
|
||||
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
|
||||
assertHasDecalredFieldsHint(PropertyValuesBean.class);
|
||||
assertHasDeclaredFieldsHint(PropertyValuesBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -286,8 +300,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
|
||||
});
|
||||
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
|
||||
assertHasDecalredFieldsHint(ExtendedPropertyValuesBean.class);
|
||||
assertHasDecalredFieldsHint(PropertyValuesBean.class);
|
||||
assertHasDeclaredFieldsHint(ExtendedPropertyValuesBean.class);
|
||||
assertHasDeclaredFieldsHint(PropertyValuesBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -348,7 +362,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
assertThat(actual.getPropertyValues().get("name")).isEqualTo("World");
|
||||
});
|
||||
assertHasMethodInvokeHints(PropertyValuesFactoryBean.class, "setPrefix", "setName" );
|
||||
assertHasDecalredFieldsHint(PropertyValuesFactoryBean.class);
|
||||
assertHasDeclaredFieldsHint(PropertyValuesFactoryBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -502,7 +516,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
|
||||
.test(this.generationContext.getRuntimeHints()));
|
||||
}
|
||||
|
||||
private void assertHasDecalredFieldsHint(Class<?> beanType) {
|
||||
private void assertHasDeclaredFieldsHint(Class<?> beanType) {
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onType(beanType).withMemberCategory(MemberCategory.DECLARED_FIELDS))
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
|
||||
Vendored
+2
-2
@@ -193,7 +193,7 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
* Set the common cache type that this cache manager builds to async.
|
||||
* This applies to {@link #setCacheNames} as well as on-demand caches.
|
||||
* <p>Individual cache registrations (such as {@link #registerCustomCache(String, AsyncCache)}
|
||||
* and {@link #registerCustomCache(String, com.github.benmanes.caffeine.cache.Cache)}
|
||||
* and {@link #registerCustomCache(String, com.github.benmanes.caffeine.cache.Cache)})
|
||||
* are not dependent on this setting.
|
||||
* <p>By default, this cache manager builds regular native Caffeine caches.
|
||||
* To switch to async caches which can also be used through the synchronous API
|
||||
@@ -322,7 +322,7 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
* Build a common {@link CaffeineCache} instance for the specified cache name,
|
||||
* using the common Caffeine configuration specified on this cache manager.
|
||||
* <p>Delegates to {@link #adaptCaffeineCache} as the adaptation method to
|
||||
* Spring's cache abstraction (allowing for centralized decoration etc),
|
||||
* Spring's cache abstraction (allowing for centralized decoration etc.),
|
||||
* passing in a freshly built native Caffeine Cache instance.
|
||||
* @param name the name of the cache
|
||||
* @return the Spring CaffeineCache adapter (or a decorator thereof)
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -102,7 +102,7 @@ class CachePutOperation extends AbstractJCacheKeyOperation<CachePut> {
|
||||
result = parameter;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("More than one @CacheValue found on " + method + "");
|
||||
throw new IllegalArgumentException("More than one @CacheValue found on " + method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -146,7 +146,6 @@ class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheResultOper
|
||||
return new CacheOperationInvoker.ThrowableWrapper(clone);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private static <T extends Throwable> T cloneException(T exception) {
|
||||
try {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -232,7 +232,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
|
||||
* Only resolve the default exception cache resolver when an exception needs to be handled.
|
||||
* <p>A non-JSR-107 setup requires either a {@link CacheManager} or a {@link CacheResolver}.
|
||||
* If only the latter is specified, it is not possible to extract a default exception
|
||||
* {@code CacheResolver} from a custom {@code CacheResolver} implementation so we have to
|
||||
* {@code CacheResolver} from a custom {@code CacheResolver} implementation, so we have to
|
||||
* fall back on the {@code CacheManager}.
|
||||
* <p>This gives this weird situation of a perfectly valid configuration that breaks all
|
||||
* of a sudden because the JCache support is enabled. To avoid this we resolve the default
|
||||
|
||||
+1
-1
@@ -511,7 +511,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
|
||||
"' since its singleton instance has been created too early. The typical cause " +
|
||||
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
|
||||
"return type: Consider declaring such methods as 'static' and/or mark the " +
|
||||
"return type: Consider declaring such methods as 'static' and/or marking the " +
|
||||
"containing configuration class as 'proxyBeanMethods=false'.");
|
||||
}
|
||||
configBeanDefs.put(beanName, abd);
|
||||
|
||||
+18
-5
@@ -73,22 +73,32 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
/**
|
||||
* Property name for a common context checkpoint: {@value}.
|
||||
* @since 6.1
|
||||
* @see #CHECKPOINT_ON_REFRESH_VALUE
|
||||
* @see #ON_REFRESH_VALUE
|
||||
* @see org.crac.Core#checkpointRestore()
|
||||
*/
|
||||
public static final String CHECKPOINT_PROPERTY_NAME = "spring.context.checkpoint";
|
||||
|
||||
/**
|
||||
* Recognized value for the context checkpoint property: {@value}.
|
||||
* Property name for terminating the JVM when the context reaches a specific phase: {@value}.
|
||||
* @since 6.1
|
||||
* @see #ON_REFRESH_VALUE
|
||||
*/
|
||||
public static final String EXIT_PROPERTY_NAME = "spring.context.exit";
|
||||
|
||||
/**
|
||||
* Recognized value for the context checkpoint and exit properties: {@value}.
|
||||
* @since 6.1
|
||||
* @see #CHECKPOINT_PROPERTY_NAME
|
||||
* @see org.crac.Core#checkpointRestore()
|
||||
* @see #EXIT_PROPERTY_NAME
|
||||
*/
|
||||
public static final String CHECKPOINT_ON_REFRESH_VALUE = "onRefresh";
|
||||
public static final String ON_REFRESH_VALUE = "onRefresh";
|
||||
|
||||
|
||||
private static final boolean checkpointOnRefresh =
|
||||
CHECKPOINT_ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(CHECKPOINT_PROPERTY_NAME));
|
||||
ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(CHECKPOINT_PROPERTY_NAME));
|
||||
|
||||
private static final boolean exitOnRefresh =
|
||||
ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(EXIT_PROPERTY_NAME));
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -182,6 +192,9 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
if (checkpointOnRefresh) {
|
||||
new CracDelegate().checkpointRestore();
|
||||
}
|
||||
if (exitOnRefresh) {
|
||||
Runtime.getRuntime().halt(0);
|
||||
}
|
||||
|
||||
this.stoppedBeans = null;
|
||||
try {
|
||||
|
||||
+27
-11
@@ -86,6 +86,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
@Nullable
|
||||
private ExecutorLifecycleDelegate lifecycleDelegate;
|
||||
|
||||
private volatile boolean lateShutdown;
|
||||
|
||||
|
||||
/**
|
||||
* Set the ThreadFactory to use for the ExecutorService's thread pool.
|
||||
@@ -127,8 +129,9 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
* <p>Default is {@code false} as of 6.1, triggering an early soft shutdown of
|
||||
* the executor and therefore rejecting any further task submissions. Switch this
|
||||
* to {@code true} in order to let other components submit tasks even during their
|
||||
* own destruction callbacks, at the expense of a longer shutdown phase.
|
||||
* This will usually go along with
|
||||
* own stop and destruction callbacks, at the expense of a longer shutdown phase.
|
||||
* The executor will not go through a coordinated lifecycle stop phase then
|
||||
* but rather only stop tasks on its own shutdown. This usually goes along with
|
||||
* {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}.
|
||||
* <p>This flag will only have effect when the executor is running in a Spring
|
||||
* application context and able to receive the {@link ContextClosedEvent}.
|
||||
@@ -144,9 +147,13 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
/**
|
||||
* Set whether to wait for scheduled tasks to complete on shutdown,
|
||||
* not interrupting running tasks and executing all tasks in the queue.
|
||||
* <p>Default is {@code false}, shutting down immediately through interrupting
|
||||
* ongoing tasks and clearing the queue. Switch this flag to {@code true} if
|
||||
* you prefer fully completed tasks at the expense of a longer shutdown phase.
|
||||
* <p>Default is {@code false}, with a coordinated lifecycle stop first (unless
|
||||
* {@link #setAcceptTasksAfterContextClose "acceptTasksAfterContextClose"}
|
||||
* has been set) and then an immediate shutdown through interrupting ongoing
|
||||
* tasks and clearing the queue. Switch this flag to {@code true} if you
|
||||
* prefer fully completed tasks at the expense of a longer shutdown phase.
|
||||
* The executor will not go through a coordinated lifecycle stop phase then
|
||||
* but rather only stop and wait for task completion on its own shutdown.
|
||||
* <p>Note that Spring's container shutdown continues while ongoing tasks
|
||||
* are being completed. If you want this executor to block and wait for the
|
||||
* termination of tasks before the rest of the container continues to shut
|
||||
@@ -374,7 +381,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
if (this.lifecycleDelegate != null) {
|
||||
if (this.lifecycleDelegate != null && !this.lateShutdown) {
|
||||
this.lifecycleDelegate.stop();
|
||||
}
|
||||
}
|
||||
@@ -386,9 +393,12 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
*/
|
||||
@Override
|
||||
public void stop(Runnable callback) {
|
||||
if (this.lifecycleDelegate != null) {
|
||||
if (this.lifecycleDelegate != null && !this.lateShutdown) {
|
||||
this.lifecycleDelegate.stop(callback);
|
||||
}
|
||||
else {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,10 +449,16 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent event) {
|
||||
if (event.getApplicationContext() == this.applicationContext && !this.acceptTasksAfterContextClose) {
|
||||
// Early shutdown signal: accept no further tasks, let existing tasks complete
|
||||
// before hitting the actual destruction step in the shutdown() method above.
|
||||
initiateShutdown();
|
||||
if (event.getApplicationContext() == this.applicationContext) {
|
||||
if (this.acceptTasksAfterContextClose || this.waitForTasksToCompleteOnShutdown) {
|
||||
// Late shutdown without early stop lifecycle.
|
||||
this.lateShutdown = true;
|
||||
}
|
||||
else {
|
||||
// Early shutdown signal: accept no further tasks, let existing tasks complete
|
||||
// before hitting the actual destruction step in the shutdown() method above.
|
||||
initiateShutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ final class ExecutorLifecycleDelegate implements SmartLifecycle {
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return (!this.executor.isShutdown() & !this.paused);
|
||||
return (!this.paused && !this.executor.isTerminated());
|
||||
}
|
||||
|
||||
void beforeExecute(Thread thread) {
|
||||
|
||||
+39
-33
@@ -301,8 +301,8 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
Function<Integer, MethodParameter> parameterFunction,
|
||||
Function<Integer, Object> argumentFunction) {
|
||||
|
||||
Map<MethodParameter, ValueResultBuilder> parameterViolations = new LinkedHashMap<>();
|
||||
Map<Path.Node, BeanResultBuilder> cascadedViolations = new LinkedHashMap<>();
|
||||
Map<MethodParameter, ParamResultBuilder> paramViolations = new LinkedHashMap<>();
|
||||
Map<BeanResultKey, BeanResultBuilder> beanViolations = new LinkedHashMap<>();
|
||||
|
||||
for (ConstraintViolation<Object> violation : violations) {
|
||||
Iterator<Path.Node> itr = violation.getPropertyPath().iterator();
|
||||
@@ -321,27 +321,29 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object argument = argumentFunction.apply(parameter.getParameterIndex());
|
||||
Object arg = argumentFunction.apply(parameter.getParameterIndex());
|
||||
if (!itr.hasNext()) {
|
||||
parameterViolations
|
||||
.computeIfAbsent(parameter, p -> new ValueResultBuilder(target, parameter, argument))
|
||||
paramViolations
|
||||
.computeIfAbsent(parameter, p -> new ParamResultBuilder(target, parameter, arg))
|
||||
.addViolation(violation);
|
||||
}
|
||||
else {
|
||||
cascadedViolations
|
||||
.computeIfAbsent(node, n -> new BeanResultBuilder(parameter, argument, itr.next()))
|
||||
Object leafBean = violation.getLeafBean();
|
||||
BeanResultKey key = new BeanResultKey(node, leafBean);
|
||||
beanViolations
|
||||
.computeIfAbsent(key, k -> new BeanResultBuilder(parameter, arg, itr.next(), leafBean))
|
||||
.addViolation(violation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<ParameterValidationResult> validatonResultList = new ArrayList<>();
|
||||
parameterViolations.forEach((parameter, builder) -> validatonResultList.add(builder.build()));
|
||||
cascadedViolations.forEach((node, builder) -> validatonResultList.add(builder.build()));
|
||||
validatonResultList.sort(resultComparator);
|
||||
List<ParameterValidationResult> resultList = new ArrayList<>();
|
||||
paramViolations.forEach((param, builder) -> resultList.add(builder.build()));
|
||||
beanViolations.forEach((key, builder) -> resultList.add(builder.build()));
|
||||
resultList.sort(resultComparator);
|
||||
|
||||
return MethodValidationResult.create(target, method, validatonResultList);
|
||||
return MethodValidationResult.create(target, method, resultList);
|
||||
}
|
||||
|
||||
private MethodParameter initMethodParameter(Method method, int index) {
|
||||
@@ -394,7 +396,7 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
* Builds a validation result for a value method parameter with constraints
|
||||
* declared directly on it.
|
||||
*/
|
||||
private final class ValueResultBuilder {
|
||||
private final class ParamResultBuilder {
|
||||
|
||||
private final Object target;
|
||||
|
||||
@@ -405,7 +407,7 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
|
||||
private final List<MessageSourceResolvable> resolvableErrors = new ArrayList<>();
|
||||
|
||||
public ValueResultBuilder(Object target, MethodParameter parameter, @Nullable Object argument) {
|
||||
public ParamResultBuilder(Object target, MethodParameter parameter, @Nullable Object argument) {
|
||||
this.target = target;
|
||||
this.parameter = parameter;
|
||||
this.argument = argument;
|
||||
@@ -431,7 +433,7 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
private final MethodParameter parameter;
|
||||
|
||||
@Nullable
|
||||
private final Object argument;
|
||||
private final Object bean;
|
||||
|
||||
@Nullable
|
||||
private final Object container;
|
||||
@@ -446,25 +448,13 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
|
||||
private final Set<ConstraintViolation<Object>> violations = new LinkedHashSet<>();
|
||||
|
||||
public BeanResultBuilder(MethodParameter parameter, @Nullable Object argument, Path.Node node) {
|
||||
this.parameter = parameter;
|
||||
|
||||
public BeanResultBuilder(MethodParameter param, @Nullable Object arg, Path.Node node, @Nullable Object leafBean) {
|
||||
this.parameter = param;
|
||||
this.bean = leafBean;
|
||||
this.container = (arg != null && !arg.equals(leafBean) ? arg : null);
|
||||
this.containerIndex = node.getIndex();
|
||||
this.containerKey = node.getKey();
|
||||
if (argument instanceof List<?> list && this.containerIndex != null) {
|
||||
this.container = list;
|
||||
argument = list.get(this.containerIndex);
|
||||
}
|
||||
else if (argument instanceof Map<?, ?> map && this.containerKey != null) {
|
||||
this.container = map;
|
||||
argument = map.get(this.containerKey);
|
||||
}
|
||||
else {
|
||||
this.container = null;
|
||||
}
|
||||
|
||||
this.argument = argument;
|
||||
this.errors = createBindingResult(parameter, argument);
|
||||
this.errors = createBindingResult(param, leafBean);
|
||||
}
|
||||
|
||||
public void addViolation(ConstraintViolation<Object> violation) {
|
||||
@@ -474,12 +464,28 @@ public class MethodValidationAdapter implements MethodValidator {
|
||||
public ParameterErrors build() {
|
||||
validatorAdapter.get().processConstraintViolations(this.violations, this.errors);
|
||||
return new ParameterErrors(
|
||||
this.parameter, this.argument, this.errors, this.container,
|
||||
this.parameter, this.bean, this.errors, this.container,
|
||||
this.containerIndex, this.containerKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unique key for cascaded violations associated with a bean.
|
||||
* <p>The bean may be an element within a container such as a List, Set, array,
|
||||
* Map, Optional, and others. In that case the {@link Path.Node} represents
|
||||
* the container element with its index or key, if applicable, while the
|
||||
* {@link ConstraintViolation#getLeafBean() leafBean} is the actual
|
||||
* element instance. The pair should be unique. For example in a Set, the
|
||||
* node is the same but element instances are unique. In a List or Map the
|
||||
* node is further qualified by an index or key while element instances
|
||||
* may be the same.
|
||||
* @param node the path to the bean associated with the violation
|
||||
* @param leafBean the bean instance
|
||||
*/
|
||||
record BeanResultKey(Path.Node node, Object leafBean) { }
|
||||
|
||||
|
||||
/**
|
||||
* Default algorithm to select an object name, as described in
|
||||
* {@link #setObjectNameResolver(ObjectNameResolver)}.
|
||||
|
||||
+1
-1
@@ -240,7 +240,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
|
||||
ReactiveAdapterRegistry.getSharedInstance();
|
||||
|
||||
|
||||
public static Object[] insertAsyncValidation(
|
||||
static Object[] insertAsyncValidation(
|
||||
Supplier<SpringValidatorAdapter> validatorAdapterSupplier, boolean adaptViolations,
|
||||
Object target, Method method, Object[] arguments) {
|
||||
|
||||
|
||||
+18
-16
@@ -32,11 +32,12 @@ import org.springframework.validation.ObjectError;
|
||||
* {@link Errors#getAllErrors()}, but this subclass provides access to the same
|
||||
* as {@link FieldError}s.
|
||||
*
|
||||
* <p>When the method parameter is a {@link List} or {@link java.util.Map},
|
||||
* a separate {@link ParameterErrors} is created for each list or map value for
|
||||
* which there are validation errors. In such cases, the {@link #getContainer()}
|
||||
* method returns the list or map, while {@link #getContainerIndex()}
|
||||
* and {@link #getContainerKey()} return the value index or key.
|
||||
* <p>When the method parameter is a container with multiple elements such as a
|
||||
* {@link List}, {@link java.util.Set}, array, {@link java.util.Map}, or others,
|
||||
* then a separate {@link ParameterErrors} is created for each element that has
|
||||
* errors. In that case, the {@link #getContainer() container},
|
||||
* {@link #getContainerIndex() containerIndex}, and {@link #getContainerKey() containerKey}
|
||||
* provide additional context.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.1
|
||||
@@ -71,11 +72,12 @@ public class ParameterErrors extends ParameterValidationResult implements Errors
|
||||
|
||||
|
||||
/**
|
||||
* When {@code @Valid} is declared on a {@link List} or {@link java.util.Map}
|
||||
* method parameter, this method returns the list or map that contained the
|
||||
* validated object {@link #getArgument() argument}, while
|
||||
* {@link #getContainerIndex()} and {@link #getContainerKey()} returns the
|
||||
* respective index or key.
|
||||
* When {@code @Valid} is declared on a container of elements such as
|
||||
* {@link java.util.Collection}, {@link java.util.Map},
|
||||
* {@link java.util.Optional}, and others, this method returns the container
|
||||
* of the validated {@link #getArgument() argument}, while
|
||||
* {@link #getContainerIndex()} and {@link #getContainerKey()} provide
|
||||
* information about the index or key if applicable.
|
||||
*/
|
||||
@Nullable
|
||||
public Object getContainer() {
|
||||
@@ -83,9 +85,9 @@ public class ParameterErrors extends ParameterValidationResult implements Errors
|
||||
}
|
||||
|
||||
/**
|
||||
* When {@code @Valid} is declared on a {@link List}, this method returns
|
||||
* the index under which the validated object {@link #getArgument() argument}
|
||||
* is stored in the list {@link #getContainer() container}.
|
||||
* When {@code @Valid} is declared on an indexed container of elements such as
|
||||
* {@link List} or array, this method returns the index of the validated
|
||||
* {@link #getArgument() argument}.
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getContainerIndex() {
|
||||
@@ -93,9 +95,9 @@ public class ParameterErrors extends ParameterValidationResult implements Errors
|
||||
}
|
||||
|
||||
/**
|
||||
* When {@code @Valid} is declared on a {@link java.util.Map}, this method
|
||||
* returns the key under which the validated object {@link #getArgument()
|
||||
* argument} is stored in the map {@link #getContainer()}.
|
||||
* When {@code @Valid} is declared on a container of elements referenced by
|
||||
* key such as {@link java.util.Map}, this method returns the key of the
|
||||
* validated {@link #getArgument() argument}.
|
||||
*/
|
||||
@Nullable
|
||||
public Object getContainerKey() {
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ class InstantFormatterTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(RandomEpochMillisProvider.class)
|
||||
void should_parse_into_an_Instant_from_epoch_mili(Instant input) throws ParseException {
|
||||
void should_parse_into_an_Instant_from_epoch_milli(Instant input) throws ParseException {
|
||||
Instant expected = input;
|
||||
|
||||
Instant actual = instantFormatter.parse(Long.toString(input.toEpochMilli()), null);
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ class FormattingConversionServiceRuntimeHintsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void montearyAmountHasHints() {
|
||||
void monetaryAmountHasHints() {
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(javax.money.MonetaryAmount.class)).accepts(this.hints);
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -344,9 +344,10 @@ public class EnableSchedulingTests {
|
||||
@Override
|
||||
public TaskScheduler myTaskScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setThreadNamePrefix("explicitScheduler-");
|
||||
scheduler.setAwaitTerminationMillis(1000);
|
||||
scheduler.setPoolSize(2);
|
||||
scheduler.setThreadNamePrefix("explicitScheduler-");
|
||||
scheduler.setAcceptTasksAfterContextClose(true);
|
||||
scheduler.setAwaitTerminationMillis(1000);
|
||||
return scheduler;
|
||||
}
|
||||
}
|
||||
|
||||
+59
-8
@@ -17,13 +17,16 @@
|
||||
package org.springframework.validation.beanvalidation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -47,9 +50,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
public class MethodValidationAdapterTests {
|
||||
|
||||
private static final Person faustino1234 = new Person("Faustino1234");
|
||||
private static final Person faustino1234 = new Person("Faustino1234", List.of("Working on Spring"));
|
||||
|
||||
private static final Person cayetana6789 = new Person("Cayetana6789");
|
||||
private static final Person cayetana6789 = new Person("Cayetana6789", List.of(" "));
|
||||
|
||||
|
||||
private final MethodValidationAdapter validationAdapter = new MethodValidationAdapter();
|
||||
@@ -88,7 +91,13 @@ public class MethodValidationAdapterTests {
|
||||
codes [Size.guardian.name,Size.name,Size.java.lang.String,Size]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [guardian.name,name]; arguments []; default message [name],10,1]; \
|
||||
default message [size must be between 1 and 10]"""));
|
||||
default message [size must be between 1 and 10]""", """
|
||||
Field error in object 'guardian' on field 'hobbies[0]': rejected value [ ]; \
|
||||
codes [NotBlank.guardian.hobbies[0],NotBlank.guardian.hobbies,NotBlank.hobbies[0],\
|
||||
NotBlank.hobbies,NotBlank.java.lang.String,NotBlank]; arguments \
|
||||
[org.springframework.context.support.DefaultMessageSourceResolvable: codes \
|
||||
[guardian.hobbies[0],hobbies[0]]; arguments []; default message [hobbies[0]]]; \
|
||||
default message [must not be blank]"""));
|
||||
|
||||
assertValueResult(ex.getValueResults().get(0), 2, 3, List.of("""
|
||||
org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
@@ -106,7 +115,7 @@ public class MethodValidationAdapterTests {
|
||||
|
||||
this.validationAdapter.setObjectNameResolver((param, value) -> "studentToAdd");
|
||||
|
||||
testArgs(target, method, new Object[] {faustino1234, new Person("Joe"), 1}, ex -> {
|
||||
testArgs(target, method, new Object[] {faustino1234, new Person("Joe", List.of()), 1}, ex -> {
|
||||
|
||||
assertThat(ex.getAllValidationResults()).hasSize(1);
|
||||
|
||||
@@ -178,7 +187,49 @@ public class MethodValidationAdapterTests {
|
||||
codes [Size.people.name,Size.name,Size.java.lang.String,Size]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [people.name,name]; arguments []; default message [name],10,1]; \
|
||||
default message [size must be between 1 and 10]"""));
|
||||
default message [size must be between 1 and 10]""", """
|
||||
Field error in object 'people' on field 'hobbies[0]': rejected value [ ]; \
|
||||
codes [NotBlank.people.hobbies[0],NotBlank.people.hobbies,NotBlank.hobbies[0],\
|
||||
NotBlank.hobbies,NotBlank.java.lang.String,NotBlank]; arguments \
|
||||
[org.springframework.context.support.DefaultMessageSourceResolvable: codes \
|
||||
[people.hobbies[0],hobbies[0]]; arguments []; default message [hobbies[0]]]; \
|
||||
default message [must not be blank]"""));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateSetArgument() {
|
||||
MyService target = new MyService();
|
||||
Method method = getMethod(target, "addPeople");
|
||||
|
||||
testArgs(target, method, new Object[] {Set.of(faustino1234, cayetana6789)}, ex -> {
|
||||
|
||||
assertThat(ex.getAllValidationResults()).hasSize(2);
|
||||
|
||||
int paramIndex = 0;
|
||||
String objectName = "people";
|
||||
List<ParameterErrors> results = ex.getBeanResults();
|
||||
|
||||
assertThat(results).satisfiesExactlyInAnyOrder(
|
||||
result -> assertBeanResult(result, paramIndex, objectName, faustino1234, List.of("""
|
||||
Field error in object 'people' on field 'name': rejected value [Faustino1234]; \
|
||||
codes [Size.people.name,Size.name,Size.java.lang.String,Size]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [people.name,name]; arguments []; default message [name],10,1]; \
|
||||
default message [size must be between 1 and 10]""")),
|
||||
result -> assertBeanResult(result, paramIndex, objectName, cayetana6789, List.of("""
|
||||
Field error in object 'people' on field 'name': rejected value [Cayetana6789]; \
|
||||
codes [Size.people.name,Size.name,Size.java.lang.String,Size]; \
|
||||
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: \
|
||||
codes [people.name,name]; arguments []; default message [name],10,1]; \
|
||||
default message [size must be between 1 and 10]""", """
|
||||
Field error in object 'people' on field 'hobbies[0]': rejected value [ ]; \
|
||||
codes [NotBlank.people.hobbies[0],NotBlank.people.hobbies,NotBlank.hobbies[0],\
|
||||
NotBlank.hobbies,NotBlank.java.lang.String,NotBlank]; arguments \
|
||||
[org.springframework.context.support.DefaultMessageSourceResolvable: codes \
|
||||
[people.hobbies[0],hobbies[0]]; arguments []; default message [hobbies[0]]]; \
|
||||
default message [must not be blank]"""))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,7 +242,7 @@ public class MethodValidationAdapterTests {
|
||||
}
|
||||
|
||||
private static void assertBeanResult(
|
||||
ParameterErrors errors, int parameterIndex, String objectName, Object argument,
|
||||
ParameterErrors errors, int parameterIndex, String objectName, @Nullable Object argument,
|
||||
List<String> fieldErrors) {
|
||||
|
||||
assertThat(errors.getMethodParameter().getParameterIndex()).isEqualTo(parameterIndex);
|
||||
@@ -234,14 +285,14 @@ public class MethodValidationAdapterTests {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void addPeople(@Valid List<Person> people) {
|
||||
public void addPeople(@Valid Collection<Person> people) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private record Person(@Size(min = 1, max = 10) String name) {
|
||||
private record Person(@Size(min = 1, max = 10) String name, List<@NotBlank String> hobbies) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -36,9 +36,11 @@ import org.springframework.validation.method.ParameterErrors;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for method validation proxy with reactor.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MethodValidationProxyReactorTests {
|
||||
class MethodValidationProxyReactorTests {
|
||||
|
||||
@Test
|
||||
void validMonoArgument() {
|
||||
|
||||
+28
-54
@@ -53,8 +53,6 @@ public final class TestCompiler {
|
||||
|
||||
private final JavaCompiler compiler;
|
||||
|
||||
private final Locale locale;
|
||||
|
||||
private final SourceFiles sourceFiles;
|
||||
|
||||
private final ResourceFiles resourceFiles;
|
||||
@@ -66,13 +64,12 @@ public final class TestCompiler {
|
||||
private final List<String> compilerOptions;
|
||||
|
||||
|
||||
private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler, Locale locale,
|
||||
SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles, List<Processor> processors,
|
||||
List<String> compilerOptions) {
|
||||
private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler,
|
||||
SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles,
|
||||
List<Processor> processors, List<String> compilerOptions) {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.compiler = compiler;
|
||||
this.locale = locale;
|
||||
this.sourceFiles = sourceFiles;
|
||||
this.resourceFiles = resourceFiles;
|
||||
this.classFiles = classFiles;
|
||||
@@ -95,9 +92,8 @@ public final class TestCompiler {
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public static TestCompiler forCompiler(JavaCompiler javaCompiler) {
|
||||
return new TestCompiler(null, javaCompiler, Locale.getDefault(),
|
||||
SourceFiles.none(), ResourceFiles.none(),
|
||||
ClassFiles.none(), Collections.emptyList(), Collections.emptyList());
|
||||
return new TestCompiler(null, javaCompiler, SourceFiles.none(),
|
||||
ResourceFiles.none(), ClassFiles.none(), Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,26 +105,13 @@ public final class TestCompiler {
|
||||
return customizer.apply(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code TestCompiler} instance that uses the specified {@link Locale}
|
||||
* to render compiler messages.
|
||||
* @param locale the locale to use
|
||||
* @return a new {@code TestCompiler} instance
|
||||
* @since 6.1
|
||||
*/
|
||||
public TestCompiler withLocale(Locale locale) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, locale,
|
||||
this.sourceFiles, this.resourceFiles,
|
||||
this.classFiles, this.processors, this.compilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code TestCompiler} instance with additional source files.
|
||||
* @param sourceFiles the additional source files
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public TestCompiler withSources(SourceFile... sourceFiles) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
return new TestCompiler(this.classLoader, this.compiler,
|
||||
this.sourceFiles.and(sourceFiles), this.resourceFiles,
|
||||
this.classFiles, this.processors, this.compilerOptions);
|
||||
}
|
||||
@@ -139,7 +122,7 @@ public final class TestCompiler {
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public TestCompiler withSources(Iterable<SourceFile> sourceFiles) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
return new TestCompiler(this.classLoader, this.compiler,
|
||||
this.sourceFiles.and(sourceFiles), this.resourceFiles,
|
||||
this.classFiles, this.processors, this.compilerOptions);
|
||||
}
|
||||
@@ -150,7 +133,7 @@ public final class TestCompiler {
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public TestCompiler withSources(SourceFiles sourceFiles) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
return new TestCompiler(this.classLoader, this.compiler,
|
||||
this.sourceFiles.and(sourceFiles), this.resourceFiles,
|
||||
this.classFiles, this.processors, this.compilerOptions);
|
||||
}
|
||||
@@ -161,9 +144,9 @@ public final class TestCompiler {
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public TestCompiler withResources(ResourceFile... resourceFiles) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
this.sourceFiles, this.resourceFiles.and(resourceFiles),
|
||||
this.classFiles, this.processors, this.compilerOptions);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
|
||||
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors,
|
||||
this.compilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,9 +155,9 @@ public final class TestCompiler {
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public TestCompiler withResources(Iterable<ResourceFile> resourceFiles) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
this.sourceFiles, this.resourceFiles.and(resourceFiles),
|
||||
this.classFiles, this.processors, this.compilerOptions);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
|
||||
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors,
|
||||
this.compilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,9 +166,9 @@ public final class TestCompiler {
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public TestCompiler withResources(ResourceFiles resourceFiles) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
this.sourceFiles, this.resourceFiles.and(resourceFiles),
|
||||
this.classFiles, this.processors, this.compilerOptions);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
|
||||
this.resourceFiles.and(resourceFiles), this.classFiles, this.processors,
|
||||
this.compilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,9 +177,9 @@ public final class TestCompiler {
|
||||
* @return a new {@code TestCompiler} instance
|
||||
*/
|
||||
public TestCompiler withClasses(Iterable<ClassFile> classFiles) {
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
this.sourceFiles, this.resourceFiles, this.classFiles.and(classFiles),
|
||||
this.processors, this.compilerOptions);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
|
||||
this.resourceFiles, this.classFiles.and(classFiles), this.processors,
|
||||
this.compilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,9 +190,8 @@ public final class TestCompiler {
|
||||
public TestCompiler withProcessors(Processor... processors) {
|
||||
List<Processor> mergedProcessors = new ArrayList<>(this.processors);
|
||||
mergedProcessors.addAll(Arrays.asList(processors));
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
this.sourceFiles, this.resourceFiles, this.classFiles, mergedProcessors,
|
||||
this.compilerOptions);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
|
||||
this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,9 +202,8 @@ public final class TestCompiler {
|
||||
public TestCompiler withProcessors(Iterable<Processor> processors) {
|
||||
List<Processor> mergedProcessors = new ArrayList<>(this.processors);
|
||||
processors.forEach(mergedProcessors::add);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
this.sourceFiles, this.resourceFiles, this.classFiles,
|
||||
mergedProcessors, this.compilerOptions);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
|
||||
this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,9 +215,8 @@ public final class TestCompiler {
|
||||
public TestCompiler withCompilerOptions(String... options) {
|
||||
List<String> mergedCompilerOptions = Stream.concat(this.compilerOptions.stream(),
|
||||
Arrays.stream(options)).distinct().toList();
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.locale,
|
||||
this.sourceFiles, this.resourceFiles, this.classFiles,
|
||||
this.processors, mergedCompilerOptions);
|
||||
return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles,
|
||||
this.resourceFiles, this.classFiles, this.processors, mergedCompilerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,7 +308,7 @@ public final class TestCompiler {
|
||||
DynamicJavaFileManager fileManager = new DynamicJavaFileManager(
|
||||
standardFileManager, classLoaderToUse, this.classFiles, this.resourceFiles);
|
||||
if (!this.sourceFiles.isEmpty()) {
|
||||
Errors errors = new Errors(this.locale);
|
||||
Errors errors = new Errors();
|
||||
CompilationTask task = this.compiler.getTask(null, fileManager, errors,
|
||||
this.compilerOptions, null, compilationUnits);
|
||||
if (!this.processors.isEmpty()) {
|
||||
@@ -369,19 +349,13 @@ public final class TestCompiler {
|
||||
*/
|
||||
static class Errors implements DiagnosticListener<JavaFileObject> {
|
||||
|
||||
private final Locale locale;
|
||||
|
||||
private final StringBuilder message = new StringBuilder();
|
||||
|
||||
Errors(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
|
||||
this.message.append('\n');
|
||||
this.message.append(diagnostic.getMessage(this.locale));
|
||||
this.message.append(diagnostic.getMessage(Locale.getDefault()));
|
||||
if (diagnostic.getSource() != null) {
|
||||
this.message.append(' ');
|
||||
this.message.append(diagnostic.getSource().getName());
|
||||
|
||||
+3
-4
@@ -19,7 +19,6 @@ package org.springframework.core.test.tools;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -171,9 +170,9 @@ class TestCompilerTests {
|
||||
}
|
||||
""");
|
||||
assertThatExceptionOfType(CompilationException.class).isThrownBy(
|
||||
() -> TestCompiler.forSystem().failOnWarning().withLocale(Locale.ENGLISH)
|
||||
.withSources(SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> {
|
||||
})).withMessageContaining("warnings found and -Werror specified");
|
||||
() -> TestCompiler.forSystem().failOnWarning().withSources(
|
||||
SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> {
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Vendored
+2
-2
@@ -62,7 +62,7 @@ public class CompositePropertySourceBenchmark {
|
||||
CompositePropertySource composite;
|
||||
|
||||
@Param({ "2", "5", "10" })
|
||||
int numberOfPropertySource;
|
||||
int numberOfPropertySources;
|
||||
|
||||
@Param({ "10", "100", "1000" })
|
||||
int numberOfPropertyNamesPerSource;
|
||||
@@ -70,7 +70,7 @@ public class CompositePropertySourceBenchmark {
|
||||
@Setup(Level.Trial)
|
||||
public void setUp() {
|
||||
this.composite = new CompositePropertySource("benchmark");
|
||||
for (int i = 0; i < this.numberOfPropertySource; i++) {
|
||||
for (int i = 0; i < this.numberOfPropertySources; i++) {
|
||||
Map<String, Object> map = new HashMap<>(this.numberOfPropertyNamesPerSource);
|
||||
for (int j = 0; j < this.numberOfPropertyNamesPerSource; j++) {
|
||||
map.put(ID_GENERATOR.generateId().toString(), VALUE);
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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,7 +17,6 @@
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@@ -42,7 +41,7 @@ class AppendableConsumerInputStreamSource implements InputStreamSource {
|
||||
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -161,7 +161,8 @@ public interface GeneratedFiles {
|
||||
|
||||
private static String getClassNamePath(String className) {
|
||||
Assert.hasLength(className, "'className' must not be empty");
|
||||
Assert.isTrue(isJavaIdentifier(className), "'className' must be a valid identifier");
|
||||
Assert.isTrue(isJavaIdentifier(className),
|
||||
"'className' must be a valid identifier, got '" + className + "'");
|
||||
return ClassUtils.convertClassNameToResourcePath(className) + ".java";
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -159,6 +159,10 @@ public class BindingReflectionHintsRegistrar {
|
||||
for (ResolvableType genericResolvableType : resolvableType.getGenerics()) {
|
||||
collectReferencedTypes(types, genericResolvableType);
|
||||
}
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass != null && superClass != Object.class && superClass != Record.class && superClass != Enum.class) {
|
||||
types.add(superClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +191,7 @@ public class BindingReflectionHintsRegistrar {
|
||||
.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
|
||||
.stream(JACKSON_ANNOTATION)
|
||||
.filter(MergedAnnotation::isMetaPresent)
|
||||
.forEach(action::accept);
|
||||
.forEach(action);
|
||||
}
|
||||
|
||||
private void registerHintsForClassAttributes(ReflectionHints hints, MergedAnnotation<Annotation> annotation) {
|
||||
|
||||
@@ -124,6 +124,6 @@ public enum MemberCategory {
|
||||
* reflection for inner classes but rather makes sure they are available
|
||||
* via a call to {@link Class#getDeclaredClasses}.
|
||||
*/
|
||||
DECLARED_CLASSES;
|
||||
DECLARED_CLASSES
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -143,7 +143,7 @@ public final class ResourcePatternHints {
|
||||
* @param excludes the exclude patterns (see {@link ResourcePatternHint} documentation)
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder excludes(TypeReference reachableType, String... excludes) {
|
||||
public Builder excludes(@Nullable TypeReference reachableType, String... excludes) {
|
||||
List<ResourcePatternHint> newExcludes = Arrays.stream(excludes)
|
||||
.map(include -> new ResourcePatternHint(include, reachableType)).toList();
|
||||
this.excludes.addAll(newExcludes);
|
||||
|
||||
-3
@@ -156,12 +156,10 @@ public class FilePatternResourceHintsRegistrar {
|
||||
|
||||
private final List<String> fileExtensions = new ArrayList<>();
|
||||
|
||||
|
||||
private Builder() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Consider the specified classpath locations.
|
||||
* <p>A location can either be a special {@value ResourceUtils#CLASSPATH_URL_PREFIX}
|
||||
@@ -254,7 +252,6 @@ public class FilePatternResourceHintsRegistrar {
|
||||
public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) {
|
||||
build().registerHints(hints, classLoader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@ package org.springframework.aot.hint.support;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} to register hints for {@link org.springframework.core.KotlinDetector}.
|
||||
@@ -29,7 +30,7 @@ import org.springframework.aot.hint.TypeReference;
|
||||
class KotlinDetectorRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
hints.reflection().registerType(TypeReference.of("kotlin.Metadata"))
|
||||
.registerType(TypeReference.of("kotlin.reflect.full.KClasses"));
|
||||
}
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@ import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} for {@link PathMatchingResourcePatternResolver}.
|
||||
@@ -28,7 +29,7 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
class PathMatchingResourcePatternResolverRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
hints.reflection().registerType(TypeReference.of("org.eclipse.core.runtime.FileLocator"));
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -47,9 +47,11 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader
|
||||
: SpringFactoriesLoaderRuntimeHints.class.getClassLoader());
|
||||
for (String resourceLocation : RESOURCE_LOCATIONS) {
|
||||
registerHints(hints, classLoader, resourceLocation);
|
||||
registerHints(hints, classLoaderToUse, resourceLocation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -60,8 +60,8 @@ class ResourceHintsWriter {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void handleResourceBundles(Map<String, Object> attributes, Stream<ResourceBundleHint> ressourceBundles) {
|
||||
addIfNotEmpty(attributes, "bundles", ressourceBundles.map(this::toAttributes).toList());
|
||||
private void handleResourceBundles(Map<String, Object> attributes, Stream<ResourceBundleHint> resourceBundles) {
|
||||
addIfNotEmpty(attributes, "bundles", resourceBundles.map(this::toAttributes).toList());
|
||||
}
|
||||
|
||||
private Map<String, Object> toAttributes(ResourceBundleHint hint) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -42,7 +42,6 @@ package org.springframework.core;
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see org.springframework.beans.factory.config.PropertyOverrideConfigurer
|
||||
* @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
|
||||
*/
|
||||
public interface PriorityOrdered extends Ordered {
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public final class ReactiveTypeDescriptor {
|
||||
|
||||
|
||||
/**
|
||||
* Descriptor for a reactive type that can produce 0..N values.
|
||||
* Descriptor for a reactive type that can produce {@code 0..N} values.
|
||||
* @param type the reactive type
|
||||
* @param emptySupplier a supplier of an empty-value instance of the reactive type
|
||||
*/
|
||||
|
||||
@@ -758,7 +758,7 @@ public class ResolvableType implements Serializable {
|
||||
* Convenience method that will {@link #getGenerics() get} and
|
||||
* {@link #resolve() resolve} generic parameters.
|
||||
* @return an array of resolved generic parameters (the resulting array
|
||||
* will never be {@code null}, but it may contain {@code null} elements})
|
||||
* will never be {@code null}, but it may contain {@code null} elements)
|
||||
* @see #getGenerics()
|
||||
* @see #resolve()
|
||||
*/
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.springframework.lang.Nullable;
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.2.7
|
||||
* @see org.springframework.beans.StandardBeanInfoFactory#IGNORE_BEANINFO_PROPERTY_NAME
|
||||
* @see org.springframework.context.index.CandidateComponentsIndexLoader#IGNORE_INDEX
|
||||
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
|
||||
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
|
||||
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
|
||||
|
||||
+13
-6
@@ -103,8 +103,8 @@ final class AnnotationTypeMapping {
|
||||
private final Set<Method> claimedAliases = new HashSet<>();
|
||||
|
||||
|
||||
AnnotationTypeMapping(@Nullable AnnotationTypeMapping source,
|
||||
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
|
||||
AnnotationTypeMapping(@Nullable AnnotationTypeMapping source, Class<? extends Annotation> annotationType,
|
||||
@Nullable Annotation annotation, Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
|
||||
this.source = source;
|
||||
this.root = (source != null ? source.getRoot() : this);
|
||||
@@ -124,7 +124,7 @@ final class AnnotationTypeMapping {
|
||||
processAliases();
|
||||
addConventionMappings();
|
||||
addConventionAnnotationValues();
|
||||
this.synthesizable = computeSynthesizableFlag();
|
||||
this.synthesizable = computeSynthesizableFlag(visitedAnnotationTypes);
|
||||
}
|
||||
|
||||
|
||||
@@ -374,7 +374,10 @@ final class AnnotationTypeMapping {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean computeSynthesizableFlag() {
|
||||
private boolean computeSynthesizableFlag(Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
// Track that we have visited the current annotation type.
|
||||
visitedAnnotationTypes.add(this.annotationType);
|
||||
|
||||
// Uses @AliasFor for local aliases?
|
||||
for (int index : this.aliasMappings) {
|
||||
if (index != -1) {
|
||||
@@ -403,8 +406,12 @@ final class AnnotationTypeMapping {
|
||||
if (type.isAnnotation() || (type.isArray() && type.componentType().isAnnotation())) {
|
||||
Class<? extends Annotation> annotationType =
|
||||
(Class<? extends Annotation>) (type.isAnnotation() ? type : type.componentType());
|
||||
if (annotationType != this.annotationType) {
|
||||
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
|
||||
// Ensure we have not yet visited the current nested annotation type, in order
|
||||
// to avoid infinite recursion for JVM languages other than Java that support
|
||||
// recursive annotation definitions.
|
||||
if (visitedAnnotationTypes.add(annotationType)) {
|
||||
AnnotationTypeMapping mapping =
|
||||
AnnotationTypeMappings.forAnnotationType(annotationType, visitedAnnotationTypes).get(0);
|
||||
if (mapping.isSynthesizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+55
-21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -20,8 +20,10 @@ import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
@@ -40,6 +42,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
* be searched once, regardless of how many times they are actually used.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 5.2
|
||||
* @see AnnotationTypeMapping
|
||||
*/
|
||||
@@ -60,19 +63,21 @@ final class AnnotationTypeMappings {
|
||||
|
||||
|
||||
private AnnotationTypeMappings(RepeatableContainers repeatableContainers,
|
||||
AnnotationFilter filter, Class<? extends Annotation> annotationType) {
|
||||
AnnotationFilter filter, Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
|
||||
this.repeatableContainers = repeatableContainers;
|
||||
this.filter = filter;
|
||||
this.mappings = new ArrayList<>();
|
||||
addAllMappings(annotationType);
|
||||
addAllMappings(annotationType, visitedAnnotationTypes);
|
||||
this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet);
|
||||
}
|
||||
|
||||
|
||||
private void addAllMappings(Class<? extends Annotation> annotationType) {
|
||||
private void addAllMappings(Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
|
||||
addIfPossible(queue, null, annotationType, null);
|
||||
addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
|
||||
while (!queue.isEmpty()) {
|
||||
AnnotationTypeMapping mapping = queue.removeFirst();
|
||||
this.mappings.add(mapping);
|
||||
@@ -102,14 +107,15 @@ final class AnnotationTypeMappings {
|
||||
}
|
||||
|
||||
private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source, Annotation ann) {
|
||||
addIfPossible(queue, source, ann.annotationType(), ann);
|
||||
addIfPossible(queue, source, ann.annotationType(), ann, new HashSet<>());
|
||||
}
|
||||
|
||||
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,
|
||||
Class<? extends Annotation> annotationType, @Nullable Annotation ann) {
|
||||
Class<? extends Annotation> annotationType, @Nullable Annotation ann,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
|
||||
try {
|
||||
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann));
|
||||
queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
|
||||
@@ -166,20 +172,22 @@ final class AnnotationTypeMappings {
|
||||
* @return type mappings for the annotation type
|
||||
*/
|
||||
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) {
|
||||
return forAnnotationType(annotationType, AnnotationFilter.PLAIN);
|
||||
return forAnnotationType(annotationType, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link AnnotationTypeMappings} for the specified annotation type.
|
||||
* @param annotationType the source annotation type
|
||||
* @param annotationFilter the annotation filter used to limit which
|
||||
* annotations are considered
|
||||
* @param visitedAnnotationTypes the set of annotations that we have already
|
||||
* visited; used to avoid infinite recursion for recursive annotations which
|
||||
* some JVM languages support (such as Kotlin)
|
||||
* @return type mappings for the annotation type
|
||||
*/
|
||||
static AnnotationTypeMappings forAnnotationType(
|
||||
Class<? extends Annotation> annotationType, AnnotationFilter annotationFilter) {
|
||||
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
|
||||
return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(), annotationFilter);
|
||||
return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(),
|
||||
AnnotationFilter.PLAIN, visitedAnnotationTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,15 +202,35 @@ final class AnnotationTypeMappings {
|
||||
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
|
||||
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
|
||||
|
||||
return forAnnotationType(annotationType, repeatableContainers, annotationFilter, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link AnnotationTypeMappings} for the specified annotation type.
|
||||
* @param annotationType the source annotation type
|
||||
* @param repeatableContainers the repeatable containers that may be used by
|
||||
* the meta-annotations
|
||||
* @param annotationFilter the annotation filter used to limit which
|
||||
* annotations are considered
|
||||
* @param visitedAnnotationTypes the set of annotations that we have already
|
||||
* visited; used to avoid infinite recursion for recursive annotations which
|
||||
* some JVM languages support (such as Kotlin)
|
||||
* @return type mappings for the annotation type
|
||||
*/
|
||||
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
|
||||
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
|
||||
if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
|
||||
return standardRepeatablesCache.computeIfAbsent(annotationFilter,
|
||||
key -> new Cache(repeatableContainers, key)).get(annotationType);
|
||||
key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
|
||||
}
|
||||
if (repeatableContainers == RepeatableContainers.none()) {
|
||||
return noRepeatablesCache.computeIfAbsent(annotationFilter,
|
||||
key -> new Cache(repeatableContainers, key)).get(annotationType);
|
||||
key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
|
||||
}
|
||||
return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType);
|
||||
return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType,
|
||||
visitedAnnotationTypes);
|
||||
}
|
||||
|
||||
static void clearCache() {
|
||||
@@ -235,14 +263,20 @@ final class AnnotationTypeMappings {
|
||||
/**
|
||||
* Get or create {@link AnnotationTypeMappings} for the specified annotation type.
|
||||
* @param annotationType the annotation type
|
||||
* @param visitedAnnotationTypes the set of annotations that we have already
|
||||
* visited; used to avoid infinite recursion for recursive annotations which
|
||||
* some JVM languages support (such as Kotlin)
|
||||
* @return a new or existing {@link AnnotationTypeMappings} instance
|
||||
*/
|
||||
AnnotationTypeMappings get(Class<? extends Annotation> annotationType) {
|
||||
return this.mappings.computeIfAbsent(annotationType, this::createMappings);
|
||||
AnnotationTypeMappings get(Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
|
||||
}
|
||||
|
||||
AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType) {
|
||||
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType);
|
||||
private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType,
|
||||
visitedAnnotationTypes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -98,7 +98,6 @@ abstract class AnnotationsScanner {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("deprecation")
|
||||
private static <C, R> R processClass(C context, Class<?> source, SearchStrategy searchStrategy,
|
||||
Predicate<Class<?>> searchEnclosingClass, AnnotationsProcessor<C, R> processor) {
|
||||
|
||||
@@ -237,7 +236,6 @@ abstract class AnnotationsScanner {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("deprecation")
|
||||
private static <C, R> R processMethod(C context, Method source,
|
||||
SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor) {
|
||||
|
||||
@@ -509,7 +507,6 @@ abstract class AnnotationsScanner {
|
||||
return (type.getName().startsWith("java.") || type == Ordered.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static boolean isWithoutHierarchy(AnnotatedElement source, SearchStrategy searchStrategy,
|
||||
Predicate<Class<?>> searchEnclosingClass) {
|
||||
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -45,7 +45,6 @@ import org.springframework.util.MimeType;
|
||||
* @since 5.0
|
||||
* @param <T> the element type
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class AbstractDataBufferDecoder<T> extends AbstractDecoder<T> {
|
||||
|
||||
private int maxInMemorySize = 256 * 1024;
|
||||
|
||||
@@ -42,14 +42,14 @@ import org.springframework.util.MimeTypeUtils;
|
||||
*/
|
||||
public final class CharBufferDecoder extends AbstractCharSequenceDecoder<CharBuffer> {
|
||||
|
||||
|
||||
public CharBufferDecoder(List<String> delimiters, boolean stripDelimiter, MimeType... mimeTypes) {
|
||||
super(delimiters, stripDelimiter, mimeTypes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
|
||||
return elementType.resolve() == CharBuffer.class && super.canDecode(elementType, mimeType);
|
||||
return (elementType.resolve() == CharBuffer.class) && super.canDecode(elementType, mimeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,9 +69,8 @@ public final class CharBufferDecoder extends AbstractCharSequenceDecoder<CharBuf
|
||||
|
||||
/**
|
||||
* Create a {@code CharBufferDecoder} for {@code "text/plain"}.
|
||||
* @param delimiters delimiter strings to use to split the input stream
|
||||
* @param stripDelimiter whether to remove delimiters from the resulting
|
||||
* input strings
|
||||
* @param delimiters delimiter strings to use to split the input stream
|
||||
* @param stripDelimiter whether to remove delimiters from the resulting input strings
|
||||
*/
|
||||
public static CharBufferDecoder textPlainOnly(List<String> delimiters, boolean stripDelimiter) {
|
||||
var textPlain = new MimeType("text", "plain", DEFAULT_CHARSET);
|
||||
@@ -87,9 +86,8 @@ public final class CharBufferDecoder extends AbstractCharSequenceDecoder<CharBuf
|
||||
|
||||
/**
|
||||
* Create a {@code CharBufferDecoder} that supports all MIME types.
|
||||
* @param delimiters delimiter strings to use to split the input stream
|
||||
* @param stripDelimiter whether to remove delimiters from the resulting
|
||||
* input strings
|
||||
* @param delimiters delimiter strings to use to split the input stream
|
||||
* @param stripDelimiter whether to remove delimiters from the resulting input strings
|
||||
*/
|
||||
public static CharBufferDecoder allMimeTypes(List<String> delimiters, boolean stripDelimiter) {
|
||||
var textPlain = new MimeType("text", "plain", DEFAULT_CHARSET);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -51,8 +51,7 @@ public final class CharSequenceEncoder extends AbstractEncoder<CharSequence> {
|
||||
*/
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
private final ConcurrentMap<Charset, Float> charsetToMaxBytesPerChar =
|
||||
new ConcurrentHashMap<>(3);
|
||||
private final ConcurrentMap<Charset, Float> charsetToMaxBytesPerChar = new ConcurrentHashMap<>(3);
|
||||
|
||||
|
||||
private CharSequenceEncoder(MimeType... mimeTypes) {
|
||||
@@ -105,8 +104,8 @@ public final class CharSequenceEncoder extends AbstractEncoder<CharSequence> {
|
||||
}
|
||||
|
||||
int calculateCapacity(CharSequence sequence, Charset charset) {
|
||||
float maxBytesPerChar = this.charsetToMaxBytesPerChar
|
||||
.computeIfAbsent(charset, cs -> cs.newEncoder().maxBytesPerChar());
|
||||
float maxBytesPerChar = this.charsetToMaxBytesPerChar.computeIfAbsent(charset,
|
||||
cs -> cs.newEncoder().maxBytesPerChar());
|
||||
float maxBytesForSequence = sequence.length() * maxBytesPerChar;
|
||||
return (int) Math.ceil(maxBytesForSequence);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2023 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,7 +29,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Converts a comma-delimited String to an Array.
|
||||
* Only matches if String.class can be converted to the target array element type.
|
||||
* Only matches if {@code String.class} can be converted to the target array element type.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
|
||||
+2
-4
@@ -25,9 +25,8 @@ import org.springframework.util.StringUtils;
|
||||
/**
|
||||
* Abstract base class for {@link PropertySource} implementations backed by command line
|
||||
* arguments. The parameterized type {@code T} represents the underlying source of command
|
||||
* line options. This may be as simple as a String array in the case of
|
||||
* {@link SimpleCommandLinePropertySource}, or specific to a particular API such as JOpt's
|
||||
* {@code OptionSet} in the case of {@link JOptCommandLinePropertySource}.
|
||||
* line options. For instance, {@link SimpleCommandLinePropertySource} uses a String
|
||||
* array.
|
||||
*
|
||||
* <h3>Purpose and General Usage</h3>
|
||||
*
|
||||
@@ -203,7 +202,6 @@ import org.springframework.util.StringUtils;
|
||||
* @param <T> the source type
|
||||
* @see PropertySource
|
||||
* @see SimpleCommandLinePropertySource
|
||||
* @see JOptCommandLinePropertySource
|
||||
*/
|
||||
public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {
|
||||
|
||||
|
||||
Vendored
+3
-5
@@ -74,15 +74,13 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* <h3>Beyond the basics</h3>
|
||||
*
|
||||
* <p>When more fully-featured command line parsing is necessary, consider using
|
||||
* the provided {@link JOptCommandLinePropertySource}, or implement your own
|
||||
* {@code CommandLinePropertySource} against the command line parsing library of your
|
||||
* choice.
|
||||
* <p>When more fully-featured command line parsing is necessary, consider
|
||||
* implementing your own {@code CommandLinePropertySource} against the command line
|
||||
* parsing library of your choice.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
* @see CommandLinePropertySource
|
||||
* @see JOptCommandLinePropertySource
|
||||
*/
|
||||
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
|
||||
|
||||
|
||||
@@ -829,7 +829,7 @@ public abstract class DataBufferUtils {
|
||||
*/
|
||||
private static class SingleByteMatcher implements NestedMatcher {
|
||||
|
||||
static SingleByteMatcher NEWLINE_MATCHER = new SingleByteMatcher(new byte[] {10});
|
||||
static final SingleByteMatcher NEWLINE_MATCHER = new SingleByteMatcher(new byte[] {10});
|
||||
|
||||
private final byte[] delimiter;
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -95,7 +95,7 @@ public class LimitedDataBufferList extends ArrayList<DataBuffer> {
|
||||
}
|
||||
|
||||
private void raiseLimitException() {
|
||||
// Do not release here, it's likely down via doOnDiscard..
|
||||
// Do not release here, it's likely done via doOnDiscard
|
||||
throw new DataBufferLimitException(
|
||||
"Exceeded limit on max bytes to buffer : " + this.maxByteCount);
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
Assert.notNull(dest, "Dest must not be null");
|
||||
|
||||
dest = dest.duplicate().clear();
|
||||
dest.put(destPos, this.byteBuf.nioBuffer(), srcPos, length);
|
||||
dest.put(destPos, this.byteBuf.nioBuffer(srcPos, length), 0, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-6
@@ -54,7 +54,7 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
|
||||
private final int chunkSize;
|
||||
|
||||
|
||||
public OutputStreamPublisher(Consumer<OutputStream> outputStreamConsumer, DataBufferFactory bufferFactory,
|
||||
OutputStreamPublisher(Consumer<OutputStream> outputStreamConsumer, DataBufferFactory bufferFactory,
|
||||
Executor executor, int chunkSize) {
|
||||
|
||||
this.outputStreamConsumer = outputStreamConsumer;
|
||||
@@ -68,8 +68,8 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
|
||||
public void subscribe(Subscriber<? super DataBuffer> subscriber) {
|
||||
Objects.requireNonNull(subscriber, "Subscriber must not be null");
|
||||
|
||||
OutputStreamSubscription subscription = new OutputStreamSubscription(subscriber, this.outputStreamConsumer,
|
||||
this.bufferFactory, this.chunkSize);
|
||||
OutputStreamSubscription subscription = new OutputStreamSubscription(
|
||||
subscriber, this.outputStreamConsumer, this.bufferFactory, this.chunkSize);
|
||||
|
||||
subscriber.onSubscribe(subscription);
|
||||
this.executor.execute(subscription::invokeHandler);
|
||||
@@ -80,7 +80,6 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
|
||||
|
||||
private static final Object READY = new Object();
|
||||
|
||||
|
||||
private final Subscriber<? super DataBuffer> actual;
|
||||
|
||||
private final Consumer<OutputStream> outputStreamHandler;
|
||||
@@ -98,8 +97,7 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
|
||||
|
||||
private long produced;
|
||||
|
||||
|
||||
public OutputStreamSubscription(Subscriber<? super DataBuffer> actual,
|
||||
OutputStreamSubscription(Subscriber<? super DataBuffer> actual,
|
||||
Consumer<OutputStream> outputStreamConsumer, DataBufferFactory bufferFactory, int chunkSize) {
|
||||
|
||||
this.actual = actual;
|
||||
@@ -351,4 +349,5 @@ final class OutputStreamPublisher implements Publisher<DataBuffer> {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -490,8 +490,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
String filePath = new File(path).getAbsolutePath();
|
||||
int prefixIndex = filePath.indexOf(':');
|
||||
if (prefixIndex == 1) {
|
||||
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
|
||||
filePath = StringUtils.capitalize(filePath);
|
||||
// Possibly a drive prefix on Windows (for example, "c:"), so we prepend a slash
|
||||
// and convert the drive letter to uppercase for consistent duplicate detection.
|
||||
filePath = "/" + StringUtils.capitalize(filePath);
|
||||
}
|
||||
// Since '#' can appear in directories/filenames, java.net.URL should not treat it as a fragment
|
||||
filePath = StringUtils.replace(filePath, "#", "%23");
|
||||
|
||||
@@ -65,7 +65,6 @@ public class DefaultDeserializer implements Deserializer<Object> {
|
||||
* @see ObjectInputStream#readObject()
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("resource")
|
||||
public Object deserialize(InputStream inputStream) throws IOException {
|
||||
ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
|
||||
try {
|
||||
|
||||
+5
-2
@@ -22,8 +22,9 @@ import io.micrometer.context.ContextSnapshotFactory;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
|
||||
/**
|
||||
* {@link TaskDecorator} that {@link ContextSnapshot#wrap(Runnable) wrap the execution} of
|
||||
* tasks, assisting with context propagation.
|
||||
* {@link TaskDecorator} that {@link ContextSnapshot#wrap(Runnable) wraps the execution}
|
||||
* of tasks, assisting with context propagation.
|
||||
*
|
||||
* <p>This operation is only useful when the task execution is scheduled on a different
|
||||
* thread than the original call stack; this depends on the choice of
|
||||
* {@link org.springframework.core.task.TaskExecutor}. This is particularly useful for
|
||||
@@ -39,6 +40,7 @@ public class ContextPropagatingTaskDecorator implements TaskDecorator {
|
||||
|
||||
private final ContextSnapshotFactory factory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new decorator that uses a default instance of the {@link ContextSnapshotFactory}.
|
||||
*/
|
||||
@@ -54,6 +56,7 @@ public class ContextPropagatingTaskDecorator implements TaskDecorator {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Runnable decorate(Runnable runnable) {
|
||||
return this.factory.captureAll().wrap(runnable);
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* <p><b>NOTE:</b> This ExecutorService adapter does <em>not</em> support the
|
||||
* lifecycle methods in the {@code java.util.concurrent.ExecutorService} API
|
||||
* ("shutdown()" etc), similar to a server-wide {@code ManagedExecutorService}
|
||||
* ("shutdown()" etc.), similar to a server-wide {@code ManagedExecutorService}
|
||||
* in a Jakarta EE environment. The lifecycle is always up to the backend pool,
|
||||
* with this adapter acting as an access-only proxy for that target pool.
|
||||
*
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
Map<String, Object> compactedAttributes =
|
||||
(this.attributes.size() == 0 ? Collections.emptyMap() : this.attributes);
|
||||
(this.attributes.isEmpty() ? Collections.emptyMap() : this.attributes);
|
||||
MergedAnnotation<A> annotation = MergedAnnotation.of(
|
||||
this.classLoader, this.source, this.annotationType, compactedAttributes);
|
||||
this.consumer.accept(annotation);
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -73,7 +73,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
|
||||
// Optimization to avoid creating ClassReader for superclass.
|
||||
Boolean superClassMatch = matchSuperClass(superClassName);
|
||||
if (superClassMatch != null) {
|
||||
if (superClassMatch.booleanValue()) {
|
||||
if (superClassMatch) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
|
||||
// Optimization to avoid creating ClassReader for superclass
|
||||
Boolean interfaceMatch = matchInterface(ifc);
|
||||
if (interfaceMatch != null) {
|
||||
if (interfaceMatch.booleanValue()) {
|
||||
if (interfaceMatch) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -402,7 +402,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||
protected String[] tokenizePattern(String pattern) {
|
||||
String[] tokenized = null;
|
||||
Boolean cachePatterns = this.cachePatterns;
|
||||
if (cachePatterns == null || cachePatterns.booleanValue()) {
|
||||
if (cachePatterns == null || cachePatterns) {
|
||||
tokenized = this.tokenizedPatternCache.get(pattern);
|
||||
}
|
||||
if (tokenized == null) {
|
||||
@@ -414,7 +414,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||
deactivatePatternCache();
|
||||
return tokenized;
|
||||
}
|
||||
if (cachePatterns == null || cachePatterns.booleanValue()) {
|
||||
if (cachePatterns == null || cachePatterns) {
|
||||
this.tokenizedPatternCache.put(pattern, tokenized);
|
||||
}
|
||||
}
|
||||
@@ -458,7 +458,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||
protected AntPathStringMatcher getStringMatcher(String pattern) {
|
||||
AntPathStringMatcher matcher = null;
|
||||
Boolean cachePatterns = this.cachePatterns;
|
||||
if (cachePatterns == null || cachePatterns.booleanValue()) {
|
||||
if (cachePatterns == null || cachePatterns) {
|
||||
matcher = this.stringMatcherCache.get(pattern);
|
||||
}
|
||||
if (matcher == null) {
|
||||
@@ -470,7 +470,7 @@ public class AntPathMatcher implements PathMatcher {
|
||||
deactivatePatternCache();
|
||||
return matcher;
|
||||
}
|
||||
if (cachePatterns == null || cachePatterns.booleanValue()) {
|
||||
if (cachePatterns == null || cachePatterns) {
|
||||
this.stringMatcherCache.put(pattern, matcher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -138,7 +138,7 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
|
||||
@Override
|
||||
public E get(int index) {
|
||||
int backingListSize = this.backingList.size();
|
||||
E element = null;
|
||||
E element;
|
||||
if (index < backingListSize) {
|
||||
element = this.backingList.get(index);
|
||||
if (element == null) {
|
||||
|
||||
@@ -98,7 +98,7 @@ public abstract class Base64Utils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-decode the given byte array from an UTF-8 String.
|
||||
* Base64-decode the given byte array from a UTF-8 String.
|
||||
* @param src the encoded UTF-8 String
|
||||
* @return the original byte array
|
||||
*/
|
||||
@@ -120,7 +120,7 @@ public abstract class Base64Utils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-decode the given byte array from an UTF-8 String using the RFC 4648
|
||||
* Base64-decode the given byte array from a UTF-8 String using the RFC 4648
|
||||
* "URL and Filename Safe Alphabet".
|
||||
* @param src the encoded UTF-8 String
|
||||
* @return the original byte array
|
||||
|
||||
@@ -106,19 +106,19 @@ public abstract class ClassUtils {
|
||||
|
||||
/**
|
||||
* Map with primitive wrapper type as key and corresponding primitive
|
||||
* type as value, for example: Integer.class -> int.class.
|
||||
* type as value, for example: {@code Integer.class -> int.class}.
|
||||
*/
|
||||
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap<>(9);
|
||||
|
||||
/**
|
||||
* Map with primitive type as key and corresponding wrapper
|
||||
* type as value, for example: int.class -> Integer.class.
|
||||
* type as value, for example: {@code int.class -> Integer.class}.
|
||||
*/
|
||||
private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(9);
|
||||
|
||||
/**
|
||||
* Map with primitive type name as key and corresponding primitive
|
||||
* type as value, for example: "int" -> "int.class".
|
||||
* type as value, for example: {@code "int" -> int.class}.
|
||||
*/
|
||||
private static final Map<String, Class<?>> primitiveTypeNameMap = new HashMap<>(32);
|
||||
|
||||
|
||||
@@ -396,7 +396,6 @@ public final class ConcurrentLruCache<K, V> {
|
||||
|
||||
private final EvictionQueue<K, V> evictionQueue;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
ReadOperations(EvictionQueue<K, V> evictionQueue) {
|
||||
this.evictionQueue = evictionQueue;
|
||||
for (int i = 0; i < BUFFER_COUNT; i++) {
|
||||
|
||||
@@ -317,7 +317,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(@Nullable Object key, final @Nullable Object value) {
|
||||
public boolean remove(@Nullable Object key, @Nullable final Object value) {
|
||||
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
|
||||
@Override
|
||||
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
|
||||
@@ -334,7 +334,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) {
|
||||
public boolean replace(@Nullable K key, @Nullable final V oldValue, @Nullable final V newValue) {
|
||||
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
|
||||
@Override
|
||||
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
|
||||
@@ -350,7 +350,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public V replace(@Nullable K key, final @Nullable V value) {
|
||||
public V replace(@Nullable K key, @Nullable final V value) {
|
||||
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
|
||||
@Override
|
||||
@Nullable
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -125,13 +125,13 @@ public abstract class DigestUtils {
|
||||
|
||||
private static byte[] digest(String algorithm, InputStream inputStream) throws IOException {
|
||||
MessageDigest messageDigest = getDigest(algorithm);
|
||||
if (inputStream instanceof UpdateMessageDigestInputStream digestIntputStream){
|
||||
digestIntputStream.updateMessageDigest(messageDigest);
|
||||
if (inputStream instanceof UpdateMessageDigestInputStream digestInputStream){
|
||||
digestInputStream.updateMessageDigest(messageDigest);
|
||||
return messageDigest.digest();
|
||||
}
|
||||
else {
|
||||
final byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
|
||||
int bytesRead = -1;
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
messageDigest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
@@ -84,12 +84,13 @@ public abstract class FileSystemUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
@@ -126,12 +127,13 @@ public abstract class FileSystemUtils {
|
||||
BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
|
||||
|
||||
if (srcAttr.isDirectory()) {
|
||||
Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
|
||||
Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
Files.createDirectories(dest.resolve(src.relativize(dir)));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.copy(file, dest.resolve(src.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
@@ -102,7 +102,6 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
|
||||
private final String subtype;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private final Map<String, String> parameters;
|
||||
|
||||
@Nullable
|
||||
@@ -642,7 +641,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this {@code MimeType} is more less than the given type.
|
||||
* Indicates whether this {@code MimeType} is less specific than the given type.
|
||||
* <ol>
|
||||
* <li>if this mime type has a {@linkplain #isWildcardType() wildcard type},
|
||||
* and the other does not, then this method returns {@code true}.</li>
|
||||
@@ -684,7 +683,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
/**
|
||||
* Parse the given String value into a {@code MimeType} object,
|
||||
* with this method name following the 'valueOf' naming convention
|
||||
* (as supported by {@link org.springframework.core.convert.ConversionService}.
|
||||
* (as supported by {@link org.springframework.core.convert.ConversionService}).
|
||||
* @see MimeTypeUtils#parseMimeType(String)
|
||||
*/
|
||||
public static MimeType valueOf(String value) {
|
||||
|
||||
@@ -362,7 +362,9 @@ public abstract class MimeTypeUtils {
|
||||
*/
|
||||
public static <T extends MimeType> void sortBySpecificity(List<T> mimeTypes) {
|
||||
Assert.notNull(mimeTypes, "'mimeTypes' must not be null");
|
||||
Assert.isTrue(mimeTypes.size() <= 50, "Too many elements");
|
||||
if (mimeTypes.size() >= 50) {
|
||||
throw new InvalidMimeTypeException(mimeTypes.toString(), "Too many elements");
|
||||
}
|
||||
|
||||
bubbleSort(mimeTypes, MimeType::isLessSpecific);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public abstract class ObjectUtils {
|
||||
return optional.isEmpty();
|
||||
}
|
||||
if (obj instanceof CharSequence charSequence) {
|
||||
return charSequence.length() == 0;
|
||||
return charSequence.isEmpty();
|
||||
}
|
||||
if (obj.getClass().isArray()) {
|
||||
return Array.getLength(obj) == 0;
|
||||
|
||||
@@ -28,9 +28,10 @@ import org.springframework.lang.Nullable;
|
||||
public abstract class PatternMatchUtils {
|
||||
|
||||
/**
|
||||
* Match a String against the given pattern, supporting the following simple
|
||||
* pattern styles: {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy}
|
||||
* matches (with an arbitrary number of pattern parts), as well as direct equality.
|
||||
* Match a String against the given pattern, supporting direct equality as
|
||||
* well as the following simple pattern styles: {@code xxx*}, {@code *xxx},
|
||||
* {@code *xxx*}, and {@code xxx*yyy} (with an arbitrary number of pattern parts).
|
||||
* <p>Returns {@code false} if the supplied String or pattern is {@code null}.
|
||||
* @param pattern the pattern to match against
|
||||
* @param str the String to match
|
||||
* @return whether the String matches the given pattern
|
||||
@@ -73,14 +74,16 @@ public abstract class PatternMatchUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a String against the given patterns, supporting the following simple
|
||||
* pattern styles: {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy}
|
||||
* matches (with an arbitrary number of pattern parts), as well as direct equality.
|
||||
* Match a String against the given patterns, supporting direct equality as
|
||||
* well as the following simple pattern styles: {@code xxx*}, {@code *xxx},
|
||||
* {@code *xxx*}, and {@code xxx*yyy} (with an arbitrary number of pattern parts).
|
||||
* <p>Returns {@code false} if the supplied String is {@code null} or if the
|
||||
* supplied patterns array is {@code null} or empty.
|
||||
* @param patterns the patterns to match against
|
||||
* @param str the String to match
|
||||
* @return whether the String matches any of the given patterns
|
||||
*/
|
||||
public static boolean simpleMatch(@Nullable String[] patterns, String str) {
|
||||
public static boolean simpleMatch(@Nullable String[] patterns, @Nullable String str) {
|
||||
if (patterns != null) {
|
||||
for (String pattern : patterns) {
|
||||
if (simpleMatch(pattern, str)) {
|
||||
|
||||
@@ -839,7 +839,7 @@ public abstract class StringUtils {
|
||||
* the {@link Locale#toString} format as well as BCP 47 language tags as
|
||||
* specified by {@link Locale#forLanguageTag}.
|
||||
* @param localeValue the locale value: following either {@code Locale's}
|
||||
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
|
||||
* {@code toString()} format ("en", "en_UK", etc.), also accepting spaces as
|
||||
* separators (as an alternative to underscores), or BCP 47 (e.g. "en-UK")
|
||||
* @return a corresponding {@code Locale} instance, or {@code null} if none
|
||||
* @throws IllegalArgumentException in case of an invalid locale specification
|
||||
@@ -868,7 +868,7 @@ public abstract class StringUtils {
|
||||
* <p><b>Note: This delegate does not accept the BCP 47 language tag format.
|
||||
* Please use {@link #parseLocale} for lenient parsing of both formats.</b>
|
||||
* @param localeString the locale {@code String}: following {@code Locale's}
|
||||
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
|
||||
* {@code toString()} format ("en", "en_UK", etc.), also accepting spaces as
|
||||
* separators (as an alternative to underscores)
|
||||
* @return a corresponding {@code Locale} instance, or {@code null} if none
|
||||
* @throws IllegalArgumentException in case of an invalid locale specification
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2023 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,6 @@ import org.springframework.lang.Nullable;
|
||||
* @since 2.5
|
||||
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveAliases
|
||||
* @see org.springframework.beans.factory.config.BeanDefinitionVisitor#BeanDefinitionVisitor(StringValueResolver)
|
||||
* @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface StringValueResolver {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -35,13 +35,13 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public abstract class SystemPropertyUtils {
|
||||
|
||||
/** Prefix for system property placeholders: "${". */
|
||||
/** Prefix for system property placeholders: {@value}. */
|
||||
public static final String PLACEHOLDER_PREFIX = "${";
|
||||
|
||||
/** Suffix for system property placeholders: "}". */
|
||||
/** Suffix for system property placeholders: {@value}. */
|
||||
public static final String PLACEHOLDER_SUFFIX = "}";
|
||||
|
||||
/** Value separator for system property placeholders: ":". */
|
||||
/** Value separator for system property placeholders: {@value}. */
|
||||
public static final String VALUE_SEPARATOR = ":";
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
|
||||
|
||||
private static final long serialVersionUID = -8697084563854098920L;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private final MultiValueMap<K, V> delegate;
|
||||
|
||||
@Nullable
|
||||
@@ -266,7 +265,6 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
|
||||
|
||||
private static final long serialVersionUID = 2407578793783925203L;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private final Set<Entry<K, List<V>>> delegate;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -516,7 +514,6 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
|
||||
|
||||
private static final long serialVersionUID = 5518377583904339588L;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private final Collection<List<V>> delegate;
|
||||
|
||||
public UnmodifiableValueCollection(Collection<List<V>> delegate) {
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -54,7 +54,7 @@ public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S>
|
||||
@Override
|
||||
public void addCallback(final SuccessCallback<? super T> successCallback, final FailureCallback failureCallback) {
|
||||
ListenableFuture<S> listenableAdaptee = (ListenableFuture<S>) getAdaptee();
|
||||
listenableAdaptee.addCallback(new ListenableFutureCallback<S>() {
|
||||
listenableAdaptee.addCallback(new ListenableFutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable S result) {
|
||||
T adapted = null;
|
||||
@@ -74,6 +74,7 @@ public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S>
|
||||
}
|
||||
successCallback.onSuccess(adapted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
failureCallback.onFailure(ex);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class SingletonSupplier<T> implements Supplier<T> {
|
||||
private volatile T singletonInstance;
|
||||
|
||||
/**
|
||||
* Guards access to write operations on the response.
|
||||
* Guards access to write operations on the {@code singletonInstance} field.
|
||||
*/
|
||||
private final Lock writeLock = new ReentrantLock();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user