mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16ecbfc9fe | |||
| 85c18caf25 | |||
| 22bd8bd704 | |||
| 9b10bb5e08 | |||
| 727ccd04ef | |||
| a9f447e8d7 | |||
| 0841e79e32 | |||
| a1868d3e9e | |||
| e5aac66157 | |||
| 8bf85d2596 | |||
| 508b31da4f | |||
| d291272736 | |||
| db01f07037 | |||
| b9e190e313 | |||
| d712ec3d49 | |||
| 5baa4fdd69 | |||
| 9273a11a2c | |||
| 7ea11baff9 | |||
| 655fd2e2bf | |||
| a31574a0a2 | |||
| 44f3e7b427 | |||
| e8e24e65d2 | |||
| 9a002a77b2 | |||
| 97e96895db | |||
| e98c2fe488 | |||
| 75705bcbb1 | |||
| 47a2e7059e | |||
| 14b6339351 | |||
| bc2e89a786 | |||
| 4abbddf601 | |||
| 926bcbd9a6 | |||
| cb8ed43be1 | |||
| 1703388074 | |||
| d84c4a39e2 | |||
| 6f03c186b9 | |||
| e10d37ad54 |
@@ -727,7 +727,7 @@ of determining parameter names, an exception will be thrown.
|
||||
parameter names. This discoverer is only used if such APIs are present on the classpath.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
flag for `javac`. Recommended approach.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{spring-framework-api}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
|
||||
@@ -531,6 +531,7 @@ following kinds of expressions cannot be compiled.
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using `Optional` with the null-safe or Elvis operator
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
* Expressions using bean references
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ xref:integration/cache/store-configuration.adoc#cache-store-configuration-jsr107
|
||||
[[cache-store-configuration-caffeine]]
|
||||
== Caffeine Cache
|
||||
|
||||
Caffeine is a Java 8 rewrite of Guava's cache, and its implementation is located in the
|
||||
Caffeine is a rewrite of Guava's cache, and its implementation is located in the
|
||||
`org.springframework.cache.caffeine` package and provides access to several features
|
||||
of Caffeine.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ behind the scenes for each annotated method, by using a `JmsListenerContainerFac
|
||||
Such a container is not registered against the application context but can be easily
|
||||
located for management purposes by using the `JmsListenerEndpointRegistry` bean.
|
||||
|
||||
TIP: `@JmsListener` is a repeatable annotation on Java 8, so you can associate
|
||||
TIP: `@JmsListener` is a repeatable annotation, so you can associate
|
||||
several JMS destinations with the same method by adding additional `@JmsListener`
|
||||
declarations to it.
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ synchronous, asynchronous, and streaming scenarios.
|
||||
* Non-blocking I/O
|
||||
* Reactive Streams back pressure
|
||||
* High concurrency with fewer hardware resources
|
||||
* Functional-style, fluent API that takes advantage of Java 8 lambdas
|
||||
* Functional-style, fluent API that takes advantage of lambda expressions
|
||||
* Synchronous and asynchronous interactions
|
||||
* Streaming up to or streaming down from a server
|
||||
|
||||
|
||||
@@ -533,8 +533,7 @@ that returns a value:
|
||||
----
|
||||
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return
|
||||
type but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of
|
||||
Spring 4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
type but also `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
the asynchronous task and for immediate composition with further processing steps.
|
||||
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as `@PostConstruct`.
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
The Spring Framework supports various Kotlin constructs, such as instantiating Kotlin classes
|
||||
through primary constructors, immutable classes data binding, and function optional parameters
|
||||
with default values.
|
||||
through primary constructors, data binding for immutable classes, and optional parameters
|
||||
with default values for functions.
|
||||
|
||||
Kotlin parameter names are recognized through a dedicated `KotlinReflectionParameterNameDiscoverer`,
|
||||
which allows finding interface method parameter names without requiring the Java 8 `-parameters`
|
||||
compiler flag to be enabled during compilation. (For completeness, we nevertheless recommend
|
||||
running the Kotlin compiler with its `-java-parameters` flag for standard Java parameter exposure.)
|
||||
which allows finding interface method parameter names without requiring the Java `-parameters`
|
||||
compiler flag to be enabled during compilation.
|
||||
|
||||
TIP: For completeness, we nevertheless recommend running the Kotlin compiler with its
|
||||
`-java-parameters` flag for standard Java parameter exposure.
|
||||
|
||||
You can declare configuration classes as
|
||||
{kotlin-docs}/nested-classes.html[top level or nested but not inner],
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@
|
||||
`@AfterTransaction` indicates that the annotated `void` method should be run after a
|
||||
transaction is ended, for test methods that have been configured to run within a
|
||||
transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` methods
|
||||
are not required to be `public` and may be declared on Java 8-based interface default
|
||||
methods.
|
||||
are not required to be `public` and may be declared on interface default methods.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@
|
||||
`@BeforeTransaction` indicates that the annotated `void` method should be run before a
|
||||
transaction is started, for test methods that have been configured to run within a
|
||||
transaction by using Spring's `@Transactional` annotation. `@BeforeTransaction` methods
|
||||
are not required to be `public` and may be declared on Java 8-based interface default
|
||||
methods.
|
||||
are not required to be `public` and may be declared on interface default methods.
|
||||
|
||||
The following example shows how to use the `@BeforeTransaction` annotation:
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. You can
|
||||
use `@SqlGroup` natively to declare several nested `@Sql` annotations, or you can use it
|
||||
in conjunction with Java 8's support for repeatable annotations, where `@Sql` can be
|
||||
in conjunction with Java's support for repeatable annotations, where `@Sql` can be
|
||||
declared several times on the same class or method, implicitly generating this container
|
||||
annotation. The following example shows how to declare an SQL group:
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ https://testng.org/[TestNG] ::
|
||||
testing, distributed testing, and other features. Supported in the
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework].
|
||||
{assertj-docs}[AssertJ] ::
|
||||
"Fluent assertions for Java", including support for Java 8 lambdas, streams, and
|
||||
"Fluent assertions for Java", including support for lambda expressions, streams, and
|
||||
numerous other features. Supported in Spring's
|
||||
xref:testing/mockmvc/assertj.adoc[MockMvc testing support].
|
||||
https://en.wikipedia.org/wiki/Mock_Object[Mock Objects] ::
|
||||
|
||||
@@ -13,9 +13,9 @@ the same xref:web/webflux/reactive-spring.adoc[Reactive Core] foundation.
|
||||
== Overview
|
||||
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-overview[See equivalent in the Servlet stack]#
|
||||
|
||||
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
|
||||
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes a
|
||||
`ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono<ServerResponse>`).
|
||||
Both the request and the response object have immutable contracts that offer JDK 8-friendly
|
||||
Both the request and the response object have immutable contracts that offer convenient
|
||||
access to the HTTP request and response.
|
||||
`HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
|
||||
annotation-based programming model.
|
||||
@@ -117,7 +117,7 @@ Most applications can run through the WebFlux Java configuration, see xref:web/w
|
||||
== HandlerFunction
|
||||
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-handler-functions[See equivalent in the Servlet stack]#
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer convenient
|
||||
access to the HTTP request and response.
|
||||
Both request and response provide {reactive-streams-site}[Reactive Streams] back pressure
|
||||
against the body streams.
|
||||
|
||||
@@ -394,7 +394,8 @@ Once API versioning is enabled, you can begin to map requests with versions.
|
||||
The `@RequestMapping` `version` attribute supports the following:
|
||||
|
||||
- Fixed version ("1.2") -- matches the given version only
|
||||
- Baseline version ("1.2+") -- matches the given version and above
|
||||
- Baseline version ("1.2+") -- matches the given and
|
||||
xref:web/webflux/config.adoc#webflux-config-api-version[supported versions] above
|
||||
- No value -- matches any version, but is superseded by a more specific version match
|
||||
|
||||
If multiple controller methods have a version less than or equal to the request version,
|
||||
@@ -443,6 +444,9 @@ For request with version `"1.3"`:
|
||||
- (3) matches as it matches 1.2 and above, and is *chosen* as the highest match
|
||||
- (4) is higher and does not match
|
||||
|
||||
NOTE: Version 1.3 must be present in the mappings, or be
|
||||
xref:web/webflux/config.adoc#webflux-config-api-version[configured as supported].
|
||||
|
||||
For request with version `"1.5"`:
|
||||
|
||||
- (1) matches as it matches any version
|
||||
@@ -459,10 +463,6 @@ versioned alternative was introduced. Therefore, even though an unversioned cont
|
||||
method is considered a match for any version, it is in fact given the lowest priority,
|
||||
and is effectively superseded by any alternative controller method with a version.
|
||||
|
||||
NOTE: The above assumes the request version is a
|
||||
xref:web/webflux/config.adoc#webflux-config-api-version["supported" version],
|
||||
or otherwise it would fail.
|
||||
|
||||
See xref:web/webflux-versioning.adoc[API Versioning] for more details on underlying
|
||||
infrastructure and support for API Versioning.
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ If a publisher cannot slow down, it has to decide whether to buffer, drop, or fa
|
||||
Reactive Streams plays an important role for interoperability. It is of interest to libraries
|
||||
and infrastructure components but less useful as an application API, because it is too
|
||||
low-level. Applications need a higher-level and richer, functional API to
|
||||
compose async logic -- similar to the Java 8 `Stream` API but not only for collections.
|
||||
compose async logic -- similar to the Java `Stream` API but not only for collections.
|
||||
This is the role that reactive libraries play.
|
||||
|
||||
{reactor-github-org}/reactor[Reactor] is the reactive library of choice for
|
||||
@@ -131,7 +131,7 @@ execution model benefits as others in this space and also provides a choice of s
|
||||
(annotated controllers and functional web endpoints), and a choice of reactive libraries
|
||||
(Reactor, RxJava, or other).
|
||||
|
||||
* If you are interested in a lightweight, functional web framework for use with Java 8 lambdas
|
||||
* If you are interested in a lightweight, functional web framework for use with Java
|
||||
or Kotlin, you can use the Spring WebFlux functional web endpoints. That can also be a good choice
|
||||
for smaller applications or microservices with less complex requirements that can benefit
|
||||
from greater transparency and control.
|
||||
|
||||
@@ -15,7 +15,7 @@ the same xref:web/webmvc/mvc-servlet.adoc[DispatcherServlet].
|
||||
|
||||
In WebMvc.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
|
||||
`ServerRequest` and returns a `ServerResponse`.
|
||||
Both the request and the response object have immutable contracts that offer JDK 8-friendly
|
||||
Both the request and the response object have immutable contracts that offer convenient
|
||||
access to the HTTP request and response.
|
||||
`HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
|
||||
annotation-based programming model.
|
||||
@@ -116,7 +116,7 @@ xref:web/webmvc-functional.adoc#webmvc-fn-running[Running a Server].
|
||||
== HandlerFunction
|
||||
[.small]#xref:web/webflux-functional.adoc#webflux-fn-handler-functions[See equivalent in the Reactive stack]#
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer convenient
|
||||
access to the HTTP request and response, including headers, body, method, and status code.
|
||||
|
||||
[[webmvc-fn-request]]
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
The next table describes the supported controller method arguments. Reactive types are not supported
|
||||
for any arguments.
|
||||
|
||||
JDK 8's `java.util.Optional` is supported as a method argument in combination with
|
||||
`java.util.Optional` is supported as a method argument in combination with
|
||||
annotations that have a `required` attribute (for example, `@RequestParam`, `@RequestHeader`,
|
||||
and others) and is equivalent to `required=false`.
|
||||
|
||||
|
||||
@@ -425,7 +425,7 @@ Once API versioning is enabled, you can begin to map requests with versions.
|
||||
The `@RequestMapping` `version` attribute supports the following:
|
||||
|
||||
- Fixed version ("1.2") -- matches the given version only
|
||||
- Baseline version ("1.2+") -- matches the given version and above
|
||||
- Baseline version ("1.2+") -- matches the given and xref:web/webmvc/mvc-config/api-version.adoc[supported versions] above
|
||||
- No value -- matches any version, but is superseded by a more specific version match
|
||||
|
||||
If multiple controller methods have a version less than or equal to the request version,
|
||||
@@ -463,7 +463,7 @@ Java::
|
||||
----
|
||||
<1> match any version
|
||||
<2> match version 1.1
|
||||
<3> match version 1.2 and above
|
||||
<3> match version 1.2 and supported versions above
|
||||
<4> match version 1.5
|
||||
======
|
||||
|
||||
@@ -474,6 +474,9 @@ For request with version `"1.3"`:
|
||||
- (3) matches as it matches 1.2 and above, and is *chosen* as the highest match
|
||||
- (4) is higher and does not match
|
||||
|
||||
NOTE: Version 1.3 must be present in the mappings, or be
|
||||
xref:web/webmvc/mvc-config/api-version.adoc[configured as supported].
|
||||
|
||||
For request with version `"1.5"`:
|
||||
|
||||
- (1) matches as it matches any version
|
||||
@@ -490,10 +493,6 @@ versioned alternative was introduced. Therefore, even though an unversioned cont
|
||||
method is considered a match for any version, it is in fact given the lowest priority,
|
||||
and is effectively superseded by any alternative controller method with a version.
|
||||
|
||||
NOTE: The above assumes the request version is a
|
||||
xref:web/webmvc/mvc-config/api-version.adoc["supported" version], or otherwise it
|
||||
would fail.
|
||||
|
||||
See xref:web/webmvc-versioning.adoc[API Versioning] for more details on underlying
|
||||
infrastructure and support for API Versioning.
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=7.0.4-SNAPSHOT
|
||||
version=7.0.5
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+1
-1
@@ -283,7 +283,7 @@ public final class CachedIntrospectionResults {
|
||||
}
|
||||
|
||||
// Explicitly check implemented interfaces for setter/getter methods as well,
|
||||
// in particular for Java 8 default methods...
|
||||
// in particular for interface default methods.
|
||||
Class<?> currClass = beanClass;
|
||||
while (currClass != null && currClass != Object.class) {
|
||||
introspectInterfaces(beanClass, currClass, readMethodNames);
|
||||
|
||||
+3
-3
@@ -62,9 +62,9 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* <h3>Multiple Arguments and 'required' Semantics</h3>
|
||||
* <p>In the case of a multi-arg constructor or method, the {@link #required} attribute
|
||||
* is applicable to all arguments. Individual parameters may be declared as Java-8 style
|
||||
* {@link java.util.Optional} as well as {@code @Nullable} or a not-null parameter
|
||||
* type in Kotlin, overriding the base 'required' semantics.
|
||||
* is applicable to all arguments. Individual parameters may be declared as
|
||||
* {@link java.util.Optional}, {@code @Nullable}, or a not-null parameter type in
|
||||
* Kotlin, overriding the base 'required' semantics.
|
||||
*
|
||||
* <h3>Autowiring Arrays, Collections, and Maps</h3>
|
||||
* <p>In case of an array, {@link java.util.Collection}, or {@link java.util.Map}
|
||||
|
||||
+4
-3
@@ -33,6 +33,7 @@ import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -621,12 +622,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
* <p>A 'required' dependency means that autowiring should fail when no beans
|
||||
* are found. Otherwise, the autowiring process will simply bypass the field
|
||||
* or method when no beans are found.
|
||||
* @param ann the Autowired annotation
|
||||
* @param ann a {@link MergedAnnotation} representing the Autowired annotation
|
||||
* @return whether the annotation indicates that a dependency is required
|
||||
*/
|
||||
protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
|
||||
return (ann.getValue(this.requiredParameterName).isEmpty() ||
|
||||
this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
|
||||
Optional<Boolean> requiredAttribute = ann.getValue(this.requiredParameterName, Boolean.class);
|
||||
return (requiredAttribute.isEmpty() || this.requiredParameterValue == requiredAttribute.get());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+14
-2
@@ -348,8 +348,20 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
if (!super.isRequired(descriptor)) {
|
||||
return false;
|
||||
}
|
||||
Autowired autowired = descriptor.getAnnotation(Autowired.class);
|
||||
return (autowired == null || autowired.required());
|
||||
|
||||
for (Annotation ann : descriptor.getAnnotations()) {
|
||||
// Directly present?
|
||||
if (ann instanceof Autowired autowired) {
|
||||
return autowired.required();
|
||||
}
|
||||
// Meta-present?
|
||||
Autowired autowired = AnnotationUtils.findAnnotation(ann.annotationType(), Autowired.class);
|
||||
if (autowired != null) {
|
||||
return autowired.required();
|
||||
}
|
||||
}
|
||||
// No @Autowired annotation present: default to true.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-4
@@ -149,10 +149,10 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
|
||||
|
||||
/**
|
||||
* Return whether this dependency is required.
|
||||
* <p>Optional semantics are derived from Java 8's {@link java.util.Optional},
|
||||
* any variant of a parameter-level {@code Nullable} annotation (such as from
|
||||
* JSR-305 or the FindBugs set of annotations), or a language-level nullable
|
||||
* type declaration in Kotlin.
|
||||
* <p>Optional semantics are derived from Java's {@link java.util.Optional},
|
||||
* any variant of a parameter-level {@code @Nullable} annotation (such as from
|
||||
* JSpecify, JSR-305, or the FindBugs set of annotations), or a language-level
|
||||
* nullable type declaration in Kotlin.
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
if (!this.required) {
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.beans.factory.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.SoftAssertions.assertSoftly;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QualifierAnnotationAutowireCandidateResolver}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.0.5
|
||||
*/
|
||||
class QualifierAnnotationAutowireCandidateResolverTests {
|
||||
|
||||
final QualifierAnnotationAutowireCandidateResolver resolver = new QualifierAnnotationAutowireCandidateResolver();
|
||||
|
||||
Method testMethod;
|
||||
|
||||
@RegisterExtension
|
||||
BeforeTestExecutionCallback extension = context -> this.testMethod = context.getRequiredTestMethod();
|
||||
|
||||
|
||||
@Test
|
||||
void isNotAutowired() {
|
||||
assertRequired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAutowiredRequired() {
|
||||
assertRequired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAutowiredOptional() {
|
||||
assertNotRequired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMetaAutowiredRequired() {
|
||||
assertRequired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMetaAutowiredOptional() {
|
||||
assertNotRequired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMetaMetaAutowiredRequired() {
|
||||
assertRequired();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMetaMetaAutowiredOptional() {
|
||||
assertNotRequired();
|
||||
}
|
||||
|
||||
|
||||
private void assertRequired() {
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(this.resolver.isRequired(getFieldDescriptor()))
|
||||
.as("%sField is required", this.testMethod.getName()).isTrue();
|
||||
softly.assertThat(this.resolver.isRequired(getParameterDescriptor()))
|
||||
.as("parameter in %sParameter() is required", this.testMethod.getName()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
private void assertNotRequired() {
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(this.resolver.isRequired(getFieldDescriptor()))
|
||||
.as("%sField is not required", this.testMethod.getName()).isFalse();
|
||||
softly.assertThat(this.resolver.isRequired(getParameterDescriptor()))
|
||||
.as("parameter in %sParameter() is not required", this.testMethod.getName()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
private DependencyDescriptor getFieldDescriptor() {
|
||||
var field = ReflectionUtils.findField(getClass(), this.testMethod.getName() + "Field");
|
||||
return new DependencyDescriptor(field, true);
|
||||
}
|
||||
|
||||
private DependencyDescriptor getParameterDescriptor() {
|
||||
var method = ReflectionUtils.findMethod(getClass(), this.testMethod.getName() + "Parameter", String.class);
|
||||
var methodParameter = MethodParameter.forExecutable(method, 0);
|
||||
return new DependencyDescriptor(methodParameter, true);
|
||||
}
|
||||
|
||||
|
||||
String isNotAutowiredField;
|
||||
|
||||
@Autowired
|
||||
String isAutowiredRequiredField;
|
||||
|
||||
@Autowired(required = false)
|
||||
String isAutowiredOptionalField;
|
||||
|
||||
@MetaAutowiredRequired
|
||||
String isMetaAutowiredRequiredField;
|
||||
|
||||
@MetaAutowiredOptional
|
||||
String isMetaAutowiredOptionalField;
|
||||
|
||||
@MetaMetaAutowiredRequired
|
||||
String isMetaMetaAutowiredRequiredField;
|
||||
|
||||
@MetaMetaAutowiredOptional
|
||||
String isMetaMetaAutowiredOptionalField;
|
||||
|
||||
|
||||
|
||||
void isNotAutowiredParameter(String enigma) {
|
||||
}
|
||||
|
||||
void isAutowiredRequiredParameter(@Autowired String enigma) {
|
||||
}
|
||||
|
||||
void isAutowiredOptionalParameter(@Autowired(required = false) String enigma) {
|
||||
}
|
||||
|
||||
void isMetaAutowiredRequiredParameter(@MetaAutowiredRequired String enigma) {
|
||||
}
|
||||
|
||||
void isMetaAutowiredOptionalParameter(@MetaAutowiredOptional String enigma) {
|
||||
}
|
||||
|
||||
void isMetaMetaAutowiredRequiredParameter(@MetaMetaAutowiredRequired String enigma) {
|
||||
}
|
||||
|
||||
void isMetaMetaAutowiredOptionalParameter(@MetaMetaAutowiredOptional String enigma) {
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Autowired
|
||||
@interface MetaAutowiredRequired {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Autowired(required = false)
|
||||
@interface MetaAutowiredOptional {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MetaAutowiredRequired
|
||||
@interface MetaMetaAutowiredRequired {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MetaAutowiredOptional
|
||||
@interface MetaMetaAutowiredOptional {
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -26,9 +26,9 @@ import java.lang.annotation.Target;
|
||||
* Container annotation that aggregates several {@link ComponentScan} annotations.
|
||||
*
|
||||
* <p>Can be used natively, declaring several nested {@link ComponentScan} annotations.
|
||||
* Can also be used in conjunction with Java 8's support for repeatable annotations,
|
||||
* where {@link ComponentScan} can simply be declared several times on the same method,
|
||||
* implicitly generating this container annotation.
|
||||
* Can also be used in conjunction with Java's support for repeatable annotations,
|
||||
* where {@link ComponentScan @ComponentScan} can simply be declared several times
|
||||
* on the same method, implicitly generating this container annotation.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3
|
||||
|
||||
+1
-1
@@ -443,7 +443,7 @@ class ConfigurationClassParser {
|
||||
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
|
||||
for (MethodMetadata methodMetadata : beanMethods) {
|
||||
if (!methodMetadata.isAbstract()) {
|
||||
// A default method or other concrete method on a Java 8+ interface...
|
||||
// A default method or other concrete method on a Java interface...
|
||||
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -26,9 +26,10 @@ import java.lang.annotation.Target;
|
||||
* Container annotation that aggregates several {@link PropertySource} annotations.
|
||||
*
|
||||
* <p>Can be used natively, declaring several nested {@link PropertySource} annotations.
|
||||
* Can also be used in conjunction with Java 8's support for <em>repeatable annotations</em>,
|
||||
* where {@link PropertySource} can simply be declared several times on the same
|
||||
* {@linkplain ElementType#TYPE type}, implicitly generating this container annotation.
|
||||
* Can also be used in conjunction with Java's support for <em>repeatable annotations</em>,
|
||||
* where {@link PropertySource @PropertySource} can simply be declared several
|
||||
* times on the same {@linkplain ElementType#TYPE type}, implicitly generating
|
||||
* this container annotation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 4.0
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Formats fields annotated with the {@link DateTimeFormat} annotation using the
|
||||
* JSR-310 <code>java.time</code> package in JDK 8.
|
||||
* JSR-310 <code>java.time</code> package.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Integration with the JSR-310 <code>java.time</code> package in JDK 8.
|
||||
* Integration with the JSR-310 <code>java.time</code> package.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.format.datetime.standard;
|
||||
|
||||
+3
-3
@@ -28,9 +28,9 @@ import org.springframework.aot.hint.annotation.Reflective;
|
||||
* Container annotation that aggregates several {@link Scheduled} annotations.
|
||||
*
|
||||
* <p>Can be used natively, declaring several nested {@link Scheduled} annotations.
|
||||
* Can also be used in conjunction with Java 8's support for repeatable annotations,
|
||||
* where {@link Scheduled} can simply be declared several times on the same method,
|
||||
* implicitly generating this container annotation.
|
||||
* Can also be used in conjunction with Java's support for repeatable annotations,
|
||||
* where {@link Scheduled @Scheduled} can simply be declared several times on the
|
||||
* same method, implicitly generating this container annotation.
|
||||
*
|
||||
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
|
||||
* <em>composed annotations</em>.
|
||||
|
||||
+8
-7
@@ -49,14 +49,15 @@ public class ForkJoinPoolFactoryBean implements FactoryBean<ForkJoinPool>, Initi
|
||||
|
||||
|
||||
/**
|
||||
* Set whether to expose JDK 8's 'common' {@link ForkJoinPool}.
|
||||
* <p>Default is "false", creating a local {@link ForkJoinPool} instance based on the
|
||||
* {@link #setParallelism "parallelism"}, {@link #setThreadFactory "threadFactory"},
|
||||
* {@link #setUncaughtExceptionHandler "uncaughtExceptionHandler"} and
|
||||
* {@link #setAsyncMode "asyncMode"} properties on this FactoryBean.
|
||||
* <p><b>NOTE:</b> Setting this flag to "true" effectively ignores all other
|
||||
* Set whether to expose Java's 'common' {@link ForkJoinPool}.
|
||||
* <p>Default is {@code false} , creating a local {@link ForkJoinPool} instance
|
||||
* based on the {@link #setParallelism parallelism},
|
||||
* {@link #setThreadFactory threadFactory},
|
||||
* {@link #setUncaughtExceptionHandler uncaughtExceptionHandler}, and
|
||||
* {@link #setAsyncMode asyncMode} properties on this FactoryBean.
|
||||
* <p><b>NOTE:</b> Setting this flag to {@code true} effectively ignores all other
|
||||
* properties on this FactoryBean, reusing the shared common JDK {@link ForkJoinPool}
|
||||
* instead. This is a fine choice on JDK 8 but does remove the application's ability
|
||||
* instead. This is a fine choice but does remove the application's ability
|
||||
* to customize ForkJoinPool behavior, in particular the use of custom threads.
|
||||
* @since 3.2
|
||||
* @see java.util.concurrent.ForkJoinPool#commonPool()
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link ParameterNameDiscoverer} strategy interface,
|
||||
* delegating to the Java 8 standard reflection mechanism.
|
||||
* delegating to Java's standard reflection mechanism.
|
||||
*
|
||||
* <p>If a Kotlin reflection implementation is present,
|
||||
* {@link KotlinReflectionParameterNameDiscoverer} is added first in the list and
|
||||
@@ -47,7 +47,7 @@ public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDisc
|
||||
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
|
||||
}
|
||||
|
||||
// Recommended approach on Java 8+: compilation with -parameters.
|
||||
// Recommended approach on Java: compilation with -parameters.
|
||||
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -26,11 +26,12 @@ import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
|
||||
* for introspecting parameter names.
|
||||
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection
|
||||
* facilities for introspecting parameter names.
|
||||
*
|
||||
* <p>Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
|
||||
* determine interface parameter names without requiring Java 8 -parameters compiler flag.
|
||||
* <p>In contrast to {@link StandardReflectionParameterNameDiscoverer}, this
|
||||
* discoverer can also determine interface parameter names without requiring Java's
|
||||
* {@code -parameters} compiler flag.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
|
||||
@@ -24,8 +24,8 @@ import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A common key class for a method against a specific target class,
|
||||
* including {@link #toString()} representation and {@link Comparable}
|
||||
* support (as suggested for custom {@code HashMap} keys as of Java 8).
|
||||
* including a {@link #toString()} representation and {@link Comparable}
|
||||
* support (as suggested for custom {@code HashMap} keys in Java).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3
|
||||
|
||||
@@ -45,13 +45,13 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method}
|
||||
* or {@link Constructor} plus a parameter index and a nested type index for a declared generic
|
||||
* type. Useful as a specification object to pass along.
|
||||
* Helper class that encapsulates the specification of a method parameter: a
|
||||
* {@link Method} or {@link Constructor} plus a parameter index and a nested type
|
||||
* index for a declared generic type. Useful as a specification object to pass along.
|
||||
*
|
||||
* <p>As of 4.2, there is a {@link org.springframework.core.annotation.SynthesizingMethodParameter}
|
||||
* subclass available which synthesizes annotations with attribute aliases. That subclass is used
|
||||
* for web and message endpoint processing, in particular.
|
||||
* <p>There is also a {@link org.springframework.core.annotation.SynthesizingMethodParameter}
|
||||
* subclass available which synthesizes annotations with attribute aliases. That
|
||||
* subclass is used for web and message endpoint processing, in particular.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
@@ -128,7 +128,8 @@ public class MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MethodParameter for the given constructor, with nesting level 1.
|
||||
* Create a new {@code MethodParameter} for the given constructor, with nesting
|
||||
* level 1.
|
||||
* @param constructor the Constructor to specify a parameter for
|
||||
* @param parameterIndex the index of the parameter
|
||||
*/
|
||||
@@ -137,7 +138,7 @@ public class MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MethodParameter for the given constructor.
|
||||
* Create a new {@code MethodParameter} for the given constructor.
|
||||
* @param constructor the Constructor to specify a parameter for
|
||||
* @param parameterIndex the index of the parameter
|
||||
* @param nestingLevel the nesting level of the target type
|
||||
@@ -152,7 +153,7 @@ public class MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal constructor used to create a {@link MethodParameter} with a
|
||||
* Internal constructor used to create a {@code MethodParameter} with a
|
||||
* containing class already set.
|
||||
* @param executable the Executable to specify a parameter for
|
||||
* @param parameterIndex the index of the parameter
|
||||
@@ -168,9 +169,9 @@ public class MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor, resulting in an independent MethodParameter object
|
||||
* Copy constructor, resulting in an independent {@code MethodParameter} object
|
||||
* based on the same metadata and cache state that the original object was in.
|
||||
* @param original the original MethodParameter object to copy from
|
||||
* @param original the original {@code MethodParameter} object to copy from
|
||||
*/
|
||||
public MethodParameter(MethodParameter original) {
|
||||
Assert.notNull(original, "Original must not be null");
|
||||
@@ -182,6 +183,7 @@ public class MethodParameter {
|
||||
this.containingClass = original.containingClass;
|
||||
this.parameterType = original.parameterType;
|
||||
this.genericParameterType = original.genericParameterType;
|
||||
this.methodAnnotations = original.methodAnnotations;
|
||||
this.parameterAnnotations = original.parameterAnnotations;
|
||||
this.parameterNameDiscoverer = original.parameterNameDiscoverer;
|
||||
this.parameterName = original.parameterName;
|
||||
@@ -279,7 +281,7 @@ public class MethodParameter {
|
||||
/**
|
||||
* Decrease this parameter's nesting level.
|
||||
* @see #getNestingLevel()
|
||||
* @deprecated in favor of retaining the original MethodParameter and
|
||||
* @deprecated in favor of retaining the original {@code MethodParameter} and
|
||||
* using {@link #nested(Integer)} if nesting is required
|
||||
*/
|
||||
@Deprecated(since = "5.2")
|
||||
@@ -392,10 +394,10 @@ public class MethodParameter {
|
||||
|
||||
/**
|
||||
* Return whether this method indicates a parameter which is not required:
|
||||
* either in the form of Java 8's {@link java.util.Optional}, JSpecify annotations,
|
||||
* any variant of a parameter-level {@code @Nullable} annotation (such as from Spring,
|
||||
* JSR-305 or Jakarta set of annotations), a language-level nullable type
|
||||
* declaration or {@code Continuation} parameter in Kotlin.
|
||||
* either in the form of {@link java.util.Optional}, JSpecify annotations,
|
||||
* any variant of a parameter-level {@code @Nullable} annotation (such as
|
||||
* from Spring, JSR-305, or Jakarta annotations), or a language-level
|
||||
* nullable type declaration or {@code Continuation} parameter in Kotlin.
|
||||
* @since 4.3
|
||||
* @see Nullness#forMethodParameter(MethodParameter)
|
||||
*/
|
||||
@@ -773,9 +775,9 @@ public class MethodParameter {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new MethodParameter for the given method or constructor.
|
||||
* <p>This is a convenience factory method for scenarios where a
|
||||
* Method or Constructor reference is treated in a generic fashion.
|
||||
* Create a new {@code MethodParameter} for the given method or constructor.
|
||||
* <p>This is a convenience factory method for scenarios where a {@link Method}
|
||||
* or {@link Constructor} reference is treated in a generic fashion.
|
||||
* @param methodOrConstructor the Method or Constructor to specify a parameter for
|
||||
* @param parameterIndex the index of the parameter
|
||||
* @return the corresponding MethodParameter instance
|
||||
@@ -791,9 +793,9 @@ public class MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MethodParameter for the given method or constructor.
|
||||
* <p>This is a convenience factory method for scenarios where a
|
||||
* Method or Constructor reference is treated in a generic fashion.
|
||||
* Create a new {@code MethodParameter} for the given method or constructor.
|
||||
* <p>This is a convenience factory method for scenarios where a {@link Method}
|
||||
* or {@link Constructor} reference is treated in a generic fashion.
|
||||
* @param executable the Method or Constructor to specify a parameter for
|
||||
* @param parameterIndex the index of the parameter
|
||||
* @return the corresponding MethodParameter instance
|
||||
@@ -812,11 +814,11 @@ public class MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MethodParameter for the given parameter descriptor.
|
||||
* <p>This is a convenience factory method for scenarios where a
|
||||
* Java 8 {@link Parameter} descriptor is already available.
|
||||
* Create a new {@code MethodParameter} for the given parameter descriptor.
|
||||
* <p>This is a convenience factory method for scenarios where a {@link Parameter}
|
||||
* descriptor is already available.
|
||||
* @param parameter the parameter descriptor
|
||||
* @return the corresponding MethodParameter instance
|
||||
* @return the corresponding {@code MethodParameter} instance
|
||||
* @since 5.0
|
||||
*/
|
||||
public static MethodParameter forParameter(Parameter parameter) {
|
||||
@@ -851,7 +853,7 @@ public class MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MethodParameter for the given field-aware constructor,
|
||||
* Create a new {@code MethodParameter} for the given field-aware constructor,
|
||||
* for example, on a data class or record type.
|
||||
* <p>A field-aware method parameter will detect field annotations as well,
|
||||
* as long as the field name matches the parameter name.
|
||||
@@ -859,7 +861,7 @@ public class MethodParameter {
|
||||
* @param parameterIndex the index of the parameter
|
||||
* @param fieldName the name of the underlying field,
|
||||
* matching the constructor's parameter name
|
||||
* @return the corresponding MethodParameter instance
|
||||
* @return the corresponding {@code MethodParameter} instance
|
||||
* @since 6.1
|
||||
*/
|
||||
public static MethodParameter forFieldAwareConstructor(Constructor<?> ctor, int parameterIndex, @Nullable String fieldName) {
|
||||
|
||||
@@ -123,13 +123,13 @@ public class ResolvableType implements Serializable {
|
||||
|
||||
private @Nullable Class<?> resolved;
|
||||
|
||||
private volatile @Nullable ResolvableType superType;
|
||||
private transient volatile @Nullable ResolvableType superType;
|
||||
|
||||
private volatile ResolvableType @Nullable [] interfaces;
|
||||
private transient volatile ResolvableType @Nullable [] interfaces;
|
||||
|
||||
private volatile ResolvableType @Nullable [] generics;
|
||||
private transient volatile ResolvableType @Nullable [] generics;
|
||||
|
||||
private volatile @Nullable Boolean unresolvableGenerics;
|
||||
private transient volatile @Nullable Boolean unresolvableGenerics;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import java.lang.reflect.Parameter;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ParameterNameDiscoverer} implementation which uses JDK 8's reflection facilities
|
||||
* {@link ParameterNameDiscoverer} implementation which uses Java's reflection facilities
|
||||
* for introspecting parameter names (based on the "-parameters" compiler flag).
|
||||
*
|
||||
* <p>This is a key element of {@link DefaultParameterNameDiscoverer} where it is being
|
||||
|
||||
@@ -58,7 +58,7 @@ public class AnnotatedMethod {
|
||||
|
||||
private final MethodParameter[] parameters;
|
||||
|
||||
private final Map<Class<? extends Annotation>, Object> annotations = new ConcurrentHashMap<>(4);
|
||||
private final Map<Class<? extends Annotation>, Object> annotations;
|
||||
|
||||
private volatile @Nullable List<Annotation[][]> inheritedParameterAnnotations;
|
||||
|
||||
@@ -73,6 +73,7 @@ public class AnnotatedMethod {
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
ReflectionUtils.makeAccessible(this.bridgedMethod);
|
||||
this.parameters = initMethodParameters();
|
||||
this.annotations = new ConcurrentHashMap<>(4);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +84,7 @@ public class AnnotatedMethod {
|
||||
this.method = annotatedMethod.method;
|
||||
this.bridgedMethod = annotatedMethod.bridgedMethod;
|
||||
this.parameters = annotatedMethod.parameters;
|
||||
this.annotations = annotatedMethod.annotations;
|
||||
this.inheritedParameterAnnotations = annotatedMethod.inheritedParameterAnnotations;
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -87,7 +87,7 @@ public class SynthesizingMethodParameter extends MethodParameter {
|
||||
/**
|
||||
* Copy constructor, resulting in an independent {@code SynthesizingMethodParameter}
|
||||
* based on the same metadata and cache state that the original object was in.
|
||||
* @param original the original SynthesizingMethodParameter object to copy from
|
||||
* @param original the original {@code SynthesizingMethodParameter} object to copy from
|
||||
*/
|
||||
protected SynthesizingMethodParameter(SynthesizingMethodParameter original) {
|
||||
super(original);
|
||||
@@ -111,12 +111,14 @@ public class SynthesizingMethodParameter extends MethodParameter {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SynthesizingMethodParameter for the given method or constructor.
|
||||
* <p>This is a convenience factory method for scenarios where a
|
||||
* Method or Constructor reference is treated in a generic fashion.
|
||||
* @param executable the Method or Constructor to specify a parameter for
|
||||
* Create a new {@code SynthesizingMethodParameter} for the given method or
|
||||
* constructor.
|
||||
* <p>This is a convenience factory method for scenarios where a {@link Method}
|
||||
* or {@link Constructor} reference is treated in a generic fashion.
|
||||
* @param executable the {@code Method} or {@code Constructor} to specify a
|
||||
* parameter for
|
||||
* @param parameterIndex the index of the parameter
|
||||
* @return the corresponding SynthesizingMethodParameter instance
|
||||
* @return the corresponding {@code SynthesizingMethodParameter} instance
|
||||
* @since 5.0
|
||||
*/
|
||||
public static SynthesizingMethodParameter forExecutable(Executable executable, int parameterIndex) {
|
||||
@@ -132,11 +134,12 @@ public class SynthesizingMethodParameter extends MethodParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SynthesizingMethodParameter for the given parameter descriptor.
|
||||
* <p>This is a convenience factory method for scenarios where a
|
||||
* Java 8 {@link Parameter} descriptor is already available.
|
||||
* Create a new {@code SynthesizingMethodParameter} for the given parameter
|
||||
* descriptor.
|
||||
* <p>This is a convenience factory method for scenarios where a Java
|
||||
* {@link Parameter} descriptor is already available.
|
||||
* @param parameter the parameter descriptor
|
||||
* @return the corresponding SynthesizingMethodParameter instance
|
||||
* @return the corresponding {@code SynthesizingMethodParameter} instance
|
||||
* @since 5.0
|
||||
*/
|
||||
public static SynthesizingMethodParameter forParameter(Parameter parameter) {
|
||||
|
||||
+5
-4
@@ -194,10 +194,11 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
|
||||
/**
|
||||
* Template method to convert a {@code null} source.
|
||||
* <p>The default implementation returns {@code null} or the Java 8
|
||||
* {@link java.util.Optional#empty()} instance if the target type is
|
||||
* {@code java.util.Optional}. Subclasses may override this to return
|
||||
* custom {@code null} objects for specific target types.
|
||||
* <p>The default implementation returns {@code null} or the
|
||||
* {@link Optional#empty()} instance if the target type is
|
||||
* {@code java.util.Optional}.
|
||||
* <p>Subclasses may override this to return custom {@code null} objects for
|
||||
* specific target types.
|
||||
* @param sourceType the source type to convert from
|
||||
* @param targetType the target type to convert to
|
||||
* @return the converted null object
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import java.util.TimeZone;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* Simple converter from Java 8's {@link java.time.ZoneId} to {@link java.util.TimeZone}.
|
||||
* Simple converter from Java's {@link java.time.ZoneId} to {@link java.util.TimeZone}.
|
||||
*
|
||||
* <p>Note that Spring's default ConversionService setup understands the 'from'/'to' convention
|
||||
* that the JSR-310 {@code java.time} package consistently uses. That convention is implemented
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import java.util.GregorianCalendar;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* Simple converter from Java 8's {@link java.time.ZonedDateTime} to {@link java.util.Calendar}.
|
||||
* Simple converter from Java's {@link java.time.ZonedDateTime} to {@link java.util.Calendar}.
|
||||
*
|
||||
* <p>Note that Spring's default ConversionService setup understands the 'from'/'to' convention
|
||||
* that the JSR-310 {@code java.time} package consistently uses. That convention is implemented
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
/**
|
||||
* A convenient accessor for Commons Logging, providing not only
|
||||
* {@code CharSequence} based log methods but also {@code Supplier}
|
||||
* based variants for use with Java 8 lambda expressions.
|
||||
* based variants for use with Java lambda expressions.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.2
|
||||
|
||||
@@ -140,31 +140,6 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
*/
|
||||
public MimeType(String type, String subtype, Charset charset) {
|
||||
this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charset.name()));
|
||||
this.resolvedCharset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy-constructor that copies the type, subtype, parameters of the given {@code MimeType},
|
||||
* and allows to set the specified character set.
|
||||
* @param other the other MimeType
|
||||
* @param charset the character set
|
||||
* @throws IllegalArgumentException if any of the parameters contains illegal characters
|
||||
* @since 4.3
|
||||
*/
|
||||
public MimeType(MimeType other, Charset charset) {
|
||||
this(other.getType(), other.getSubtype(), addCharsetParameter(charset, other.getParameters()));
|
||||
this.resolvedCharset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy-constructor that copies the type and subtype of the given {@code MimeType},
|
||||
* and allows for different parameter.
|
||||
* @param other the other MimeType
|
||||
* @param parameters the parameters (may be {@code null})
|
||||
* @throws IllegalArgumentException if any of the parameters contains illegal characters
|
||||
*/
|
||||
public MimeType(MimeType other, @Nullable Map<String, String> parameters) {
|
||||
this(other.getType(), other.getSubtype(), parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,16 +156,43 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
checkToken(subtype);
|
||||
this.type = type.toLowerCase(Locale.ROOT);
|
||||
this.subtype = subtype.toLowerCase(Locale.ROOT);
|
||||
if (!CollectionUtils.isEmpty(parameters)) {
|
||||
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ROOT);
|
||||
parameters.forEach((parameter, value) -> {
|
||||
checkParameters(parameter, value);
|
||||
map.put(parameter, value);
|
||||
});
|
||||
this.parameters = Collections.unmodifiableMap(map);
|
||||
this.parameters = createParametersMap(parameters);
|
||||
if (this.parameters.containsKey(PARAM_CHARSET)) {
|
||||
this.resolvedCharset = Charset.forName(unquote(this.parameters.get(PARAM_CHARSET)));
|
||||
}
|
||||
else {
|
||||
this.parameters = Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy-constructor that copies the type, subtype, parameters of the given {@code MimeType},
|
||||
* and allows to set the specified character set.
|
||||
* @param other the other MimeType
|
||||
* @param charset the character set
|
||||
* @throws IllegalArgumentException if any of the parameters contains illegal characters
|
||||
* @since 4.3
|
||||
*/
|
||||
public MimeType(MimeType other, Charset charset) {
|
||||
this.type = other.type;
|
||||
this.subtype = other.subtype;
|
||||
Map<String, String> map = new LinkedCaseInsensitiveMap<>(other.parameters.size() + 1, Locale.ROOT);
|
||||
map.putAll(other.parameters);
|
||||
map.put(PARAM_CHARSET, charset.name());
|
||||
this.parameters = Collections.unmodifiableMap(map);
|
||||
this.resolvedCharset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy-constructor that copies the type and subtype of the given {@code MimeType},
|
||||
* and allows for different parameter.
|
||||
* @param other the other MimeType
|
||||
* @param parameters the parameters (may be {@code null})
|
||||
* @throws IllegalArgumentException if any of the parameters contains illegal characters
|
||||
*/
|
||||
public MimeType(MimeType other, @Nullable Map<String, String> parameters) {
|
||||
this.type = other.type;
|
||||
this.subtype = other.subtype;
|
||||
this.parameters = createParametersMap(parameters);
|
||||
if (this.parameters.containsKey(PARAM_CHARSET)) {
|
||||
this.resolvedCharset = Charset.forName(unquote(this.parameters.get(PARAM_CHARSET)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,16 +225,25 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createParametersMap(@Nullable Map<String, String> parameters) {
|
||||
if (!CollectionUtils.isEmpty(parameters)) {
|
||||
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ROOT);
|
||||
parameters.forEach((parameter, value) -> {
|
||||
checkParameters(parameter, value);
|
||||
map.put(parameter, value);
|
||||
});
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
else {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkParameters(String parameter, String value) {
|
||||
Assert.hasLength(parameter, "'parameter' must not be empty");
|
||||
Assert.hasLength(value, "'value' must not be empty");
|
||||
checkToken(parameter);
|
||||
if (PARAM_CHARSET.equals(parameter)) {
|
||||
if (this.resolvedCharset == null) {
|
||||
this.resolvedCharset = Charset.forName(unquote(value));
|
||||
}
|
||||
}
|
||||
else if (!isQuotedString(value)) {
|
||||
if (!isQuotedString(value)) {
|
||||
checkToken(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public abstract class NumberUtils {
|
||||
else if (number instanceof BigDecimal bigDecimal) {
|
||||
bigInt = bigDecimal.toBigInteger();
|
||||
}
|
||||
// Effectively analogous to JDK 8's BigInteger.longValueExact()
|
||||
// Effectively analogous to Java's BigInteger.longValueExact()
|
||||
if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) {
|
||||
raiseOverflowException(number, targetClass);
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ public abstract class ReflectionUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods
|
||||
* from Java 8 based interfaces, allowing for fast iteration.
|
||||
* Cache for {@link Class#getDeclaredMethods()} plus equivalent interface
|
||||
* default methods, allowing for fast iteration.
|
||||
*/
|
||||
private static final Map<Class<?>, Method[]> declaredMethodsCache = new ConcurrentReferenceHashMap<>(256);
|
||||
|
||||
@@ -309,7 +309,7 @@ public abstract class ReflectionUtils {
|
||||
/**
|
||||
* Perform the given callback operation on all matching methods of the given
|
||||
* class, as locally declared or equivalent thereof (such as default methods
|
||||
* on Java 8 based interfaces that the given class implements).
|
||||
* from interfaces that the given class implements).
|
||||
* @param clazz the class to introspect
|
||||
* @param mc the callback to invoke for each method
|
||||
* @throws IllegalStateException if introspection fails
|
||||
@@ -444,7 +444,7 @@ public abstract class ReflectionUtils {
|
||||
|
||||
/**
|
||||
* Variant of {@link Class#getDeclaredMethods()} that uses a local cache in
|
||||
* order to avoid new Method instances. In addition, it also includes Java 8
|
||||
* order to avoid new {@link Method} instances. In addition, it also includes
|
||||
* default methods from locally implemented interfaces, since those are
|
||||
* effectively to be treated just like declared methods.
|
||||
* @param clazz the class to introspect
|
||||
|
||||
@@ -231,8 +231,7 @@ class BridgeMethodResolverTests {
|
||||
}
|
||||
}
|
||||
assertThat(bridgeMethod != null && bridgeMethod.isBridge()).isTrue();
|
||||
boolean condition = bridgedMethod != null && !bridgedMethod.isBridge();
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(bridgedMethod != null && !bridgedMethod.isBridge()).isTrue();
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(bridgeMethod)).isEqualTo(bridgedMethod);
|
||||
}
|
||||
|
||||
|
||||
@@ -1335,6 +1335,22 @@ class ResolvableTypeTests {
|
||||
assertThat(deserializedNone).isSameAs(ResolvableType.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeWithCachedState() throws Exception {
|
||||
ResolvableType type = ResolvableType.forClass(List.class);
|
||||
testSerialization(type);
|
||||
type.getSuperType();
|
||||
type.getInterfaces();
|
||||
type.getGenerics();
|
||||
type.hasUnresolvableGenerics();
|
||||
testSerialization(type);
|
||||
type.getSuperType();
|
||||
type.getInterfaces();
|
||||
type.getGenerics();
|
||||
type.hasUnresolvableGenerics();
|
||||
testSerialization(type);
|
||||
}
|
||||
|
||||
@Test
|
||||
void canResolveVoid() {
|
||||
ResolvableType type = ResolvableType.forClass(void.class);
|
||||
|
||||
@@ -133,7 +133,7 @@ class SortedPropertiesTests {
|
||||
String[] lines = lines(baos);
|
||||
|
||||
assertThat(lines).isNotEmpty();
|
||||
// Leniently match first line due to differences between JDK 8 and JDK 9+.
|
||||
// Leniently match first line due to potential differences between JDK versions.
|
||||
String regex = "<\\?xml .*\\?>";
|
||||
assertThat(lines[0]).matches(regex);
|
||||
assertThat(lines).filteredOn(line -> !line.matches(regex)).containsExactly( //
|
||||
|
||||
+11
-10
@@ -160,10 +160,11 @@ class AnnotationUtilsTests {
|
||||
assertThat(getAnnotation(bridgeMethod, Order.class)).isNull();
|
||||
assertThat(findAnnotation(bridgeMethod, Order.class)).isNotNull();
|
||||
|
||||
// As of JDK 8, invoking getAnnotation() on a bridge method actually finds an
|
||||
// annotation on its 'bridged' method [1]; however, the Eclipse compiler does
|
||||
// not support this [2]. Thus, we effectively ignore the following
|
||||
// assertion if the test is currently executing within the Eclipse IDE.
|
||||
// For code compiled with OpenJDK, invoking getAnnotation() on a bridge
|
||||
// method actually finds an annotation on its 'bridged' method [1]; however,
|
||||
// the Eclipse compiler does not support this [2]. Thus, we effectively
|
||||
// ignore the following assertion if the test is currently executing within
|
||||
// the Eclipse IDE.
|
||||
//
|
||||
// [1] https://bugs.openjdk.java.net/browse/JDK-6695379
|
||||
// [2] https://bugs.eclipse.org/bugs/show_bug.cgi?id=495396
|
||||
@@ -576,7 +577,7 @@ class AnnotationUtilsTests {
|
||||
final List<String> expectedValuesJava = asList("A", "B", "C");
|
||||
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
|
||||
|
||||
// Java 8
|
||||
// Java
|
||||
MyRepeatable[] array = MyRepeatableClass.class.getAnnotationsByType(MyRepeatable.class);
|
||||
assertThat(array).isNotNull();
|
||||
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
|
||||
@@ -601,7 +602,7 @@ class AnnotationUtilsTests {
|
||||
final List<String> expectedValuesJava = asList("A", "B", "C");
|
||||
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
|
||||
|
||||
// Java 8
|
||||
// Java
|
||||
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
||||
assertThat(array).isNotNull();
|
||||
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
|
||||
@@ -626,7 +627,7 @@ class AnnotationUtilsTests {
|
||||
final List<String> expectedValuesJava = asList("X", "Y", "Z");
|
||||
final List<String> expectedValuesSpring = asList("X", "Y", "Z", "meta2");
|
||||
|
||||
// Java 8
|
||||
// Java
|
||||
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
||||
assertThat(array).isNotNull();
|
||||
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
|
||||
@@ -651,7 +652,7 @@ class AnnotationUtilsTests {
|
||||
final List<String> expectedValuesJava = asList("X", "Y", "Z");
|
||||
final List<String> expectedValuesSpring = asList("X", "Y", "Z", "meta2");
|
||||
|
||||
// Java 8
|
||||
// Java
|
||||
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
||||
assertThat(array).isNotNull();
|
||||
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
|
||||
@@ -675,7 +676,7 @@ class AnnotationUtilsTests {
|
||||
final List<String> expectedValuesJava = asList("A", "B", "C");
|
||||
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
|
||||
|
||||
// Java 8
|
||||
// Java
|
||||
MyRepeatable[] array = MyRepeatableClass.class.getDeclaredAnnotationsByType(MyRepeatable.class);
|
||||
assertThat(array).isNotNull();
|
||||
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
|
||||
@@ -699,7 +700,7 @@ class AnnotationUtilsTests {
|
||||
void getDeclaredRepeatableAnnotationsDeclaredOnSuperclass() {
|
||||
final Class<?> clazz = SubMyRepeatableClass.class;
|
||||
|
||||
// Java 8
|
||||
// Java
|
||||
MyRepeatable[] array = clazz.getDeclaredAnnotationsByType(MyRepeatable.class);
|
||||
assertThat(array).isNotNull();
|
||||
assertThat(array).isEmpty();
|
||||
|
||||
+5
-4
@@ -893,10 +893,11 @@ class MergedAnnotationsTests {
|
||||
assertThat(MergedAnnotations.from(method).get(Order.class).getDistance()).isEqualTo(-1);
|
||||
assertThat(MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY).get(
|
||||
Order.class).getDistance()).isEqualTo(0);
|
||||
// As of JDK 8, invoking getAnnotation() on a bridge method actually finds an
|
||||
// annotation on its 'bridged' method [1]; however, the Eclipse compiler does
|
||||
// not support this [2]. Thus, we effectively ignore the following
|
||||
// assertion if the test is currently executing within the Eclipse IDE.
|
||||
// For code compiled with OpenJDK, invoking getAnnotation() on a bridge
|
||||
// method actually finds an annotation on its 'bridged' method [1]; however,
|
||||
// the Eclipse compiler does not support this [2]. Thus, we effectively
|
||||
// ignore the following assertion if the test is currently executing within
|
||||
// the Eclipse IDE.
|
||||
//
|
||||
// [1] https://bugs.openjdk.java.net/browse/JDK-6695379
|
||||
// [2] https://bugs.eclipse.org/bugs/show_bug.cgi?id=495396
|
||||
|
||||
+2
-3
@@ -105,7 +105,7 @@ class MultipleComposedAnnotationsOnSingleAnnotatedElementTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Disabled since some Java 8 updates handle the bridge method differently")
|
||||
@Disabled("Disabled since some Java versions/compilers handle the bridge method differently")
|
||||
void getMultipleComposedAnnotationsOnBridgeMethod() {
|
||||
Set<Cacheable> cacheables = getAllMergedAnnotations(getBridgeMethod(), Cacheable.class);
|
||||
assertThat(cacheables).isNotNull();
|
||||
@@ -202,8 +202,7 @@ class MultipleComposedAnnotationsOnSingleAnnotatedElementTests {
|
||||
}
|
||||
}
|
||||
assertThat(bridgeMethod != null && bridgeMethod.isBridge()).isTrue();
|
||||
boolean condition = bridgedMethod != null && !bridgedMethod.isBridge();
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(bridgedMethod != null && !bridgedMethod.isBridge()).isTrue();
|
||||
|
||||
return bridgeMethod;
|
||||
}
|
||||
|
||||
@@ -957,20 +957,22 @@ public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations
|
||||
try {
|
||||
Message requestMessage = messageCreator.createMessage(session);
|
||||
producer = session.createProducer(destination);
|
||||
if (!useCorrelationId) {
|
||||
consumer = session.createConsumer(responseQueue);
|
||||
}
|
||||
requestMessage.setJMSReplyTo(responseQueue);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sending created message: " + requestMessage);
|
||||
}
|
||||
doSend(producer, requestMessage);
|
||||
String messageSelector = null;
|
||||
if (useCorrelationId) {
|
||||
if (consumer == null) { // useCorrelationId=true
|
||||
String correlationId = requestMessage.getJMSCorrelationID();
|
||||
if (correlationId == null) {
|
||||
correlationId = requestMessage.getJMSMessageID();
|
||||
}
|
||||
messageSelector = "JMSCorrelationID='" + correlationId + "'";
|
||||
String messageSelector = "JMSCorrelationID='" + correlationId + "'";
|
||||
consumer = session.createConsumer(responseQueue, messageSelector);
|
||||
}
|
||||
consumer = session.createConsumer(responseQueue, messageSelector);
|
||||
return receiveFromConsumer(consumer, getReceiveTimeout());
|
||||
}
|
||||
finally {
|
||||
|
||||
@@ -648,7 +648,7 @@ class JmsTemplateTests {
|
||||
given(localSession.createTemporaryQueue()).willReturn(replyDestination);
|
||||
|
||||
MessageConsumer messageConsumer = mock();
|
||||
given(localSession.createConsumer(replyDestination, null)).willReturn(messageConsumer);
|
||||
given(localSession.createConsumer(replyDestination)).willReturn(messageConsumer);
|
||||
|
||||
TextMessage request = mock();
|
||||
MessageCreator messageCreator = mock();
|
||||
|
||||
+1
-1
@@ -208,7 +208,7 @@ public class ReactorNettyTcpClient<P> implements TcpOperations<P> {
|
||||
return handleShuttingDownConnectFailure(handler);
|
||||
}
|
||||
|
||||
// Report first connect to the ListenableFuture
|
||||
// Report first connect to the CompletableFuture
|
||||
CompletableFuture<@Nullable Void> connectFuture = new CompletableFuture<>();
|
||||
|
||||
extendTcpClient(this.tcpClient, handler)
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.lang.annotation.Target;
|
||||
* Container annotation that aggregates several {@link Sql @Sql} annotations.
|
||||
*
|
||||
* <p>Can be used natively, declaring several nested {@code @Sql} annotations.
|
||||
* Can also be used in conjunction with Java 8's support for repeatable
|
||||
* Can also be used in conjunction with Java's support for repeatable
|
||||
* annotations, where {@code @Sql} can simply be declared several times on the
|
||||
* same class or method, implicitly generating this container annotation.
|
||||
*
|
||||
|
||||
+2
-3
@@ -100,9 +100,8 @@ import org.springframework.util.StringUtils;
|
||||
* execute certain <em>set up</em> or <em>tear down</em> code outside a
|
||||
* transaction. {@code TransactionalTestExecutionListener} provides such
|
||||
* support for methods annotated with {@link BeforeTransaction @BeforeTransaction}
|
||||
* or {@link AfterTransaction @AfterTransaction}. As of Spring Framework 4.3,
|
||||
* {@code @BeforeTransaction} and {@code @AfterTransaction} may also be declared
|
||||
* on Java 8 based interface default methods.
|
||||
* or {@link AfterTransaction @AfterTransaction}. {@code @BeforeTransaction} and
|
||||
* {@code @AfterTransaction} may also be declared on interface default methods.
|
||||
*
|
||||
* <h3>Configuring a Transaction Manager</h3>
|
||||
* <p>{@code TransactionalTestExecutionListener} expects a
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||
* Mainly intended for code that wants to set the current transaction
|
||||
* rollback-only but not throw an application exception.
|
||||
* <p>This exposes the locally declared transaction boundary with its declared name
|
||||
* and characteristics, as managed by the aspect. Ar runtime, the local boundary may
|
||||
* and characteristics, as managed by the aspect. At runtime, the local boundary may
|
||||
* participate in an outer transaction: If you need transaction metadata from such
|
||||
* an outer transaction (the actual resource transaction) instead, consider using
|
||||
* {@link org.springframework.transaction.support.TransactionSynchronizationManager}.
|
||||
|
||||
+2
-2
@@ -54,8 +54,8 @@ public interface TransactionOperations {
|
||||
* {@link org.springframework.transaction.TransactionStatus} from within the callback,
|
||||
* use {@link #execute(TransactionCallback)} instead.
|
||||
* <p>This variant is analogous to using a {@link TransactionCallbackWithoutResult}
|
||||
* but with a simplified signature for common cases - and conveniently usable with
|
||||
* Java 8 lambda expressions.
|
||||
* but with a simplified signature for common cases and conveniently usable with
|
||||
* lambda expressions.
|
||||
* @param action the Runnable that specifies the transactional action
|
||||
* @throws TransactionException in case of initialization, rollback, or system errors
|
||||
* @throws RuntimeException if thrown by the Runnable
|
||||
|
||||
@@ -491,7 +491,12 @@ public class HttpHeaders implements Serializable {
|
||||
*/
|
||||
public static HttpHeaders copyOf(MultiValueMap<String, String> headers) {
|
||||
HttpHeaders httpHeadersCopy = new HttpHeaders();
|
||||
headers.forEach((key, values) -> httpHeadersCopy.put(key, new ArrayList<>(values)));
|
||||
for (String name : headers.keySet()) {
|
||||
List<String> values = headers.get(name);
|
||||
if (values != null) {
|
||||
httpHeadersCopy.put(name, new ArrayList<>(values));
|
||||
}
|
||||
}
|
||||
return httpHeadersCopy;
|
||||
}
|
||||
|
||||
@@ -1984,7 +1989,9 @@ public class HttpHeaders implements Serializable {
|
||||
* @see #put(String, List)
|
||||
*/
|
||||
public void putAll(Map<? extends String, ? extends List<String>> headers) {
|
||||
headers.forEach(this::put);
|
||||
for (String name : headers.keySet()) {
|
||||
put(name, headers.get(name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2181,6 +2188,7 @@ public class HttpHeaders implements Serializable {
|
||||
private static final Object VALUE = new Object();
|
||||
|
||||
private final MultiValueMap<String, String> headers;
|
||||
|
||||
private final Map<String, Object> deduplicatedNames;
|
||||
|
||||
public CaseInsensitiveHeaderNameSet(MultiValueMap<String, String> headers) {
|
||||
@@ -2222,13 +2230,15 @@ public class HttpHeaders implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private @Nullable String currentName;
|
||||
|
||||
private final MultiValueMap<String, String> headers;
|
||||
|
||||
private final Iterator<String> namesIterator;
|
||||
|
||||
private @Nullable String currentName;
|
||||
|
||||
public HeaderNamesIterator(MultiValueMap<String, String> headers, Map<String, Object> caseInsensitiveNames) {
|
||||
this.headers = headers;
|
||||
this.namesIterator = caseInsensitiveNames.keySet().iterator();
|
||||
@@ -2262,6 +2272,7 @@ public class HttpHeaders implements Serializable {
|
||||
private static final class CaseInsensitiveEntrySet extends AbstractSet<Entry<String, List<String>>> {
|
||||
|
||||
private final MultiValueMap<String, String> headers;
|
||||
|
||||
private final CaseInsensitiveHeaderNameSet nameSet;
|
||||
|
||||
public CaseInsensitiveEntrySet(MultiValueMap<String, String> headers) {
|
||||
@@ -2279,6 +2290,7 @@ public class HttpHeaders implements Serializable {
|
||||
return this.nameSet.size();
|
||||
}
|
||||
|
||||
|
||||
private final class CaseInsensitiveIterator implements Iterator<Entry<String, List<String>>> {
|
||||
|
||||
private final Iterator<String> namesIterator;
|
||||
@@ -2303,6 +2315,7 @@ public class HttpHeaders implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class CaseInsensitiveEntry implements Entry<String, List<String>> {
|
||||
|
||||
private final String key;
|
||||
@@ -2323,26 +2336,21 @@ public class HttpHeaders implements Serializable {
|
||||
|
||||
@Override
|
||||
public List<String> setValue(List<String> value) {
|
||||
List<String> previousValues = Objects.requireNonNull(
|
||||
CaseInsensitiveEntrySet.this.headers.get(this.key));
|
||||
List<String> previous = Objects.requireNonNull(CaseInsensitiveEntrySet.this.headers.get(this.key));
|
||||
CaseInsensitiveEntrySet.this.headers.put(this.key, value);
|
||||
return previousValues;
|
||||
return previous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Map.Entry<?,?> that)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(getKey(), that.getKey()) && ObjectUtils.nullSafeEquals(getValue(), that.getValue());
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof Map.Entry<?, ?> that &&
|
||||
ObjectUtils.nullSafeEquals(getKey(), that.getKey()) &&
|
||||
ObjectUtils.nullSafeEquals(getValue(), that.getValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHash(getKey(), getValue());
|
||||
return this.key.hashCode(); // avoid value lookup for hashCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@ public class MediaType extends MimeType implements Serializable {
|
||||
* @throws IllegalArgumentException if any of the parameters contain illegal characters
|
||||
*/
|
||||
public MediaType(MediaType other, @Nullable Map<String, String> parameters) {
|
||||
super(other.getType(), other.getSubtype(), parameters);
|
||||
super(other, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -181,7 +181,10 @@ class ReadOnlyHttpHeaders extends HttpHeaders {
|
||||
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super String, ? super List<String>> action) {
|
||||
this.headers.forEach((k, vs) -> action.accept(k, Collections.unmodifiableList(vs)));
|
||||
for (String name : this.headers.keySet()) {
|
||||
List<String> values = this.headers.get(name);
|
||||
action.accept(name, (values != null ? Collections.unmodifiableList(values) : Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -100,7 +100,8 @@ final class PartGenerator extends BaseSubscriber<MultipartParser.Token> {
|
||||
|
||||
sink.onCancel(generator);
|
||||
sink.onRequest(l -> generator.requestToken());
|
||||
tokens.subscribe(generator);
|
||||
tokens.doOnDiscard(MultipartParser.BodyToken.class, bodyToken -> DataBufferUtils.release(bodyToken.buffer()))
|
||||
.subscribe(generator);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -252,13 +252,14 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
|
||||
contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
|
||||
}
|
||||
if (contentTypeToUse != null) {
|
||||
String value = contentTypeToUse.toString();
|
||||
if (contentTypeToUse.getCharset() == null) {
|
||||
Charset defaultCharset = getDefaultCharset();
|
||||
if (defaultCharset != null) {
|
||||
contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
|
||||
value += ";charset=" + defaultCharset.name();
|
||||
}
|
||||
}
|
||||
headers.setContentType(contentTypeToUse);
|
||||
headers.set(HttpHeaders.CONTENT_TYPE, value);
|
||||
}
|
||||
}
|
||||
if (headers.getContentLength() < 0 && !headers.containsHeader(HttpHeaders.TRANSFER_ENCODING)) {
|
||||
|
||||
+2
-2
@@ -225,12 +225,12 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
|
||||
}
|
||||
|
||||
void addMessageConverterConfigurer(Consumer<HttpMessageConverter<?>> configurer) {
|
||||
this.configurer = (this.configurer != null) ? configurer.andThen(this.configurer) : configurer;
|
||||
this.configurer = (this.configurer != null) ? this.configurer.andThen(configurer) : configurer;
|
||||
}
|
||||
|
||||
void addMessageConvertersListConfigurer(Consumer<List<HttpMessageConverter<?>>> configurer) {
|
||||
this.convertersListConfigurer = (this.convertersListConfigurer != null) ?
|
||||
this.convertersListConfigurer.andThen(this.convertersListConfigurer) : configurer;
|
||||
this.convertersListConfigurer.andThen(configurer) : configurer;
|
||||
}
|
||||
|
||||
List<HttpMessageConverter<?>> getBaseConverters() {
|
||||
|
||||
+2
-2
@@ -83,9 +83,9 @@ import org.springframework.util.xml.StaxUtils;
|
||||
* detected on the classpath:
|
||||
* <ul>
|
||||
* <li><a href="https://github.com/FasterXML/jackson-datatype-jdk8">jackson-datatype-jdk8</a>:
|
||||
* support for other Java 8 types like {@link java.util.Optional}</li>
|
||||
* support for Java 8 types like {@link java.util.Optional}</li>
|
||||
* <li><a href="https://github.com/FasterXML/jackson-datatype-jsr310">jackson-datatype-jsr310</a>:
|
||||
* support for Java 8 Date & Time API types</li>
|
||||
* support for Java Date & Time API types</li>
|
||||
* <li><a href="https://github.com/FasterXML/jackson-module-kotlin">jackson-module-kotlin</a>:
|
||||
* support for Kotlin classes and data classes</li>
|
||||
* <li><a href="https://github.com/FasterXML/jackson-modules-java8/tree/2.18/parameter-names">jackson-modules-java8/parameter-names</a>:
|
||||
|
||||
+2
-2
@@ -114,9 +114,9 @@ import org.springframework.context.ApplicationContextAware;
|
||||
* <li><a href="https://github.com/FasterXML/jackson-datatype-jdk7">jackson-datatype-jdk7</a>:
|
||||
* support for Java 7 types like {@link java.nio.file.Path}</li>
|
||||
* <li><a href="https://github.com/FasterXML/jackson-datatype-jdk8">jackson-datatype-jdk8</a>:
|
||||
* support for other Java 8 types like {@link java.util.Optional}</li>
|
||||
* support for Java 8 types like {@link java.util.Optional}</li>
|
||||
* <li><a href="https://github.com/FasterXML/jackson-datatype-jsr310">jackson-datatype-jsr310</a>:
|
||||
* support for Java 8 Date & Time API types</li>
|
||||
* support for Java Date & Time API types</li>
|
||||
* <li><a href="https://github.com/FasterXML/jackson-module-kotlin">jackson-module-kotlin</a>:
|
||||
* support for Kotlin classes and data classes</li>
|
||||
* </ul>
|
||||
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* 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.http.server;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* {@code MultiValueMap} implementation for wrapping Servlet request headers.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 7.0.5
|
||||
*/
|
||||
final class ServletRequestHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
|
||||
private ServletRequestHeadersAdapter(HttpServletRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable String getFirst(String key) {
|
||||
return this.request.getHeader(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String key, @Nullable String value) {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(String key, List<? extends String> values) {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(MultiValueMap<String, String> map) {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, @Nullable String value) {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAll(Map<String, String> map) {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> toSingleValueMap() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
Enumeration<String> names = this.request.getHeaderNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
map.put(name, this.request.getHeader(name));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return keySet().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return !this.request.getHeaderNames().hasMoreElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
Enumeration<String> names = this.request.getHeaderNames();
|
||||
while (names.hasMoreElements()) {
|
||||
if (headerName.equalsIgnoreCase(names.nextElement())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object rawValue) {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> get(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
Enumeration<String> values = this.request.getHeaders(headerName);
|
||||
if (values.hasMoreElements()) {
|
||||
String value = values.nextElement();
|
||||
if (!values.hasMoreElements()) {
|
||||
return Collections.singletonList(value);
|
||||
}
|
||||
List<String> result = new ArrayList<>(4);
|
||||
result.add(value);
|
||||
while (values.hasMoreElements()) {
|
||||
result.add(values.nextElement());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> put(String key, List<String> value) {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> remove(Object key) {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends List<String>> map) {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<String>> values() {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, List<String>>> entrySet() {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException immutableRequestException() {
|
||||
return new UnsupportedOperationException("Request headers are immutable");
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException httpHeadersMapException() {
|
||||
return new UnsupportedOperationException("HttpHeaders does not support all Map operations");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return HttpHeaders.formatHeaders(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method to create a Servlet request headers adapter.
|
||||
* @param request the request to access headers from
|
||||
* @return the created adapter instance
|
||||
*/
|
||||
static MultiValueMap<String, String> create(HttpServletRequest request) {
|
||||
return new RequestHeaderOverrideWrapper(new ServletRequestHeadersAdapter(request));
|
||||
}
|
||||
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(request.getHeaderNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
int size = 0;
|
||||
while (names.hasMoreElements()) {
|
||||
names.nextElement();
|
||||
size++;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Enumeration<String> enumeration;
|
||||
|
||||
private HeaderNamesIterator(Enumeration<String> enumeration) {
|
||||
this.enumeration = enumeration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.enumeration.hasMoreElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return this.enumeration.nextElement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw immutableRequestException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper that holds override values.
|
||||
*/
|
||||
private static class RequestHeaderOverrideWrapper implements MultiValueMap<String, String> {
|
||||
|
||||
private final MultiValueMap<String, String> delegate;
|
||||
|
||||
private @Nullable MultiValueMap<String, String> overrideMap;
|
||||
|
||||
RequestHeaderOverrideWrapper(MultiValueMap<String, String> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getFirst(String key) {
|
||||
String value = (this.overrideMap != null ? this.overrideMap.getFirst(key) : null);
|
||||
return (value != null ? value : this.delegate.getFirst(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String key, @Nullable String value) {
|
||||
initOverrideMap().add(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(String key, List<? extends String> values) {
|
||||
initOverrideMap().addAll(key, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(MultiValueMap<String, String> map) {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, @Nullable String value) {
|
||||
initOverrideMap().set(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAll(Map<String, String> map) {
|
||||
initOverrideMap().setAll(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> toSingleValueMap() {
|
||||
Map<String, String> map = this.delegate.toSingleValueMap();
|
||||
if (this.overrideMap != null) {
|
||||
this.overrideMap.forEach((key, values) -> map.put(key, values.get(0)));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
if (this.overrideMap == null) {
|
||||
return this.delegate.size();
|
||||
}
|
||||
Set<String> set = new LinkedHashSet<>();
|
||||
for (String name : this.delegate.keySet()) {
|
||||
set.add(name.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
this.overrideMap.keySet().forEach(key -> set.add(key.toLowerCase(Locale.ROOT)));
|
||||
return set.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return (this.delegate.isEmpty() && (this.overrideMap == null || this.overrideMap.isEmpty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
if (this.delegate.containsKey(headerName)) {
|
||||
return true;
|
||||
}
|
||||
if (this.overrideMap != null) {
|
||||
return this.overrideMap.containsKey(headerName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object rawValue) {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> get(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
if (this.overrideMap != null) {
|
||||
List<String> values = this.overrideMap.get(headerName);
|
||||
if (values != null) {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
return this.delegate.get(headerName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> put(String key, List<String> value) {
|
||||
return initOverrideMap().put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> remove(Object key) {
|
||||
return initOverrideMap().remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends List<String>> map) {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (this.overrideMap != null) {
|
||||
this.overrideMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
if (this.overrideMap != null) {
|
||||
Set<String> set = new LinkedHashSet<>(this.delegate.keySet());
|
||||
set.addAll(this.overrideMap.keySet());
|
||||
return set;
|
||||
}
|
||||
return this.delegate.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<String>> values() {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, List<String>>> entrySet() {
|
||||
throw httpHeadersMapException();
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> initOverrideMap() {
|
||||
if (this.overrideMap == null) {
|
||||
this.overrideMap = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ROOT));
|
||||
}
|
||||
return this.overrideMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toMultiValueMap().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof MultiValueMap<?,?> that && toMultiValueMap().equals(that)));
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> toMultiValueMap() {
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||
for (String name : keySet()) {
|
||||
List<String> values = get(name);
|
||||
if (values != null) {
|
||||
for (String value : values) {
|
||||
map.add(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return HttpHeaders.formatHeaders(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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.http.server;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* {@code MultiValueMap} implementation for wrapping Servlet response headers.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 7.0.5
|
||||
*/
|
||||
class ServletResponseHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
private final HttpServletResponse response;
|
||||
|
||||
|
||||
ServletResponseHeadersAdapter(HttpServletResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable String getFirst(String key) {
|
||||
String header = this.response.getHeader(key);
|
||||
if (header == null && key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE)) {
|
||||
header = this.response.getContentType();
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String key, @Nullable String value) {
|
||||
this.response.addHeader(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(String key, List<? extends String> values) {
|
||||
for (String value : values) {
|
||||
this.response.addHeader(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(MultiValueMap<String, String> map) {
|
||||
throw httpHeadersUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, @Nullable String value) {
|
||||
this.response.setHeader(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAll(Map<String, String> map) {
|
||||
for (Entry<String, String> entry : map.entrySet()) {
|
||||
this.response.setHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> toSingleValueMap() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
Collection<String> names = this.response.getHeaderNames();
|
||||
for (String name : names) {
|
||||
map.put(name, this.response.getHeader(name));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.response.getHeaderNames().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.response.getHeaderNames().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
return this.response.containsHeader(headerName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object rawValue) {
|
||||
throw httpHeadersUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> get(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
Collection<String> values = this.response.getHeaders(headerName);
|
||||
if (values.isEmpty() && headerName.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE)) {
|
||||
String contentType = this.response.getContentType();
|
||||
return (contentType != null ? Collections.singletonList(contentType) : null);
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
return new ArrayList<>(values);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> put(String key, List<String> values) {
|
||||
List<String> previous = remove(key);
|
||||
for (String value : values) {
|
||||
this.response.addHeader(key, value);
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> remove(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
List<String> previous = get(headerName);
|
||||
if (previous != null) {
|
||||
this.response.setHeader(headerName, null);
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends List<String>> map) {
|
||||
throw httpHeadersUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (String headerName : this.response.getHeaderNames()) {
|
||||
this.response.setHeader(headerName, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<String>> values() {
|
||||
throw httpHeadersUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, List<String>>> entrySet() {
|
||||
throw httpHeadersUnsupportedOperationException();
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException httpHeadersUnsupportedOperationException() {
|
||||
return new UnsupportedOperationException("HttpHeaders does not support all Map operations");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toMultiValueMap().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof MultiValueMap<?,?> that && toMultiValueMap().equals(that)));
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> toMultiValueMap() {
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||
for (String name : this.response.getHeaderNames()) {
|
||||
for (String value : this.response.getHeaders(name)) {
|
||||
map.add(name, value);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return HttpHeaders.formatHeaders(this);
|
||||
}
|
||||
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(response.getHeaderNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return ServletResponseHeadersAdapter.this.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<String> values;
|
||||
|
||||
private HeaderNamesIterator(Collection<String> values) {
|
||||
this.values = values.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.values.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return this.values.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+3
-14
@@ -156,16 +156,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
this.headers = new HttpHeaders();
|
||||
|
||||
for (Enumeration<?> names = this.servletRequest.getHeaderNames(); names.hasMoreElements();) {
|
||||
String headerName = (String) names.nextElement();
|
||||
for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName);
|
||||
headerValues.hasMoreElements();) {
|
||||
String headerValue = (String) headerValues.nextElement();
|
||||
this.headers.add(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
this.headers = new HttpHeaders(ServletRequestHeadersAdapter.create(this.servletRequest));
|
||||
|
||||
// HttpServletRequest exposes some headers as properties:
|
||||
// we should include those if not already present
|
||||
@@ -183,10 +174,10 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
if (contentType != null && contentType.getCharset() == null) {
|
||||
String requestEncoding = this.servletRequest.getCharacterEncoding();
|
||||
if (StringUtils.hasLength(requestEncoding)) {
|
||||
Charset charSet = Charset.forName(requestEncoding);
|
||||
Charset charset = Charset.forName(requestEncoding);
|
||||
Map<String, String> params = new LinkedCaseInsensitiveMap<>();
|
||||
params.putAll(contentType.getParameters());
|
||||
params.put("charset", charSet.toString());
|
||||
params.put("charset", charset.toString());
|
||||
MediaType mediaType = new MediaType(contentType.getType(), contentType.getSubtype(), params);
|
||||
this.headers.setContentType(mediaType);
|
||||
}
|
||||
@@ -203,11 +194,9 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Principal getPrincipal() {
|
||||
return this.servletRequest.getUserPrincipal();
|
||||
}
|
||||
|
||||
+18
-87
@@ -18,10 +18,7 @@ package org.springframework.http.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -30,7 +27,6 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link ServerHttpResponse} implementation that is based on a {@link HttpServletResponse}.
|
||||
@@ -59,7 +55,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
|
||||
public ServletServerHttpResponse(HttpServletResponse servletResponse) {
|
||||
Assert.notNull(servletResponse, "HttpServletResponse must not be null");
|
||||
this.servletResponse = servletResponse;
|
||||
this.headers = new ServletResponseHttpHeaders();
|
||||
this.headers = new HttpHeaders(new ServletResponseHeadersAdapter(servletResponse));
|
||||
}
|
||||
|
||||
|
||||
@@ -112,92 +108,27 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
|
||||
|
||||
private void writeHeaders() {
|
||||
if (!this.headersWritten) {
|
||||
getHeaders().forEach((headerName, headerValues) -> {
|
||||
for (String headerValue : headerValues) {
|
||||
this.servletResponse.addHeader(headerName, headerValue);
|
||||
}
|
||||
});
|
||||
// HttpServletResponse exposes some headers as properties: we should include those if not already present
|
||||
MediaType contentTypeHeader = this.headers.getContentType();
|
||||
if (this.servletResponse.getContentType() == null && contentTypeHeader != null) {
|
||||
this.servletResponse.setContentType(contentTypeHeader.toString());
|
||||
if (this.servletResponse.getContentType() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
|
||||
this.servletResponse.setContentType(this.headers.getFirst(HttpHeaders.CONTENT_TYPE));
|
||||
}
|
||||
if (this.servletResponse.getCharacterEncoding() == null && contentTypeHeader != null &&
|
||||
contentTypeHeader.getCharset() != null) {
|
||||
this.servletResponse.setCharacterEncoding(contentTypeHeader.getCharset().name());
|
||||
}
|
||||
long contentLength = getHeaders().getContentLength();
|
||||
if (contentLength != -1) {
|
||||
this.servletResponse.setContentLengthLong(contentLength);
|
||||
if (this.servletResponse.getCharacterEncoding() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
|
||||
try {
|
||||
// Lazy parsing into MediaType
|
||||
MediaType contentType = this.headers.getContentType();
|
||||
if (contentType != null) {
|
||||
Charset charset = contentType.getCharset();
|
||||
if (charset != null) {
|
||||
this.servletResponse.setCharacterEncoding(charset);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Leave character encoding unspecified
|
||||
}
|
||||
}
|
||||
this.headersWritten = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extends HttpHeaders with the ability to look up headers already present in
|
||||
* the underlying HttpServletResponse.
|
||||
*
|
||||
* <p>The intent is merely to expose what is available through the HttpServletResponse
|
||||
* i.e. the ability to look up specific header values by name. All other
|
||||
* map-related operations (for example, iteration, removal, etc) apply only to values
|
||||
* added directly through HttpHeaders methods.
|
||||
*
|
||||
* @since 4.0.3
|
||||
*/
|
||||
private class ServletResponseHttpHeaders extends HttpHeaders {
|
||||
|
||||
private static final long serialVersionUID = 3410708522401046302L;
|
||||
|
||||
@Override
|
||||
public boolean containsHeader(String key) {
|
||||
return (super.containsHeader(key) || (get(key) != null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getFirst(String headerName) {
|
||||
if (headerName.equalsIgnoreCase(CONTENT_TYPE)) {
|
||||
// Content-Type is written as an override so check super first
|
||||
String value = super.getFirst(headerName);
|
||||
return (value != null ? value : servletResponse.getContentType());
|
||||
}
|
||||
else {
|
||||
String value = servletResponse.getHeader(headerName);
|
||||
return (value != null ? value : super.getFirst(headerName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> get(String headerName) {
|
||||
if (headerName.equalsIgnoreCase(CONTENT_TYPE)) {
|
||||
// Content-Type is written as an override so don't merge
|
||||
String value = getFirst(headerName);
|
||||
return (value != null ? Collections.singletonList(value) : null);
|
||||
}
|
||||
|
||||
Collection<String> values1 = servletResponse.getHeaders(headerName);
|
||||
if (headersWritten) {
|
||||
return new ArrayList<>(values1);
|
||||
}
|
||||
boolean isEmpty1 = CollectionUtils.isEmpty(values1);
|
||||
|
||||
List<String> values2 = super.get(headerName);
|
||||
boolean isEmpty2 = CollectionUtils.isEmpty(values2);
|
||||
|
||||
if (isEmpty1 && isEmpty2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> values = new ArrayList<>();
|
||||
if (!isEmpty1) {
|
||||
values.addAll(values1);
|
||||
}
|
||||
if (!isEmpty2) {
|
||||
values.addAll(values2);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-19
@@ -122,31 +122,35 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
|
||||
}
|
||||
|
||||
protected void adaptHeaders(boolean removeAdaptedHeaders) {
|
||||
MediaType contentType = null;
|
||||
try {
|
||||
contentType = getHeaders().getContentType();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
|
||||
this.response.setContentType(rawContentType);
|
||||
}
|
||||
if (this.response.getContentType() == null && contentType != null) {
|
||||
this.response.setContentType(contentType.toString());
|
||||
}
|
||||
HttpHeaders headers = getHeaders();
|
||||
|
||||
Charset charset = (contentType != null ? contentType.getCharset() : null);
|
||||
if (this.response.getCharacterEncoding() == null && charset != null) {
|
||||
this.response.setCharacterEncoding(charset.name());
|
||||
// HttpServletResponse exposes some headers as properties: we should include those if not already present
|
||||
if (this.response.getContentType() == null && headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
|
||||
this.response.setContentType(headers.getFirst(HttpHeaders.CONTENT_TYPE));
|
||||
}
|
||||
|
||||
long contentLength = getHeaders().getContentLength();
|
||||
if (this.response.getCharacterEncoding() == null && headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
|
||||
try {
|
||||
// Lazy parsing into MediaType
|
||||
MediaType contentType = headers.getContentType();
|
||||
if (contentType != null) {
|
||||
Charset charset = contentType.getCharset();
|
||||
if (charset != null) {
|
||||
this.response.setCharacterEncoding(charset);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Leave character encoding unspecified
|
||||
}
|
||||
}
|
||||
long contentLength = headers.getContentLength();
|
||||
if (contentLength != -1) {
|
||||
this.response.setContentLengthLong(contentLength);
|
||||
}
|
||||
|
||||
if (removeAdaptedHeaders) {
|
||||
getHeaders().remove(HttpHeaders.CONTENT_TYPE);
|
||||
getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
|
||||
headers.remove(HttpHeaders.CONTENT_TYPE);
|
||||
headers.remove(HttpHeaders.CONTENT_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +356,6 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
|
||||
|
||||
private class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> {
|
||||
|
||||
|
||||
public ResponseBodyProcessor() {
|
||||
super(request.getLogPrefix());
|
||||
}
|
||||
|
||||
+33
-19
@@ -65,12 +65,16 @@ class TomcatHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void addAll(String key, List<? extends String> values) {
|
||||
values.forEach(value -> add(key, value));
|
||||
for (String value : values) {
|
||||
add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(MultiValueMap<String, String> values) {
|
||||
values.forEach(this::addAll);
|
||||
for (Entry<String, List<String>> entry : values.entrySet()) {
|
||||
addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,24 +84,28 @@ class TomcatHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void setAll(Map<String, String> values) {
|
||||
values.forEach(this::set);
|
||||
for (Entry<String, String> entry : values.entrySet()) {
|
||||
set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> toSingleValueMap() {
|
||||
Map<String, String> singleValueMap = CollectionUtils.newLinkedHashMap(this.headers.size());
|
||||
this.keySet().forEach(key -> singleValueMap.put(key, getFirst(key)));
|
||||
return singleValueMap;
|
||||
Map<String, String> map = CollectionUtils.newLinkedHashMap(this.headers.size());
|
||||
for (String name : this.keySet()) {
|
||||
map.put(name, getFirst(name));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
Enumeration<String> names = this.headers.names();
|
||||
Set<String> deduplicated = new LinkedHashSet<>();
|
||||
Set<String> set = new LinkedHashSet<>(this.headers.size());
|
||||
while (names.hasMoreElements()) {
|
||||
deduplicated.add(names.nextElement().toLowerCase(Locale.ROOT));
|
||||
set.add(names.nextElement().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
return deduplicated.size();
|
||||
return set.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,10 +124,10 @@ class TomcatHeadersAdapter implements MultiValueMap<String, String> {
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
if (value instanceof String text) {
|
||||
MessageBytes messageBytes = MessageBytes.newInstance();
|
||||
messageBytes.setString(text);
|
||||
MessageBytes bytes = MessageBytes.newInstance();
|
||||
bytes.setString(text);
|
||||
for (int i = 0; i < this.headers.size(); i++) {
|
||||
if (this.headers.getValue(i).equals(messageBytes)) {
|
||||
if (this.headers.getValue(i).equals(bytes)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -129,33 +137,38 @@ class TomcatHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> get(Object key) {
|
||||
if (containsKey(key)) {
|
||||
return Collections.list(this.headers.values((String) key));
|
||||
if (key instanceof String headerName) {
|
||||
Enumeration<String> values = this.headers.values(headerName);
|
||||
if (values.hasMoreElements()) {
|
||||
return Collections.list(values);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> put(String key, List<String> value) {
|
||||
List<String> previousValues = get(key);
|
||||
List<String> previous = get(key);
|
||||
this.headers.removeHeader(key);
|
||||
value.forEach(v -> this.headers.addValue(key).setString(v));
|
||||
return previousValues;
|
||||
return previous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> remove(Object key) {
|
||||
if (key instanceof String headerName) {
|
||||
List<String> previousValues = get(key);
|
||||
List<String> previous = get(key);
|
||||
this.headers.removeHeader(headerName);
|
||||
return previousValues;
|
||||
return previous;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends List<String>> map) {
|
||||
map.forEach(this::put);
|
||||
for (Entry<? extends String, ? extends List<String>> entry : map.entrySet()) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -258,6 +271,7 @@ class TomcatHeadersAdapter implements MultiValueMap<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Enumeration<String> enumeration;
|
||||
|
||||
@@ -52,14 +52,14 @@ public final class JettyHeadersAdapter implements MultiValueMap<String, String>
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@code JettyHeadersAdapter} based on the given
|
||||
* Construct a new {@code JettyHeadersAdapter} based on the given
|
||||
* {@code HttpFields} instance.
|
||||
* @param headers the {@code HttpFields} to base this adapter on
|
||||
*/
|
||||
public JettyHeadersAdapter(HttpFields headers) {
|
||||
Assert.notNull(headers, "Headers must not be null");
|
||||
this.headers = headers;
|
||||
this.mutable = headers instanceof HttpFields.Mutable m ? m : null;
|
||||
this.mutable = (headers instanceof HttpFields.Mutable m ? m : null);
|
||||
}
|
||||
|
||||
|
||||
@@ -146,12 +146,12 @@ public final class JettyHeadersAdapter implements MultiValueMap<String, String>
|
||||
public @Nullable List<String> get(Object key) {
|
||||
List<String> list = null;
|
||||
if (key instanceof String name) {
|
||||
for (HttpField f : this.headers) {
|
||||
if (f.is(name)) {
|
||||
for (HttpField field : this.headers) {
|
||||
if (field.is(name)) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
list.add(f.getValue());
|
||||
list.add(field.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,6 @@ public final class JettyHeadersAdapter implements MultiValueMap<String, String>
|
||||
public @Nullable List<String> put(String key, List<String> value) {
|
||||
HttpFields.Mutable mutableHttpFields = mutableFields();
|
||||
List<String> oldValues = get(key);
|
||||
|
||||
if (oldValues == null) {
|
||||
switch (value.size()) {
|
||||
case 0 -> {}
|
||||
|
||||
@@ -57,10 +57,12 @@ public @interface PathVariable {
|
||||
|
||||
/**
|
||||
* Whether the path variable is required.
|
||||
* <p>Defaults to {@code true}, leading to an exception being thrown if the path
|
||||
* variable is missing in the incoming request. Switch this to {@code false} if
|
||||
* you prefer a {@code null} or Java 8 {@code java.util.Optional} in this case.
|
||||
* for example, on a {@code ModelAttribute} method which serves for different requests.
|
||||
* <p>Defaults to {@code true}, leading to an exception being thrown if the
|
||||
* path variable is missing in the incoming request.
|
||||
* <p>Switch this to {@code false} if you prefer a {@code null} or
|
||||
* {@code java.util.Optional} if the path variable does not exist —
|
||||
* for example, on a {@code ModelAttribute} method which serves for different
|
||||
* requests.
|
||||
* @since 4.3.3
|
||||
*/
|
||||
boolean required() default true;
|
||||
|
||||
+3
-3
@@ -57,9 +57,9 @@ public @interface RequestAttribute {
|
||||
/**
|
||||
* Whether the request attribute is required.
|
||||
* <p>Defaults to {@code true}, leading to an exception being thrown if
|
||||
* the attribute is missing. Switch this to {@code false} if you prefer
|
||||
* a {@code null} or Java 8 {@code java.util.Optional} if the attribute
|
||||
* doesn't exist.
|
||||
* the attribute is missing.
|
||||
* <p>Switch this to {@code false} if you prefer a {@code null} or
|
||||
* {@code java.util.Optional} if the attribute does not exist.
|
||||
*/
|
||||
boolean required() default true;
|
||||
|
||||
|
||||
+2
-2
@@ -66,8 +66,8 @@ public @interface SessionAttribute {
|
||||
* Whether the session attribute is required.
|
||||
* <p>Defaults to {@code true}, leading to an exception being thrown
|
||||
* if the attribute is missing in the session or there is no session.
|
||||
* Switch this to {@code false} if you prefer a {@code null} or Java 8
|
||||
* {@code java.util.Optional} if the attribute doesn't exist.
|
||||
* <p>Switch this to {@code false} if you prefer a {@code null} or
|
||||
* {@code java.util.Optional} if the attribute does not exist.
|
||||
*/
|
||||
boolean required() default true;
|
||||
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ import org.springframework.util.Assert;
|
||||
* @since 15.03.2004
|
||||
* @see #setEncoding
|
||||
* @see #setForceEncoding
|
||||
* @see jakarta.servlet.http.HttpServletRequest#setCharacterEncoding
|
||||
* @see jakarta.servlet.http.HttpServletResponse#setCharacterEncoding
|
||||
* @see jakarta.servlet.http.HttpServletRequest#setCharacterEncoding(String)
|
||||
* @see jakarta.servlet.http.HttpServletResponse#setCharacterEncoding(String)
|
||||
*/
|
||||
public class CharacterEncodingFilter extends OncePerRequestFilter {
|
||||
|
||||
|
||||
-2
@@ -64,8 +64,6 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
private static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
private static final Class<?>[] EMPTY_GROUPS = new Class<?>[0];
|
||||
|
||||
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
|
||||
|
||||
|
||||
|
||||
+4
-3
@@ -117,10 +117,11 @@ public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpSe
|
||||
// MaxUploadSizeExceededException ?
|
||||
Throwable cause = ex;
|
||||
do {
|
||||
String msg = cause.getMessage();
|
||||
String msg = cause.toString();
|
||||
if (msg != null) {
|
||||
msg = msg.toLowerCase(Locale.ROOT);
|
||||
if ((msg.contains("exceed") && (msg.contains("size") || msg.contains("length"))) ||
|
||||
if (((msg.contains("exceed") || msg.contains("limit")) &&
|
||||
(msg.contains("size") || msg.contains("length") || msg.contains("count"))) ||
|
||||
(msg.contains("request") && (msg.contains("big") || msg.contains("large")))) {
|
||||
throw new MaxUploadSizeExceededException(-1, ex);
|
||||
}
|
||||
@@ -266,7 +267,7 @@ public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpSe
|
||||
if (dest.isAbsolute() && !dest.exists()) {
|
||||
// Servlet Part.write is not guaranteed to support absolute file paths:
|
||||
// may translate the given path to a relative location within a temp dir
|
||||
// (for example, on Jetty whereas Tomcat detects absolute paths).
|
||||
// (for example, on Jetty whereas Tomcat and Undertow detect absolute paths).
|
||||
// At least we offloaded the file from memory storage; it'll get deleted
|
||||
// from the temp dir eventually in any case. And for our user's purposes,
|
||||
// we can manually copy it to the requested location as a fallback.
|
||||
|
||||
+29
-11
@@ -210,22 +210,31 @@ class DefaultHttpMessageConvertersTests {
|
||||
.addCustomConverter(customConverter)
|
||||
.configureMessageConverters(converter -> {
|
||||
if (converter instanceof CustomHttpMessageConverter custom) {
|
||||
custom.processed = true;
|
||||
assertThat(custom.processCount).isZero();
|
||||
custom.processCount++;
|
||||
}
|
||||
})
|
||||
.configureMessageConverters(converter -> {
|
||||
if (converter instanceof CustomHttpMessageConverter custom) {
|
||||
assertThat(custom.processCount).isEqualTo(1);
|
||||
custom.processCount++;
|
||||
}
|
||||
}).build();
|
||||
|
||||
assertThat(customConverter.processed).isTrue();
|
||||
assertThat(customConverter.processCount).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendCustomConverterToList() {
|
||||
var customConverter = new CustomHttpMessageConverter();
|
||||
var firstCustom = new CustomHttpMessageConverter();
|
||||
var secondCustom = new CustomHttpMessageConverter();
|
||||
var messageConverters = HttpMessageConverters.forClient()
|
||||
.registerDefaults()
|
||||
.configureMessageConvertersList(converters -> converters.add(customConverter))
|
||||
.configureMessageConvertersList(converters -> converters.add(firstCustom))
|
||||
.configureMessageConvertersList(converters -> converters.add(secondCustom))
|
||||
.build();
|
||||
|
||||
assertThat(messageConverters).last().isInstanceOf(CustomHttpMessageConverter.class);
|
||||
assertThat(messageConverters).containsSequence(firstCustom, secondCustom);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -336,22 +345,31 @@ class DefaultHttpMessageConvertersTests {
|
||||
.addCustomConverter(customConverter)
|
||||
.configureMessageConverters(converter -> {
|
||||
if (converter instanceof CustomHttpMessageConverter custom) {
|
||||
custom.processed = true;
|
||||
assertThat(custom.processCount).isZero();
|
||||
custom.processCount++;
|
||||
}
|
||||
})
|
||||
.configureMessageConverters(converter -> {
|
||||
if (converter instanceof CustomHttpMessageConverter custom) {
|
||||
assertThat(custom.processCount).isEqualTo(1);
|
||||
custom.processCount++;
|
||||
}
|
||||
}).build();
|
||||
|
||||
assertThat(customConverter.processed).isTrue();
|
||||
assertThat(customConverter.processCount).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendCustomConverterToList() {
|
||||
var customConverter = new CustomHttpMessageConverter();
|
||||
var firstCustom = new CustomHttpMessageConverter();
|
||||
var secondCustom = new CustomHttpMessageConverter();
|
||||
var messageConverters = HttpMessageConverters.forServer()
|
||||
.registerDefaults()
|
||||
.configureMessageConvertersList(converters -> converters.add(customConverter))
|
||||
.configureMessageConvertersList(converters -> converters.add(firstCustom))
|
||||
.configureMessageConvertersList(converters -> converters.add(secondCustom))
|
||||
.build();
|
||||
|
||||
assertThat(messageConverters).last().isInstanceOf(CustomHttpMessageConverter.class);
|
||||
assertThat(messageConverters).containsSequence(firstCustom, secondCustom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +383,7 @@ class DefaultHttpMessageConvertersTests {
|
||||
|
||||
static class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
|
||||
|
||||
boolean processed = false;
|
||||
int processCount;
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
|
||||
+8
-6
@@ -298,12 +298,14 @@ class HeadersAdaptersTests {
|
||||
|
||||
static Stream<Arguments> nativeHeadersWithCasedEntries() {
|
||||
return Stream.of(
|
||||
argumentSet("Netty", new Netty4HeadersAdapter(withHeaders(new DefaultHttpHeaders(), h -> h::add))),
|
||||
argumentSet("Tomcat", new TomcatHeadersAdapter(withHeaders(new MimeHeaders(),
|
||||
h -> (k, v) -> h.addValue(k).setString(v)))),
|
||||
argumentSet("Jetty", new JettyHeadersAdapter(withHeaders(HttpFields.build(), h -> h::add))),
|
||||
argumentSet("HttpComponents", new HttpComponentsHeadersAdapter(withHeaders(new HttpGet("https://example.com"),
|
||||
h -> h::addHeader)))
|
||||
argumentSet("Netty", new Netty4HeadersAdapter(
|
||||
withHeaders(new DefaultHttpHeaders(), h -> h::add))),
|
||||
argumentSet("Tomcat", new TomcatHeadersAdapter(
|
||||
withHeaders(new MimeHeaders(), h -> (k, v) -> h.addValue(k).setString(v)))),
|
||||
argumentSet("Jetty", new JettyHeadersAdapter(
|
||||
withHeaders(HttpFields.build(), h -> h::add))),
|
||||
argumentSet("HttpComponents", new HttpComponentsHeadersAdapter(
|
||||
withHeaders(new HttpGet("https://example.com"), h -> h::addHeader)))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+68
@@ -123,6 +123,47 @@ class StandardMultipartHttpServletRequestTests {
|
||||
.isThrownBy(() -> requestWithException(ex)).withCause(ex);
|
||||
}
|
||||
|
||||
@Test // gh-32549
|
||||
void undertowRequestTooBigException() {
|
||||
IOException ex = new IOException("Connection terminated as request was larger than 10000");
|
||||
|
||||
assertThatExceptionOfType(MaxUploadSizeExceededException.class)
|
||||
.isThrownBy(() -> requestWithException(ex)).withCause(ex);
|
||||
}
|
||||
|
||||
@Test // gh-36317: Tomcat's Commons FileUpload
|
||||
void commonsFileSizeLimitExceededException() {
|
||||
IOException ex = new FileSizeLimitExceededException();
|
||||
|
||||
assertThatExceptionOfType(MaxUploadSizeExceededException.class)
|
||||
.isThrownBy(() -> requestWithException(ex)).withCause(ex);
|
||||
}
|
||||
|
||||
@Test // gh-36317: Tomcat's Commons FileUpload
|
||||
void commonsFileCountLimitExceededException() {
|
||||
IOException ex = new FileCountLimitExceededException();
|
||||
|
||||
assertThatExceptionOfType(MaxUploadSizeExceededException.class)
|
||||
.isThrownBy(() -> requestWithException(ex)).withCause(ex);
|
||||
}
|
||||
|
||||
@Test // gh-36317: Commons FileUpload 2.x
|
||||
void commonsFileUploadByteCountLimitException() {
|
||||
IOException ex = new FileUploadByteCountLimitException();
|
||||
|
||||
assertThatExceptionOfType(MaxUploadSizeExceededException.class)
|
||||
.isThrownBy(() -> requestWithException(ex)).withCause(ex);
|
||||
}
|
||||
|
||||
@Test // gh-36317: Commons FileUpload 2.x
|
||||
void commonsFileUploadFileCountLimitException() {
|
||||
IOException ex = new FileUploadFileCountLimitException();
|
||||
|
||||
assertThatExceptionOfType(MaxUploadSizeExceededException.class)
|
||||
.isThrownBy(() -> requestWithException(ex)).withCause(ex);
|
||||
}
|
||||
|
||||
|
||||
private static StandardMultipartHttpServletRequest requestWithPart(String name, String disposition, String content) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockPart part = new MockPart(name, null, content.getBytes(StandardCharsets.UTF_8));
|
||||
@@ -141,4 +182,31 @@ class StandardMultipartHttpServletRequestTests {
|
||||
return new StandardMultipartHttpServletRequest(request);
|
||||
}
|
||||
|
||||
private static StandardMultipartHttpServletRequest requestWithException(IOException ex) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest() {
|
||||
@Override
|
||||
public Collection<Part> getParts() throws IOException {
|
||||
throw ex;
|
||||
}
|
||||
};
|
||||
return new StandardMultipartHttpServletRequest(request);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class FileSizeLimitExceededException extends IOException {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class FileCountLimitExceededException extends IOException {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class FileUploadByteCountLimitException extends IOException {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class FileUploadFileCountLimitException extends IOException {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-7
@@ -55,7 +55,6 @@ import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.validation.annotation.ValidationAnnotationUtils;
|
||||
import org.springframework.validation.method.MethodValidator;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
@@ -97,8 +96,6 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
private @Nullable MethodValidator methodValidator;
|
||||
|
||||
private Class<?>[] validationGroups = EMPTY_GROUPS;
|
||||
|
||||
private @Nullable Scheduler invocationScheduler;
|
||||
|
||||
|
||||
@@ -151,7 +148,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
/**
|
||||
* Configure a reactive adapter registry. This is needed for cases where the response is
|
||||
* fully handled within the controller in combination with an async void return value.
|
||||
* <p>By default this is a {@link ReactiveAdapterRegistry} with default settings.
|
||||
* <p>By default, this is a {@link ReactiveAdapterRegistry} with default settings.
|
||||
*/
|
||||
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) {
|
||||
this.reactiveAdapterRegistry = registry;
|
||||
@@ -165,8 +162,6 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
*/
|
||||
public void setMethodValidator(@Nullable MethodValidator methodValidator) {
|
||||
this.methodValidator = methodValidator;
|
||||
this.validationGroups = (methodValidator != null ?
|
||||
ValidationAnnotationUtils.determineValidationGroups(getBean(), getBridgedMethod()) : EMPTY_GROUPS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,7 +188,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
try {
|
||||
LocaleContextHolder.setLocaleContext(exchange.getLocaleContext());
|
||||
this.methodValidator.applyArgumentValidation(
|
||||
getBean(), getBridgedMethod(), getMethodParameters(), args, this.validationGroups);
|
||||
getBean(), getBridgedMethod(), getMethodParameters(), args, getValidationGroups());
|
||||
}
|
||||
finally {
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
|
||||
+7
-2
@@ -19,6 +19,8 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -53,6 +55,9 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
*/
|
||||
public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandler implements HandlerResultHandler {
|
||||
|
||||
private final Map<Class<?>, Boolean> responseBodyControllerCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
|
||||
* @param writers the writers for serializing to the response body
|
||||
@@ -93,8 +98,8 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle
|
||||
@Override
|
||||
public boolean supports(HandlerResult result) {
|
||||
MethodParameter returnType = result.getReturnTypeSource();
|
||||
Class<?> containingClass = returnType.getContainingClass();
|
||||
return (AnnotatedElementUtils.hasAnnotation(containingClass, ResponseBody.class) ||
|
||||
return (this.responseBodyControllerCache.computeIfAbsent(returnType.getContainingClass(),
|
||||
clazz -> AnnotatedElementUtils.hasAnnotation(clazz, ResponseBody.class)) ||
|
||||
returnType.hasMethodAnnotation(ResponseBody.class));
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -59,7 +59,6 @@ class WebFluxViewResolutionIntegrationTests {
|
||||
private static final MediaType TEXT_HTML_ISO_8859_1 = MediaType.parseMediaType("text/html;charset=ISO-8859-1");
|
||||
|
||||
|
||||
|
||||
@Nested
|
||||
class FreeMarkerTests {
|
||||
|
||||
@@ -115,6 +114,7 @@ class WebFluxViewResolutionIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class FreeMarkerWebFluxConfig extends AbstractWebFluxConfig {
|
||||
|
||||
@@ -131,6 +131,7 @@ class WebFluxViewResolutionIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ExplicitDefaultEncodingConfig extends AbstractWebFluxConfig {
|
||||
|
||||
@@ -148,6 +149,7 @@ class WebFluxViewResolutionIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ExplicitDefaultEncodingAndContentTypeConfig extends AbstractWebFluxConfig {
|
||||
|
||||
@@ -196,6 +198,7 @@ class WebFluxViewResolutionIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
static class SampleController {
|
||||
|
||||
|
||||
+26
-8
@@ -17,6 +17,7 @@
|
||||
package org.springframework.web.servlet.function;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -51,15 +53,16 @@ abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
|
||||
|
||||
private final MultiValueMap<String, Cookie> cookies;
|
||||
|
||||
|
||||
protected AbstractServerResponse(
|
||||
HttpStatusCode statusCode, HttpHeaders headers, MultiValueMap<String, Cookie> cookies) {
|
||||
|
||||
this.statusCode = statusCode;
|
||||
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
|
||||
this.cookies =
|
||||
CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(cookies));
|
||||
this.cookies = CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(cookies));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final HttpStatusCode statusCode() {
|
||||
return this.statusCode;
|
||||
@@ -110,14 +113,29 @@ abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
|
||||
servletResponse.addHeader(headerName, headerValue);
|
||||
}
|
||||
});
|
||||
|
||||
// HttpServletResponse exposes some headers as properties: we should include those if not already present
|
||||
if (servletResponse.getContentType() == null && this.headers.getContentType() != null) {
|
||||
servletResponse.setContentType(this.headers.getContentType().toString());
|
||||
if (servletResponse.getContentType() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
|
||||
servletResponse.setContentType(this.headers.getFirst(HttpHeaders.CONTENT_TYPE));
|
||||
}
|
||||
if (servletResponse.getCharacterEncoding() == null &&
|
||||
this.headers.getContentType() != null &&
|
||||
this.headers.getContentType().getCharset() != null) {
|
||||
servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name());
|
||||
if (servletResponse.getCharacterEncoding() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
|
||||
try {
|
||||
// Lazy parsing into MediaType
|
||||
MediaType contentType = this.headers.getContentType();
|
||||
if (contentType != null) {
|
||||
Charset charset = contentType.getCharset();
|
||||
if (charset != null) {
|
||||
servletResponse.setCharacterEncoding(charset);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Leave character encoding unspecified
|
||||
}
|
||||
}
|
||||
long contentLength = this.headers.getContentLength();
|
||||
if (contentLength != -1) {
|
||||
servletResponse.setContentLengthLong(contentLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -34,6 +34,7 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* Base class for {@link ServerResponse} implementations with error handling.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3
|
||||
*/
|
||||
@@ -81,6 +82,7 @@ abstract class ErrorHandlingServerResponse implements ServerResponse {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static class ErrorHandler<T extends ServerResponse> {
|
||||
|
||||
private final Predicate<Throwable> predicate;
|
||||
|
||||
+1
-1
@@ -229,7 +229,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
if (HttpHeaders.VARY.equals(key) && outputHeaders.containsHeader(HttpHeaders.VARY)) {
|
||||
List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
|
||||
if (!values.isEmpty()) {
|
||||
outputHeaders.setVary(values);
|
||||
outputHeaders.addAll(HttpHeaders.VARY, values);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
+7
-1
@@ -20,6 +20,8 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -67,6 +69,9 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
||||
*/
|
||||
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
|
||||
|
||||
private final Map<Class<?>, Boolean> responseBodyControllerCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Basic constructor with converters only. Suitable for resolving
|
||||
* {@code @RequestBody}. For handling {@code @ResponseBody} consider also
|
||||
@@ -132,7 +137,8 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
|
||||
return (this.responseBodyControllerCache.computeIfAbsent(returnType.getContainingClass(),
|
||||
clazz -> AnnotatedElementUtils.hasAnnotation(clazz, ResponseBody.class)) ||
|
||||
returnType.hasMethodAnnotation(ResponseBody.class));
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -458,6 +458,11 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterEncoding(Charset encoding) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentLength(int len) {
|
||||
// ignore
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
* Create a nested ServletInvocableHandlerMethod subclass that returns the
|
||||
* given value (or raises an Exception if the value is one) rather than
|
||||
* actually invoking the controller method. This is useful when processing
|
||||
* async return values (for example, Callable, DeferredResult, ListenableFuture).
|
||||
* async return values (for example, Callable, DeferredResult, CompletableFuture).
|
||||
*/
|
||||
ServletInvocableHandlerMethod wrapConcurrentResult(@Nullable Object result) {
|
||||
return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));
|
||||
|
||||
+1
-1
@@ -374,7 +374,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
|
||||
|
||||
setResponseContentType(request, response);
|
||||
if (this.charset != null) {
|
||||
response.setCharacterEncoding(this.charset.name());
|
||||
response.setCharacterEncoding(this.charset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -395,7 +395,7 @@ public class XsltView extends AbstractUrlBasedView {
|
||||
* Configure the supplied {@link HttpServletResponse}.
|
||||
* <p>The default implementation of this method sets the
|
||||
* {@link HttpServletResponse#setContentType content type} and
|
||||
* {@link HttpServletResponse#setCharacterEncoding encoding}
|
||||
* {@link HttpServletResponse#setCharacterEncoding(String) encoding}
|
||||
* from the "media-type" and "encoding" output properties
|
||||
* specified in the {@link Transformer}.
|
||||
* @param model merged output Map (never {@code null})
|
||||
|
||||
+1
-1
@@ -288,7 +288,7 @@ class ResponseBodyEmitterReturnValueHandlerTests {
|
||||
|
||||
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.webRequest);
|
||||
assertThat(asyncManager.getConcurrentResult()).isSameAs(ex);
|
||||
assertThat(this.response.getContentType()).isNull();
|
||||
assertThat(this.response.getContentType()).isEqualTo(MediaType.TEXT_EVENT_STREAM_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
* @param handshakeHeaders the headers for the WebSocket handshake
|
||||
* @param handler the session handler
|
||||
* @param uriVariables the URI variables to expand into the URL
|
||||
* @return a {@code ListenableFuture} for access to the session when ready for use
|
||||
* @return a {@code CompletableFuture} for access to the session when ready for use
|
||||
* @since 6.0
|
||||
*/
|
||||
public CompletableFuture<StompSession> connectAsync(String url, @Nullable WebSocketHttpHeaders handshakeHeaders,
|
||||
|
||||
Reference in New Issue
Block a user