mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f64480c9f |
-11
@@ -77,17 +77,6 @@ exactly one candidate bean exists.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
As stated in the documentation for Mockito, there are times when using `Mockito.when()` is
|
||||
inappropriate for stubbing a spy – for example, if calling a real method on a spy results
|
||||
in undesired side effects.
|
||||
|
||||
To avoid such undesired side effects, consider using
|
||||
`Mockito.doReturn(...).when(spy)...`, `Mockito.doThrow(...).when(spy)...`,
|
||||
`Mockito.doNothing().when(spy)...`, and similar methods.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
|
||||
will result in an exception.
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ Kotlin::
|
||||
|
||||
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by leveraging the Page Object Pattern. As we mentioned in
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
|
||||
with HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
|
||||
+28
-23
@@ -2,10 +2,10 @@
|
||||
= Application Events
|
||||
|
||||
The TestContext framework provides support for recording
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-events[application events]
|
||||
published in the `ApplicationContext` so that assertions can be performed against those
|
||||
events within tests. All events published during the execution of a single test are made
|
||||
available via the `ApplicationEvents` API which allows you to process the events as a
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-events[application events] published in the
|
||||
`ApplicationContext` so that assertions can be performed against those events within
|
||||
tests. All events published during the execution of a single test are made available via
|
||||
the `ApplicationEvents` API which allows you to process the events as a
|
||||
`java.util.Stream`.
|
||||
|
||||
To use `ApplicationEvents` in your tests, do the following.
|
||||
@@ -16,23 +16,16 @@ To use `ApplicationEvents` in your tests, do the following.
|
||||
that `ApplicationEventsTestExecutionListener` is registered by default and only needs
|
||||
to be manually registered if you have custom configuration via
|
||||
`@TestExecutionListeners` that does not include the default listeners.
|
||||
* When using the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[SpringExtension for JUnit Jupiter],
|
||||
declare a method parameter of type `ApplicationEvents` in a `@Test`, `@BeforeEach`, or
|
||||
`@AfterEach` method.
|
||||
** Since `ApplicationEvents` is scoped to the lifecycle of the current test method, this
|
||||
is the recommended approach.
|
||||
* Alternatively, you can annotate a field of type `ApplicationEvents` with `@Autowired`
|
||||
and use that instance of `ApplicationEvents` in your test and lifecycle methods.
|
||||
|
||||
NOTE: `ApplicationEvents` is registered with the `ApplicationContext` as a _resolvable
|
||||
dependency_ which is scoped to the lifecycle of the current test method. Consequently,
|
||||
`ApplicationEvents` cannot be accessed outside the lifecycle of a test method and cannot be
|
||||
`@Autowired` into the constructor of a test class.
|
||||
* Annotate a field of type `ApplicationEvents` with `@Autowired` and use that instance of
|
||||
`ApplicationEvents` in your test and lifecycle methods (such as `@BeforeEach` and
|
||||
`@AfterEach` methods in JUnit Jupiter).
|
||||
** When using the xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[SpringExtension for JUnit Jupiter], you may declare a method
|
||||
parameter of type `ApplicationEvents` in a test or lifecycle method as an alternative
|
||||
to an `@Autowired` field in the test class.
|
||||
|
||||
The following test class uses the `SpringExtension` for JUnit Jupiter and
|
||||
{assertj-docs}[AssertJ] to assert the types of application events published while
|
||||
invoking a method in a Spring-managed component:
|
||||
{assertj-docs}[AssertJ] to assert the types of application events
|
||||
published while invoking a method in a Spring-managed component:
|
||||
|
||||
// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */
|
||||
[tabs]
|
||||
@@ -45,10 +38,16 @@ Java::
|
||||
@RecordApplicationEvents // <1>
|
||||
class OrderServiceTests {
|
||||
|
||||
@Autowired
|
||||
OrderService orderService;
|
||||
|
||||
@Autowired
|
||||
ApplicationEvents events; // <2>
|
||||
|
||||
@Test
|
||||
void submitOrder(@Autowired OrderService service, ApplicationEvents events) { // <2>
|
||||
void submitOrder() {
|
||||
// Invoke method in OrderService that publishes an event
|
||||
service.submitOrder(new Order(/* ... */));
|
||||
orderService.submitOrder(new Order(/* ... */));
|
||||
// Verify that an OrderSubmitted event was published
|
||||
long numEvents = events.stream(OrderSubmitted.class).count(); // <3>
|
||||
assertThat(numEvents).isEqualTo(1);
|
||||
@@ -67,10 +66,16 @@ Kotlin::
|
||||
@RecordApplicationEvents // <1>
|
||||
class OrderServiceTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var orderService: OrderService
|
||||
|
||||
@Autowired
|
||||
lateinit var events: ApplicationEvents // <2>
|
||||
|
||||
@Test
|
||||
fun submitOrder(@Autowired service: OrderService, events: ApplicationEvents) { // <2>
|
||||
fun submitOrder() {
|
||||
// Invoke method in OrderService that publishes an event
|
||||
service.submitOrder(Order(/* ... */))
|
||||
orderService.submitOrder(Order(/* ... */))
|
||||
// Verify that an OrderSubmitted event was published
|
||||
val numEvents = events.stream(OrderSubmitted::class).count() // <3>
|
||||
assertThat(numEvents).isEqualTo(1)
|
||||
|
||||
@@ -294,28 +294,7 @@ allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: The body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
The following shows how to bind request parameters, including an optional `DataBinder` customization:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val pet = request.bind(Pet::class.java, {dataBinder -> dataBinder.setAllowedFields("name")})
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
Note that the body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
[[webflux-fn-response]]
|
||||
=== ServerResponse
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
= WebClient
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring WebFlux includes a client to perform HTTP requests. `WebClient` has a
|
||||
functional, fluent API based on Reactor (see xref:web/webflux-reactive-libraries.adoc[Reactive Libraries])
|
||||
Spring WebFlux includes a client to perform HTTP requests with. `WebClient` has a
|
||||
functional, fluent API based on Reactor, see xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries],
|
||||
which enables declarative composition of asynchronous logic without the need to deal with
|
||||
threads or concurrency. It is fully non-blocking, supports streaming, and relies on
|
||||
threads or concurrency. It is fully non-blocking, it supports streaming, and relies on
|
||||
the same xref:web/webflux/reactive-spring.adoc#webflux-codecs[codecs] that are also used to encode and
|
||||
decode request and response content on the server side.
|
||||
|
||||
`WebClient` needs an HTTP client library to perform requests. There is built-in
|
||||
`WebClient` needs an HTTP client library to perform requests with. There is built-in
|
||||
support for the following:
|
||||
|
||||
* {reactor-github-org}/reactor-netty[Reactor Netty]
|
||||
* {java-api}/java.net.http/java/net/http/HttpClient.html[JDK HttpClient]
|
||||
* https://github.com/jetty-project/jetty-reactive-httpclient[Jetty Reactive HttpClient]
|
||||
* https://hc.apache.org/index.html[Apache HttpComponents]
|
||||
* Others can be plugged in via `ClientHttpConnector`.
|
||||
* Others can be plugged via `ClientHttpConnector`.
|
||||
|
||||
@@ -15,27 +15,27 @@ See xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] for more de
|
||||
[[webmvc-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
`WebClient` is a reactive client for making HTTP requests with a fluent API.
|
||||
`WebClient` is a reactive client to perform HTTP requests with a fluent API.
|
||||
|
||||
See xref:web/webflux-webclient.adoc[`WebClient`] for more details.
|
||||
See xref:web/webflux-webclient.adoc[WebClient] for more details.
|
||||
|
||||
|
||||
[[webmvc-resttemplate]]
|
||||
== `RestTemplate`
|
||||
|
||||
`RestTemplate` is a synchronous client for making HTTP requests. It is the original
|
||||
`RestTemplate` is a synchronous client to perform HTTP requests. It is the original
|
||||
Spring REST client and exposes a simple, template-method API over underlying HTTP client
|
||||
libraries.
|
||||
|
||||
See xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] for details.
|
||||
See xref:integration/rest-clients.adoc[REST Endpoints] for details.
|
||||
|
||||
|
||||
[[webmvc-http-interface]]
|
||||
== HTTP Interface
|
||||
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with HTTP
|
||||
The Spring Frameworks lets you define an HTTP service as a Java interface with HTTP
|
||||
exchange methods. You can then generate a proxy that implements this interface and
|
||||
performs the exchanges. This helps to simplify HTTP remote access and provides additional
|
||||
flexibility for choosing an API style such as synchronous or reactive.
|
||||
flexibility for to choose an API style such as synchronous or reactive.
|
||||
|
||||
See xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] for details.
|
||||
See xref:integration/rest-clients.adoc#rest-http-interface[REST Endpoints] for details.
|
||||
|
||||
@@ -184,26 +184,6 @@ val map = request.params()
|
||||
----
|
||||
======
|
||||
|
||||
The following shows how to bind request parameters, including an optional `DataBinder` customization:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val pet = request.bind(Pet::class.java, {dataBinder -> dataBinder.setAllowedFields("name")})
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-response]]
|
||||
=== ServerResponse
|
||||
|
||||
|
||||
@@ -8,30 +8,30 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4.1"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.11"))
|
||||
api(platform("io.netty:netty-bom:4.1.127.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.10"))
|
||||
api(platform("io.netty:netty-bom:4.1.124.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.10"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.9"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.28"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.26"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.26"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.25"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.25"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.13.4"))
|
||||
api(platform("org.mockito:mockito-bom:5.19.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.18.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.3")
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.2.2")
|
||||
api("com.github.librepdf:openpdf:1.3.43")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.13.1")
|
||||
api("com.google.protobuf:protobuf-java-util:4.32.0")
|
||||
api("com.google.protobuf:protobuf-java-util:4.31.1")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
@@ -53,11 +53,11 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.11")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.10")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.19.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.19.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.19.Final")
|
||||
api("io.undertow:undertow-core:2.3.18.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.18.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.18.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -100,7 +100,7 @@ dependencies {
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.5")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.5")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
|
||||
@@ -115,7 +115,7 @@ dependencies {
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.easymock:easymock:5.5.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.11")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
@@ -129,7 +129,7 @@ dependencies {
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.4")
|
||||
api("org.htmlunit:htmlunit:4.16.0")
|
||||
api("org.htmlunit:htmlunit:4.14.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.13.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
@@ -137,7 +137,7 @@ dependencies {
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.4")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.35.0")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.34.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.35.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.11
|
||||
version=6.2.10
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+2
-1
@@ -520,7 +520,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
* to check whether the bean with the given name matches the specified type. Allow
|
||||
* additional constraints to be applied to ensure that beans are not created early.
|
||||
* @param name the name of the bean to query
|
||||
* @param typeToMatch the type to match against (as a {@code ResolvableType})
|
||||
* @param typeToMatch the type to match against (as a
|
||||
* {@code ResolvableType})
|
||||
* @return {@code true} if the bean type matches, {@code false} if it
|
||||
* doesn't match or cannot be determined yet
|
||||
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
|
||||
|
||||
+18
-30
@@ -58,7 +58,6 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
|
||||
import org.springframework.beans.factory.CannotLoadBeanClassException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
@@ -197,8 +196,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
/** Map from bean name to merged BeanDefinitionHolder. */
|
||||
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
|
||||
|
||||
/** Map of bean definition names with a primary marker plus corresponding type. */
|
||||
private final Map<String, Class<?>> primaryBeanNamesWithType = new ConcurrentHashMap<>(16);
|
||||
/** Set of bean definition names with a primary marker. */
|
||||
private final Set<String> primaryBeanNames = ConcurrentHashMap.newKeySet(16);
|
||||
|
||||
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
|
||||
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);
|
||||
@@ -1038,7 +1037,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
|
||||
super.cacheMergedBeanDefinition(mbd, beanName);
|
||||
if (mbd.isPrimary()) {
|
||||
this.primaryBeanNamesWithType.put(beanName, Void.class);
|
||||
this.primaryBeanNames.add(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,10 +1112,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
|
||||
|
||||
// Trigger initialization of all non-lazy singleton beans...
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
|
||||
this.preInstantiationThread.set(PreInstantiation.MAIN);
|
||||
this.mainThreadPrefix = getThreadNamePrefix();
|
||||
try {
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
for (String beanName : beanNames) {
|
||||
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
|
||||
if (!mbd.isAbstract() && mbd.isSingleton()) {
|
||||
@@ -1126,20 +1126,21 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!futures.isEmpty()) {
|
||||
try {
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
|
||||
}
|
||||
catch (CompletionException ex) {
|
||||
ReflectionUtils.rethrowRuntimeException(ex.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.mainThreadPrefix = null;
|
||||
this.preInstantiationThread.remove();
|
||||
}
|
||||
|
||||
if (!futures.isEmpty()) {
|
||||
try {
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
|
||||
}
|
||||
catch (CompletionException ex) {
|
||||
ReflectionUtils.rethrowRuntimeException(ex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger post-initialization callback for all applicable beans...
|
||||
for (String beanName : beanNames) {
|
||||
Object singletonInstance = getSingleton(beanName, false);
|
||||
@@ -1312,7 +1313,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
|
||||
// Cache a primary marker for the given bean.
|
||||
if (beanDefinition.isPrimary()) {
|
||||
this.primaryBeanNamesWithType.put(beanName, Void.class);
|
||||
this.primaryBeanNames.add(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1404,7 +1405,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
destroySingleton(beanName);
|
||||
|
||||
// Remove a cached primary marker for the given bean.
|
||||
this.primaryBeanNamesWithType.remove(beanName);
|
||||
this.primaryBeanNames.remove(beanName);
|
||||
|
||||
// Notify all post-processors that the specified bean definition has been reset.
|
||||
for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
|
||||
@@ -1457,18 +1458,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@Override
|
||||
protected void addSingleton(String beanName, Object singletonObject) {
|
||||
super.addSingleton(beanName, singletonObject);
|
||||
|
||||
Predicate<Class<?>> filter = (beanType -> beanType != Object.class && beanType.isInstance(singletonObject));
|
||||
this.allBeanNamesByType.keySet().removeIf(filter);
|
||||
this.singletonBeanNamesByType.keySet().removeIf(filter);
|
||||
|
||||
if (this.primaryBeanNamesWithType.containsKey(beanName) && singletonObject.getClass() != NullBean.class) {
|
||||
Class<?> beanType = (singletonObject instanceof FactoryBean<?> fb ?
|
||||
getTypeForFactoryBean(fb) : singletonObject.getClass());
|
||||
if (beanType != null) {
|
||||
this.primaryBeanNamesWithType.put(beanName, beanType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2276,12 +2268,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
* not matching the given bean name.
|
||||
*/
|
||||
private boolean hasPrimaryConflict(String beanName, Class<?> dependencyType) {
|
||||
for (Map.Entry<String, Class<?>> candidate : this.primaryBeanNamesWithType.entrySet()) {
|
||||
String candidateName = candidate.getKey();
|
||||
Class<?> candidateType = candidate.getValue();
|
||||
if (!candidateName.equals(beanName) && (candidateType != Void.class ?
|
||||
dependencyType.isAssignableFrom(candidateType) : // cached singleton class for primary bean
|
||||
isTypeMatch(candidateName, dependencyType))) { // not instantiated yet or not a singleton
|
||||
for (String candidate : this.primaryBeanNames) {
|
||||
if (isTypeMatch(candidate, dependencyType) && !candidate.equals(beanName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-19
@@ -23,7 +23,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -419,29 +418,14 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
|
||||
if (destroyMethodName == null) {
|
||||
destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
boolean autoCloseable = AutoCloseable.class.isAssignableFrom(target);
|
||||
boolean executorService = ExecutorService.class.isAssignableFrom(target);
|
||||
boolean autoCloseable = (AutoCloseable.class.isAssignableFrom(target));
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
|
||||
(destroyMethodName == null && (autoCloseable || executorService))) {
|
||||
(destroyMethodName == null && autoCloseable)) {
|
||||
// Only perform destroy method inference in case of the bean
|
||||
// not explicitly implementing the DisposableBean interface
|
||||
destroyMethodName = null;
|
||||
if (!(DisposableBean.class.isAssignableFrom(target))) {
|
||||
if (executorService) {
|
||||
destroyMethodName = SHUTDOWN_METHOD_NAME;
|
||||
try {
|
||||
// On JDK 19+, avoid the ExecutorService-level AutoCloseable default implementation
|
||||
// which awaits task termination for 1 day, even for delayed tasks such as cron jobs.
|
||||
// Custom close() implementations in ExecutorService subclasses are still accepted.
|
||||
if (target.getMethod(CLOSE_METHOD_NAME).getDeclaringClass() != ExecutorService.class) {
|
||||
destroyMethodName = CLOSE_METHOD_NAME;
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// Ignore - stick with shutdown()
|
||||
}
|
||||
}
|
||||
else if (autoCloseable) {
|
||||
if (autoCloseable) {
|
||||
destroyMethodName = CLOSE_METHOD_NAME;
|
||||
}
|
||||
else {
|
||||
|
||||
+1
-46
@@ -17,7 +17,6 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -60,38 +59,13 @@ class RootBeanDefinitionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForCloseMethod() {
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedInferredVaue() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithCloseMethod.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("close");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForShutdownMethod() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithShutdownMethod.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("shutdown");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForExecutorService() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanImplementingExecutorService.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("shutdown");
|
||||
// even on JDK 19+ where the ExecutorService interface declares a default AutoCloseable implementation
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithMatchingCandidateReplacedForAutoCloseableExecutorService() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanImplementingExecutorServiceAndAutoCloseable.class);
|
||||
beanDefinition.setDestroyMethodName(AbstractBeanDefinition.INFER_METHOD);
|
||||
beanDefinition.resolveDestroyMethodIfNecessary();
|
||||
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("close");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDestroyMethodWithNoCandidateSetDestroyMethodNameToNull() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanWithNoDestroyMethod.class);
|
||||
@@ -116,25 +90,6 @@ class RootBeanDefinitionTests {
|
||||
}
|
||||
|
||||
|
||||
static class BeanWithShutdownMethod {
|
||||
|
||||
public void shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract static class BeanImplementingExecutorService implements ExecutorService {
|
||||
}
|
||||
|
||||
|
||||
abstract static class BeanImplementingExecutorServiceAndAutoCloseable implements ExecutorService, AutoCloseable {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class BeanWithNoDestroyMethod {
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -60,6 +60,12 @@ class CacheOperationExpressionEvaluatorTests {
|
||||
private final AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
|
||||
|
||||
|
||||
private Collection<CacheOperation> getOps(String name) {
|
||||
Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class);
|
||||
return this.source.getCacheOperations(method, AnnotatedClass.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMultipleCachingSource() {
|
||||
Collection<CacheOperation> ops = getOps("multipleCaching");
|
||||
@@ -138,12 +144,6 @@ class CacheOperationExpressionEvaluatorTests {
|
||||
assertThat(value).isEqualTo(String.class.getName());
|
||||
}
|
||||
|
||||
|
||||
private Collection<CacheOperation> getOps(String name) {
|
||||
Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class);
|
||||
return this.source.getCacheOperations(method, AnnotatedClass.class);
|
||||
}
|
||||
|
||||
private EvaluationContext createEvaluationContext(Object result) {
|
||||
return createEvaluationContext(result, null);
|
||||
}
|
||||
|
||||
Vendored
-4
@@ -104,7 +104,6 @@ class CachePutEvaluationTests {
|
||||
assertThat(this.cache.get(anotherValue + 100).get()).as("Wrong value for @CachePut key").isEqualTo(anotherValue);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
static class Config implements CachingConfigurer {
|
||||
@@ -122,10 +121,8 @@ class CachePutEvaluationTests {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@CacheConfig("test")
|
||||
public static class SimpleService {
|
||||
|
||||
private AtomicLong counter = new AtomicLong();
|
||||
|
||||
/**
|
||||
@@ -147,5 +144,4 @@ class CachePutEvaluationTests {
|
||||
return this.counter.getAndIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -39,7 +38,6 @@ import org.springframework.util.StringUtils;
|
||||
* interface-declared parameter annotations from the concrete target method.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 6.1
|
||||
* @see #getMethodAnnotation(Class)
|
||||
* @see #getMethodParameters()
|
||||
@@ -183,7 +181,7 @@ public class AnnotatedMethod {
|
||||
clazz = null;
|
||||
}
|
||||
if (clazz != null) {
|
||||
for (Method candidate : clazz.getDeclaredMethods()) {
|
||||
for (Method candidate : clazz.getMethods()) {
|
||||
if (isOverrideFor(candidate)) {
|
||||
parameterAnnotations.add(candidate.getParameterAnnotations());
|
||||
}
|
||||
@@ -196,9 +194,8 @@ public class AnnotatedMethod {
|
||||
}
|
||||
|
||||
private boolean isOverrideFor(Method candidate) {
|
||||
if (Modifier.isPrivate(candidate.getModifiers()) ||
|
||||
!candidate.getName().equals(this.method.getName()) ||
|
||||
(candidate.getParameterCount() != this.method.getParameterCount())) {
|
||||
if (!candidate.getName().equals(this.method.getName()) ||
|
||||
candidate.getParameterCount() != this.method.getParameterCount()) {
|
||||
return false;
|
||||
}
|
||||
Class<?>[] paramTypes = this.method.getParameterTypes();
|
||||
@@ -207,7 +204,7 @@ public class AnnotatedMethod {
|
||||
}
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
if (paramTypes[i] !=
|
||||
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).toClass()) {
|
||||
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -372,14 +372,14 @@ abstract class AnnotationsScanner {
|
||||
private static boolean hasSameGenericTypeParameters(
|
||||
Method rootMethod, Method candidateMethod, Class<?>[] rootParameterTypes) {
|
||||
|
||||
Class<?> rootDeclaringClass = rootMethod.getDeclaringClass();
|
||||
Class<?> sourceDeclaringClass = rootMethod.getDeclaringClass();
|
||||
Class<?> candidateDeclaringClass = candidateMethod.getDeclaringClass();
|
||||
if (!candidateDeclaringClass.isAssignableFrom(rootDeclaringClass)) {
|
||||
if (!candidateDeclaringClass.isAssignableFrom(sourceDeclaringClass)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < rootParameterTypes.length; i++) {
|
||||
Class<?> resolvedParameterType = ResolvableType.forMethodParameter(
|
||||
candidateMethod, i, rootDeclaringClass).toClass();
|
||||
candidateMethod, i, sourceDeclaringClass).resolve();
|
||||
if (rootParameterTypes[i] != resolvedParameterType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -117,10 +117,8 @@ public interface Resource extends InputStreamSource {
|
||||
|
||||
/**
|
||||
* Return a File handle for this resource.
|
||||
* <p>Note: This only works for files in the default file system.
|
||||
* @throws UnsupportedOperationException if the resource is a file but cannot be
|
||||
* exposed as a {@code java.io.File}; an alternative to {@code FileNotFoundException}
|
||||
* @throws java.io.FileNotFoundException if the resource cannot be resolved as a file
|
||||
* @throws java.io.FileNotFoundException if the resource cannot be resolved as
|
||||
* absolute file path, i.e. if the resource is not available in a file system
|
||||
* @throws IOException in case of general resolution/reading failures
|
||||
* @see #getInputStream()
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -222,17 +223,17 @@ public abstract class DataBufferUtils {
|
||||
|
||||
try {
|
||||
if (resource.isFile()) {
|
||||
Path filePath = resource.getFile().toPath();
|
||||
File file = resource.getFile();
|
||||
return readAsynchronousFileChannel(
|
||||
() -> AsynchronousFileChannel.open(filePath, StandardOpenOption.READ),
|
||||
() -> AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ),
|
||||
position, bufferFactory, bufferSize);
|
||||
}
|
||||
}
|
||||
catch (IOException | UnsupportedOperationException ignore) {
|
||||
catch (IOException ignore) {
|
||||
// fallback to resource.readableChannel(), below
|
||||
}
|
||||
Flux<DataBuffer> result = readByteChannel(resource::readableChannel, bufferFactory, bufferSize);
|
||||
return (position == 0 ? result : skipUntilByteCount(result, position));
|
||||
return position == 0 ? result : skipUntilByteCount(result, position);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ public final class JettyDataBuffer implements PooledDataBuffer {
|
||||
this.bufferFactory = bufferFactory;
|
||||
this.delegate = delegate;
|
||||
this.chunk = chunk;
|
||||
this.chunk.retain();
|
||||
}
|
||||
|
||||
JettyDataBuffer(JettyDataBufferFactory bufferFactory, DefaultDataBuffer delegate) {
|
||||
|
||||
+9
-50
@@ -92,8 +92,6 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
@Nullable
|
||||
private Set<Thread> activeThreads;
|
||||
|
||||
private boolean cancelRemainingTasksOnClose = false;
|
||||
|
||||
private boolean rejectTasksWhenLimitReached = false;
|
||||
|
||||
private volatile boolean active = true;
|
||||
@@ -186,33 +184,12 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
* @param timeout the timeout in milliseconds
|
||||
* @since 6.1
|
||||
* @see #close()
|
||||
* @see #setCancelRemainingTasksOnClose
|
||||
* @see org.springframework.scheduling.concurrent.ExecutorConfigurationSupport#setAwaitTerminationMillis
|
||||
*/
|
||||
public void setTaskTerminationTimeout(long timeout) {
|
||||
Assert.isTrue(timeout >= 0, "Timeout value must be >=0");
|
||||
this.taskTerminationTimeout = timeout;
|
||||
trackActiveThreadsIfNecessary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to cancel remaining tasks on close: that is, whether to
|
||||
* interrupt any active threads at the time of the {@link #close()} call.
|
||||
* <p>The default is {@code false}, not tracking active threads at all or
|
||||
* just interrupting any remaining threads that still have not finished after
|
||||
* the specified {@link #setTaskTerminationTimeout taskTerminationTimeout}.
|
||||
* Switch this to {@code true} for immediate interruption on close, either in
|
||||
* combination with a subsequent termination timeout or without any waiting
|
||||
* at all, depending on whether a {@code taskTerminationTimeout} has been
|
||||
* specified as well.
|
||||
* @since 6.2.11
|
||||
* @see #close()
|
||||
* @see #setTaskTerminationTimeout
|
||||
* @see org.springframework.scheduling.concurrent.ExecutorConfigurationSupport#setWaitForTasksToCompleteOnShutdown
|
||||
*/
|
||||
public void setCancelRemainingTasksOnClose(boolean cancelRemainingTasksOnClose) {
|
||||
this.cancelRemainingTasksOnClose = cancelRemainingTasksOnClose;
|
||||
trackActiveThreadsIfNecessary();
|
||||
this.activeThreads = (timeout > 0 ? ConcurrentHashMap.newKeySet() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,15 +249,6 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
return this.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track active threads only when a task termination timeout has been
|
||||
* specified or interruption of remaining threads has been requested.
|
||||
*/
|
||||
private void trackActiveThreadsIfNecessary() {
|
||||
this.activeThreads = (this.taskTerminationTimeout > 0 || this.cancelRemainingTasksOnClose ?
|
||||
ConcurrentHashMap.newKeySet() : null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given task, within a concurrency throttle
|
||||
@@ -385,7 +353,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
}
|
||||
|
||||
/**
|
||||
* This close method tracks the termination of active threads if a concrete
|
||||
* This close methods tracks the termination of active threads if a concrete
|
||||
* {@link #setTaskTerminationTimeout task termination timeout} has been set.
|
||||
* Otherwise, it is not necessary to close this executor.
|
||||
* @since 6.1
|
||||
@@ -396,26 +364,17 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
|
||||
this.active = false;
|
||||
Set<Thread> threads = this.activeThreads;
|
||||
if (threads != null) {
|
||||
if (this.cancelRemainingTasksOnClose) {
|
||||
// Early interrupt for remaining tasks on close
|
||||
threads.forEach(Thread::interrupt);
|
||||
}
|
||||
if (this.taskTerminationTimeout > 0) {
|
||||
synchronized (threads) {
|
||||
try {
|
||||
if (!threads.isEmpty()) {
|
||||
threads.wait(this.taskTerminationTimeout);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
synchronized (threads) {
|
||||
try {
|
||||
if (!threads.isEmpty()) {
|
||||
threads.wait(this.taskTerminationTimeout);
|
||||
}
|
||||
}
|
||||
if (!this.cancelRemainingTasksOnClose) {
|
||||
// Late interrupt for remaining tasks after timeout
|
||||
threads.forEach(Thread::interrupt);
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
threads.forEach(Thread::interrupt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ import java.lang.annotation.Target;
|
||||
* <p>The additional return values denote the following:
|
||||
* <ul>
|
||||
* <li>{@code fail} - the method throws an exception, if the arguments satisfy argument constraints
|
||||
* <li>{@code new} - the method returns a non-null new object which is distinct from any other object existing in the heap prior to method execution.
|
||||
* If the method has no visible side effects, then we can be sure that the new object is not stored to any field/array and will be lost if the method's return value is not used.
|
||||
* <li>{@code new} - the method returns a non-null new object which is distinct from any other object existing in the heap prior to method execution. If method is also pure, then we can be sure that the new object is not stored to any field/array and will be lost if method return value is not used.
|
||||
* <li>{@code this} - the method returns its qualifier value (not applicable for static methods)
|
||||
* <li>{@code param1, param2, ...} - the method returns its first (second, ...) parameter value
|
||||
* </ul>
|
||||
|
||||
@@ -86,12 +86,13 @@ public abstract class FileSystemUtils {
|
||||
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException {
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException ex) throws IOException {
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
@@ -126,34 +127,19 @@ public abstract class FileSystemUtils {
|
||||
BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
|
||||
|
||||
if (srcAttr.isDirectory()) {
|
||||
if (src.getClass() == dest.getClass()) { // dest.resolve(Path) only works for same Path type
|
||||
Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException {
|
||||
Files.createDirectories(dest.resolve(src.relativize(dir)));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException {
|
||||
Files.copy(file, dest.resolve(src.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
else { // use dest.resolve(String) for different Path types
|
||||
Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) throws IOException {
|
||||
Files.createDirectories(dest.resolve(src.relativize(dir).toString()));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) throws IOException {
|
||||
Files.copy(file, dest.resolve(src.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
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);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (srcAttr.isRegularFile()) {
|
||||
Files.copy(src, dest);
|
||||
|
||||
@@ -137,19 +137,14 @@ public final class DataSize implements Comparable<DataSize>, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link DataSize} from a text string such as {@code "5MB"} using
|
||||
* Obtain a {@link DataSize} from a text string such as {@code 12MB} using
|
||||
* {@link DataUnit#BYTES} if no unit is specified.
|
||||
* <h4>Examples</h4>
|
||||
* <table border="1">
|
||||
* <tr><th>Text</th><th>Parsed As</th><th>Size in Bytes</th></tr>
|
||||
* <tr><td>"20"</td><td>20 bytes</td><td>20</td></tr>
|
||||
* <tr><td>"20B"</td><td>20 bytes</td><td>20</td></tr>
|
||||
* <tr><td>"12KB"</td><td>12 kilobytes</td><td>12,288</td></tr>
|
||||
* <tr><td>"5MB"</td><td>5 megabytes</td><td>5,242,880</td></tr>
|
||||
* </table>
|
||||
* <p>Note that the terms and units used in the above examples are based on
|
||||
* <a href="https://en.wikipedia.org/wiki/Binary_prefix">binary prefixes</a>.
|
||||
* Consult the {@linkplain DataSize class-level Javadoc} for details.
|
||||
* <p>Examples:
|
||||
* <pre>
|
||||
* "12KB" -- parses as "12 kilobytes"
|
||||
* "5MB" -- parses as "5 megabytes"
|
||||
* "20" -- parses as "20 bytes"
|
||||
* </pre>
|
||||
* @param text the text to parse
|
||||
* @return the parsed {@code DataSize}
|
||||
* @see #parse(CharSequence, DataUnit)
|
||||
@@ -159,24 +154,19 @@ public final class DataSize implements Comparable<DataSize>, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link DataSize} from a text string such as {@code "5MB"} using
|
||||
* Obtain a {@link DataSize} from a text string such as {@code 12MB} using
|
||||
* the specified default {@link DataUnit} if no unit is specified.
|
||||
* <p>The string starts with a number followed optionally by a unit matching
|
||||
* one of the supported {@linkplain DataUnit suffixes}.
|
||||
* <p>If neither a unit nor a default {@code DataUnit} is specified,
|
||||
* {@link DataUnit#BYTES} will be inferred.
|
||||
* <h4>Examples</h4>
|
||||
* <table border="1">
|
||||
* <tr><th>Text</th><th>Default Unit</th><th>Parsed As</th><th>Size in Bytes</th></tr>
|
||||
* <tr><td>"20"</td><td>{@code null}</td><td>20 bytes</td><td>20</td></tr>
|
||||
* <tr><td>"20"</td><td>{@link DataUnit#KILOBYTES KILOBYTES}</td><td>20 kilobytes</td><td>20,480</td></tr>
|
||||
* <tr><td>"20B"</td><td>N/A</td><td>20 bytes</td><td>20</td></tr>
|
||||
* <tr><td>"12KB"</td><td>N/A</td><td>12 kilobytes</td><td>12,288</td></tr>
|
||||
* <tr><td>"5MB"</td><td>N/A</td><td>5 megabytes</td><td>5,242,880</td></tr>
|
||||
* </table>
|
||||
* <p>Note that the terms and units used in the above examples are based on
|
||||
* <a href="https://en.wikipedia.org/wiki/Binary_prefix">binary prefixes</a>.
|
||||
* Consult the {@linkplain DataSize class-level Javadoc} for details.
|
||||
* <p>Examples:
|
||||
* <pre>
|
||||
* "12KB" -- parses as "12 kilobytes"
|
||||
* "5MB" -- parses as "5 megabytes"
|
||||
* "20" -- parses as "20 kilobytes" (where the {@code defaultUnit} is {@link DataUnit#KILOBYTES})
|
||||
* "20" -- parses as "20 bytes" (if the {@code defaultUnit} is {@code null})
|
||||
* </pre>
|
||||
* @param text the text to parse
|
||||
* @param defaultUnit the default {@code DataUnit} to use
|
||||
* @return the parsed {@code DataSize}
|
||||
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AnnotatedMethod}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2.11
|
||||
*/
|
||||
class AnnotatedMethodTests {
|
||||
|
||||
@Test
|
||||
void shouldFindAnnotationOnMethodInGenericAbstractSuperclass() {
|
||||
Method processTwo = getMethod("processTwo", String.class);
|
||||
|
||||
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processTwo);
|
||||
|
||||
assertThat(annotatedMethod.hasMethodAnnotation(Handler.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAnnotationOnMethodInGenericInterface() {
|
||||
Method processOneAndTwo = getMethod("processOneAndTwo", Long.class, Object.class);
|
||||
|
||||
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processOneAndTwo);
|
||||
|
||||
assertThat(annotatedMethod.hasMethodAnnotation(Handler.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAnnotationOnMethodParameterInGenericAbstractSuperclass() {
|
||||
// Prerequisites for gh-35349
|
||||
Method abstractMethod = ReflectionUtils.findMethod(GenericAbstractSuperclass.class, "processTwo", Object.class);
|
||||
assertThat(abstractMethod).isNotNull();
|
||||
assertThat(Modifier.isAbstract(abstractMethod.getModifiers())).as("abstract").isTrue();
|
||||
assertThat(Modifier.isPublic(abstractMethod.getModifiers())).as("public").isFalse();
|
||||
|
||||
Method processTwo = getMethod("processTwo", String.class);
|
||||
|
||||
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processTwo);
|
||||
MethodParameter[] methodParameters = annotatedMethod.getMethodParameters();
|
||||
|
||||
assertThat(methodParameters).hasSize(1);
|
||||
assertThat(methodParameters[0].hasParameterAnnotation(Param.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAnnotationOnMethodParameterInGenericInterface() {
|
||||
Method processOneAndTwo = getMethod("processOneAndTwo", Long.class, Object.class);
|
||||
|
||||
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processOneAndTwo);
|
||||
MethodParameter[] methodParameters = annotatedMethod.getMethodParameters();
|
||||
|
||||
assertThat(methodParameters).hasSize(2);
|
||||
assertThat(methodParameters[0].hasParameterAnnotation(Param.class)).isFalse();
|
||||
assertThat(methodParameters[1].hasParameterAnnotation(Param.class)).isTrue();
|
||||
}
|
||||
|
||||
|
||||
private static Method getMethod(String name, Class<?>...parameterTypes) {
|
||||
Class<?> clazz = GenericInterfaceImpl.class;
|
||||
Method method = ReflectionUtils.findMethod(clazz, name, parameterTypes);
|
||||
if (method == null) {
|
||||
String parameterNames = stream(parameterTypes).map(Class::getName).collect(joining(", "));
|
||||
throw new IllegalStateException("Expected method not found: %s#%s(%s)"
|
||||
.formatted(clazz.getSimpleName(), name, parameterNames));
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Handler {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Param {
|
||||
}
|
||||
|
||||
interface GenericInterface<A, B> {
|
||||
|
||||
@Handler
|
||||
void processOneAndTwo(A value1, @Param B value2);
|
||||
}
|
||||
|
||||
abstract static class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
|
||||
|
||||
@Override
|
||||
public void processOneAndTwo(Long value1, C value2) {
|
||||
}
|
||||
|
||||
@Handler
|
||||
// Intentionally NOT public
|
||||
abstract void processTwo(@Param C value);
|
||||
}
|
||||
|
||||
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
|
||||
|
||||
@Override
|
||||
void processTwo(String value) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-29
@@ -945,15 +945,6 @@ class MergedAnnotationsTests {
|
||||
Order.class).getDistance()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFromMethodWithUnresolvedGenericsInGenericTypeHierarchy() {
|
||||
// The following method is GenericAbstractSuperclass.processOneAndTwo(java.lang.Long, C),
|
||||
// where 'C' is an unresolved generic, for which ResolvableType.resolve() returns null.
|
||||
Method method = ClassUtils.getMethod(GenericInterfaceImpl.class, "processOneAndTwo", Long.class, Object.class);
|
||||
assertThat(MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY)
|
||||
.get(Transactional.class).isDirectlyPresent()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFromMethodWithInterfaceOnSuper() throws Exception {
|
||||
Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
|
||||
@@ -3041,26 +3032,6 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
}
|
||||
|
||||
interface GenericInterface<A, B> {
|
||||
|
||||
@Transactional
|
||||
void processOneAndTwo(A value1, B value2);
|
||||
}
|
||||
|
||||
abstract static class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
|
||||
|
||||
@Override
|
||||
public void processOneAndTwo(Long value1, C value2) {
|
||||
}
|
||||
}
|
||||
|
||||
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
|
||||
// The compiler does not require us to declare a concrete
|
||||
// processOneAndTwo(Long, String) method, and we intentionally
|
||||
// do not declare one here.
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@interface MyRepeatableContainer {
|
||||
|
||||
+13
-18
@@ -18,34 +18,33 @@ package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.io.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
/**
|
||||
* Tests for {@link JettyDataBuffer}
|
||||
* @author Arjen Poutsma
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class JettyDataBufferTests {
|
||||
|
||||
private final JettyDataBufferFactory dataBufferFactory = new JettyDataBufferFactory();
|
||||
|
||||
private ArrayByteBufferPool.Tracking byteBufferPool = new ArrayByteBufferPool.Tracking();
|
||||
|
||||
@Test
|
||||
void releaseRetainChunk() {
|
||||
RetainableByteBuffer retainableBuffer = byteBufferPool.acquire(3, false);
|
||||
ByteBuffer buffer = retainableBuffer.getByteBuffer();
|
||||
buffer.position(0).limit(1);
|
||||
Content.Chunk chunk = Content.Chunk.asChunk(buffer, false, retainableBuffer);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(3);
|
||||
Content.Chunk mockChunk = mock();
|
||||
given(mockChunk.getByteBuffer()).willReturn(buffer);
|
||||
given(mockChunk.release()).willReturn(false, false, true);
|
||||
|
||||
JettyDataBuffer dataBuffer = this.dataBufferFactory.wrap(chunk);
|
||||
|
||||
|
||||
JettyDataBuffer dataBuffer = this.dataBufferFactory.wrap(mockChunk);
|
||||
dataBuffer.retain();
|
||||
dataBuffer.retain();
|
||||
assertThat(dataBuffer.release()).isFalse();
|
||||
@@ -53,12 +52,8 @@ public class JettyDataBufferTests {
|
||||
assertThat(dataBuffer.release()).isTrue();
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(dataBuffer::release);
|
||||
assertThat(retainableBuffer.isRetained()).isFalse();
|
||||
assertThat(byteBufferPool.getLeaks()).isEmpty();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
this.byteBufferPool.clear();
|
||||
then(mockChunk).should(times(3)).retain();
|
||||
then(mockChunk).should(times(3)).release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,29 +17,20 @@
|
||||
package org.springframework.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileSystemUtils}.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class FileSystemUtilsTests {
|
||||
|
||||
@Test
|
||||
void deleteRecursively(@TempDir File tempDir) throws Exception {
|
||||
File root = new File(tempDir, "root");
|
||||
void deleteRecursively() throws Exception {
|
||||
File root = new File("./tmp/root");
|
||||
File child = new File(root, "child");
|
||||
File grandchild = new File(child, "grandchild");
|
||||
|
||||
@@ -62,8 +53,8 @@ class FileSystemUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyRecursively(@TempDir File tempDir) throws Exception {
|
||||
File src = new File(tempDir, "src");
|
||||
void copyRecursively() throws Exception {
|
||||
File src = new File("./tmp/src");
|
||||
File child = new File(src, "child");
|
||||
File grandchild = new File(child, "grandchild");
|
||||
|
||||
@@ -77,29 +68,27 @@ class FileSystemUtilsTests {
|
||||
assertThat(grandchild).exists();
|
||||
assertThat(bar).exists();
|
||||
|
||||
File dest = new File(tempDir, "/dest");
|
||||
File dest = new File("./dest");
|
||||
FileSystemUtils.copyRecursively(src, dest);
|
||||
|
||||
assertThat(dest).exists();
|
||||
assertThat(new File(dest, "child")).exists();
|
||||
assertThat(new File(dest, "child/bar.txt")).exists();
|
||||
assertThat(new File(dest, child.getName())).exists();
|
||||
|
||||
String destPath = dest.toString().replace('\\', '/');
|
||||
if (!destPath.startsWith("/")) {
|
||||
destPath = "/" + destPath;
|
||||
}
|
||||
URI uri = URI.create("jar:file:" + destPath + "/archive.zip");
|
||||
Map<String, String> env = Map.of("create", "true");
|
||||
FileSystem zipfs = FileSystems.newFileSystem(uri, env);
|
||||
Path ziproot = zipfs.getPath("/");
|
||||
FileSystemUtils.copyRecursively(src.toPath(), ziproot);
|
||||
|
||||
assertThat(zipfs.getPath("/child")).exists();
|
||||
assertThat(zipfs.getPath("/child/bar.txt")).exists();
|
||||
|
||||
zipfs.close();
|
||||
FileSystemUtils.deleteRecursively(src);
|
||||
assertThat(src).doesNotExist();
|
||||
}
|
||||
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
File tmp = new File("./tmp");
|
||||
if (tmp.exists()) {
|
||||
FileSystemUtils.deleteRecursively(tmp);
|
||||
}
|
||||
File dest = new File("./dest");
|
||||
if (dest.exists()) {
|
||||
FileSystemUtils.deleteRecursively(dest);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-121
@@ -129,72 +129,34 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify the default root context object (including a type descriptor)
|
||||
* against which unqualified properties, methods, etc. should be resolved.
|
||||
* @param rootObject the root object to use
|
||||
* @param typeDescriptor a corresponding type descriptor
|
||||
*/
|
||||
public void setRootObject(@Nullable Object rootObject, TypeDescriptor typeDescriptor) {
|
||||
this.rootObject = new TypedValue(rootObject, typeDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the default root context object against which unqualified
|
||||
* properties, methods, etc. should be resolved.
|
||||
* @param rootObject the root object to use
|
||||
*/
|
||||
public void setRootObject(@Nullable Object rootObject) {
|
||||
this.rootObject = (rootObject != null ? new TypedValue(rootObject) : TypedValue.NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured default root context object against which unqualified
|
||||
* properties, methods, etc. should be resolved (can be {@link TypedValue#NULL}).
|
||||
*/
|
||||
@Override
|
||||
public TypedValue getRootObject() {
|
||||
return this.rootObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of property accessors to use in this evaluation context.
|
||||
* <p>Replaces any previously configured property accessors.
|
||||
*/
|
||||
public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
|
||||
this.propertyAccessors = propertyAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of property accessors configured in this evaluation context.
|
||||
*/
|
||||
@Override
|
||||
public List<PropertyAccessor> getPropertyAccessors() {
|
||||
return initPropertyAccessors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the supplied property accessor to this evaluation context.
|
||||
* @param propertyAccessor the property accessor to add
|
||||
* @see #getPropertyAccessors()
|
||||
* @see #setPropertyAccessors(List)
|
||||
* @see #removePropertyAccessor(PropertyAccessor)
|
||||
*/
|
||||
public void addPropertyAccessor(PropertyAccessor propertyAccessor) {
|
||||
addBeforeDefault(initPropertyAccessors(), propertyAccessor);
|
||||
public void addPropertyAccessor(PropertyAccessor accessor) {
|
||||
addBeforeDefault(initPropertyAccessors(), accessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the supplied property accessor from this evaluation context.
|
||||
* @param propertyAccessor the property accessor to remove
|
||||
* @return {@code true} if the property accessor was removed, {@code false}
|
||||
* if the property accessor was not configured in this evaluation context
|
||||
* @see #getPropertyAccessors()
|
||||
* @see #setPropertyAccessors(List)
|
||||
* @see #addPropertyAccessor(PropertyAccessor)
|
||||
*/
|
||||
public boolean removePropertyAccessor(PropertyAccessor propertyAccessor) {
|
||||
return initPropertyAccessors().remove(propertyAccessor);
|
||||
public boolean removePropertyAccessor(PropertyAccessor accessor) {
|
||||
return initPropertyAccessors().remove(accessor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,8 +198,8 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||
/**
|
||||
* Remove the supplied index accessor from this evaluation context.
|
||||
* @param indexAccessor the index accessor to remove
|
||||
* @return {@code true} if the index accessor was removed, {@code false}
|
||||
* if the index accessor was not configured in this evaluation context
|
||||
* @return {@code true} if the index accessor was removed, {@code false} if
|
||||
* the index accessor was not configured in this evaluation context
|
||||
* @since 6.2
|
||||
* @see #getIndexAccessors()
|
||||
* @see #setIndexAccessors(List)
|
||||
@@ -247,96 +209,44 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||
return initIndexAccessors().remove(indexAccessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of constructor resolvers to use in this evaluation context.
|
||||
* <p>Replaces any previously configured constructor resolvers.
|
||||
*/
|
||||
public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers) {
|
||||
this.constructorResolvers = constructorResolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of constructor resolvers to use in this evaluation context.
|
||||
*/
|
||||
@Override
|
||||
public List<ConstructorResolver> getConstructorResolvers() {
|
||||
return initConstructorResolvers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the supplied constructor resolver to this evaluation context.
|
||||
* @param constructorResolver the constructor resolver to add
|
||||
* @see #getConstructorResolvers()
|
||||
* @see #setConstructorResolvers(List)
|
||||
* @see #removeConstructorResolver(ConstructorResolver)
|
||||
*/
|
||||
public void addConstructorResolver(ConstructorResolver constructorResolver) {
|
||||
addBeforeDefault(initConstructorResolvers(), constructorResolver);
|
||||
public void addConstructorResolver(ConstructorResolver resolver) {
|
||||
addBeforeDefault(initConstructorResolvers(), resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the supplied constructor resolver from this evaluation context.
|
||||
* @param constructorResolver the constructor resolver to remove
|
||||
* @return {@code true} if the constructor resolver was removed, {@code false}
|
||||
* if the constructor resolver was not configured in this evaluation context
|
||||
* @see #getConstructorResolvers()
|
||||
* @see #setConstructorResolvers(List)
|
||||
* @see #addConstructorResolver(ConstructorResolver)
|
||||
*/
|
||||
public boolean removeConstructorResolver(ConstructorResolver constructorResolver) {
|
||||
return initConstructorResolvers().remove(constructorResolver);
|
||||
public boolean removeConstructorResolver(ConstructorResolver resolver) {
|
||||
return initConstructorResolvers().remove(resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of method resolvers to use in this evaluation context.
|
||||
* <p>Replaces any previously configured method resolvers.
|
||||
*/
|
||||
public void setMethodResolvers(List<MethodResolver> methodResolvers) {
|
||||
this.methodResolvers = methodResolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of method resolvers to use in this evaluation context.
|
||||
*/
|
||||
@Override
|
||||
public List<MethodResolver> getMethodResolvers() {
|
||||
return initMethodResolvers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the supplied method resolver to this evaluation context.
|
||||
* @param methodResolver the method resolver to add
|
||||
* @see #getMethodResolvers()
|
||||
* @see #setMethodResolvers(List)
|
||||
* @see #removeMethodResolver(MethodResolver)
|
||||
*/
|
||||
public void addMethodResolver(MethodResolver methodResolver) {
|
||||
addBeforeDefault(initMethodResolvers(), methodResolver);
|
||||
public void addMethodResolver(MethodResolver resolver) {
|
||||
addBeforeDefault(initMethodResolvers(), resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the supplied method resolver from this evaluation context.
|
||||
* @param methodResolver the method resolver to remove
|
||||
* @return {@code true} if the method resolver was removed, {@code false}
|
||||
* if the method resolver was not configured in this evaluation context
|
||||
* @see #getMethodResolvers()
|
||||
* @see #setMethodResolvers(List)
|
||||
* @see #addMethodResolver(MethodResolver)
|
||||
*/
|
||||
public boolean removeMethodResolver(MethodResolver methodResolver) {
|
||||
return initMethodResolvers().remove(methodResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link BeanResolver} to use for looking up beans, if any.
|
||||
*/
|
||||
public void setBeanResolver(@Nullable BeanResolver beanResolver) {
|
||||
public void setBeanResolver(BeanResolver beanResolver) {
|
||||
this.beanResolver = beanResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured {@link BeanResolver} for looking up beans, if any.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanResolver getBeanResolver() {
|
||||
@@ -374,17 +284,11 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||
return this.typeLocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link TypeConverter} for value conversion.
|
||||
*/
|
||||
public void setTypeConverter(TypeConverter typeConverter) {
|
||||
Assert.notNull(typeConverter, "TypeConverter must not be null");
|
||||
this.typeConverter = typeConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured {@link TypeConverter} for value conversion.
|
||||
*/
|
||||
@Override
|
||||
public TypeConverter getTypeConverter() {
|
||||
if (this.typeConverter == null) {
|
||||
@@ -393,33 +297,21 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||
return this.typeConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link TypeComparator} for comparing pairs of objects.
|
||||
*/
|
||||
public void setTypeComparator(TypeComparator typeComparator) {
|
||||
Assert.notNull(typeComparator, "TypeComparator must not be null");
|
||||
this.typeComparator = typeComparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured {@link TypeComparator} for comparing pairs of objects.
|
||||
*/
|
||||
@Override
|
||||
public TypeComparator getTypeComparator() {
|
||||
return this.typeComparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link OperatorOverloader} for mathematical operations.
|
||||
*/
|
||||
public void setOperatorOverloader(OperatorOverloader operatorOverloader) {
|
||||
Assert.notNull(operatorOverloader, "OperatorOverloader must not be null");
|
||||
this.operatorOverloader = operatorOverloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured {@link OperatorOverloader} for mathematical operations.
|
||||
*/
|
||||
@Override
|
||||
public OperatorOverloader getOperatorOverloader() {
|
||||
return this.operatorOverloader;
|
||||
|
||||
+1
-3
@@ -94,9 +94,7 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
|
||||
301, // SAP HANA
|
||||
1062, // MySQL/MariaDB
|
||||
2601, // MS SQL Server
|
||||
2627, // MS SQL Server
|
||||
-239, // Informix
|
||||
-268 // Informix
|
||||
2627 // MS SQL Server
|
||||
);
|
||||
|
||||
|
||||
|
||||
-10
@@ -90,16 +90,6 @@ class SQLStateSQLExceptionTranslatorTests {
|
||||
assertTranslation("23000", 301, DuplicateKeyException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void translateDuplicateKeyInformix1() {
|
||||
assertTranslation("23000", -239, DuplicateKeyException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void translateDuplicateKeyInformix2() {
|
||||
assertTranslation("23000", -268, DuplicateKeyException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void translateDataAccessResourceFailure() {
|
||||
assertTranslation("53", DataAccessResourceFailureException.class);
|
||||
|
||||
+2
-2
@@ -88,12 +88,12 @@ import org.springframework.util.Assert;
|
||||
* such a scenario (see container setup).
|
||||
*
|
||||
* <p>This transaction manager supports nested transactions via JDBC Savepoints.
|
||||
* The {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults
|
||||
* The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"} flag defaults
|
||||
* to "false", though, as nested transactions will just apply to the JDBC Connection,
|
||||
* not to the Hibernate Session and its cached entity objects and related context.
|
||||
* You can manually set the flag to "true" if you want to use nested transactions
|
||||
* for JDBC access code which participates in Hibernate transactions (provided that
|
||||
* your JDBC driver supports savepoints). <i>Note that Hibernate itself does not
|
||||
* your JDBC driver supports Savepoints). <i>Note that Hibernate itself does not
|
||||
* support nested transactions! Hence, do not expect Hibernate access code to
|
||||
* semantically participate in a nested transaction.</i>
|
||||
*
|
||||
|
||||
@@ -93,14 +93,13 @@ import org.springframework.util.CollectionUtils;
|
||||
*
|
||||
* <p>This transaction manager supports nested transactions via JDBC Savepoints.
|
||||
* The {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults
|
||||
* to "true" but should rather be "false", as nested transactions will just apply to
|
||||
* the JDBC Connection, not to the JPA EntityManager and its cached entity objects
|
||||
* and related context. As of Spring Framework 7.0, the default will be "false" in
|
||||
* alignment with other transaction managers, requiring an explicit switch to "true"
|
||||
* if you want to use nested transactions for JDBC access code which participates
|
||||
* in JPA transactions (provided that your JDBC driver supports savepoints).
|
||||
* <i>Note that JPA itself does not support nested transactions! Hence, do not
|
||||
* expect JPA access code to semantically participate in a nested transaction.</i>
|
||||
* to {@code false} though, since nested transactions will just apply to the JDBC
|
||||
* Connection, not to the JPA EntityManager and its cached entity objects and related
|
||||
* context. You can manually set the flag to {@code true} if you want to use nested
|
||||
* transactions for JDBC access code which participates in JPA transactions (provided
|
||||
* that your JDBC driver supports Savepoints). <i>Note that JPA itself does not support
|
||||
* nested transactions! Hence, do not expect JPA access code to semantically
|
||||
* participate in a nested transaction.</i>
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
|
||||
+1
-3
@@ -76,9 +76,7 @@ public abstract class ConnectionFactoryUtils {
|
||||
301, // SAP HANA
|
||||
1062, // MySQL/MariaDB
|
||||
2601, // MS SQL Server
|
||||
2627, // MS SQL Server
|
||||
-239, // Informix
|
||||
-268 // Informix
|
||||
2627 // MS SQL Server
|
||||
);
|
||||
|
||||
|
||||
|
||||
+28
-32
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.r2dbc.connection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.r2dbc.spi.R2dbcBadGrammarException;
|
||||
import io.r2dbc.spi.R2dbcDataIntegrityViolationException;
|
||||
import io.r2dbc.spi.R2dbcException;
|
||||
@@ -27,9 +25,6 @@ import io.r2dbc.spi.R2dbcRollbackException;
|
||||
import io.r2dbc.spi.R2dbcTimeoutException;
|
||||
import io.r2dbc.spi.R2dbcTransientResourceException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.FieldSource;
|
||||
|
||||
import org.springframework.dao.CannotAcquireLockException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
@@ -43,7 +38,6 @@ import org.springframework.r2dbc.BadSqlGrammarException;
|
||||
import org.springframework.r2dbc.UncategorizedR2dbcException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConnectionFactoryUtils}.
|
||||
@@ -97,25 +91,30 @@ class ConnectionFactoryUtilsTests {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException());
|
||||
assertThat(exception).isExactlyInstanceOf(DataIntegrityViolationException.class);
|
||||
}
|
||||
|
||||
static final List<Arguments> duplicateKeyErrorCodes = List.of(
|
||||
arguments("Oracle", "23505", 0),
|
||||
arguments("Oracle", "23000", 1),
|
||||
arguments("SAP HANA", "23000", 301),
|
||||
arguments("MySQL/MariaDB", "23000", 1062),
|
||||
arguments("MS SQL Server", "23000", 2601),
|
||||
arguments("MS SQL Server", "23000", 2627),
|
||||
arguments("Informix", "23000", -239),
|
||||
arguments("Informix", "23000", -268)
|
||||
);
|
||||
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException("reason", "23505"));
|
||||
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
|
||||
|
||||
@ParameterizedTest
|
||||
@FieldSource("duplicateKeyErrorCodes")
|
||||
void shouldTranslateIntegrityViolationExceptionToDuplicateKeyException(String db, String sqlState, int errorCode) {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException("reason", sqlState, errorCode));
|
||||
assertThat(exception).as(db).isExactlyInstanceOf(DuplicateKeyException.class);
|
||||
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException("reason", "23000", 1));
|
||||
assertThat(exception).as("Oracle").isExactlyInstanceOf(DuplicateKeyException.class);
|
||||
|
||||
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException("reason", "23000", 301));
|
||||
assertThat(exception).as("SAP HANA").isExactlyInstanceOf(DuplicateKeyException.class);
|
||||
|
||||
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException("reason", "23000", 1062));
|
||||
assertThat(exception).as("MySQL/MariaDB").isExactlyInstanceOf(DuplicateKeyException.class);
|
||||
|
||||
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException("reason", "23000", 2601));
|
||||
assertThat(exception).as("MS SQL Server").isExactlyInstanceOf(DuplicateKeyException.class);
|
||||
|
||||
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException("reason", "23000", 2627));
|
||||
assertThat(exception).as("MS SQL Server").isExactlyInstanceOf(DuplicateKeyException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -136,27 +135,24 @@ class ConnectionFactoryUtilsTests {
|
||||
void messageGeneration() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK",
|
||||
"SOME-SQL", new R2dbcTransientResourceException("MESSAGE"));
|
||||
assertThat(exception)
|
||||
.isExactlyInstanceOf(TransientDataAccessResourceException.class)
|
||||
.hasMessage("TASK; SQL [SOME-SQL]; MESSAGE");
|
||||
assertThat(exception).isExactlyInstanceOf(
|
||||
TransientDataAccessResourceException.class).hasMessage("TASK; SQL [SOME-SQL]; MESSAGE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void messageGenerationNullSQL() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK", null,
|
||||
new R2dbcTransientResourceException("MESSAGE"));
|
||||
assertThat(exception)
|
||||
.isExactlyInstanceOf(TransientDataAccessResourceException.class)
|
||||
.hasMessage("TASK; MESSAGE");
|
||||
assertThat(exception).isExactlyInstanceOf(
|
||||
TransientDataAccessResourceException.class).hasMessage("TASK; MESSAGE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void messageGenerationNullMessage() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK",
|
||||
"SOME-SQL", new R2dbcTransientResourceException());
|
||||
assertThat(exception)
|
||||
.isExactlyInstanceOf(TransientDataAccessResourceException.class)
|
||||
.hasMessage("TASK; SQL [SOME-SQL]; null");
|
||||
assertThat(exception).isExactlyInstanceOf(
|
||||
TransientDataAccessResourceException.class).hasMessage("TASK; SQL [SOME-SQL]; null");
|
||||
}
|
||||
|
||||
|
||||
|
||||
-9
@@ -67,15 +67,6 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
* {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object)
|
||||
* registered directly} as resolvable dependencies.
|
||||
*
|
||||
* <p><strong>NOTE</strong>: As stated in the documentation for Mockito, there are
|
||||
* times when using {@code Mockito.when()} is inappropriate for stubbing a spy
|
||||
* — for example, if calling a real method on a spy results in undesired
|
||||
* side effects. To avoid such undesired side effects, consider using
|
||||
* {@link org.mockito.Mockito#doReturn(Object) Mockito.doReturn(...).when(spy)...},
|
||||
* {@link org.mockito.Mockito#doThrow(Class) Mockito.doThrow(...).when(spy)...},
|
||||
* {@link org.mockito.Mockito#doNothing() Mockito.doNothing().when(spy)...}, and
|
||||
* similar methods.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Using {@code @MockitoSpyBean} in conjunction with
|
||||
* {@code @ContextHierarchy} can lead to undesirable results since each
|
||||
* {@code @MockitoSpyBean} will be applied to all context hierarchy levels by default.
|
||||
|
||||
+4
-11
@@ -33,21 +33,14 @@ import org.springframework.context.ApplicationEvent;
|
||||
* to be manually registered if you have custom configuration via
|
||||
* {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}
|
||||
* that does not include the default listeners.</li>
|
||||
* <li>With JUnit Jupiter, declare a parameter of type {@code ApplicationEvents}
|
||||
* in a {@code @Test}, {@code @BeforeEach}, or {@code @AfterEach} method. Since
|
||||
* {@code ApplicationEvents} is scoped to the lifecycle of the current test method,
|
||||
* this is the recommended approach.</li>
|
||||
* <li>Alternatively, you can annotate a field of type {@code ApplicationEvents} with
|
||||
* <li>Annotate a field of type {@code ApplicationEvents} with
|
||||
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired} and
|
||||
* use that instance of {@code ApplicationEvents} in your test and lifecycle methods.</li>
|
||||
* <li>With JUnit Jupiter, you may optionally declare a parameter of type
|
||||
* {@code ApplicationEvents} in a test or lifecycle method as an alternative to
|
||||
* an {@code @Autowired} field in the test class.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>NOTE: {@code ApplicationEvents} is registered with the {@code ApplicationContext} as a
|
||||
* {@linkplain org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency
|
||||
* resolvable dependency} which is scoped to the lifecycle of the current test method.
|
||||
* Consequently, {@code ApplicationEvents} cannot be accessed outside the lifecycle of a
|
||||
* test method and cannot be {@code @Autowired} into the constructor of a test class.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Oliver Drotbohm
|
||||
* @since 5.3.3
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class JsonPathAssertions {
|
||||
/**
|
||||
* Applies {@link JsonPathExpectationsHelper#assertValue(String, Object)}.
|
||||
*/
|
||||
public WebTestClient.BodyContentSpec isEqualTo(@Nullable Object expectedValue) {
|
||||
public WebTestClient.BodyContentSpec isEqualTo(Object expectedValue) {
|
||||
this.pathHelper.assertValue(this.content, expectedValue);
|
||||
return this.bodySpec;
|
||||
}
|
||||
|
||||
+1
-7
@@ -57,8 +57,7 @@ class JsonPathExpectationsHelperTests {
|
||||
'whitespace': ' ',
|
||||
'emptyString': '',
|
||||
'emptyArray': [],
|
||||
'emptyMap': {},
|
||||
'nullValue': null
|
||||
'emptyMap': {}
|
||||
}""";
|
||||
|
||||
private static final String SIMPSONS = """
|
||||
@@ -250,11 +249,6 @@ class JsonPathExpectationsHelperTests {
|
||||
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertNullValue() {
|
||||
new JsonPathExpectationsHelper("$.nullValue").assertValue(CONTENT, (Object) null);
|
||||
}
|
||||
|
||||
@Test // SPR-14498
|
||||
void assertValueWithNumberConversion() {
|
||||
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, 5.0);
|
||||
|
||||
@@ -211,7 +211,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
* @return a builder to create the cookie with
|
||||
* @since 6.0
|
||||
*/
|
||||
public static ResponseCookieBuilder from(String name) {
|
||||
public static ResponseCookieBuilder from(final String name) {
|
||||
return new DefaultResponseCookieBuilder(name, null, false);
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
* @param value the cookie value
|
||||
* @return a builder to create the cookie with
|
||||
*/
|
||||
public static ResponseCookieBuilder from(String name, @Nullable String value) {
|
||||
public static ResponseCookieBuilder from(final String name, final String value) {
|
||||
return new DefaultResponseCookieBuilder(name, value, false);
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
* @return a builder to create the cookie with
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public static ResponseCookieBuilder fromClientResponse(String name, @Nullable String value) {
|
||||
public static ResponseCookieBuilder fromClientResponse(final String name, final String value) {
|
||||
return new DefaultResponseCookieBuilder(name, value, true);
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ public final class ResponseCookie extends HttpCookie {
|
||||
@Nullable
|
||||
private String sameSite;
|
||||
|
||||
DefaultResponseCookieBuilder(String name, @Nullable String value, boolean lenient) {
|
||||
public DefaultResponseCookieBuilder(String name, @Nullable String value, boolean lenient) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.lenient = lenient;
|
||||
|
||||
+2
-2
@@ -203,8 +203,8 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> {
|
||||
}
|
||||
return zeroCopyHttpOutputMessage.writeWith(file, pos, count);
|
||||
}
|
||||
catch (IOException | UnsupportedOperationException ignore) {
|
||||
// returning null below leads to fallback code path
|
||||
catch (IOException ex) {
|
||||
// should not happen
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
+1
-4
@@ -115,10 +115,7 @@ class JettyCoreServerHttpRequest extends AbstractServerHttpRequest {
|
||||
// We access the request body as a Flow.Publisher, which is wrapped as an org.reactivestreams.Publisher and
|
||||
// then wrapped as a Flux.
|
||||
return Flux.from(FlowAdapters.toPublisher(Content.Source.asPublisher(this.request)))
|
||||
.map(chunk -> {
|
||||
chunk.retain();
|
||||
return this.dataBufferFactory.wrap(chunk);
|
||||
});
|
||||
.map(this.dataBufferFactory::wrap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,46 +35,32 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* Tests for {@link HandlerMethod}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class HandlerMethodTests {
|
||||
|
||||
@Test
|
||||
void shouldValidateArgsWithConstraintsDirectlyInClass() {
|
||||
void shouldValidateArgsWithConstraintsDirectlyOnClass() {
|
||||
Object target = new MyClass();
|
||||
testValidateArgs(target, List.of("addIntValue", "addPersonAndIntValue", "addPersons", "addPeople", "addNames"), true);
|
||||
testValidateArgs(target, List.of("addPerson", "getPerson", "getIntValue", "addPersonNotValidated"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateArgsWithConstraintsInInterface() {
|
||||
void shouldValidateArgsWithConstraintsOnInterface() {
|
||||
Object target = new MyInterfaceImpl();
|
||||
testValidateArgs(target, List.of("addIntValue", "addPersonAndIntValue", "addPersons", "addPeople"), true);
|
||||
testValidateArgs(target, List.of("addPerson", "addPersonNotValidated", "getPerson", "getIntValue"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateArgsWithConstraintsInGenericAbstractSuperclass() {
|
||||
Object target = new GenericInterfaceImpl();
|
||||
shouldValidateArguments(getHandlerMethod(target, "processTwo", String.class), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateArgsWithConstraintsInGenericInterface() {
|
||||
Object target = new GenericInterfaceImpl();
|
||||
shouldValidateArguments(getHandlerMethod(target, "processOne", Long.class), false);
|
||||
shouldValidateArguments(getHandlerMethod(target, "processOneAndTwo", Long.class, Object.class), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateReturnValueWithConstraintsDirectlyInClass() {
|
||||
void shouldValidateReturnValueWithConstraintsDirectlyOnClass() {
|
||||
Object target = new MyClass();
|
||||
testValidateReturnValue(target, List.of("getPerson", "getIntValue"), true);
|
||||
testValidateReturnValue(target, List.of("addPerson", "addIntValue", "addPersonNotValidated"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateReturnValueWithConstraintsInInterface() {
|
||||
void shouldValidateReturnValueWithConstraintsOnInterface() {
|
||||
Object target = new MyInterfaceImpl();
|
||||
testValidateReturnValue(target, List.of("getPerson", "getIntValue"), true);
|
||||
testValidateReturnValue(target, List.of("addPerson", "addIntValue", "addPersonNotValidated"), false);
|
||||
@@ -111,19 +97,9 @@ class HandlerMethodTests {
|
||||
assertThat(hm3.getResolvedFromHandlerMethod()).isSameAs(hm1);
|
||||
}
|
||||
|
||||
|
||||
private static void shouldValidateArguments(HandlerMethod handlerMethod, boolean expected) {
|
||||
if (expected) {
|
||||
assertThat(handlerMethod.shouldValidateArguments()).as(handlerMethod.getMethod().getName()).isTrue();
|
||||
}
|
||||
else {
|
||||
assertThat(handlerMethod.shouldValidateArguments()).as(handlerMethod.getMethod().getName()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
private static void testValidateArgs(Object target, List<String> methodNames, boolean expected) {
|
||||
for (String methodName : methodNames) {
|
||||
shouldValidateArguments(getHandlerMethod(target, methodName), expected);
|
||||
assertThat(getHandlerMethod(target, methodName).shouldValidateArguments()).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +110,7 @@ class HandlerMethodTests {
|
||||
}
|
||||
|
||||
private static HandlerMethod getHandlerMethod(Object target, String methodName) {
|
||||
return getHandlerMethod(target, methodName, (Class<?>[]) null);
|
||||
}
|
||||
|
||||
private static HandlerMethod getHandlerMethod(Object target, String methodName, Class<?>... parameterTypes) {
|
||||
Method method = ClassUtils.getMethod(target.getClass(), methodName, parameterTypes);
|
||||
Method method = ClassUtils.getMethod(target.getClass(), methodName, (Class<?>[]) null);
|
||||
return new HandlerMethod(target, method).createWithValidateFlags();
|
||||
}
|
||||
|
||||
@@ -264,32 +236,4 @@ class HandlerMethodTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface GenericInterface<A, B> {
|
||||
|
||||
void processOne(@Valid A value1);
|
||||
|
||||
void processOneAndTwo(A value1, @Max(42) B value2);
|
||||
}
|
||||
|
||||
abstract static class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
|
||||
|
||||
@Override
|
||||
public void processOne(Long value1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processOneAndTwo(Long value1, C value2) {
|
||||
}
|
||||
|
||||
public abstract void processTwo(@Max(42) C value);
|
||||
}
|
||||
|
||||
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
|
||||
|
||||
@Override
|
||||
public void processTwo(String value) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -117,6 +117,7 @@ public class JettyWebSocketHandlerAdapter implements Session.Listener {
|
||||
|
||||
private final Callback callback;
|
||||
|
||||
|
||||
public JettyCallbackDataBuffer(DataBuffer delegate, Callback callback) {
|
||||
Assert.notNull(delegate, "'delegate` must not be null");
|
||||
Assert.notNull(callback, "Callback must not be null");
|
||||
|
||||
+1
-3
@@ -68,7 +68,6 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
@Nullable
|
||||
private final Sinks.Empty<Void> handlerCompletionSink;
|
||||
|
||||
|
||||
public JettyWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory) {
|
||||
this(session, info, factory, null);
|
||||
}
|
||||
@@ -108,7 +107,6 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void handleMessage(WebSocketMessage message) {
|
||||
Assert.state(this.sink != null, "No sink available");
|
||||
this.sink.next(message);
|
||||
@@ -191,6 +189,7 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
}
|
||||
|
||||
protected Mono<Void> sendMessage(WebSocketMessage message) {
|
||||
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
DataBuffer dataBuffer = message.getPayload();
|
||||
Session session = getDelegate();
|
||||
@@ -246,5 +245,4 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
}
|
||||
return Mono.fromFuture(completable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+52
-111
@@ -21,8 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -64,7 +62,6 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @author Brian Clozel
|
||||
* @author Taeik Lim
|
||||
* @since 4.2
|
||||
*/
|
||||
public class ResponseBodyEmitter {
|
||||
@@ -91,8 +88,6 @@ public class ResponseBodyEmitter {
|
||||
|
||||
private final DefaultCallback completionCallback = new DefaultCallback();
|
||||
|
||||
/** Guards access to write operations on the response. */
|
||||
protected final Lock writeLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Create a new ResponseBodyEmitter instance.
|
||||
@@ -122,48 +117,36 @@ public class ResponseBodyEmitter {
|
||||
}
|
||||
|
||||
|
||||
void initialize(Handler handler) throws IOException {
|
||||
this.writeLock.lock();
|
||||
synchronized void initialize(Handler handler) throws IOException {
|
||||
this.handler = handler;
|
||||
|
||||
try {
|
||||
this.handler = handler;
|
||||
|
||||
try {
|
||||
sendInternal(this.earlySendAttempts);
|
||||
}
|
||||
finally {
|
||||
this.earlySendAttempts.clear();
|
||||
}
|
||||
|
||||
if (this.complete) {
|
||||
if (this.failure != null) {
|
||||
this.handler.completeWithError(this.failure);
|
||||
}
|
||||
else {
|
||||
this.handler.complete();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.handler.onTimeout(this.timeoutCallback);
|
||||
this.handler.onError(this.errorCallback);
|
||||
this.handler.onCompletion(this.completionCallback);
|
||||
}
|
||||
sendInternal(this.earlySendAttempts);
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
this.earlySendAttempts.clear();
|
||||
}
|
||||
|
||||
if (this.complete) {
|
||||
if (this.failure != null) {
|
||||
this.handler.completeWithError(this.failure);
|
||||
}
|
||||
else {
|
||||
this.handler.complete();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.handler.onTimeout(this.timeoutCallback);
|
||||
this.handler.onError(this.errorCallback);
|
||||
this.handler.onCompletion(this.completionCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void initializeWithError(Throwable ex) {
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
this.complete = true;
|
||||
this.failure = ex;
|
||||
this.earlySendAttempts.clear();
|
||||
this.errorCallback.accept(ex);
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
}
|
||||
synchronized void initializeWithError(Throwable ex) {
|
||||
this.complete = true;
|
||||
this.failure = ex;
|
||||
this.earlySendAttempts.clear();
|
||||
this.errorCallback.accept(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,28 +183,22 @@ public class ResponseBodyEmitter {
|
||||
* @throws IOException raised when an I/O error occurs
|
||||
* @throws java.lang.IllegalStateException wraps any other errors
|
||||
*/
|
||||
public void send(Object object, @Nullable MediaType mediaType) throws IOException {
|
||||
public synchronized void send(Object object, @Nullable MediaType mediaType) throws IOException {
|
||||
Assert.state(!this.complete, () -> "ResponseBodyEmitter has already completed" +
|
||||
(this.failure != null ? " with error: " + this.failure : ""));
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
if (this.handler != null) {
|
||||
try {
|
||||
this.handler.send(object, mediaType);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException("Failed to send " + object, ex);
|
||||
}
|
||||
if (this.handler != null) {
|
||||
try {
|
||||
this.handler.send(object, mediaType);
|
||||
}
|
||||
else {
|
||||
this.earlySendAttempts.add(new DataWithMediaType(object, mediaType));
|
||||
catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException("Failed to send " + object, ex);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
else {
|
||||
this.earlySendAttempts.add(new DataWithMediaType(object, mediaType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,16 +211,10 @@ public class ResponseBodyEmitter {
|
||||
* @throws java.lang.IllegalStateException wraps any other errors
|
||||
* @since 6.0.12
|
||||
*/
|
||||
public void send(Set<DataWithMediaType> items) throws IOException {
|
||||
public synchronized void send(Set<DataWithMediaType> items) throws IOException {
|
||||
Assert.state(!this.complete, () -> "ResponseBodyEmitter has already completed" +
|
||||
(this.failure != null ? " with error: " + this.failure : ""));
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
sendInternal(items);
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
}
|
||||
sendInternal(items);
|
||||
}
|
||||
|
||||
private void sendInternal(Set<DataWithMediaType> items) throws IOException {
|
||||
@@ -274,16 +245,10 @@ public class ResponseBodyEmitter {
|
||||
* to complete request processing. It should not be used after container
|
||||
* related events such as an error while {@link #send(Object) sending}.
|
||||
*/
|
||||
public void complete() {
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
this.complete = true;
|
||||
if (this.handler != null) {
|
||||
this.handler.complete();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
public synchronized void complete() {
|
||||
this.complete = true;
|
||||
if (this.handler != null) {
|
||||
this.handler.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,17 +263,11 @@ public class ResponseBodyEmitter {
|
||||
* container related events such as an error while
|
||||
* {@link #send(Object) sending}.
|
||||
*/
|
||||
public void completeWithError(Throwable ex) {
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
this.complete = true;
|
||||
this.failure = ex;
|
||||
if (this.handler != null) {
|
||||
this.handler.completeWithError(ex);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
public synchronized void completeWithError(Throwable ex) {
|
||||
this.complete = true;
|
||||
this.failure = ex;
|
||||
if (this.handler != null) {
|
||||
this.handler.completeWithError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,14 +276,8 @@ public class ResponseBodyEmitter {
|
||||
* called from a container thread when an async request times out.
|
||||
* <p>As of 6.2, one can register multiple callbacks for this event.
|
||||
*/
|
||||
public void onTimeout(Runnable callback) {
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
this.timeoutCallback.addDelegate(callback);
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
}
|
||||
public synchronized void onTimeout(Runnable callback) {
|
||||
this.timeoutCallback.addDelegate(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,14 +287,8 @@ public class ResponseBodyEmitter {
|
||||
* <p>As of 6.2, one can register multiple callbacks for this event.
|
||||
* @since 5.0
|
||||
*/
|
||||
public void onError(Consumer<Throwable> callback) {
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
this.errorCallback.addDelegate(callback);
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
}
|
||||
public synchronized void onError(Consumer<Throwable> callback) {
|
||||
this.errorCallback.addDelegate(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,14 +298,8 @@ public class ResponseBodyEmitter {
|
||||
* detecting that a {@code ResponseBodyEmitter} instance is no longer usable.
|
||||
* <p>As of 6.2, one can register multiple callbacks for this event.
|
||||
*/
|
||||
public void onCompletion(Runnable callback) {
|
||||
this.writeLock.lock();
|
||||
try {
|
||||
this.completionCallback.addDelegate(callback);
|
||||
}
|
||||
finally {
|
||||
this.writeLock.unlock();
|
||||
}
|
||||
public synchronized void onCompletion(Runnable callback) {
|
||||
this.completionCallback.addDelegate(callback);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+6
@@ -21,6 +21,8 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -44,6 +46,10 @@ public class SseEmitter extends ResponseBodyEmitter {
|
||||
|
||||
private static final MediaType TEXT_PLAIN = new MediaType("text", "plain", StandardCharsets.UTF_8);
|
||||
|
||||
/** Guards access to write operations on the response. */
|
||||
private final Lock writeLock = new ReentrantLock();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SseEmitter instance.
|
||||
*/
|
||||
|
||||
-2
@@ -58,7 +58,6 @@ public class JettyWebSocketHandlerAdapter implements Session.Listener {
|
||||
this.wsSession = wsSession;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onWebSocketOpen(Session session) {
|
||||
try {
|
||||
@@ -148,5 +147,4 @@ public class JettyWebSocketHandlerAdapter implements Session.Listener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -173,6 +173,7 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
return getNativeSession().isOpen();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initializeNativeSession(Session session) {
|
||||
super.initializeNativeSession(session);
|
||||
@@ -212,6 +213,7 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void sendTextMessage(TextMessage message) throws IOException {
|
||||
useSession((session, callback) -> session.sendText(message.getPayload(), callback));
|
||||
@@ -245,6 +247,7 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
}
|
||||
catch (ExecutionException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
|
||||
if (cause instanceof IOException ioEx) {
|
||||
throw ioEx;
|
||||
}
|
||||
@@ -260,7 +263,6 @@ public class JettyWebSocketSession extends AbstractWebSocketSession<Session> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SessionConsumer {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user