Merge remote-tracking branch 'upstream/main' into issue-33355
This commit is contained in:
commit
c14ca9d199
|
@ -19,7 +19,7 @@ inputs:
|
|||
java-version:
|
||||
description: 'Java version to compile and test with'
|
||||
required: false
|
||||
default: '17'
|
||||
default: '24'
|
||||
publish:
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
required: false
|
||||
|
|
|
@ -19,7 +19,7 @@ inputs:
|
|||
java-version:
|
||||
description: 'Java version to use for the build'
|
||||
required: false
|
||||
default: '17'
|
||||
default: '24'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
|
@ -31,7 +31,7 @@ runs:
|
|||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 23
|
||||
- version: 24
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
|
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
|
|
19
build.gradle
19
build.gradle
|
@ -6,7 +6,7 @@ plugins {
|
|||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
id "io.spring.nullability" version "0.0.1" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -57,17 +57,13 @@ configure([rootProject] + javaProjects) { project ->
|
|||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params")
|
||||
testImplementation("org.junit.platform:junit-platform-suite-api")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.junit.platform:junit-platform-suite")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
// Pull in the latest JUnit 5 Launcher API to ensure proper support in IDEs.
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-suite-engine")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
}
|
||||
|
||||
|
@ -75,12 +71,11 @@ configure([rootProject] + javaProjects) { project ->
|
|||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://junit.org/junit5/docs/5.12.2/api/",
|
||||
"https://docs.junit.org/5.13.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
@ -88,7 +83,9 @@ configure([rootProject] + javaProjects) { project ->
|
|||
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
|
||||
// JakartaEE equivalents in the jakarta.annotation package.
|
||||
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
"https://jspecify.dev/docs/api/",
|
||||
"https://www.javadoc.io/doc/tools.jackson.core/jackson-databind/3.0.0-rc4/"
|
||||
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<module name="io.spring.javaformat.checkstyle.check.SpringHeaderCheck">
|
||||
<property name="fileExtensions" value="java"/>
|
||||
<property name="headerType" value="apache2"/>
|
||||
<property name="headerCopyrightPattern" value="20\d\d-20\d\d"/>
|
||||
<property name="headerCopyrightPattern" value="20\d\d-present"/>
|
||||
<property name="packageInfoHeaderType" value="none"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck"/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
|||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.23.0");
|
||||
checkstyle.setToolVersion("10.26.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
@ -26,7 +26,6 @@ import org.gradle.api.plugins.JavaPlugin;
|
|||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JvmVendorSpec;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
|
||||
|
@ -86,7 +85,6 @@ public class JavaConventions {
|
|||
*/
|
||||
private static void applyToolchainConventions(Project project) {
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
|
||||
toolchain.getLanguageVersion().set(DEFAULT_LANGUAGE_VERSION);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
@ -34,8 +34,8 @@ public class KotlinConventions {
|
|||
|
||||
private void configure(KotlinCompile compile) {
|
||||
compile.compilerOptions(options -> {
|
||||
options.getApiVersion().set(KotlinVersion.KOTLIN_2_1);
|
||||
options.getLanguageVersion().set(KotlinVersion.KOTLIN_2_1);
|
||||
options.getApiVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getLanguageVersion().set(KotlinVersion.KOTLIN_2_2);
|
||||
options.getJvmTarget().set(JvmTarget.JVM_17);
|
||||
options.getJavaParameters().set(true);
|
||||
options.getAllWarningsAsErrors().set(true);
|
||||
|
@ -43,7 +43,8 @@ public class KotlinConventions {
|
|||
"-Xsuppress-version-warnings",
|
||||
"-Xjsr305=strict", // For dependencies using JSR 305
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-Xjdk-release=17" // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
@ -62,7 +62,8 @@ class TestConventions {
|
|||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
"io.netty.leakDetection.level", "paranoid"
|
||||
"io.netty.leakDetection.level", "paranoid",
|
||||
"junit.platform.discovery.issue.severity.critical", "INFO"
|
||||
));
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -46,6 +46,7 @@ repositories {
|
|||
// To avoid a redeclaration error with Kotlin compiler
|
||||
tasks.named('compileKotlin', KotlinCompilationTask.class) {
|
||||
javaSources.from = []
|
||||
compilerOptions.freeCompilerArgs = [ "-Xannotation-default-target=param-property" ] // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -63,6 +64,7 @@ dependencies {
|
|||
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
implementation("com.github.ben-manes.caffeine:caffeine")
|
||||
implementation("com.mchange:c3p0:0.9.5.5")
|
||||
implementation("com.oracle.database.jdbc:ojdbc11")
|
||||
implementation("io.projectreactor.netty:reactor-netty-http")
|
||||
|
|
|
@ -197,6 +197,7 @@
|
|||
*** xref:web/webmvc/mvc-uri-building.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-async.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc-versioning.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webmvc/mvc-security.adoc[]
|
||||
*** xref:web/webmvc/mvc-caching.adoc[]
|
||||
|
@ -225,6 +226,7 @@
|
|||
**** xref:web/webmvc/mvc-config/static-resources.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/default-servlet-handler.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/path-matching.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/api-version.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-java.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-xml.adoc[]
|
||||
*** xref:web/webmvc/mvc-http2.adoc[]
|
||||
|
@ -292,6 +294,7 @@
|
|||
*** xref:web/webflux-functional.adoc[]
|
||||
*** xref:web/webflux/uri-building.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux-versioning.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webflux/security.adoc[]
|
||||
*** xref:web/webflux/caching.adoc[]
|
||||
|
|
|
@ -103,6 +103,14 @@ for details.
|
|||
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
||||
for details.
|
||||
|
||||
| `spring.placeholder.escapeCharacter.default`
|
||||
| The default escape character for property placeholder support. If not set, `'\'` will
|
||||
be used. Can be set to a custom escape character or an empty string to disable support
|
||||
for an escape character. The default escape character be explicitly overridden in
|
||||
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
|
||||
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
|
||||
for details.
|
||||
|
||||
| `spring.test.aot.processing.failOnError`
|
||||
| A boolean flag that controls whether errors encountered during AOT processing in the
|
||||
_Spring TestContext Framework_ should result in an exception that fails the overall process.
|
||||
|
|
|
@ -101,8 +101,11 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
|||
|
||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||
placeholder could not be resolved. It is also possible to use methods like
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
||||
`setEscapeCharacter` to customize placeholders.
|
||||
`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or
|
||||
`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default
|
||||
escape character can be changed or disabled globally by setting the
|
||||
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
|
||||
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
|
||||
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||
will get properties from `application.properties` and `application.yml` files.
|
||||
|
|
|
@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the
|
|||
|
||||
|
||||
[[beans-factory-placeholderconfigurer]]
|
||||
=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
|
||||
=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer`
|
||||
|
||||
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
|
||||
from a bean definition in a separate file by using the standard Java `Properties` format.
|
||||
|
@ -341,8 +341,8 @@ with placeholder values is defined:
|
|||
|
||||
The example shows properties configured from an external `Properties` file. At runtime,
|
||||
a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some
|
||||
properties of the DataSource. The values to replace are specified as placeholders of the
|
||||
form pass:q[`${property-name}`], which follows the Ant and log4j and JSP EL style.
|
||||
properties of the `DataSource`. The values to replace are specified as placeholders of the
|
||||
form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style.
|
||||
|
||||
The actual values come from another file in the standard Java `Properties` format:
|
||||
|
||||
|
@ -355,11 +355,15 @@ jdbc.password=root
|
|||
----
|
||||
|
||||
Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and
|
||||
the same applies for other placeholder values that match keys in the properties file.
|
||||
The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
||||
attributes of a bean definition. Furthermore, you can customize the placeholder prefix and suffix.
|
||||
the same applies for other placeholder values that match keys in the properties file. The
|
||||
`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
||||
attributes of a bean definition. Furthermore, you can customize the placeholder prefix,
|
||||
suffix, default value separator, and escape character. In addition, the default escape
|
||||
character can be changed or disabled globally by setting the
|
||||
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
|
||||
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
|
||||
With the `context` namespace introduced in Spring 2.5, you can configure property placeholders
|
||||
With the `context` namespace, you can configure property placeholders
|
||||
with a dedicated configuration element. You can provide one or more locations as a
|
||||
comma-separated list in the `location` attribute, as the following example shows:
|
||||
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
[[null-safety]]
|
||||
= Null-safety
|
||||
|
||||
Although Java does not let you express null-safety with its type system, the Spring Framework codebase is annotated with
|
||||
https://jspecify.dev/docs/start-here/[JSpecify] annotations to declare the nullness of APIs, fields and related type
|
||||
usages. Reading the https://jspecify.dev/docs/user-guide/[JSpecify user guide] is highly recommended in order to get
|
||||
familiar with those annotations and semantics.
|
||||
Although Java does not let you express nullness markers with its type system yet, the Spring Framework codebase is
|
||||
annotated with https://jspecify.dev/docs/start-here/[JSpecify] annotations to declare the nullability of its APIs,
|
||||
fields, and related type usages. Reading the https://jspecify.dev/docs/user-guide/[JSpecify user guide] is highly
|
||||
recommended in order to get familiar with those annotations and semantics.
|
||||
|
||||
The primary goal of this explicit null-safety arrangement is to prevent `NullPointerException` to be thrown at runtime via
|
||||
build time checks and to turn explicit nullness into a way to express the possible absence of value. It is useful in
|
||||
both Java by leveraging some tooling (https://github.com/uber/NullAway[NullAway] or IDEs supporting null-safety
|
||||
annotations such as IntelliJ IDEA or Eclipse) and Kotlin where JSpecify annotations are automatically translated to
|
||||
The primary goal of this null-safety arrangement is to prevent a `NullPointerException` from being thrown at runtime via build
|
||||
time checks and to use explicit nullability as a way to express the possible absence of value. It is useful in both
|
||||
Java by leveraging some tooling (https://github.com/uber/NullAway[NullAway] or IDEs supporting JSpecify annotations
|
||||
such as IntelliJ IDEA) and Kotlin where JSpecify annotations are automatically translated to
|
||||
{kotlin-docs}/null-safety.html[Kotlin's null safety].
|
||||
|
||||
The {spring-framework-api}/core/Nullness.html[`Nullness` Spring API] can be used at runtime to detect the nullness of a
|
||||
type usage, a field, a method return type or a parameter. It provides full support for JSpecify annotations,
|
||||
Kotlin null safety, Java primitive types, as well as a pragmatic check on any `@Nullable` annotation (regardless of the
|
||||
type usage, a field, a method return type, or a parameter. It provides full support for JSpecify annotations,
|
||||
Kotlin null safety, and Java primitive types, as well as a pragmatic check on any `@Nullable` annotation (regardless of the
|
||||
package).
|
||||
|
||||
[[null-safety-libraries]]
|
||||
== Annotating libraries with JSpecify annotations
|
||||
|
||||
As of Spring Framework 7, the Spring Framework codebase leverages JSpecify annotations to expose null-safe APIs and
|
||||
to check the consistency of those null-safety declarations with https://github.com/uber/NullAway[NullAway] as part of
|
||||
its build. It is recommended for each library depending on Spring Framework (Spring portfolio projects), as
|
||||
well as other libraries related to the Spring ecosystem (Reactor, Micrometer and Spring community projects), to do the
|
||||
to check the consistency of those nullability declarations with https://github.com/uber/NullAway[NullAway] as part of
|
||||
its build. It is recommended for each library depending on Spring Framework and Spring portfolio projects, as
|
||||
well as other libraries related to the Spring ecosystem (Reactor, Micrometer, and Spring community projects), to do the
|
||||
same.
|
||||
|
||||
[[null-safety-applications]]
|
||||
== Leveraging JSpecify annotations in Spring applications
|
||||
|
||||
Developing applications with IDEs supporting null-safety annotations, such as IntelliJ IDEA or Eclipse, will provide
|
||||
warnings in Java and errors in Kotlin when the null-safety contracts are not honored, allowing Spring application
|
||||
developers to refine their null handling to prevent `NullPointerException` to be thrown at runtime.
|
||||
Developing applications with IDEs that support nullness annotations will provide warnings in Java and errors in Kotlin
|
||||
when the nullability contracts are not honored, allowing Spring application developers to refine their null handling to
|
||||
prevent a `NullPointerException` from being thrown at runtime.
|
||||
|
||||
Optionally, Spring application developers can annotate their codebase and use https://github.com/uber/NullAway[NullAway]
|
||||
to enforce null-safety during build time at application level.
|
||||
Optionally, Spring application developers can annotate their codebase and use build plugins like
|
||||
https://github.com/uber/NullAway[NullAway] to enforce null-safety at the application level during build time.
|
||||
|
||||
[[null-safety-guidelines]]
|
||||
== Guidelines
|
||||
|
||||
The purpose of this section is to share some guidelines proposed for specifying explicitly the nullness of Spring-related
|
||||
libraries or applications.
|
||||
The purpose of this section is to share some proposed guidelines for explicitly specifying the nullability of
|
||||
Spring-related libraries or applications.
|
||||
|
||||
|
||||
[[null-safety-guidelines-jpecify]]
|
||||
[[null-safety-guidelines-jspecify]]
|
||||
=== JSpecify
|
||||
|
||||
The key points to understand is that by default, the nullness of types is unknown in Java, and that non-null type
|
||||
usages are by far more frequent than nullable ones. In order to keep codebases readable, we typically want to define
|
||||
that by default, type usages are non-null unless marked as nullable for a specific scope. This is exactly the purpose of
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/NullMarked.html[`@NullMarked`] that is typically set with Spring
|
||||
at package level via a `package-info.java` file, for example:
|
||||
The key points to understand are that the nullness of types is unknown in Java by default and that non-null type
|
||||
usage is by far more frequent than nullable usage. In order to keep codebases readable, we typically want to define
|
||||
by default that type usage is non-null unless marked as nullable for a specific scope. This is exactly the purpose of
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/NullMarked.html[`@NullMarked`] which is typically set in Spring
|
||||
projects at the package level via a `package-info.java` file, for example:
|
||||
|
||||
[source,java,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
----
|
||||
|
@ -60,9 +60,9 @@ package org.springframework.core;
|
|||
import org.jspecify.annotations.NullMarked;
|
||||
----
|
||||
|
||||
In the various Java files belonging to the package, nullable type usages are defined explicitly with
|
||||
In the various Java files belonging to the package, nullable type usage is defined explicitly with
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/Nullable.html[`@Nullable`]. It is recommended that this
|
||||
annotation is specified just before the related type.
|
||||
annotation is specified just before the related type on the same line.
|
||||
|
||||
For example, for a field:
|
||||
|
||||
|
@ -71,7 +71,7 @@ For example, for a field:
|
|||
private @Nullable String fileEncoding;
|
||||
----
|
||||
|
||||
Or for method parameters and return value:
|
||||
Or for method parameters and method return types:
|
||||
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -81,20 +81,23 @@ public static @Nullable String buildMessage(@Nullable String message,
|
|||
}
|
||||
----
|
||||
|
||||
When overriding a method, nullness annotations are not inherited from the superclass method. That means those
|
||||
nullness annotations should be repeated if you just want to override the implementation and keep the same API
|
||||
nullness.
|
||||
[NOTE]
|
||||
====
|
||||
When overriding a method, JSpecify annotations are not inherited from the original
|
||||
method. That means the JSpecify annotations should be copied to the overriding method if
|
||||
you want to override the implementation and keep the same nullability semantics.
|
||||
====
|
||||
|
||||
With arrays and varargs, you need to be able to differentiate the nullness of the elements from the nullness of
|
||||
the array itself. Pay attention to the syntax
|
||||
https://docs.oracle.com/javase/specs/jls/se17/html/jls-9.html#jls-9.7.4[defined by the Java specification] which may be
|
||||
initially surprising:
|
||||
|
||||
- `@Nullable Object[] array` means individual elements can be null but the array itself can't.
|
||||
- `Object @Nullable [] array` means individual elements can't be null but the array itself can.
|
||||
- `@Nullable Object[] array` means individual elements can be null but the array itself cannot.
|
||||
- `Object @Nullable [] array` means individual elements cannot be null but the array itself can.
|
||||
- `@Nullable Object @Nullable [] array` means both individual elements and the array can be null.
|
||||
|
||||
The Java specifications also enforces that annotations defined with `@Target(ElementType.TYPE_USE)` like JSpecify
|
||||
The Java specification also enforces that annotations defined with `@Target(ElementType.TYPE_USE)` like JSpecify
|
||||
`@Nullable` should be specified after the last `.` with inner or fully qualified types:
|
||||
|
||||
- `Cache.@Nullable ValueWrapper`
|
||||
|
@ -111,15 +114,15 @@ typical use cases.
|
|||
|
||||
The recommended configuration is:
|
||||
|
||||
- `NullAway:OnlyNullMarked=true` in order to perform nullness checks only for packages annotated with `@NullMarked`.
|
||||
- `NullAway:OnlyNullMarked=true` in order to perform nullability checks only for packages annotated with `@NullMarked`.
|
||||
- `NullAway:CustomContractAnnotations=org.springframework.lang.Contract` which makes NullAway aware of the
|
||||
{spring-framework-api}/lang/Contract.html[@Contract] annotation in the `org.springframework.lang` package which
|
||||
can be used to express complementary semantics to avoid non-relevant null-safety warnings in your codebase.
|
||||
can be used to express complementary semantics to avoid irrelevant warnings in your codebase.
|
||||
|
||||
A good example of `@Contract` benefits is
|
||||
{spring-framework-api}/util/Assert.html#notNull(java.lang.Object,java.lang.String)[`Assert#notnull`] which is annotated
|
||||
with `@Contract("null, _ -> fail")`. With the configuration above, NullAway will understand that after a successful
|
||||
invocation, the value passed as a parameter is not null.
|
||||
A good example of the benefits of a `@Contract` declaration can be seen with
|
||||
{spring-framework-api}/util/Assert.html#notNull(java.lang.Object,java.lang.String)[`Assert.notNull()`] which is annotated
|
||||
with `@Contract("null, _ -> fail")`. With that contract declaration, NullAway will understand that the value passed as a
|
||||
parameter cannot be null after a successful invocation of `Assert.notNull()`.
|
||||
|
||||
Optionally, it is possible to set `NullAway:JSpecifyMode=true` to enable
|
||||
https://github.com/uber/NullAway/wiki/JSpecify-Support[checks on the full JSpecify semantics], including annotations on
|
||||
|
@ -127,26 +130,26 @@ generic types. Be aware that this mode is
|
|||
https://github.com/uber/NullAway/issues?q=is%3Aissue+is%3Aopen+label%3Ajspecify[still under development] and requires
|
||||
using JDK 22 or later (typically combined with the `--release` Java compiler flag to configure the
|
||||
expected baseline). It is recommended to enable the JSpecify mode only as a second step, after making sure the codebase
|
||||
generates no warning with the recommended configuration mentioned above.
|
||||
generates no warning with the recommended configuration mentioned previously in this section.
|
||||
|
||||
==== Warnings suppression
|
||||
|
||||
There are a few valid use cases where NullAway will wrongly detect nullness problems. In such case, it is recommended
|
||||
There are a few valid use cases where NullAway will incorrectly detect nullability problems. In such case, it is recommended
|
||||
to suppress related warnings and to document the reason:
|
||||
|
||||
- `@SuppressWarnings("NullAway.Init")` at field, constructor or class level can be used to avoid unnecessary warnings
|
||||
due to the lazy initialization of fields, for example due to a class implementing
|
||||
- `@SuppressWarnings("NullAway.Init")` at field, constructor, or class level can be used to avoid unnecessary warnings
|
||||
due to the lazy initialization of fields – for example, due to a class implementing
|
||||
{spring-framework-api}/beans/factory/InitializingBean.html[`InitializingBean`].
|
||||
- `@SuppressWarnings("NullAway") // Dataflow analysis limitation` can be used when NullAway dataflow analysis is not
|
||||
able to detect that the path involving a nullness problem will never happen.
|
||||
able to detect that the path involving a nullability problem will never happen.
|
||||
- `@SuppressWarnings("NullAway") // Lambda` can be used when NullAway does not take into account assertions performed
|
||||
outside of a lambda for the code path within the lambda.
|
||||
- `@SuppressWarnings("NullAway") // Reflection` can be used for some reflection operations that are known returning
|
||||
non-null values even if that can't be expressed by the API.
|
||||
- `@SuppressWarnings("NullAway") // Well-known map keys` can be used when `Map#get` invocations are done with keys known
|
||||
to be present and non-null related values inserted previously.
|
||||
- `@SuppressWarnings("NullAway") // Overridden method does not define nullness` can be used when the super class does
|
||||
not define nullness (typically when the super class is coming from a dependency).
|
||||
- `@SuppressWarnings("NullAway") // Reflection` can be used for some reflection operations that are known to return
|
||||
non-null values even if that cannot be expressed by the API.
|
||||
- `@SuppressWarnings("NullAway") // Well-known map keys` can be used when `Map#get` invocations are performed with keys that are known
|
||||
to be present and when non-null related values have been inserted previously.
|
||||
- `@SuppressWarnings("NullAway") // Overridden method does not define nullability` can be used when the superclass does
|
||||
not define nullability (typically when the superclass comes from a dependency).
|
||||
|
||||
|
||||
[[null-safety-migrating]]
|
||||
|
@ -155,30 +158,30 @@ not define nullness (typically when the super class is coming from a dependency)
|
|||
Spring null-safety annotations {spring-framework-api}/lang/Nullable.html[`@Nullable`],
|
||||
{spring-framework-api}/lang/NonNull.html[`@NonNull`],
|
||||
{spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`], and
|
||||
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package have been
|
||||
introduced in Spring Framework 5 when JSpecify did not exist and the best option was to leverage JSR 305 (a dormant
|
||||
but widespread JSR) meta-annotations. They are deprecated as of Spring Framework 7 in favor of
|
||||
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package were
|
||||
introduced in Spring Framework 5 when JSpecify did not exist, and the best option at that time was to leverage
|
||||
meta-annotations from JSR 305 (a dormant but widespread JSR). They are deprecated as of Spring Framework 7 in favor of
|
||||
https://jspecify.dev/docs/start-here/[JSpecify] annotations, which provide significant enhancements such as properly
|
||||
defined specifications, a canonical dependency with no split-package issue, better tooling, better Kotlin integration
|
||||
and the capability to specify the nullness more precisely for more use cases.
|
||||
defined specifications, a canonical dependency with no split-package issues, better tooling, better Kotlin integration,
|
||||
and the capability to specify nullability more precisely for more use cases.
|
||||
|
||||
A key difference is that Spring null-safety annotations, following JSR 305 semantics, apply to fields,
|
||||
parameters and return values while JSpecify annotations apply to type usages. This subtle difference
|
||||
is in practice pretty significant, as it allows for example to differentiate the nullness of elements from the
|
||||
nullness of arrays/varargs as well as defining the nullness of generic types.
|
||||
A key difference is that Spring's deprecated null-safety annotations, which follow JSR 305 semantics, apply to fields,
|
||||
parameters, and return values; while JSpecify annotations apply to type usage. This subtle difference
|
||||
is in practice pretty significant, as it allows developers to differentiate between the nullness of elements and the
|
||||
nullness of arrays/varargs as well as to define the nullness of generic types.
|
||||
|
||||
That means array and varargs null-safety declarations have to be updated to keep the same semantic. For example
|
||||
That means array and varargs null-safety declarations have to be updated to keep the same semantics. For example
|
||||
`@Nullable Object[] array` with Spring annotations needs to be changed to `Object @Nullable [] array` with JSpecify
|
||||
annotations. Same for varargs.
|
||||
annotations. The same applies to varargs.
|
||||
|
||||
It is also recommended to move field and return value annotations closer to the type, for example:
|
||||
It is also recommended to move field and return value annotations closer to the type and on the same line, for example:
|
||||
|
||||
- For fields, instead of `@Nullable private String field` with Spring annotations, use `private @Nullable String field`
|
||||
with JSpecify annotations.
|
||||
- For return values, instead of `@Nullable public String method()` with Spring annotations, use
|
||||
- For method return types, instead of `@Nullable public String method()` with Spring annotations, use
|
||||
`public @Nullable String method()` with JSpecify annotations.
|
||||
|
||||
Also, with JSpecify, you don't need to specify `@NonNull` when overriding a type usage annotated with `@Nullable` in the
|
||||
Also, with JSpecify, you do not need to specify `@NonNull` when overriding a type usage annotated with `@Nullable` in the
|
||||
super method to "undo" the nullable declaration in null-marked code. Just declare it unannotated and the null-marked
|
||||
defaults (a type usage is considered non-null unless explicitly annotated as nullable) will apply.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[[orm-hibernate]]
|
||||
= Hibernate
|
||||
|
||||
We start with a coverage of https://hibernate.org/[Hibernate 5] in a Spring environment,
|
||||
We start with a coverage of https://hibernate.org/[Hibernate] in a Spring environment,
|
||||
using it to demonstrate the approach that Spring takes towards integrating OR mappers.
|
||||
This section covers many issues in detail and shows different variations of DAO
|
||||
implementations and transaction demarcation. Most of these patterns can be directly
|
||||
|
@ -10,13 +10,12 @@ cover the other ORM technologies and show brief examples.
|
|||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 6.0, Spring requires Hibernate ORM 5.5+ for Spring's
|
||||
As of Spring Framework 7.0, Spring requires Hibernate ORM 7.0 for Spring's
|
||||
`HibernateJpaVendorAdapter` as well as for a native Hibernate `SessionFactory` setup.
|
||||
We recommend Hibernate ORM 5.6 as the last feature branch in that Hibernate generation.
|
||||
|
||||
Hibernate ORM 6.x is only supported as a JPA provider (`HibernateJpaVendorAdapter`).
|
||||
Plain `SessionFactory` setup with the `orm.hibernate5` package is not supported anymore.
|
||||
We recommend Hibernate ORM 6.1/6.2 with JPA-style setup for new development projects.
|
||||
The `org.springframework.orm.jpa.hibernate` package supersedes the former `orm.hibernate5`:
|
||||
now for use with Hibernate ORM 7.0, tightly integrated with `HibernateJpaVendorAdapter`
|
||||
as well as supporting Hibernate's native `SessionFactory.getCurrentSession()` style.
|
||||
====
|
||||
|
||||
|
||||
|
@ -43,7 +42,7 @@ JDBC `DataSource` and a Hibernate `SessionFactory` on top of it:
|
|||
<property name="password" value=""/>
|
||||
</bean>
|
||||
|
||||
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<bean id="mySessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="myDataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
|
@ -271,7 +270,7 @@ processing at runtime. The following example shows how to do so:
|
|||
<!-- SessionFactory, DataSource, etc. omitted -->
|
||||
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="sessionFactory"/>
|
||||
</bean>
|
||||
|
||||
|
@ -301,7 +300,7 @@ and an example for a business method implementation:
|
|||
----
|
||||
<beans>
|
||||
|
||||
<bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<bean id="myTxManager" class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="mySessionFactory"/>
|
||||
</bean>
|
||||
|
||||
|
|
|
@ -212,7 +212,7 @@ example declares `sessionFactory` and `txManager` beans:
|
|||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
|
@ -226,7 +226,7 @@ example declares `sessionFactory` and `txManager` beans:
|
|||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<bean id="txManager" class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="sessionFactory"/>
|
||||
</bean>
|
||||
----
|
||||
|
@ -238,7 +238,7 @@ transaction coordinator and possibly also its connection release mode configurat
|
|||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
|
@ -262,7 +262,7 @@ for enforcing the same defaults:
|
|||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
|
|
|
@ -3,26 +3,34 @@
|
|||
|
||||
The Spring Framework provides the following choices for making calls to REST endpoints:
|
||||
|
||||
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] - synchronous client with a fluent API.
|
||||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] - non-blocking, reactive client with fluent API.
|
||||
* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] - synchronous client with template method API.
|
||||
* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] - annotated interface with generated, dynamic proxy implementation.
|
||||
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] -- synchronous client with a fluent API
|
||||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] -- non-blocking, reactive client with fluent API
|
||||
* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] -- synchronous client with template method API
|
||||
* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface Clients] -- annotated interface backed by generated proxy
|
||||
|
||||
|
||||
[[rest-restclient]]
|
||||
== `RestClient`
|
||||
|
||||
The `RestClient` is a synchronous HTTP client that offers a modern, fluent API.
|
||||
It offers an abstraction over HTTP libraries that allows for convenient conversion from a Java object to an HTTP request, and the creation of objects from an HTTP response.
|
||||
`RestClient` is a synchronous HTTP client that provides a fluent API to perform requests.
|
||||
It serves as an abstraction over HTTP libraries, and handles conversion of HTTP request and response content to and from higher level Java objects.
|
||||
|
||||
=== Creating a `RestClient`
|
||||
=== Create a `RestClient`
|
||||
|
||||
The `RestClient` is created using one of the static `create` methods.
|
||||
You can also use `builder()` to get a builder with further options, such as specifying which HTTP library to use (see <<rest-request-factories>>) and which message converters to use (see <<rest-message-conversion>>), setting a default URI, default path variables, default request headers, or `uriBuilderFactory`, or registering interceptors and initializers.
|
||||
`RestClient` has static `create` shortcut methods.
|
||||
It also exposes a `builder()` with further options:
|
||||
|
||||
Once created (or built), the `RestClient` can be used safely by multiple threads.
|
||||
- select the HTTP library to use, see <<rest-request-factories>>
|
||||
- configure message converters, see <<rest-message-conversion>>
|
||||
- set a baseUrl
|
||||
- set default request headers, cookies, path variables, API version
|
||||
- configure an `ApiVersionInserter`
|
||||
- register interceptors
|
||||
- register request initializers
|
||||
|
||||
The following sample shows how to create a default `RestClient`, and how to build a custom one.
|
||||
Once created, a `RestClient` is safe to use in multiple threads.
|
||||
|
||||
The below shows how to create or build a `RestClient`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -39,6 +47,8 @@ Java::
|
|||
.defaultUriVariables(Map.of("variable", "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.defaultVersion("1.2")
|
||||
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build();
|
||||
|
@ -57,23 +67,25 @@ Kotlin::
|
|||
.defaultUriVariables(mapOf("variable" to "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.defaultVersion("1.2")
|
||||
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
=== Using the `RestClient`
|
||||
=== Use the `RestClient`
|
||||
|
||||
When making an HTTP request with the `RestClient`, the first thing to specify is which HTTP method to use.
|
||||
This can be done with `method(HttpMethod)` or with the convenience methods `get()`, `head()`, `post()`, and so on.
|
||||
To perform an HTTP request, first specify the HTTP method to use.
|
||||
Use the convenience methods like `get()`, `head()`, `post()`, and others, or `method(HttpMethod)`.
|
||||
|
||||
==== Request URL
|
||||
|
||||
Next, the request URI can be specified with the `uri` methods.
|
||||
This step is optional and can be skipped if the `RestClient` is configured with a default URI.
|
||||
Next, specify the request URI with the `uri` methods.
|
||||
This is optional, and you can skip this step if you configured a baseUrl through the builder.
|
||||
The URL is typically specified as a `String`, with optional URI template variables.
|
||||
The following example configures a GET request to `https://example.com/orders/42`:
|
||||
The following shows how to perform a request:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -108,6 +120,7 @@ For more details on working with and encoding URIs, see xref:web/webmvc/mvc-uri-
|
|||
|
||||
If necessary, the HTTP request can be manipulated by adding request headers with `header(String, String)`, `headers(Consumer<HttpHeaders>`, or with the convenience methods `accept(MediaType...)`, `acceptCharset(Charset...)` and so on.
|
||||
For HTTP requests that can contain a body (`POST`, `PUT`, and `PATCH`), additional methods are available: `contentType(MediaType)`, and `contentLength(long)`.
|
||||
You can set an API version for the request if the client is configured with `ApiVersionInserter`.
|
||||
|
||||
The request body itself can be set by `body(Object)`, which internally uses <<rest-message-conversion>>.
|
||||
Alternatively, the request body can be set using a `ParameterizedTypeReference`, allowing you to use generics.
|
||||
|
@ -844,15 +857,17 @@ It can be used to migrate from the latter to the former.
|
|||
|
||||
|
||||
[[rest-http-interface]]
|
||||
== HTTP Interface
|
||||
== HTTP Interface Clients
|
||||
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with
|
||||
`@HttpExchange` methods. You can pass such an interface to `HttpServiceProxyFactory`
|
||||
to create a proxy which performs requests through an HTTP client such as `RestClient`
|
||||
or `WebClient`. You can also implement the interface from an `@Controller` for server
|
||||
request handling.
|
||||
You can define an HTTP Service as a Java interface with `@HttpExchange` methods, and use
|
||||
`HttpServiceProxyFactory` to create a client proxy from it for remote access over HTTP via
|
||||
`RestClient`, `WebClient`, or `RestTemplate`. On the server side, an `@Controller` class
|
||||
can implement the same interface to handle requests with
|
||||
xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[@HttpExchange]
|
||||
controller methods.
|
||||
|
||||
Start by creating the interface with `@HttpExchange` methods:
|
||||
|
||||
First, create the Java interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -866,43 +881,7 @@ Start by creating the interface with `@HttpExchange` methods:
|
|||
}
|
||||
----
|
||||
|
||||
Now you can create a proxy that performs requests when methods are called.
|
||||
|
||||
For `RestClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
For `WebClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
For `RestTemplate`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/"));
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
`@HttpExchange` is supported at the type level where it applies to all methods:
|
||||
Optionally, use `@HttpExchange` at the type level to declare common attributes for all methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -920,15 +899,46 @@ For `RestTemplate`:
|
|||
----
|
||||
|
||||
|
||||
Next, configure the client and create the `HttpServiceProxyFactory`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Using RestClient...
|
||||
|
||||
RestClient restClient = RestClient.create("...");
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
|
||||
// or WebClient...
|
||||
|
||||
WebClient webClient = WebClient.create("...");
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
|
||||
// or RestTemplate...
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
Now, you're ready to create client proxies:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
// Use service methods for remote calls...
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[rest-http-interface-method-parameters]]
|
||||
=== Method Parameters
|
||||
|
||||
Annotated, HTTP exchange methods support flexible method signatures with the following
|
||||
method parameters:
|
||||
`@HttpExchange` methods support flexible method signatures with the following inputs:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method argument | Description
|
||||
| Method parameter | Description
|
||||
|
||||
| `URI`
|
||||
| Dynamically set the URL for the request, overriding the annotation's `url` attribute.
|
||||
|
@ -989,29 +999,33 @@ Method parameters cannot be `null` unless the `required` attribute (where availa
|
|||
parameter annotation) is set to `false`, or the parameter is marked optional as determined by
|
||||
{spring-framework-api}/core/MethodParameter.html#isOptional()[`MethodParameter#isOptional`].
|
||||
|
||||
`RestClientAdapter` provides additional support for a method parameter of type
|
||||
`StreamingHttpOutputMessage.Body` that allows sending the request body by writing to an
|
||||
`OutputStream`.
|
||||
|
||||
|
||||
|
||||
[[rest-http-interface.custom-resolver]]
|
||||
=== Custom argument resolver
|
||||
=== Custom Arguments
|
||||
|
||||
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters.
|
||||
This would take over the entire HTTP request and not improve the semantics of the interface.
|
||||
Instead of adding many method parameters, developers can combine them into a custom type
|
||||
and configure a dedicated `HttpServiceArgumentResolver` implementation.
|
||||
|
||||
In the following HTTP interface, we are using a custom `Search` type as a parameter:
|
||||
You can configure a custom `HttpServiceArgumentResolver`. The example interface below
|
||||
uses a custom `Search` method parameter type:
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
|
||||
|
||||
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type
|
||||
and writes its data in the outgoing HTTP request.
|
||||
A custom argument resolver could be implemented like this:
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0]
|
||||
|
||||
Finally, we can use this argument resolver during the setup and use our HTTP interface.
|
||||
To configure the custom argument resolver:
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0]
|
||||
|
||||
TIP: By default, `RequestEntity` is not supported as a method parameter, instead encouraging
|
||||
the use of more fine-grained method parameters for individual parts of the request.
|
||||
|
||||
|
||||
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
|
@ -1084,65 +1098,180 @@ depends on how the underlying HTTP client is configured. You can set a `blockTim
|
|||
value on the adapter level as well, but we recommend relying on timeout settings of the
|
||||
underlying HTTP client, which operates at a lower level and provides more control.
|
||||
|
||||
`RestClientAdapter` provides supports additional support for a return value of type
|
||||
`InputStream` or `ResponseEntity<InputStream>` that provides access to the raw response
|
||||
body content.
|
||||
|
||||
|
||||
|
||||
[[rest-http-interface-exceptions]]
|
||||
=== Error Handling
|
||||
|
||||
To customize error response handling, you need to configure the underlying HTTP client.
|
||||
|
||||
For `RestClient`:
|
||||
|
||||
By default, `RestClient` raises `RestClientException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register a response status handler that applies to all responses
|
||||
performed through the client:
|
||||
To customize error handling for HTTP Service client proxies, you can configure the
|
||||
underlying client as needed. By default, clients raise an exception for 4xx and 5xx HTTP
|
||||
status codes. To customize this, register a response status handler that applies to all
|
||||
responses performed through the client as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// For RestClient
|
||||
RestClient restClient = RestClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
|
||||
.build();
|
||||
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `RestClient.Builder`.
|
||||
|
||||
For `WebClient`:
|
||||
|
||||
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register a response status handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// or for WebClient...
|
||||
WebClient webClient = WebClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
|
||||
.build();
|
||||
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `WebClient.Builder`.
|
||||
|
||||
For `RestTemplate`:
|
||||
|
||||
By default, `RestTemplate` raises `RestClientException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register an error handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// or for RestTemplate...
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(myErrorHandler);
|
||||
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and
|
||||
the `ResponseErrorHandler` hierarchy.
|
||||
For more details and options such as suppressing error status codes, see the reference
|
||||
documentation for each client, as well as the Javadoc of `defaultStatusHandler` in
|
||||
`RestClient.Builder` or `WebClient.Builder`, and the `setErrorHandler` of `RestTemplate`.
|
||||
|
||||
|
||||
|
||||
[[rest-http-interface-group-config]]
|
||||
=== HTTP Interface Groups
|
||||
|
||||
It's trivial to create client proxies with `HttpServiceProxyFactory`, but to have them
|
||||
declared as beans leads to repetitive configuration. You may also have multiple
|
||||
target hosts, and therefore multiple clients to configure, and even more client proxy
|
||||
beans to create.
|
||||
|
||||
To make it easier to work with interface clients at scale the Spring Framework provides
|
||||
dedicated configuration support. It lets applications focus on identifying HTTP Services
|
||||
by group, and customizing the client for each group, while the framework transparently
|
||||
creates a registry of client proxies, and declares each proxy as a bean.
|
||||
|
||||
An HTTP Service group is simply a set of interfaces that share the same client setup and
|
||||
`HttpServiceProxyFactory` instance to create proxies. Typically, that means one group per
|
||||
host, but you can have more than one group for the same target host in case the
|
||||
underlying client needs to be configured differently.
|
||||
|
||||
One way to declare HTTP Service groups is via `@ImportHttpServices` annotations in
|
||||
`@Configuration` classes as shown below:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class}) // <1>
|
||||
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) // <2>
|
||||
public class ClientConfig {
|
||||
}
|
||||
|
||||
----
|
||||
<1> Manually list interfaces for group "echo"
|
||||
<2> Detect interfaces for group "greeting" under a base package
|
||||
|
||||
It is also possible to declare groups programmatically by creating an HTTP Service
|
||||
registrar and then importing it:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MyHttpServiceRegistrar extends AbstractHttpServiceRegistrar { // <1>
|
||||
|
||||
@Override
|
||||
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
|
||||
registry.forGroup("echo").register(EchoServiceA.class, EchoServiceB.class); // <2>
|
||||
registry.forGroup("greeting").detectInBasePackages(GreetServiceA.class); // <3>
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(MyHttpServiceRegistrar.class) // <4>
|
||||
public class ClientConfig {
|
||||
}
|
||||
|
||||
----
|
||||
<1> Create extension class of `AbstractHttpServiceRegistrar`
|
||||
<2> Manually list interfaces for group "echo"
|
||||
<3> Detect interfaces for group "greeting" under a base package
|
||||
<4> Import the registrar
|
||||
|
||||
TIP: You can mix and match `@ImportHttpService` annotations with programmatic registrars,
|
||||
and you can spread the imports across multiple configuration classes. All imports
|
||||
contribute collaboratively the same, shared `HttpServiceProxyRegistry` instance.
|
||||
|
||||
Once HTTP Service groups are declared, add an `HttpServiceGroupConfigurer` bean to
|
||||
customize the client for each group. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class})
|
||||
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class)
|
||||
public class ClientConfig {
|
||||
|
||||
@Bean
|
||||
public RestClientHttpServiceGroupConfigurer groupConfigurer() {
|
||||
return groups -> {
|
||||
// configure client for group "echo"
|
||||
groups.filterByName("echo").forEachClient((group, clientBuilder) -> ...);
|
||||
|
||||
// configure the clients for all groups
|
||||
groups.forEachClient((group, clientBuilder) -> ...);
|
||||
|
||||
// configure client and proxy factory for each group
|
||||
groups.forEachGroup((group, clientBuilder, factoryBuilder) -> ...);
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
TIP: Spring Boot uses an `HttpServiceGroupConfigurer` to add support for client properties
|
||||
by HTTP Service group, Spring Security to add OAuth support, and Spring Cloud to add load
|
||||
balancing.
|
||||
|
||||
As a result of the above, each client proxy is available as a bean that you can
|
||||
conveniently autowire by type:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
public class EchoController {
|
||||
|
||||
private final EchoService echoService;
|
||||
|
||||
public EchoController(EchoService echoService) {
|
||||
this.echoService = echoService;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
However, if there are multiple client proxies of the same type, e.g. the same interface
|
||||
in multiple groups, then there is no unique bean of that type, and you cannot autowire by
|
||||
type only. For such cases, you can work directly with the `HttpServiceProxyRegistry` that
|
||||
holds all proxies, and obtain the ones you need by group:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
public class EchoController {
|
||||
|
||||
private final EchoService echoService1;
|
||||
|
||||
private final EchoService echoService2;
|
||||
|
||||
public EchoController(HttpServiceProxyRegistry registry) {
|
||||
this.echoService1 = registry.getClient("echo1", EchoService.class); // <1>
|
||||
this.echoService2 = registry.getClient("echo2", EchoService.class); // <2>
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Access the `EchoService` client proxy for group "echo1"
|
||||
<2> Access the `EchoService` client proxy for group "echo2"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
= Requirements
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Framework supports Kotlin 2.1+ and requires
|
||||
Spring Framework supports Kotlin 2.2+ and requires
|
||||
https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`]
|
||||
and https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`]
|
||||
to be present on the classpath. They are provided by default if you bootstrap a Kotlin project on
|
||||
|
|
|
@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use
|
|||
instead of `@Value` annotations.
|
||||
|
||||
As an alternative, you can customize the property placeholder prefix by declaring the
|
||||
following configuration beans:
|
||||
following `PropertySourcesPlaceholderConfigurer` bean:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
|
@ -200,8 +200,10 @@ following configuration beans:
|
|||
}
|
||||
----
|
||||
|
||||
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
|
||||
that uses the `${...}` syntax, with configuration beans, as the following example shows:
|
||||
You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use
|
||||
the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by
|
||||
declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example
|
||||
shows:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
|
@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl
|
|||
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
|
||||
----
|
||||
|
||||
In addition, the default escape character can be changed or disabled globally by setting
|
||||
the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or
|
||||
via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
|
||||
|
||||
[[checked-exceptions]]
|
||||
|
@ -319,7 +324,7 @@ progresses.
|
|||
== Testing
|
||||
|
||||
This section addresses testing with the combination of Kotlin and Spring Framework.
|
||||
The recommended testing framework is https://junit.org/junit5/[JUnit 5] along with
|
||||
The recommended testing framework is https://junit.org/junit5/[JUnit] along with
|
||||
https://mockk.io/[Mockk] for mocking.
|
||||
|
||||
NOTE: If you are using Spring Boot, see
|
||||
|
@ -330,7 +335,7 @@ NOTE: If you are using Spring Boot, see
|
|||
=== Constructor injection
|
||||
|
||||
As described in the xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[dedicated section],
|
||||
JUnit Jupiter (JUnit 5) allows constructor injection of beans which is pretty useful with Kotlin
|
||||
JUnit Jupiter allows constructor injection of beans which is pretty useful with Kotlin
|
||||
in order to use `val` instead of `lateinit var`. You can use
|
||||
{spring-framework-api}/test/context/TestConstructor.html[`@TestConstructor(autowireMode = AutowireMode.ALL)`]
|
||||
to enable autowiring for all parameters.
|
||||
|
@ -355,7 +360,7 @@ file with a `spring.test.constructor.autowire.mode = all` property.
|
|||
=== `PER_CLASS` Lifecycle
|
||||
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
With JUnit Jupiter (JUnit 5), Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
||||
With JUnit Jupiter, Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
||||
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
|
||||
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
|
||||
|
||||
|
@ -399,8 +404,8 @@ class IntegrationTests {
|
|||
[[specification-like-tests]]
|
||||
=== Specification-like Tests
|
||||
|
||||
You can create specification-like tests with JUnit 5 and Kotlin.
|
||||
The following example shows how to do so:
|
||||
You can create specification-like tests with Kotlin and JUnit Jupiter's `@Nested` test
|
||||
class support. The following example shows how to do so:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
= Spring JUnit Jupiter Testing Annotations
|
||||
|
||||
The following annotations are supported when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`] and JUnit Jupiter
|
||||
(that is, the programming model in JUnit 5):
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and JUnit Jupiter (that is, the programming model in JUnit):
|
||||
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitwebconfig[`@SpringJUnitWebConfig`]
|
||||
|
|
|
@ -140,8 +140,8 @@ Kotlin::
|
|||
======
|
||||
|
||||
If we write tests that use JUnit Jupiter, we can reduce code duplication even further,
|
||||
since annotations in JUnit 5 can also be used as meta-annotations. Consider the following
|
||||
example:
|
||||
since annotations in JUnit Jupiter can also be used as meta-annotations. Consider the
|
||||
following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
|
|
@ -10,7 +10,8 @@ Resource locations are typically XML configuration files or Groovy scripts locat
|
|||
classpath, while component classes are typically `@Configuration` classes. However,
|
||||
resource locations can also refer to files and scripts in the file system, and component
|
||||
classes can be `@Component` classes, `@Service` classes, and so on. See
|
||||
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc#testcontext-ctx-management-javaconfig-component-classes[Component Classes] for further details.
|
||||
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc#testcontext-ctx-management-javaconfig-component-classes[Component Classes]
|
||||
for further details.
|
||||
|
||||
The following example shows a `@ContextConfiguration` annotation that refers to an XML
|
||||
file:
|
||||
|
@ -137,6 +138,6 @@ configuration classes as well as context initializers that are declared by super
|
|||
or enclosing classes.
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management.adoc[Context Management],
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration], and the `@ContextConfiguration`
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration],
|
||||
and the {spring-framework-api}/test/context/ContextConfiguration.html[`@ContextConfiguration`]
|
||||
javadocs for further details.
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ following sections to make this pattern much easier to implement.
|
|||
== MockMvc and WebDriver Setup
|
||||
|
||||
To use Selenium WebDriver with `MockMvc`, make sure that your project includes a test
|
||||
dependency on `org.seleniumhq.selenium:selenium-htmlunit3-driver`.
|
||||
dependency on `org.seleniumhq.selenium:htmlunit3-driver`.
|
||||
|
||||
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
|
||||
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
[[spring-mvc-test-client]]
|
||||
= Testing Client Applications
|
||||
|
||||
You can use client-side tests to test code that internally uses the `RestTemplate`. The
|
||||
idea is to declare expected requests and to provide "`stub`" responses so that you can
|
||||
focus on testing the code in isolation (that is, without running a server). The following
|
||||
example shows how to do so:
|
||||
To test code that uses the `RestClient` or `RestTemplate`, you can use a mock web server, such as
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
|
||||
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
|
||||
server, and that means you can test with the same HTTP client that is also configured in
|
||||
the same way as in production, which is important because there are often subtle
|
||||
differences in the way different clients handle network I/O. Another advantage of mock
|
||||
web servers is the ability to simulate specific network issues and conditions at the
|
||||
transport level, in combination with the client used in production.
|
||||
|
||||
In addition to dedicated mock web servers, historically the Spring Framework has provided
|
||||
a built-in option to test `RestClient` or `RestTemplate` through `MockRestServiceServer`.
|
||||
This relies on configuring the client under test with a custom `ClientHttpRequestFactory`
|
||||
backed by the mock server that is in turn set up to expect requests and send "`stub`"
|
||||
responses so that you can focus on testing the code in isolation, without running a server.
|
||||
|
||||
TIP: `MockRestServiceServer` predates the existence of mock web servers. At present, we
|
||||
recommend using mock web servers for more complete testing of the transport layer and
|
||||
network conditions.
|
||||
|
||||
The following example shows an example of using `MockRestServiceServer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
|
|
@ -9,11 +9,11 @@ deal of importance on convention over configuration, with reasonable defaults th
|
|||
can override through annotation-based configuration.
|
||||
|
||||
In addition to generic testing infrastructure, the TestContext framework provides
|
||||
explicit support for JUnit 4, JUnit Jupiter (AKA JUnit 5), and TestNG. For JUnit 4 and
|
||||
TestNG, Spring provides `abstract` support classes. Furthermore, Spring provides a custom
|
||||
JUnit `Runner` and custom JUnit `Rules` for JUnit 4 and a custom `Extension` for JUnit
|
||||
Jupiter that let you write so-called POJO test classes. POJO test classes are not
|
||||
required to extend a particular class hierarchy, such as the `abstract` support classes.
|
||||
explicit support for JUnit Jupiter, JUnit 4, and TestNG. For JUnit 4 and TestNG, Spring
|
||||
provides `abstract` support classes. Furthermore, Spring provides a custom JUnit `Runner`
|
||||
and custom JUnit `Rules` for JUnit 4 and a custom `Extension` for JUnit Jupiter that let
|
||||
you write so-called POJO test classes. POJO test classes are not required to extend a
|
||||
particular class hierarchy, such as the `abstract` support classes.
|
||||
|
||||
The following section provides an overview of the internals of the TestContext framework.
|
||||
If you are interested only in using the framework and are not interested in extending it
|
||||
|
|
|
@ -250,7 +250,7 @@ Java::
|
|||
@SqlGroup({
|
||||
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
|
||||
@Sql("/test-user-data.sql")
|
||||
)}
|
||||
})
|
||||
void userTest() {
|
||||
// run code that uses the test schema and test data
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ shows this configuration:
|
|||
<property name="sessionFactory" ref="sessionFactory"/>
|
||||
</bean>
|
||||
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean">
|
||||
<!-- configuration elided for brevity -->
|
||||
</bean>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ in JUnit and TestNG.
|
|||
== SpringExtension for JUnit Jupiter
|
||||
|
||||
The Spring TestContext Framework offers full integration with the JUnit Jupiter testing
|
||||
framework, introduced in JUnit 5. By annotating test classes with
|
||||
framework, originally introduced in JUnit 5. By annotating test classes with
|
||||
`@ExtendWith(SpringExtension.class)`, you can implement standard JUnit Jupiter-based unit
|
||||
and integration tests and simultaneously reap the benefits of the TestContext framework,
|
||||
such as support for loading application contexts, dependency injection of test instances,
|
||||
|
@ -72,8 +72,8 @@ Kotlin::
|
|||
----
|
||||
======
|
||||
|
||||
Since you can also use annotations in JUnit 5 as meta-annotations, Spring provides the
|
||||
`@SpringJUnitConfig` and `@SpringJUnitWebConfig` composed annotations to simplify the
|
||||
Since you can also use annotations in JUnit Jupiter as meta-annotations, Spring provides
|
||||
the `@SpringJUnitConfig` and `@SpringJUnitWebConfig` composed annotations to simplify the
|
||||
configuration of the test `ApplicationContext` and JUnit Jupiter.
|
||||
|
||||
The following example uses `@SpringJUnitConfig` to reduce the amount of configuration
|
||||
|
@ -485,7 +485,8 @@ Kotlin::
|
|||
|
||||
[WARNING]
|
||||
====
|
||||
JUnit 4 support is deprecated since Spring Framework 7.0 in favor of the
|
||||
JUnit 4 is officially in maintenance mode, and JUnit 4 support in Spring is deprecated
|
||||
since Spring Framework 7.0 in favor of the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and JUnit Jupiter.
|
||||
====
|
||||
|
@ -547,7 +548,8 @@ be configured through `@ContextConfiguration`.
|
|||
|
||||
[WARNING]
|
||||
====
|
||||
JUnit 4 support is deprecated since Spring Framework 7.0 in favor of the
|
||||
JUnit 4 is officially in maintenance mode, and JUnit 4 support in Spring is deprecated
|
||||
since Spring Framework 7.0 in favor of the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and JUnit Jupiter.
|
||||
====
|
||||
|
@ -623,9 +625,10 @@ Kotlin::
|
|||
|
||||
[WARNING]
|
||||
====
|
||||
JUnit 4 support is deprecated since Spring Framework 7.0 in favor of the
|
||||
JUnit 4 is officially in maintenance mode, and JUnit 4 support in Spring is deprecated
|
||||
since Spring Framework 7.0 in favor of the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
for JUnit Jupiter.
|
||||
and JUnit Jupiter.
|
||||
====
|
||||
|
||||
The `org.springframework.test.context.junit4` package provides the following support
|
||||
|
|
|
@ -265,6 +265,7 @@ Java::
|
|||
client = WebTestClient.bindToController(new TestController())
|
||||
.configureClient()
|
||||
.baseUrl("/test")
|
||||
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
|
||||
.build();
|
||||
----
|
||||
|
||||
|
@ -275,6 +276,7 @@ Kotlin::
|
|||
client = WebTestClient.bindToController(TestController())
|
||||
.configureClient()
|
||||
.baseUrl("/test")
|
||||
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
[[webflux-versioning]]
|
||||
= API Versioning
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webmvc-versioning.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux supports API versioning. This section provides an overview of the support
|
||||
and underlying strategies.
|
||||
|
||||
Please, see also related content in:
|
||||
|
||||
- Configure xref:web/webflux/config.adoc#webflux-config-api-version[API versioning]
|
||||
in the WebFlux Config
|
||||
- xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[Map requests]
|
||||
to annotated controller methods with an API version
|
||||
|
||||
Client support for API versioning is available also in `RestClient`, `WebClient`, and
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Service] clients, as well as
|
||||
for testing in `WebTestClient`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-versioning-strategy]]
|
||||
== ApiVersionStrategy
|
||||
[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-strategy[See equivalent in the Servlet stack]#
|
||||
|
||||
This is the central strategy for API versioning that holds all configured preferences
|
||||
related to versioning. It does the following:
|
||||
|
||||
- Resolves versions from the requests via xref:#webflux-versioning-resolver[ApiVersionResolver]
|
||||
- Parses raw version values into `Comparable<?>` with xref:#webflux-versioning-parser[ApiVersionParser]
|
||||
- xref:#webflux-versioning-validation[Validates] request versions
|
||||
|
||||
`ApiVersionStrategy` helps to map requests to `@RequestMapping` controller methods,
|
||||
and is initialized by the WebFlux config. Typically, applications do not interact
|
||||
directly with it.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-versioning-resolver]]
|
||||
== ApiVersionResolver
|
||||
[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-resolver[See equivalent in the Servlet stack]#
|
||||
|
||||
This strategy resolves the API version from a request. The WebFlux config provides built-in
|
||||
options to resolve from a header, a request parameter, or from the URL path.
|
||||
You can also use a custom `ApiVersionResolver`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-versioning-parser]]
|
||||
== ApiVersionParser
|
||||
[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-parser[See equivalent in the Servlet stack]#
|
||||
|
||||
This strategy helps to parse raw version values into `Comparable<?>`, which helps to
|
||||
compare, sort, and select versions. By default, the built-in `SemanticApiVersionParser`
|
||||
parses a version into `major`, `minor`, and `patWebFluxch` integer values. Minor and patch
|
||||
values are set to 0 if not present.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-versioning-validation]]
|
||||
== Validation
|
||||
[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-validation[See equivalent in the Servlet stack]#
|
||||
|
||||
If a request version is not supported, `InvalidApiVersionException` is raised resulting
|
||||
in a 400 response. By default, the list of supported versions is initialized from declared
|
||||
versions in annotated controller mappings, but you can turn that off through a flag in the
|
||||
WebFlux config, and use only the versions configured explicitly in the config.
|
||||
|
||||
By default, a version is required when API versioning is enabled, and
|
||||
`MissingApiVersionException` is raised resulting in a 400 response if not present.
|
||||
You can make it optional in which case the most recent version is used.
|
||||
You can also specify a default version to use.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-versioning-deprecation-handler]]
|
||||
== ApiVersionDeprecationHandler
|
||||
[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-deprecation-handler[See equivalent in the Reactive stack]#
|
||||
|
||||
This strategy can be configured to send hints and information about deprecated versions to
|
||||
clients via response headers. The built-in `StandardApiVersionDeprecationHandler`
|
||||
can set the "Deprecation" "Sunset" headers and "Link" headers as defined in
|
||||
https://datatracker.ietf.org/doc/html/rfc9745[RFC 9745] and
|
||||
https://datatracker.ietf.org/doc/html/rfc8594[RFC 8594]. You can also configure a custom
|
||||
handler for different headers.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-versioning-mapping]]
|
||||
== Request Mapping
|
||||
[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-mapping[See equivalent in the Servlet stack]#
|
||||
|
||||
`ApiVersionStrategy` supports the mapping of requests to annotated controller methods.
|
||||
See xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[API Versions]
|
||||
for more details.
|
|
@ -1,7 +1,7 @@
|
|||
[[webflux-client-builder]]
|
||||
= Configuration
|
||||
|
||||
The simplest way to create a `WebClient` is through one of the static factory methods:
|
||||
The simplest way to create `WebClient` is through one of the static factory methods:
|
||||
|
||||
* `WebClient.create()`
|
||||
* `WebClient.create(String baseUrl)`
|
||||
|
@ -12,10 +12,12 @@ You can also use `WebClient.builder()` with further options:
|
|||
* `defaultUriVariables`: default values to use when expanding URI templates.
|
||||
* `defaultHeader`: Headers for every request.
|
||||
* `defaultCookie`: Cookies for every request.
|
||||
* `defaultApiVersion`: API version for every request.
|
||||
* `defaultRequest`: `Consumer` to customize every request.
|
||||
* `filter`: Client filter for every request.
|
||||
* `exchangeStrategies`: HTTP message reader/writer customizations.
|
||||
* `clientConnector`: HTTP client library settings.
|
||||
* `apiVersionInserter`: to insert API version values in the request
|
||||
* `observationRegistry`: the registry to use for enabling xref:integration/observability.adoc#http-client.webclient[Observability support].
|
||||
* `observationConvention`: xref:integration/observability.adoc#config[an optional, custom convention to extract metadata] for recorded observations.
|
||||
|
||||
|
|
|
@ -2,9 +2,16 @@
|
|||
= Testing
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
To test code that uses the `WebClient`, you can use a mock web server, such as the
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer]. To see an example
|
||||
of its use, check out
|
||||
To test code that uses the `WebClient`, you can use a mock web server, such as
|
||||
https://github.com/square/okhttp#mockwebserver[OkHttp MockWebServer] or
|
||||
https://wiremock.org/[WireMock]. Mock web servers accept requests over HTTP like a regular
|
||||
server, and that means you can test with the same HTTP client that is also configured in
|
||||
the same way as in production, which is important because there are often subtle
|
||||
differences in the way different clients handle network I/O. Another advantage of mock
|
||||
web servers is the ability to simulate specific network issues and conditions at the
|
||||
transport level, in combination with the client used in production.
|
||||
|
||||
For example use of MockWebServer, see
|
||||
{spring-framework-code}/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java[`WebClientIntegrationTests`]
|
||||
in the Spring Framework test suite or the
|
||||
https://github.com/square/okhttp/tree/master/samples/static-server[`static-server`]
|
||||
|
|
|
@ -686,6 +686,74 @@ reliance on it.
|
|||
|
||||
|
||||
|
||||
[[webflux-config-api-version]]
|
||||
== API Version
|
||||
[.small]#xref:web/webmvc/mvc-config/api-version.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
To enable API versioning, use the `ApiVersionConfigurer` callback of `WebFluxConfigurer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Configuration
|
||||
public class WebConfiguration implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureApiVersioning(ApiVersionConfigurer configurer) {
|
||||
configurer.useRequestHeader("X-API-Version");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Configuration
|
||||
class WebConfiguration : WebMvcConfigurer {
|
||||
|
||||
override fun configureApiVersioning(configurer: ApiVersionConfigurer) {
|
||||
configurer.useRequestHeader("X-API-Version")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can resolve the version through one of the built-in options listed below, or
|
||||
alternatively use a custom `ApiVersionResolver`:
|
||||
|
||||
- Request header
|
||||
- Request parameter
|
||||
- Path segment
|
||||
- Media type parameter
|
||||
|
||||
TIP: When using a path segment, consider configuring a shared path prefix externally
|
||||
in xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching] options.
|
||||
|
||||
By default, the version is parsed with `SemanticVersionParser`, but you can also configure
|
||||
a custom xref:web/webflux-versioning.adoc#webflux-versioning-parser[ApiVersionParser].
|
||||
|
||||
Supported versions are transparently detected from versions declared in request mappings
|
||||
for convenience, but you can turn that off through a flag in the WebFlux config, and
|
||||
consider only the versions configured explicitly in the config as supported.
|
||||
Requests with a version that is not supported are rejected with
|
||||
`InvalidApiVersionException` resulting in a 400 response.
|
||||
|
||||
You can set an `ApiVersionDeprecationHandler` to send information about deprecated
|
||||
versions to clients. The built-in standard handler can set "Deprecation", "Sunset", and
|
||||
"Link" headers based on https://datatracker.ietf.org/doc/html/rfc9745[RFC 9745] and
|
||||
https://datatracker.ietf.org/doc/html/rfc8594[RFC 8594].
|
||||
|
||||
Once API versioning is configured, you can begin to map requests to
|
||||
xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[controller methods]
|
||||
according to the request version.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-config-blocking-execution]]
|
||||
== Blocking Execution
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ Java::
|
|||
|
||||
private String name;
|
||||
|
||||
private MultipartFile file;
|
||||
private FilePart file;
|
||||
|
||||
// ...
|
||||
|
||||
|
@ -42,7 +42,7 @@ Kotlin::
|
|||
----
|
||||
class MyForm(
|
||||
val name: String,
|
||||
val file: MultipartFile)
|
||||
val file: FilePart)
|
||||
|
||||
@Controller
|
||||
class FileUploadController {
|
||||
|
|
|
@ -34,11 +34,7 @@ Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated oper
|
|||
| `HttpHeaders`
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| `ErrorResponse`, `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
|
|
|
@ -234,8 +234,8 @@ Kotlin::
|
|||
--
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this to, for example, parameterize a base URL based on
|
||||
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this, for example, to parameterize a base URL based on
|
||||
some external configuration.
|
||||
|
||||
NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support.
|
||||
|
@ -408,6 +408,86 @@ Kotlin::
|
|||
======
|
||||
|
||||
|
||||
[[webflux-ann-requestmapping-version]]
|
||||
== API Version
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[See equivalent in the Servlet stack]#
|
||||
|
||||
There is no standard way to specify an API version, so when you enable API versioning
|
||||
in the xref:web/webflux/config.adoc#webflux-config-api-version[WebFlux Config] you need
|
||||
to specify how to resolve the version. The WebFlux Config creates an
|
||||
xref:web/webflux-versioning.adoc#webflux-versioning-strategy[ApiVersionStrategy] that in turn
|
||||
is used to map requests.
|
||||
|
||||
Once API versioning is enabled, you can begin to map requests with versions.
|
||||
The `@RequestMapping` `version` attribute supports the following:
|
||||
|
||||
- No value -- matches any version
|
||||
- Fixed version ("1.2") -- matches the given version only
|
||||
- Baseline version ("1.2+") -- matches the given version and above
|
||||
|
||||
If multiple controller methods have a version less than or equal to the request version,
|
||||
the highest of those, and closest to the request version, is the one considered,
|
||||
in effect superseding the rest.
|
||||
|
||||
To illustrate this, consider the following mappings:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account/{id}")
|
||||
public class AccountController {
|
||||
|
||||
@GetMapping // <1>
|
||||
public Account getAccount() {
|
||||
}
|
||||
|
||||
@GetMapping(version = "1.1") // <2>
|
||||
public Account getAccount1_1() {
|
||||
}
|
||||
|
||||
@GetMapping(version = "1.2+") // <3>
|
||||
public Account getAccount1_2() {
|
||||
}
|
||||
|
||||
@GetMapping(version = "1.5") // <4>
|
||||
public Account getAccount1_5() {
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> match any version
|
||||
<2> match version 1.1
|
||||
<3> match version 1.2 and above
|
||||
<4> match version 1.5
|
||||
======
|
||||
|
||||
For request with version `"1.3"`:
|
||||
|
||||
- (1) matches as it matches any version
|
||||
- (2) does not match
|
||||
- (3) matches as it matches 1.2 and above, and is *chosen* as the highest match
|
||||
- (4) is higher and does not match
|
||||
|
||||
For request with version `"1.5"`:
|
||||
|
||||
- (1) matches as it matches any version
|
||||
- (2) does not match
|
||||
- (3) matches as it matches 1.2 and above
|
||||
- (4) matches and is *chosen* as the highest match
|
||||
|
||||
A request with version `"1.6"` does not have a match. (1) and (3) do match, but are
|
||||
superseded by (4), which allows only a strict match, and therefore does not match.
|
||||
In this scenario, a `NotAcceptableApiVersionException` results in a 400 response.
|
||||
|
||||
NOTE: The above assumes the request version is a
|
||||
xref:web/webmvc/mvc-config/api-version.adoc["supported" version], or otherwise it
|
||||
would fail xref:web/webflux-versioning.adoc#webflux-versioning-validation[Validation].
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-ann-requestmapping-head-options]]
|
||||
== HTTP HEAD, OPTIONS
|
||||
|
|
|
@ -81,7 +81,7 @@ The following table describes server dependencies (also see
|
|||
|jetty-server, jetty-servlet
|
||||
|===
|
||||
|
||||
The code snippets below show using the `HttpHandler` adapters with each server API:
|
||||
The code snippets below show using the `HttpHandler` adapters with each server API.
|
||||
|
||||
*Reactor Netty*
|
||||
[tabs]
|
||||
|
@ -176,17 +176,16 @@ Java::
|
|||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
HttpHandler handler = ...
|
||||
Servlet servlet = new JettyHttpHandlerAdapter(handler);
|
||||
JettyCoreHttpHandlerAdapter adapter = new JettyCoreHttpHandlerAdapter(handler);
|
||||
|
||||
Server server = new Server();
|
||||
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
|
||||
contextHandler.addServlet(new ServletHolder(servlet), "/");
|
||||
contextHandler.start();
|
||||
server.setHandler(adapter);
|
||||
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setHost(host);
|
||||
connector.setPort(port);
|
||||
server.addConnector(connector);
|
||||
|
||||
server.start();
|
||||
----
|
||||
|
||||
|
@ -195,27 +194,27 @@ Kotlin::
|
|||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val handler: HttpHandler = ...
|
||||
val servlet = JettyHttpHandlerAdapter(handler)
|
||||
val adapter = JettyCoreHttpHandlerAdapter(handler)
|
||||
|
||||
val server = Server()
|
||||
val contextHandler = ServletContextHandler(server, "")
|
||||
contextHandler.addServlet(ServletHolder(servlet), "/")
|
||||
contextHandler.start();
|
||||
server.setHandler(adapter)
|
||||
|
||||
val connector = ServerConnector(server)
|
||||
connector.host = host
|
||||
connector.port = port
|
||||
server.addConnector(connector)
|
||||
|
||||
server.start()
|
||||
----
|
||||
======
|
||||
|
||||
*Servlet Container*
|
||||
TIP: In Spring Framework 6.2, `JettyHttpHandlerAdapter` was deprecated in favor of
|
||||
`JettyCoreHttpHandlerAdapter`, which integrates directly with Jetty 12 APIs
|
||||
without a Servlet layer.
|
||||
|
||||
To deploy as a WAR to any Servlet container, you can extend and include
|
||||
{spring-framework-api}/web/server/adapter/AbstractReactiveWebInitializer.html[`AbstractReactiveWebInitializer`]
|
||||
in the WAR. That class wraps an `HttpHandler` with `ServletHttpHandlerAdapter` and registers
|
||||
that as a `Servlet`.
|
||||
To deploy as a WAR to a Servlet container instead, use
|
||||
{spring-framework-api}/web/server/adapter/AbstractReactiveWebInitializer.html[`AbstractReactiveWebInitializer`],
|
||||
to adapt `HttpHandler` to a `Servlet` via `ServletHttpHandlerAdapter`.
|
||||
|
||||
|
||||
|
||||
|
@ -800,4 +799,3 @@ Kotlin::
|
|||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
|
|
@ -846,7 +846,7 @@ processing lifecycle and also (potentially) run side by side with annotated cont
|
|||
any are declared. It is also how functional endpoints are enabled by the Spring Boot Web
|
||||
starter.
|
||||
|
||||
The following example shows a WebFlux Java configuration:
|
||||
The following example shows a WebMvc Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
[[mvc-versioning]]
|
||||
= API Versioning
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webflux-versioning.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring MVC supports API versioning. This section provides an overview of the support
|
||||
and underlying strategies.
|
||||
|
||||
Please, see also related content in:
|
||||
|
||||
- Configure xref:web/webmvc/mvc-config/api-version.adoc[API versioning] in the MVC Config
|
||||
- xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[Map requests]
|
||||
to annotated controller methods with an API version
|
||||
|
||||
Client support for API versioning is available also in `RestClient`, `WebClient`, and
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Service] clients, as well as
|
||||
for testing in MockMvc and `WebTestClient`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-versioning-strategy]]
|
||||
== ApiVersionStrategy
|
||||
[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-strategy[See equivalent in the Reactive stack]#
|
||||
|
||||
This is the central strategy for API versioning that holds all configured preferences
|
||||
related to versioning. It does the following:
|
||||
|
||||
- Resolves versions from the requests via xref:#mvc-versioning-resolver[ApiVersionResolver]
|
||||
- Parses raw version values into `Comparable<?>` with an xref:#mvc-versioning-parser[ApiVersionParser]
|
||||
- xref:#mvc-versioning-validation[Validates] request versions
|
||||
- Sends deprecation hints in the responses
|
||||
|
||||
`ApiVersionStrategy` helps to map requests to `@RequestMapping` controller methods,
|
||||
and is initialized by the MVC config. Typically, applications do not interact
|
||||
directly with it.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-versioning-resolver]]
|
||||
== ApiVersionResolver
|
||||
[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-resolver[See equivalent in the Reactive stack]#
|
||||
|
||||
This strategy resolves the API version from a request. The MVC config provides built-in
|
||||
options to resolve from a header, from a request parameter, or from the URL path.
|
||||
You can also use a custom `ApiVersionResolver`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-versioning-parser]]
|
||||
== ApiVersionParser
|
||||
[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-parser[See equivalent in the Reactive stack]#
|
||||
|
||||
This strategy helps to parse raw version values into `Comparable<?>`, which helps to
|
||||
compare, sort, and select versions. By default, the built-in `SemanticApiVersionParser`
|
||||
parses a version into `major`, `minor`, and `patch` integer values. Minor and patch
|
||||
values are set to 0 if not present.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-versioning-validation]]
|
||||
== Validation
|
||||
[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-validation[See equivalent in the Reactive stack]#
|
||||
|
||||
If a request version is not supported, `InvalidApiVersionException` is raised resulting
|
||||
in a 400 response. By default, the list of supported versions is initialized from declared
|
||||
versions in annotated controller mappings, but you can turn that off through a flag in the
|
||||
MVC config, and use only the versions configured explicitly in the config.
|
||||
|
||||
By default, a version is required when API versioning is enabled, and
|
||||
`MissingApiVersionException` is raised resulting in a 400 response if not present.
|
||||
You can make it optional in which case the most recent version is used.
|
||||
You can also specify a default version to use.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-versioning-deprecation-handler]]
|
||||
== ApiVersionDeprecationHandler
|
||||
[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-deprecation-handler[See equivalent in the Reactive stack]#
|
||||
|
||||
This strategy can be configured to send hints and information about deprecated versions to
|
||||
clients via response headers. The built-in `StandardApiVersionDeprecationHandler`
|
||||
can set the "Deprecation" "Sunset" headers and "Link" headers as defined in
|
||||
https://datatracker.ietf.org/doc/html/rfc9745[RFC 9745] and
|
||||
https://datatracker.ietf.org/doc/html/rfc8594[RFC 8594]. You can also configure a custom
|
||||
handler for different headers.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-versioning-mapping]]
|
||||
== Request Mapping
|
||||
[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-mapping[See equivalent in the Reactive stack]#
|
||||
|
||||
`ApiVersionStrategy` supports the mapping of requests to annotated controller methods.
|
||||
See xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[API Version]
|
||||
for more details.
|
|
@ -4,11 +4,13 @@
|
|||
Spring MVC has an extensive integration with Servlet asynchronous request
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-processing[processing]:
|
||||
|
||||
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`]
|
||||
return values in controller methods provide basic support for a single asynchronous
|
||||
return value.
|
||||
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`],
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`], and
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-webasynctask[`WebAsyncTask`] return values
|
||||
in controller methods provide support for a single asynchronous return value.
|
||||
* Controllers can xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[stream] multiple values, including
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
|
||||
* Controllers can use reactive clients and return
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types] for response handling.
|
||||
|
||||
|
@ -96,6 +98,47 @@ xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[config
|
|||
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-async-webasynctask]]
|
||||
== `WebAsyncTask`
|
||||
|
||||
`WebAsyncTask` is comparable to using xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[Callable]
|
||||
but allows customizing additional settings such a request timeout value, and the
|
||||
`AsyncTaskExecutor` to execute the `java.util.concurrent.Callable` with instead
|
||||
of the defaults set up globally for Spring MVC. Below is an example of using `WebAsyncTask`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@GetMapping("/callable")
|
||||
WebAsyncTask<String> handle() {
|
||||
return new WebAsyncTask<String>(20000L,()->{
|
||||
Thread.sleep(10000); //simulate long-running task
|
||||
return "asynchronous request completed";
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@GetMapping("/callable")
|
||||
fun handle(): WebAsyncTask<String> {
|
||||
return WebAsyncTask(20000L) {
|
||||
Thread.sleep(10000) // simulate long-running task
|
||||
"asynchronous request completed"
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-async-processing]]
|
||||
== Processing
|
||||
|
||||
|
@ -390,7 +433,7 @@ reactive types from the controller method.
|
|||
Reactive return values are handled as follows:
|
||||
|
||||
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
|
||||
include `Mono` (Reactor) or `Single` (RxJava).
|
||||
include `CompletionStage` (JDK), Mono` (Reactor), and `Single` (RxJava).
|
||||
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
|
||||
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
|
||||
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
[[mvc-config-api-version]]
|
||||
= API Version
|
||||
|
||||
[.small]#xref:web/webflux/config.adoc#webflux-config-api-version[See equivalent in the Reactive stack]#
|
||||
|
||||
To enable API versioning, use the `ApiVersionConfigurer` callback of `WebMvcConfigurer`:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can resolve the version through one of the built-in options listed below, or
|
||||
alternatively use a custom `ApiVersionResolver`:
|
||||
|
||||
- Request header
|
||||
- Request parameter
|
||||
- Path segment
|
||||
- Media type parameter
|
||||
|
||||
TIP: When using a path segment, consider configuring a shared path prefix externally
|
||||
in xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching] options.
|
||||
|
||||
By default, the version is parsed with `SemanticVersionParser`, but you can also configure
|
||||
a custom xref:web/webmvc-versioning.adoc#mvc-versioning-parser[ApiVersionParser].
|
||||
|
||||
Supported versions are transparently detected from versions declared in request mappings
|
||||
for convenience, but you can turn that off through a flag in the MVC config, and
|
||||
consider only the versions configured explicitly in the config as supported.
|
||||
Requests with a version that is not supported are rejected with
|
||||
`InvalidApiVersionException` resulting in a 400 response.
|
||||
|
||||
You can set an `ApiVersionDeprecationHandler` to send information about deprecated
|
||||
versions to clients. The built-in standard handler can set "Deprecation", "Sunset", and
|
||||
"Link" headers based on https://datatracker.ietf.org/doc/html/rfc9745[RFC 9745] and
|
||||
https://datatracker.ietf.org/doc/html/rfc8594[RFC 8594].
|
||||
|
||||
Once API versioning is configured, you can begin to map requests to
|
||||
xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[controller methods]
|
||||
according to the request version.
|
|
@ -10,18 +10,20 @@ to any controller. Moreover, as of 5.3, `@ExceptionHandler` methods in `@Control
|
|||
can be used to handle exceptions from any `@Controller` or any other handler.
|
||||
|
||||
`@ControllerAdvice` is meta-annotated with `@Component` and therefore can be registered as
|
||||
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning]
|
||||
. `@RestControllerAdvice` is meta-annotated with `@ControllerAdvice`
|
||||
and `@ResponseBody`, and that means `@ExceptionHandler` methods will have their return
|
||||
value rendered via response body message conversion, rather than via HTML views.
|
||||
a Spring bean through xref:core/beans/java/instantiating-container.adoc#beans-java-instantiating-container-scan[component scanning].
|
||||
|
||||
`@RestControllerAdvice` is a shortcut annotation that combines `@ControllerAdvice`
|
||||
with `@ResponseBody`, in effect simply an `@ControllerAdvice` whose exception handler
|
||||
methods render to the response body.
|
||||
|
||||
On startup, `RequestMappingHandlerMapping` and `ExceptionHandlerExceptionResolver` detect
|
||||
controller advice beans and apply them at runtime. Global `@ExceptionHandler` methods,
|
||||
from an `@ControllerAdvice`, are applied _after_ local ones, from the `@Controller`.
|
||||
By contrast, global `@ModelAttribute` and `@InitBinder` methods are applied _before_ local ones.
|
||||
|
||||
The `@ControllerAdvice` annotation has attributes that let you narrow the set of controllers
|
||||
and handlers that they apply to. For example:
|
||||
By default, both `@ControllerAdvice` and `@RestControllerAdvice` apply to any controller,
|
||||
including `@Controller` and `@RestController`. Use attributes of the annotation to narrow
|
||||
the set of controllers and handlers that they apply to. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
|
|
@ -177,13 +177,9 @@ the content negotiation during the error handling phase will decide which conten
|
|||
be converted through `HttpMessageConverter` instances and written to the response.
|
||||
See xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[ResponseEntity].
|
||||
|
||||
| `ErrorResponse`
|
||||
| `ErrorResponse`, `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
| A view name to be resolved with `ViewResolver` implementations and used together with the
|
||||
|
|
|
@ -243,7 +243,7 @@ Kotlin::
|
|||
======
|
||||
|
||||
If there is no `BindingResult` parameter after the `@ModelAttribute`, then
|
||||
`MethodArgumentNotValueException` is raised with the validation errors. However, if method
|
||||
a `MethodArgumentNotValidException` is raised with the validation errors. However, if method
|
||||
validation applies because other parameters have `@jakarta.validation.Constraint` annotations,
|
||||
then `HandlerMethodValidationException` is raised instead. For more details, see the section
|
||||
xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
|
|
@ -22,11 +22,7 @@ supported for all return values.
|
|||
| `HttpHeaders`
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| `ErrorResponse`, `ProblemDetail`
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
|
|
|
@ -429,6 +429,87 @@ xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-co
|
|||
instead.
|
||||
|
||||
|
||||
[[mvc-ann-requestmapping-version]]
|
||||
== API Version
|
||||
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[See equivalent in the Reactive stack]#
|
||||
|
||||
There is no standard way to specify an API version, so when you enable API versioning
|
||||
in the xref:web/webmvc/mvc-config/api-version.adoc[MVC Config] you need
|
||||
to specify how to resolve the version. The MVC Config creates an
|
||||
xref:web/webmvc-versioning.adoc#mvc-versioning-strategy[ApiVersionStrategy] that in turn
|
||||
is used to map requests.
|
||||
|
||||
Once API versioning is enabled, you can begin to map requests with versions.
|
||||
The `@RequestMapping` `version` attribute supports the following:
|
||||
|
||||
- No value -- matches any version
|
||||
- Fixed version ("1.2") -- matches the given version only
|
||||
- Baseline version ("1.2+") -- matches the given version and above
|
||||
|
||||
If multiple controller methods have a version less than or equal to the request version,
|
||||
the highest of those, and closest to the request version, is the one considered,
|
||||
in effect superseding the rest.
|
||||
|
||||
To illustrate this, consider the following mappings:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account/{id}")
|
||||
public class AccountController {
|
||||
|
||||
@GetMapping // <1>
|
||||
public Account getAccount() {
|
||||
}
|
||||
|
||||
@GetMapping(version = "1.1") // <2>
|
||||
public Account getAccount1_1() {
|
||||
}
|
||||
|
||||
@GetMapping(version = "1.2+") // <3>
|
||||
public Account getAccount1_2() {
|
||||
}
|
||||
|
||||
@GetMapping(version = "1.5") // <4>
|
||||
public Account getAccount1_5() {
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> match any version
|
||||
<2> match version 1.1
|
||||
<3> match version 1.2 and above
|
||||
<4> match version 1.5
|
||||
======
|
||||
|
||||
For request with version `"1.3"`:
|
||||
|
||||
- (1) matches as it matches any version
|
||||
- (2) does not match
|
||||
- (3) matches as it matches 1.2 and above, and is *chosen* as the highest match
|
||||
- (4) is higher and does not match
|
||||
|
||||
For request with version `"1.5"`:
|
||||
|
||||
- (1) matches as it matches any version
|
||||
- (2) does not match
|
||||
- (3) matches as it matches 1.2 and above
|
||||
- (4) matches and is *chosen* as the highest match
|
||||
|
||||
A request with version `"1.6"` does not have a match. (1) and (3) do match, but are
|
||||
superseded by (4), which allows only a strict match, and therefore does not match.
|
||||
In this scenario, a `NotAcceptableApiVersionException` results in a 400 response.
|
||||
|
||||
NOTE: The above assumes the request version is a
|
||||
xref:web/webmvc/mvc-config/api-version.adoc["supported" version], or otherwise it
|
||||
would fail xref:web/webmvc-versioning.adoc#mvc-versioning-validation[Validation].
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-requestmapping-head-options]]
|
||||
== HTTP HEAD, OPTIONS
|
||||
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-head-options[See equivalent in the Reactive stack]#
|
||||
|
|
|
@ -38,7 +38,7 @@ to inform the server that the original port was `443`.
|
|||
==== X-Forwarded-Proto
|
||||
|
||||
While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto[`X-Forwarded-Proto: (https|http)`]
|
||||
is a de-facto standard header that is used to communicate the original protocol (for example, https / https)
|
||||
is a de-facto standard header that is used to communicate the original protocol (for example, https / http)
|
||||
to a downstream server. For example, if a request of `https://example.com/resource` is sent to
|
||||
a proxy which forwards the request to `http://localhost:8080/resource`, then a header of
|
||||
`X-Forwarded-Proto: https` can be sent to inform the server that the original protocol was `https`.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue