mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56f0f0ec53 |
@@ -19,7 +19,7 @@ inputs:
|
||||
java-version:
|
||||
description: 'Java version to compile and test with'
|
||||
required: false
|
||||
default: '25'
|
||||
default: '24'
|
||||
publish:
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
required: false
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":warning: Attention Required"
|
||||
labels:
|
||||
- "for: upgrade-attention"
|
||||
summary:
|
||||
mode: "member-comment"
|
||||
config:
|
||||
prefix: "Attention Required:"
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
|
||||
@@ -19,20 +19,19 @@ inputs:
|
||||
java-version:
|
||||
description: 'Java version to use for the build'
|
||||
required: false
|
||||
default: '25'
|
||||
default: '24'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
|
||||
java-version: |
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
25
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
|
||||
@@ -17,14 +17,14 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
shell: bash
|
||||
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
|
||||
- name: Sync
|
||||
uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0
|
||||
uses: spring-io/central-publish-action@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.0
|
||||
with:
|
||||
token: ${{ inputs.central-token-password }}
|
||||
token-name: ${{ inputs.central-token-username }}
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -21,13 +21,13 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: 'spring-framework-7.1.x'
|
||||
build-name: 'spring-framework-7.0.x'
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 25
|
||||
- version: 24
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
|
||||
@@ -2,8 +2,8 @@ name: Release Milestone
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.1.0-M[1-9]
|
||||
- v7.1.0-RC[1-9]
|
||||
- v7.0.0-M[1-9]
|
||||
- v7.0.0-RC[1-9]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.1.[0-9]+
|
||||
- v7.0.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.2.x', '7.0.x', 'main' ]
|
||||
branch: [ '6.1.x' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
|
||||
@@ -30,23 +30,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'v0.0.2'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
run: ./gradlew spring-framework-release-verification-tests:test
|
||||
- name: Upload Build Reports on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@ ivy-cache
|
||||
argfile*
|
||||
activemq-data/
|
||||
classes/
|
||||
.cursor/
|
||||
|
||||
# Log files
|
||||
jxl.log
|
||||
@@ -39,7 +38,7 @@ bin
|
||||
.springBeans
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
|
||||
# IntelliJ IDEA artifacts and output dirs
|
||||
# IDEA artifacts and output dirs
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=25-librca
|
||||
java=24.0.1-librca
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# Contributing to the Spring Framework
|
||||
# Contributing to the Spring Framework
|
||||
|
||||
First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
@@ -120,7 +120,7 @@ source code into your IDE.
|
||||
The wiki pages
|
||||
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
|
||||
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
|
||||
define the source file coding standards we use along with some IntelliJ editor settings we customize.
|
||||
define the source file coding standards we use along with some IDEA editor settings we customize.
|
||||
|
||||
### Reference Docs
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
|
||||
|
||||
## Continuous Integration Builds
|
||||
|
||||
CI builds are defined with [GitHub Actions workflows](.github/workflows).
|
||||
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
|
||||
+21
-17
@@ -4,9 +4,9 @@ plugins {
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka'
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'com.gradleup.shadow' version "9.2.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 'io.spring.nullability' version '0.0.11' apply false
|
||||
id "io.spring.nullability" version "0.0.1" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -61,27 +61,31 @@ configure([rootProject] + javaProjects) { project ->
|
||||
testImplementation("org.junit.platform:junit-platform-suite")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk") {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/7.2/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/5.6/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/core/release/api/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.1.0/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
"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://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"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://docs.junit.org/5.13.3/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/",
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
// 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://www.javadoc.io/doc/tools.jackson.core/jackson-databind/3.0.0-rc4/"
|
||||
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ ext {
|
||||
dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.2.0"
|
||||
implementation "com.tngtech.archunit:archunit:1.4.1"
|
||||
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.0.0"
|
||||
implementation "com.tngtech.archunit:archunit:1.4.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
|
||||
@@ -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("13.4.2");
|
||||
checkstyle.setToolVersion("10.26.1");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -45,7 +45,7 @@ public class JavaConventions {
|
||||
* <p>NOTE: If you update this value, you should also update the value used in
|
||||
* the {@code javadoc} task in {@code framework-api.gradle}.
|
||||
*/
|
||||
private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(25);
|
||||
private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(24);
|
||||
|
||||
/**
|
||||
* The Java version we should use as the baseline for the compiled bytecode
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
public class KotlinConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm", plugin -> {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm", _ -> {
|
||||
project.getTasks().withType(KotlinCompile.class, this::configure);
|
||||
if (project.getLayout().getProjectDirectory().dir("src/main/kotlin").getAsFile().exists()) {
|
||||
project.getPlugins().apply(DokkaPlugin.class);
|
||||
|
||||
+3
-1
@@ -49,6 +49,7 @@ import static org.springframework.build.architecture.ArchitectureRules.classesSh
|
||||
import static org.springframework.build.architecture.ArchitectureRules.javaClassesShouldNotImportKotlinAnnotations;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToLowerCaseWithoutLocale;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToUpperCaseWithoutLocale;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.packageInfoShouldBeNullMarked;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks for architecture problems.
|
||||
@@ -63,7 +64,8 @@ public abstract class ArchitectureCheck extends DefaultTask {
|
||||
public ArchitectureCheck() {
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
getProhibitObjectsRequireNonNull().convention(true);
|
||||
getRules().addAll(classesShouldNotImportForbiddenTypes(),
|
||||
getRules().addAll(packageInfoShouldBeNullMarked(),
|
||||
classesShouldNotImportForbiddenTypes(),
|
||||
javaClassesShouldNotImportKotlinAnnotations(),
|
||||
allPackagesShouldBeFreeOfTangles(),
|
||||
noClassesShouldCallStringToLowerCaseWithoutLocale(),
|
||||
|
||||
@@ -46,6 +46,13 @@ abstract class ArchitectureRules {
|
||||
.because("String.toUpperCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule packageInfoShouldBeNullMarked() {
|
||||
return ArchRuleDefinition.classes()
|
||||
.that().haveSimpleName("package-info")
|
||||
.should().beAnnotatedWith("org.jspecify.annotations.NullMarked")
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
static ArchRule classesShouldNotImportForbiddenTypes() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should().dependOnClassesThat()
|
||||
|
||||
-15
@@ -18,7 +18,6 @@ package org.springframework.build.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
@@ -28,10 +27,6 @@ import org.gradle.api.plugins.ExtensionContainer;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.TaskContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
/**
|
||||
@@ -43,8 +38,6 @@ import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
*/
|
||||
public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||
|
||||
public static String VALIDATE_JAR_TASK_NAME = "validateMultiReleaseJar";
|
||||
|
||||
@Inject
|
||||
protected JavaToolchainService getToolchains() {
|
||||
throw new UnsupportedOperationException();
|
||||
@@ -64,13 +57,5 @@ public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||
tasks,
|
||||
dependencies,
|
||||
objects);
|
||||
|
||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.multirelease;
|
||||
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.CacheableTask;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@CacheableTask
|
||||
public abstract class MultiReleaseJarValidateTask extends JavaExec {
|
||||
|
||||
|
||||
public MultiReleaseJarValidateTask() {
|
||||
getMainModule().set("jdk.jartool");
|
||||
getArgumentProviders().add(() -> List.of("--validate", "--file", getJar().get().getAsFile().getAbsolutePath()));
|
||||
}
|
||||
|
||||
@Inject
|
||||
protected abstract JavaToolchainService getJavaToolchainService();
|
||||
|
||||
@InputFile
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract RegularFileProperty getJar();
|
||||
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class ShadowSource extends DefaultTask {
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
public DirectoryProperty getOutputDirectory() {
|
||||
DirectoryProperty getOutputDirectory() {
|
||||
return this.outputDirectory;
|
||||
}
|
||||
|
||||
|
||||
-45
@@ -26,15 +26,11 @@ import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link MultiReleaseJarPlugin}
|
||||
@@ -45,13 +41,10 @@ public class MultiReleaseJarPluginTests {
|
||||
|
||||
private File buildFile;
|
||||
|
||||
private File propertiesFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
this.buildFile = new File(this.projectDir, "build.gradle");
|
||||
this.propertiesFile = new File(this.projectDir, "gradle.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,50 +116,12 @@ public class MultiReleaseJarPluginTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
|
||||
void validateJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.build.multiReleaseJar'
|
||||
}
|
||||
version = '1.2.3'
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.release = 11
|
||||
}
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeGradleProperties("""
|
||||
org.gradle.jvmargs=-Duser.language=en
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {
|
||||
|
||||
public void method() {}
|
||||
|
||||
}
|
||||
""");
|
||||
writeClass("src/main/java", "Main.java", """
|
||||
public class Main {}
|
||||
""");
|
||||
assertThatThrownBy(() ->runGradle("validateMultiReleaseJar"))
|
||||
.isInstanceOf(UnexpectedBuildFailure.class)
|
||||
.hasMessageContaining("entry: META-INF/versions/17/Main.class, contains a class with different api from earlier version");
|
||||
}
|
||||
|
||||
private void writeBuildFile(String buildContent) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
|
||||
out.print(buildContent);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGradleProperties(String properties) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
|
||||
out.print(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClass(String path, String fileName, String fileContent) throws IOException {
|
||||
Path folder = this.projectDir.toPath().resolve(path);
|
||||
Files.createDirectories(folder);
|
||||
|
||||
@@ -20,10 +20,9 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
def springAspectsOutput = project(":spring-aspects").sourceSets.main.output
|
||||
javadoc {
|
||||
javadocTool.set(javaToolchains.javadocToolFor({
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
languageVersion = JavaLanguageVersion.of(24)
|
||||
}))
|
||||
|
||||
title = "${rootProject.description} ${version} API"
|
||||
@@ -49,8 +48,8 @@ javadoc {
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
classpath += files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
project(":spring-aspects").sourceSets.main.output
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
|
||||
@@ -13,10 +13,8 @@ content:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: include all releases from 6.2.0 to 9.*.*.
|
||||
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
|
||||
branches: ['main', '{6..9}.+({1..9}).x']
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
@@ -38,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
|
||||
|
||||
plugins {
|
||||
@@ -15,15 +14,11 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
node {
|
||||
version = '24.15.0'
|
||||
}
|
||||
|
||||
tasks.named("generateAntoraYml") {
|
||||
asciidocAttributes = project.provider( {
|
||||
return ["spring-version": project.version ]
|
||||
@@ -48,14 +43,10 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
// To avoid a redeclaration error with Kotlin compiler and set the JVM target
|
||||
tasks.withType(KotlinCompilationTask.class).configureEach {
|
||||
// To avoid a redeclaration error with Kotlin compiler
|
||||
tasks.named('compileKotlin', KotlinCompilationTask.class) {
|
||||
javaSources.from = []
|
||||
compilerOptions.jvmTarget = JvmTarget.JVM_17
|
||||
compilerOptions.freeCompilerArgs.addAll(
|
||||
"-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
|
||||
)
|
||||
compilerOptions.freeCompilerArgs = [ "-Xannotation-default-target=param-property" ] // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -74,7 +65,6 @@ dependencies {
|
||||
implementation("com.github.ben-manes.caffeine:caffeine")
|
||||
implementation("com.mchange:c3p0:0.9.5.5")
|
||||
implementation("com.oracle.database.jdbc:ojdbc11")
|
||||
implementation("io.micrometer:context-propagation")
|
||||
implementation("io.projectreactor.netty:reactor-netty-http")
|
||||
implementation("jakarta.jms:jakarta.jms-api")
|
||||
implementation("jakarta.servlet:jakarta.servlet-api")
|
||||
@@ -84,14 +74,10 @@ dependencies {
|
||||
implementation("javax.cache:cache-api")
|
||||
implementation("org.apache.activemq:activemq-ra:6.1.2")
|
||||
implementation("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
implementation("org.apache.groovy:groovy-templates")
|
||||
implementation("org.aspectj:aspectjweaver")
|
||||
implementation("org.assertj:assertj-core")
|
||||
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
implementation("org.freemarker:freemarker")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
implementation("tools.jackson.core:jackson-databind")
|
||||
implementation("tools.jackson.dataformat:jackson-dataformat-xml")
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 63 KiB |
@@ -17,7 +17,6 @@
|
||||
class="st5"
|
||||
id="svg5499"
|
||||
version="1.1"
|
||||
font-family="Helvetica, Arial, sans-serif"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mvc-splitted-contexts.svg"
|
||||
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
|
||||
@@ -37,7 +36,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path9164" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -48,7 +47,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8836" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -59,7 +58,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8520" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -70,7 +69,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8216" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -81,7 +80,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7924" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -92,7 +91,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7644" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -103,7 +102,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7375" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -114,7 +113,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7119" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -125,7 +124,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6875" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -136,7 +135,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6643" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -147,7 +146,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6423" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -158,7 +157,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6215" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -169,7 +168,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6019" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -180,7 +179,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5835" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -191,7 +190,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5663" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -202,7 +201,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5503" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -213,7 +212,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5355" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -224,7 +223,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5219" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -235,7 +234,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5095" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -246,7 +245,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path4983" /></marker><marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
@@ -257,7 +256,7 @@
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"><path
|
||||
id="path7394"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
@@ -268,7 +267,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8123"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
@@ -280,7 +279,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path8035"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -291,7 +290,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7959"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -302,7 +301,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7376"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
@@ -312,7 +311,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7388"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
@@ -324,7 +323,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7370"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
@@ -353,9 +352,9 @@
|
||||
v:viewMarkup="false" /><style
|
||||
type="text/css"
|
||||
id="style5501"><![CDATA[
|
||||
.st1 {fill:#969696;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#F8F9FA;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#333333;font-family:Helvetica, Arial, sans-serif;font-size:2.50001em;font-weight:bold}
|
||||
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
|
||||
.st4 {font-size:0.333333em;font-weight:normal}
|
||||
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]></style><g
|
||||
@@ -364,22 +363,20 @@
|
||||
v:groupContext="foregroundPage"
|
||||
id="g5503"
|
||||
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6599"
|
||||
width="382.68423"
|
||||
height="146.09897"
|
||||
x="87.884865"
|
||||
y="148.26482"
|
||||
rx="8" /><v:userDefs><v:ud
|
||||
y="148.26482" /><v:userDefs><v:ud
|
||||
v:nameU="SchemeName"
|
||||
v:val="VT4(Default)" /></v:userDefs><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5725"
|
||||
width="464.31128"
|
||||
height="374.11411"
|
||||
x="41.684383"
|
||||
y="112.3262"
|
||||
rx="8" /><title
|
||||
y="112.3262" /><title
|
||||
id="title5505">Page-1</title><v:pageProperties
|
||||
v:drawingScale="0.0393701"
|
||||
v:pageScale="0.0393701"
|
||||
@@ -388,16 +385,15 @@
|
||||
v:shadowOffsetY="-8.50394" /><v:layer
|
||||
v:name="Connector"
|
||||
v:index="0" /><rect
|
||||
style="fill:#F8F9FA;fill-opacity:1;fill-rule:evenodd;stroke:#6db33f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5715"
|
||||
width="322.8194"
|
||||
height="43.63184"
|
||||
x="119.95335"
|
||||
y="-135.66222"
|
||||
transform="scale(1,-1)"
|
||||
rx="8" /><text
|
||||
transform="scale(1,-1)" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
id="text5717"
|
||||
@@ -405,12 +401,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5719"
|
||||
x="172.843"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">DispatcherServlet</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
id="text6589"
|
||||
@@ -418,12 +414,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6591"
|
||||
x="106.770851"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">Servlet WebApplicationContext</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
id="text6593"
|
||||
@@ -433,39 +429,36 @@
|
||||
id="tspan6595"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555">(containing controllers, view resolvers,</tspan><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="260.00443"
|
||||
y="212.83057"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan6597">and other web-related beans)</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6620"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="114.52653"
|
||||
y="-259.43161"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="scale(1,-1)" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6622"
|
||||
width="87.843979"
|
||||
height="36.697304"
|
||||
x="223.39864"
|
||||
y="-287.19809"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-264.81918"
|
||||
x="117.92834"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6614"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
id="text6616"
|
||||
@@ -473,20 +466,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6618"
|
||||
x="124.24728"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
style="font-size:11.53426838px;fill:#333333">Controllers</tspan></text>
|
||||
style="font-size:11.53426838px">Controllers</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
style="font-size:11.53426838px"
|
||||
y="282.70709"
|
||||
x="221.61203"
|
||||
x="219.61203"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">ViewResolver</tspan></text>
|
||||
<rect
|
||||
@@ -496,10 +489,9 @@
|
||||
height="36.577778"
|
||||
width="114.4539"
|
||||
id="rect6628"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
id="text6630"
|
||||
@@ -507,27 +499,26 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6632"
|
||||
x="332.51276"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
style="font-size:11.53426838px;fill:#333333">HandlerMapping</tspan></text>
|
||||
style="font-size:11.53426838px">HandlerMapping</tspan></text>
|
||||
<rect
|
||||
y="338.69724"
|
||||
x="87.803261"
|
||||
height="121.5683"
|
||||
width="382.84744"
|
||||
id="rect6634"
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="8" /><text
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6636"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
|
||||
y="376.61673"
|
||||
x="113.61351"
|
||||
x="108.61351"
|
||||
id="tspan6638"
|
||||
sodipodi:role="line">Root WebApplicationContext</tspan></text>
|
||||
<text
|
||||
@@ -536,10 +527,10 @@
|
||||
id="text6640"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan6644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
|
||||
@@ -550,47 +541,43 @@
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6648"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6650"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="167.73116"
|
||||
y="-445.45563"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><text
|
||||
transform="scale(1,-1)" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6652"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
style="font-size:11.53426838px"
|
||||
y="448.55054"
|
||||
x="178.87148"
|
||||
x="175.87148"
|
||||
id="tspan6654"
|
||||
sodipodi:role="line">Services</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6656"
|
||||
width="86.393044"
|
||||
height="36.704323"
|
||||
x="306.86328"
|
||||
y="-439.60837"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-444.99475"
|
||||
x="310.26624"
|
||||
height="36.701977"
|
||||
width="86.876686"
|
||||
id="rect6658"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
id="text6660"
|
||||
@@ -598,20 +585,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6662"
|
||||
x="308.30771"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
style="font-size:11.53426838px;fill:#333333">Repositories</tspan></text>
|
||||
style="font-size:11.53426838px">Repositories</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
|
||||
id="path7643"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
style="fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
|
||||
id="path7645"
|
||||
inkscape:connector-curvature="0" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
id="text4963"
|
||||
@@ -620,6 +607,6 @@
|
||||
sodipodi:role="line"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan4965">Delegates if no bean found</tspan></text>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
@@ -198,7 +198,6 @@
|
||||
*** xref:web/webmvc/mvc-uri-building.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-async.adoc[]
|
||||
*** xref:web/webmvc/mvc-range.adoc[]
|
||||
*** xref:web/webmvc/mvc-data-binding.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc-versioning.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
@@ -262,6 +261,7 @@
|
||||
**** xref:web/websocket/stomp/configuration-performance.adoc[]
|
||||
**** xref:web/websocket/stomp/stats.adoc[]
|
||||
**** xref:web/websocket/stomp/testing.adoc[]
|
||||
** xref:web/integration.adoc[]
|
||||
* xref:web-reactive.adoc[]
|
||||
** xref:web/webflux.adoc[]
|
||||
*** xref:web/webflux/new-framework.adoc[]
|
||||
@@ -296,7 +296,6 @@
|
||||
*** xref:web/webflux-functional.adoc[]
|
||||
*** xref:web/webflux/uri-building.adoc[]
|
||||
*** xref:web/webflux/range.adoc[]
|
||||
*** xref:web/webflux/data-binding.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux-versioning.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
@@ -315,7 +314,7 @@
|
||||
*** xref:web/webflux-webclient/client-context.adoc[]
|
||||
*** xref:web/webflux-webclient/client-synchronous.adoc[]
|
||||
*** xref:web/webflux-webclient/client-testing.adoc[]
|
||||
** xref:web/webflux-http-service-client.adoc[]
|
||||
** xref:web/webflux-http-interface-client.adoc[]
|
||||
** xref:web/webflux-websocket.adoc[]
|
||||
** xref:web/webflux-test.adoc[]
|
||||
** xref:rsocket.adoc[]
|
||||
@@ -332,10 +331,9 @@
|
||||
*** xref:testing/testcontext-framework/application-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/ctx-management.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
@@ -346,7 +344,6 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
@@ -358,7 +355,6 @@
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/resttestclient.adoc[]
|
||||
** xref:testing/mockmvc.adoc[]
|
||||
*** xref:testing/mockmvc/overview.adoc[]
|
||||
*** xref:testing/mockmvc/setup-options.adoc[]
|
||||
@@ -452,13 +448,14 @@
|
||||
*** xref:languages/kotlin/null-safety.adoc[]
|
||||
*** xref:languages/kotlin/classes-interfaces.adoc[]
|
||||
*** xref:languages/kotlin/annotations.adoc[]
|
||||
*** xref:languages/kotlin/bean-registration-dsl.adoc[]
|
||||
*** xref:languages/kotlin/bean-definition-dsl.adoc[]
|
||||
*** xref:languages/kotlin/web.adoc[]
|
||||
*** xref:languages/kotlin/coroutines.adoc[]
|
||||
*** xref:languages/kotlin/spring-projects-in.adoc[]
|
||||
*** xref:languages/kotlin/getting-started.adoc[]
|
||||
*** xref:languages/kotlin/resources.adoc[]
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
|
||||
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
|
||||
|
||||
@@ -74,11 +74,6 @@ expressions used in XML bean definitions, `@Value`, etc.
|
||||
| The mode to use when compiling expressions for the
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
|
||||
|
||||
| `spring.expression.maxOperations`
|
||||
| The default maximum number of operations permitted during
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
expression evaluation.
|
||||
|
||||
| `spring.getenv.ignore`
|
||||
| Instructs Spring to ignore operating system environment variables if a Spring
|
||||
`Environment` property -- for example, a placeholder in a configuration String -- isn't
|
||||
@@ -86,11 +81,6 @@ resolvable otherwise. See
|
||||
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.http.response.flush.enabled`
|
||||
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
|
||||
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
|
||||
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
@@ -134,20 +124,11 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int
|
||||
| The maximum size of the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
|
||||
|
||||
| `spring.test.context.cache.pause`
|
||||
| The pause mode for the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context Pausing].
|
||||
|
||||
| `spring.test.context.failure.threshold`
|
||||
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
|
||||
in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
|
||||
|
||||
| `spring.test.extension.context.scope`
|
||||
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
|
||||
class hierarchies. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
|
||||
@@ -87,12 +87,14 @@ A crucial difference between Spring pooling and SLSB pooling is that Spring pool
|
||||
be applied to any POJO. As with Spring in general, this service can be applied in a
|
||||
non-invasive way.
|
||||
|
||||
Spring provides support for Commons Pool 2, which provides a
|
||||
Spring provides support for Commons Pool 2.2, which provides a
|
||||
fairly efficient pooling implementation. You need the `commons-pool` Jar on your
|
||||
application's classpath to use this feature. You can also subclass
|
||||
`org.springframework.aop.target.AbstractPoolingTargetSource` to support any other
|
||||
pooling API.
|
||||
|
||||
NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.
|
||||
|
||||
The following listing shows an example configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
|
||||
@@ -727,7 +727,7 @@ of determining parameter names, an exception will be thrown.
|
||||
parameter names. This discoverer is only used if such APIs are present on the classpath.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach.
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{spring-framework-api}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
|
||||
@@ -104,7 +104,7 @@ Note that pointcut definitions are generally matched against any intercepted met
|
||||
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
|
||||
potential non-public interactions through proxies, it needs to be defined accordingly.
|
||||
|
||||
If your interception needs to include method calls or even constructors within the target
|
||||
If your interception needs include method calls or even constructors within the target
|
||||
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
|
||||
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
|
||||
with different characteristics, so be sure to make yourself familiar with weaving
|
||||
|
||||
@@ -28,10 +28,6 @@ you can do so. However, you should consider the following issues:
|
||||
deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
|
||||
[[aop-forcing-proxy-types]]
|
||||
== Forcing Specific AOP Proxy Types
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
of the `<aop:config>` element to true, as follows:
|
||||
|
||||
@@ -64,24 +60,6 @@ To be clear, using `proxy-target-class="true"` on `<tx:annotation-driven/>`,
|
||||
proxies _for all three of them_.
|
||||
====
|
||||
|
||||
`@EnableAspectJAutoProxy`, `@EnableTransactionManagement` and related configuration
|
||||
annotations offer a corresponding `proxyTargetClass` attribute. These are collapsed
|
||||
into a single unified auto-proxy creator too, effectively applying the _strongest_
|
||||
proxy settings at runtime. As of 7.0, this applies to individual proxy processors
|
||||
as well, for example `@EnableAsync`, consistently participating in unified global
|
||||
default settings for all auto-proxying attempts in a given application.
|
||||
|
||||
The global default proxy type may differ between setups. While the core framework
|
||||
suggests interface-based proxies by default, Spring Boot may - depending on
|
||||
configuration properties - enable class-based proxies by default.
|
||||
|
||||
As of 7.0, forcing a specific proxy type for individual beans is possible through
|
||||
the `@Proxyable` annotation on a given `@Bean` method or `@Component` class, with
|
||||
`@Proxyable(INTERFACES)` or `@Proxyable(TARGET_CLASS)` overriding any globally
|
||||
configured default. For very specific purposes, you may even specify the proxy
|
||||
interface(s) to use through `@Proxyable(interfaces=...)`, limiting the exposure
|
||||
to selected interfaces rather than all interfaces that the target bean implements.
|
||||
|
||||
|
||||
[[aop-understanding-aop-proxies]]
|
||||
== Understanding AOP Proxies
|
||||
|
||||
@@ -378,7 +378,7 @@ The container also supports creating a bean with {spring-framework-api}++/beans/
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
. Bypassing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
@@ -19,6 +19,10 @@ its behavior changes.
|
||||
| Initialization of `SmartInitializingSingleton` beans.
|
||||
| `beanName` the name of the bean.
|
||||
|
||||
| `spring.context.annotated-bean-reader.create`
|
||||
| Creation of the `AnnotatedBeanDefinitionReader`.
|
||||
|
|
||||
|
||||
| `spring.context.base-packages.scan`
|
||||
| Scanning of base packages.
|
||||
| `packages` array of base packages for scanning.
|
||||
|
||||
+11
-11
@@ -151,17 +151,17 @@ injected into a `Set<MovieCatalog>` annotated with `@Qualifier("action")`.
|
||||
[TIP]
|
||||
====
|
||||
Letting qualifier values select against target bean names, within the type-matching
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point. If there
|
||||
is no other resolution indicator (such as a qualifier, a primary marker, or a fallback
|
||||
marker), for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses the
|
||||
same-named candidate, if any (either by bean name or by associated alias).
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point.
|
||||
If there is no other resolution indicator (such as a qualifier or a primary marker),
|
||||
for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses
|
||||
the same-named candidate, if any (either by bean name or by associated alias).
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present. As
|
||||
of 6.2, the container applies fast shortcut resolution for bean name matches, bypassing
|
||||
the full type matching algorithm when the parameter name matches the bean name and no
|
||||
type, qualifier, primary, or fallback conditions override the match. It is therefore
|
||||
recommendable for your parameter names to match the target bean names.
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
As of 6.2, the container applies fast shortcut resolution for bean name matches,
|
||||
bypassing the full type matching algorithm when the parameter name matches the
|
||||
bean name and no type, qualifier or primary conditions override the match. It is
|
||||
therefore recommendable for your parameter names to match the target bean names.
|
||||
====
|
||||
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
@@ -274,7 +274,7 @@ Kotlin::
|
||||
Next, you can provide the information for the candidate bean definitions. You can add
|
||||
`<qualifier/>` tags as sub-elements of the `<bean/>` tag and then specify the `type` and
|
||||
`value` to match your custom qualifier annotations. The type is matched against the
|
||||
fully-qualified class name of the annotation. Alternatively, as a convenience if no risk of
|
||||
fully-qualified class name of the annotation. Alternately, as a convenience if no risk of
|
||||
conflicting names exists, you can use the short class name. The following example
|
||||
demonstrates both approaches:
|
||||
|
||||
|
||||
@@ -37,18 +37,18 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
[NOTE]
|
||||
====
|
||||
An `@Autowired` annotation on such a constructor is not necessary if the target bean
|
||||
defines only one constructor. However, if several constructors are available and there is
|
||||
no primary or default constructor, at least one of the constructors must be annotated
|
||||
with `@Autowired` in order to instruct the container which one to use. See the discussion
|
||||
on xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution]
|
||||
for details.
|
||||
As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is no longer
|
||||
necessary if the target bean defines only one constructor to begin with. However, if
|
||||
several constructors are available and there is no primary/default constructor, at least
|
||||
one of the constructors must be annotated with `@Autowired` in order to instruct the
|
||||
container which one to use. See the discussion on
|
||||
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution] for details.
|
||||
====
|
||||
|
||||
You can apply the `@Autowired` annotation to _traditional_ setter methods, as the
|
||||
following example shows:
|
||||
You can also apply the `@Autowired` annotation to _traditional_ setter methods,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -84,8 +84,8 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can apply `@Autowired` to methods with arbitrary names and multiple arguments, as the
|
||||
following example shows:
|
||||
You can also apply the annotation to methods with arbitrary names and multiple
|
||||
arguments, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -176,15 +176,14 @@ Kotlin::
|
||||
====
|
||||
Make sure that your target components (for example, `MovieCatalog` or `CustomerPreferenceDao`)
|
||||
are consistently declared by the type that you use for your `@Autowired`-annotated
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at
|
||||
runtime.
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at runtime.
|
||||
|
||||
For XML-defined beans or component classes found via classpath scanning, the container
|
||||
usually knows the concrete type up front. However, for `@Bean` factory methods, you need
|
||||
to make sure that the declared return type is sufficiently expressive. For components
|
||||
that implement several interfaces or for components potentially referred to by their
|
||||
implementation type, declare the most specific return type on your factory method (at
|
||||
least as specific as required by the injection points referring to your bean).
|
||||
implementation type, consider declaring the most specific return type on your factory
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
.[[beans-autowired-annotation-self-injection]]Self Injection
|
||||
@@ -309,13 +308,12 @@ set of multiple matches for the specific bean type (as returned by the factory m
|
||||
|
||||
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
|
||||
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
|
||||
through `@Order` values in combination with `@Primary` or `@Fallback` on a single bean
|
||||
for each type.
|
||||
through `@Order` values in combination with `@Primary` on a single bean for each type.
|
||||
====
|
||||
|
||||
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
|
||||
The map values are all beans of the expected type, and the keys are the corresponding
|
||||
bean names, as the following example shows:
|
||||
The map values contain all beans of the expected type, and the keys contain the
|
||||
corresponding bean names, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -433,7 +431,7 @@ annotated constructor does not have to be public.
|
||||
====
|
||||
|
||||
Alternatively, you can express the non-required nature of a particular dependency
|
||||
through Java's `java.util.Optional`, as the following example shows:
|
||||
through Java 8's `java.util.Optional`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -447,8 +445,8 @@ through Java's `java.util.Optional`, as the following example shows:
|
||||
----
|
||||
|
||||
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
|
||||
for example, `org.jspecify.annotations.Nullable` from JSpecify) or just leverage Kotlin's
|
||||
built-in null-safety support:
|
||||
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
|
||||
null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -479,6 +477,13 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
|
||||
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
|
||||
detects type-level annotations and consistently declares JSpecify in its own codebase.
|
||||
====
|
||||
|
||||
You can also use `@Autowired` for interfaces that are well-known resolvable
|
||||
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
|
||||
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
|
||||
@@ -523,6 +528,5 @@ class MovieRecommender {
|
||||
The `@Autowired`, `@Inject`, `@Value`, and `@Resource` annotations are handled by Spring
|
||||
`BeanPostProcessor` implementations. This means that you cannot apply these annotations
|
||||
within your own `BeanPostProcessor` or `BeanFactoryPostProcessor` types (if any).
|
||||
|
||||
These types must be 'wired up' explicitly by using XML or a Spring `@Bean` method.
|
||||
====
|
||||
|
||||
+1
-2
@@ -27,5 +27,4 @@ with the `CustomAutowireConfigurer`
|
||||
|
||||
When multiple beans qualify as autowire candidates, the determination of a "`primary`" is
|
||||
as follows: If exactly one bean definition among the candidates has a `primary`
|
||||
attribute set to `true`, it is selected. For annotation-based configuration, see
|
||||
xref:core/beans/annotation-config/autowired-primary.adoc[Fine-tuning with `@Primary` or `@Fallback`].
|
||||
attribute set to `true`, it is selected.
|
||||
|
||||
@@ -239,6 +239,40 @@ The namespace itself provides the import directive feature. Further
|
||||
configuration features beyond plain bean definitions are available in a selection
|
||||
of XML namespaces provided by Spring -- for example, the `context` and `util` namespaces.
|
||||
|
||||
[[beans-factory-groovy]]
|
||||
=== The Groovy Bean Definition DSL
|
||||
|
||||
As a further example for externalized configuration metadata, bean definitions can also
|
||||
be expressed in Spring's Groovy Bean Definition DSL, as known from the Grails framework.
|
||||
Typically, such configuration live in a ".groovy" file with the structure shown in the
|
||||
following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
beans {
|
||||
dataSource(BasicDataSource) {
|
||||
driverClassName = "org.hsqldb.jdbcDriver"
|
||||
url = "jdbc:hsqldb:mem:grailsDB"
|
||||
username = "sa"
|
||||
password = ""
|
||||
settings = [mynew:"setting"]
|
||||
}
|
||||
sessionFactory(SessionFactory) {
|
||||
dataSource = dataSource
|
||||
}
|
||||
myService(MyService) {
|
||||
nestedBean = { AnotherBean bean ->
|
||||
dataSource = dataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This configuration style is largely equivalent to XML bean definitions and even
|
||||
supports Spring's XML configuration namespaces. It also allows for importing XML
|
||||
bean definition files through an `importBeans` directive.
|
||||
|
||||
|
||||
[[beans-factory-client]]
|
||||
== Using the Container
|
||||
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
[[beans-classpath-scanning]]
|
||||
= Classpath Scanning and Managed Components
|
||||
|
||||
Most examples in this chapter use XML to specify the configuration metadata that
|
||||
produces each `BeanDefinition` within the Spring container. The previous section
|
||||
(xref:core/beans/annotation-config.adoc[Annotation-based Container Configuration])
|
||||
demonstrates how to provide a lot of the configuration metadata through source-level
|
||||
annotations. Even in those examples, however, the "base" bean definitions are explicitly
|
||||
defined in the XML file, while the annotations drive only the dependency injection.
|
||||
|
||||
This section describes an option for implicitly detecting the candidate components by
|
||||
scanning the classpath. Candidate components are classes that match against filter
|
||||
criteria and have a corresponding bean definition registered with the container.
|
||||
This removes the need to use XML to perform bean registration. Instead, you can use
|
||||
annotations (for example, `@Component`), AspectJ type expressions, or your own
|
||||
Most examples in this chapter use XML to specify the configuration metadata that produces
|
||||
each `BeanDefinition` within the Spring container. The previous section
|
||||
(xref:core/beans/annotation-config.adoc[Annotation-based Container Configuration]) demonstrates how to provide a lot of the configuration
|
||||
metadata through source-level annotations. Even in those examples, however, the "base"
|
||||
bean definitions are explicitly defined in the XML file, while the annotations drive only
|
||||
the dependency injection. This section describes an option for implicitly detecting the
|
||||
candidate components by scanning the classpath. Candidate components are classes that
|
||||
match against a filter criteria and have a corresponding bean definition registered with
|
||||
the container. This removes the need to use XML to perform bean registration. Instead, you
|
||||
can use annotations (for example, `@Component`), AspectJ type expressions, or your own
|
||||
custom filter criteria to select which classes have bean definitions registered with
|
||||
the container.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
You can define beans using Java rather than using XML files. Take a look at the
|
||||
`@Configuration`, `@Bean`, `@Import`, and `@DependsOn` annotations for examples
|
||||
of how to use these features.
|
||||
`@Configuration`, `@Bean`, `@Import`, and `@DependsOn` annotations for examples of how to
|
||||
use these features.
|
||||
====
|
||||
|
||||
|
||||
@@ -70,7 +68,7 @@ Java::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -85,7 +83,7 @@ Kotlin::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Component` meta-annotation causes `@Service` to be treated in the same way as `@Component`.
|
||||
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
|
||||
======
|
||||
|
||||
You can also combine meta-annotations to create "`composed annotations`". For example,
|
||||
@@ -97,7 +95,7 @@ meta-annotations to allow customization. This can be particularly useful when yo
|
||||
want to only expose a subset of the meta-annotation's attributes. For example, Spring's
|
||||
`@SessionScope` annotation hard codes the scope name to `session` but still allows
|
||||
customization of the `proxyMode`. The following listing shows the definition of the
|
||||
`@SessionScope` annotation:
|
||||
`SessionScope` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -211,7 +209,7 @@ Java::
|
||||
@Service
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private final MovieFinder movieFinder;
|
||||
private MovieFinder movieFinder;
|
||||
|
||||
public SimpleMovieLister(MovieFinder movieFinder) {
|
||||
this.movieFinder = movieFinder;
|
||||
@@ -251,11 +249,11 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
To autodetect these classes and register the corresponding beans, you need to add
|
||||
`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute is
|
||||
configured with a common parent package for the two classes. Alternatively, you can
|
||||
specify a comma-, semicolon-, or space-separated list that includes the parent package
|
||||
of each class.
|
||||
`@ComponentScan` to your `@Configuration` class, where the `basePackages` attribute
|
||||
is a common parent package for the two classes. (Alternatively, you can specify a
|
||||
comma- or semicolon- or space-separated list that includes the parent package of each class.)
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -282,10 +280,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
TIP: For brevity, the preceding example could have used the implicit `value` attribute of
|
||||
the annotation instead: `@ComponentScan("org.example")`
|
||||
NOTE: For brevity, the preceding example could have used the `value` attribute of the
|
||||
annotation (that is, `@ComponentScan("org.example")`).
|
||||
|
||||
The following example uses XML configuration:
|
||||
The following alternative uses XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -325,68 +323,16 @@ sure that they are 'opened' (that is, that they use an `opens` declaration inste
|
||||
|
||||
Furthermore, the `AutowiredAnnotationBeanPostProcessor` and
|
||||
`CommonAnnotationBeanPostProcessor` are both implicitly included when you use the
|
||||
`<context:component-scan>` element. That means that the two components are autodetected
|
||||
and wired together -- all without any bean configuration metadata provided in XML.
|
||||
component-scan element. That means that the two components are autodetected and
|
||||
wired together -- all without any bean configuration metadata provided in XML.
|
||||
|
||||
NOTE: You can disable the registration of `AutowiredAnnotationBeanPostProcessor` and
|
||||
`CommonAnnotationBeanPostProcessor` by including the `annotation-config` attribute
|
||||
with a value of `false`.
|
||||
|
||||
|
||||
[[beans-scanning-placeholders-and-patterns]]
|
||||
=== Property Placeholders and Ant-style Patterns
|
||||
|
||||
The `basePackages` and `value` attributes in `@ComponentScan` support `${...}` property
|
||||
placeholders which are resolved against the `Environment` as well as Ant-style package
|
||||
patterns such as `"org.example.+++**+++"`.
|
||||
|
||||
In addition, multiple packages or patterns may be specified, either separately or within
|
||||
a single String — for example, `{"org.example.config", "org.example.service.+++**+++"}`
|
||||
or `"org.example.config, org.example.service.+++**+++"`.
|
||||
|
||||
The following example specifies the `app.scan.packages` property placeholder for the
|
||||
implicit `value` attribute in `@ComponentScan`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("${app.scan.packages}") // <1>
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(["\${app.scan.packages}"]) // <1>
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> `app.scan.packages` property placeholder to be resolved against the `Environment`
|
||||
======
|
||||
|
||||
The following listing represents a properties file which defines the `app.scan.packages`
|
||||
property. In the preceding example, it is assumed that this properties file has been
|
||||
registered with the `Environment` – for example, via `@PropertySource` or a similar
|
||||
mechanism.
|
||||
|
||||
[source,properties,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
app.scan.packages=org.example.config, org.example.service.**
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-filters]]
|
||||
=== Using Filters to Customize Scanning
|
||||
== Using Filters to Customize Scanning
|
||||
|
||||
By default, classes annotated with `@Component`, `@Repository`, `@Service`, `@Controller`,
|
||||
`@Configuration`, or a custom annotation that itself is annotated with `@Component` are
|
||||
@@ -423,8 +369,8 @@ The following table describes the filtering options:
|
||||
| A custom implementation of the `org.springframework.core.type.TypeFilter` interface.
|
||||
|===
|
||||
|
||||
The following example shows `@ComponentScan` configuration that excludes all
|
||||
`@Repository` annotations and includes "`Stub`" repositories instead:
|
||||
The following example shows the configuration ignoring all `@Repository` annotations
|
||||
and using "`stub`" repositories instead:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -476,367 +422,6 @@ annotated or meta-annotated with `@Component`, `@Repository`, `@Service`, `@Cont
|
||||
`@RestController`, or `@Configuration`.
|
||||
|
||||
|
||||
[[beans-scanning-name-generator]]
|
||||
=== Naming Autodetected Components
|
||||
|
||||
When a component is autodetected as part of the scanning process, its bean name is
|
||||
generated by the `BeanNameGenerator` strategy known to that scanner.
|
||||
|
||||
By default, the `AnnotationBeanNameGenerator` is used. For Spring
|
||||
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
|
||||
if you supply a name via the annotation's `value` attribute that name will be used as
|
||||
the name in the corresponding bean definition. This convention also applies when the
|
||||
`@jakarta.inject.Named` annotation is used instead of Spring stereotype annotations.
|
||||
|
||||
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
|
||||
the bean name is no longer required to be `value`. Custom stereotype annotations can
|
||||
declare an attribute with a different name (such as `name`) and annotate that attribute
|
||||
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
|
||||
declaration of `ControllerAdvice#name()` for a concrete example.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
|
||||
and will be removed in a future version of the framework. Consequently, custom stereotype
|
||||
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
|
||||
in `@Component`. See the source code declaration of `Repository#value()` and
|
||||
`ControllerAdvice#name()` for concrete examples.
|
||||
====
|
||||
|
||||
If an explicit bean name cannot be derived from such an annotation or for any other
|
||||
detected component (such as those discovered by custom filters), the default bean name
|
||||
generator returns the uncapitalized non-qualified class name. For example, if the
|
||||
following component classes were detected, the names would be `myMovieLister` and
|
||||
`movieFinderImpl`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
public class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement either the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`] or
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following examples show.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example"
|
||||
name-generator="org.example.MyNameGenerator" />
|
||||
</beans>
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you run into naming conflicts due to multiple autodetected components having the same
|
||||
non-qualified class name (for example, classes with identical names but residing in
|
||||
different packages), you can configure a `BeanNameGenerator` that defaults to the
|
||||
fully-qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` can be used for such purposes.
|
||||
|
||||
As of Spring Framework 7.0, if you encounter naming conflicts among `@Bean` methods in
|
||||
`@Configuration` classes, you can alternatively configure a
|
||||
`ConfigurationBeanNameGenerator` that generates unique bean names for `@Bean` methods.
|
||||
The `FullyQualifiedConfigurationBeanNameGenerator` can be used to generate
|
||||
fully-qualified default bean names for `@Bean` methods without an explicit `name`
|
||||
attribute — for example, `com.example.MyConfig.myBean` for an `@Bean` method named
|
||||
`myBean()` declared in `@Configuration` class `com.example.MyConfig`.
|
||||
|
||||
The `FullyQualifiedAnnotationBeanNameGenerator` and
|
||||
`FullyQualifiedConfigurationBeanNameGenerator` both reside in the
|
||||
`org.springframework.context.annotation` package.
|
||||
====
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
|
||||
[[beans-scanning-scope-resolver]]
|
||||
=== Providing a Scope for Autodetected Components
|
||||
|
||||
As with Spring-managed components in general, the default and most common scope for
|
||||
autodetected components is `singleton`. However, sometimes you need a different scope
|
||||
that can be specified by the `@Scope` annotation. You can provide the name of the
|
||||
scope within the annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Scope` annotations are only introspected on the concrete bean class (for annotated
|
||||
components) or the factory method (for `@Bean` methods). In contrast to XML bean
|
||||
definitions, there is no notion of bean definition inheritance, and inheritance
|
||||
hierarchies at the class level are irrelevant for metadata purposes.
|
||||
|
||||
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes].
|
||||
As with the pre-built annotations for those scopes, you may also compose your own scoping
|
||||
annotations by using Spring's meta-annotation approach: for example, a custom annotation
|
||||
meta-annotated with `@Scope("prototype")`, possibly also declaring a custom scoped-proxy mode.
|
||||
|
||||
NOTE: To provide a custom strategy for scope resolution rather than relying on the
|
||||
annotation-based approach, you can implement the
|
||||
{spring-framework-api}/context/annotation/ScopeMetadataResolver.html[`ScopeMetadataResolver`]
|
||||
interface. Be sure to include a default no-arg constructor. Then you can provide the
|
||||
fully qualified class name when configuring the scanner, as the following example of both
|
||||
an annotation and a bean definition shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
When using certain non-singleton scopes, it may be necessary to generate proxies for the
|
||||
scoped objects. The reasoning is described in
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
For this purpose, a scoped-proxy attribute is available on the component-scan
|
||||
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
|
||||
the following configuration results in standard JDK dynamic proxies:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-qualifiers]]
|
||||
=== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
metadata was provided on the candidate bean definitions by using the `qualifier` or `meta`
|
||||
child elements of the `bean` element in the XML. When relying upon classpath scanning for
|
||||
auto-detection of components, you can provide the qualifier metadata with type-level
|
||||
annotations on the candidate class. The following three examples demonstrate this
|
||||
technique:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
class ActionMovieCatalog : MovieCatalog
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
class ActionMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
public class CachingMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
class CachingMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As with most annotation-based alternatives, keep in mind that the annotation metadata is
|
||||
bound to the class definition itself, while the use of XML allows for multiple beans
|
||||
of the same type to provide variations in their qualifier metadata, because that
|
||||
metadata is provided per-instance rather than per-class.
|
||||
|
||||
|
||||
[[beans-factorybeans-annotations]]
|
||||
== Defining Bean Metadata within Components
|
||||
|
||||
@@ -1059,10 +644,10 @@ methods anywhere. However, regular `@Bean` methods in `@Configuration` classes n
|
||||
to be overridable -- that is, they must not be declared as `private` or `final`.
|
||||
|
||||
`@Bean` methods are also discovered on base classes of a given component or
|
||||
configuration class, as well as on Java default methods declared in interfaces
|
||||
configuration class, as well as on Java 8 default methods declared in interfaces
|
||||
implemented by the component or configuration class. This allows for a lot of
|
||||
flexibility in composing complex configuration arrangements, with even multiple
|
||||
inheritance being possible through Java default methods.
|
||||
inheritance being possible through Java 8 default methods as of Spring 4.2.
|
||||
|
||||
Finally, a single class may hold multiple `@Bean` methods for the same
|
||||
bean, as an arrangement of multiple factory methods to use depending on available
|
||||
@@ -1071,3 +656,349 @@ constructor or factory method in other configuration scenarios: The variant with
|
||||
the largest number of satisfiable dependencies is picked at construction time,
|
||||
analogous to how the container selects between multiple `@Autowired` constructors.
|
||||
====
|
||||
|
||||
|
||||
[[beans-scanning-name-generator]]
|
||||
== Naming Autodetected Components
|
||||
|
||||
When a component is autodetected as part of the scanning process, its bean name is
|
||||
generated by the `BeanNameGenerator` strategy known to that scanner.
|
||||
|
||||
By default, the `AnnotationBeanNameGenerator` is used. For Spring
|
||||
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
|
||||
if you supply a name via the annotation's `value` attribute that name will be used as
|
||||
the name in the corresponding bean definition. This convention also applies when the
|
||||
`@jakarta.inject.Named` annotation is used instead of Spring stereotype annotations.
|
||||
|
||||
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
|
||||
the bean name is no longer required to be `value`. Custom stereotype annotations can
|
||||
declare an attribute with a different name (such as `name`) and annotate that attribute
|
||||
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
|
||||
declaration of `ControllerAdvice#name()` for a concrete example.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
|
||||
and will be removed in a future version of the framework. Consequently, custom stereotype
|
||||
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
|
||||
in `@Component`. See the source code declaration of `Repository#value()` and
|
||||
`ControllerAdvice#name()` for concrete examples.
|
||||
====
|
||||
|
||||
If an explicit bean name cannot be derived from such an annotation or for any other
|
||||
detected component (such as those discovered by custom filters), the default bean name
|
||||
generator returns the uncapitalized non-qualified class name. For example, if the
|
||||
following component classes were detected, the names would be `myMovieLister` and
|
||||
`movieFinderImpl`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
public class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Service("myMovieLister")
|
||||
class SimpleMovieLister {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following example annotation
|
||||
and bean definition show.
|
||||
|
||||
TIP: If you run into naming conflicts due to multiple autodetected components having the
|
||||
same non-qualified class name (i.e., classes with identical names but residing in
|
||||
different packages), you may need to configure a `BeanNameGenerator` that defaults to the
|
||||
fully qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` located in package
|
||||
`org.springframework.context.annotation` can be used for such purposes.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example"
|
||||
name-generator="org.example.MyNameGenerator" />
|
||||
</beans>
|
||||
----
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
|
||||
[[beans-scanning-scope-resolver]]
|
||||
== Providing a Scope for Autodetected Components
|
||||
|
||||
As with Spring-managed components in general, the default and most common scope for
|
||||
autodetected components is `singleton`. However, sometimes you need a different scope
|
||||
that can be specified by the `@Scope` annotation. You can provide the name of the
|
||||
scope within the annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
public class MovieFinderImpl implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scope("prototype")
|
||||
@Repository
|
||||
class MovieFinderImpl : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Scope` annotations are only introspected on the concrete bean class (for annotated
|
||||
components) or the factory method (for `@Bean` methods). In contrast to XML bean
|
||||
definitions, there is no notion of bean definition inheritance, and inheritance
|
||||
hierarchies at the class level are irrelevant for metadata purposes.
|
||||
|
||||
For details on web-specific scopes such as "`request`" or "`session`" in a Spring context,
|
||||
see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other[Request, Session, Application, and WebSocket Scopes]. As with the pre-built annotations for those scopes,
|
||||
you may also compose your own scoping annotations by using Spring's meta-annotation
|
||||
approach: for example, a custom annotation meta-annotated with `@Scope("prototype")`,
|
||||
possibly also declaring a custom scoped-proxy mode.
|
||||
|
||||
NOTE: To provide a custom strategy for scope resolution rather than relying on the
|
||||
annotation-based approach, you can implement the
|
||||
{spring-framework-api}/context/annotation/ScopeMetadataResolver.html[`ScopeMetadataResolver`]
|
||||
interface. Be sure to include a default no-arg constructor. Then you can provide the
|
||||
fully qualified class name when configuring the scanner, as the following example of both
|
||||
an annotation and a bean definition shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
When using certain non-singleton scopes, it may be necessary to generate proxies for the
|
||||
scoped objects. The reasoning is described in xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection[Scoped Beans as Dependencies].
|
||||
For this purpose, a scoped-proxy attribute is available on the component-scan
|
||||
element. The three possible values are: `no`, `interfaces`, and `targetClass`. For example,
|
||||
the following configuration results in standard JDK dynamic proxies:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[beans-scanning-qualifiers]]
|
||||
== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
metadata was provided on the candidate bean definitions by using the `qualifier` or `meta`
|
||||
child elements of the `bean` element in the XML. When relying upon classpath scanning for
|
||||
auto-detection of components, you can provide the qualifier metadata with type-level
|
||||
annotations on the candidate class. The following three examples demonstrate this
|
||||
technique:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Qualifier("Action")
|
||||
class ActionMovieCatalog : MovieCatalog
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
public class ActionMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Genre("Action")
|
||||
class ActionMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
public class CachingMovieCatalog implements MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
@Offline
|
||||
class CachingMovieCatalog : MovieCatalog {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: As with most annotation-based alternatives, keep in mind that the annotation metadata is
|
||||
bound to the class definition itself, while the use of XML allows for multiple beans
|
||||
of the same type to provide variations in their qualifier metadata, because that
|
||||
metadata is provided per-instance rather than per-class.
|
||||
|
||||
@@ -575,7 +575,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honor that and will not register the method to listen to events.
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
|
||||
|
||||
The method signature once again declares the event type to which it listens,
|
||||
but, this time, with a flexible name and without implementing a specific listener interface.
|
||||
@@ -933,12 +933,13 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
try (StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
}
|
||||
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
// end the current step
|
||||
scanPackages.end();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -946,12 +947,13 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
try (val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) {
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages);
|
||||
}
|
||||
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
|
||||
// perform the actual phase we're instrumenting
|
||||
this.scanner.scan(basePackages)
|
||||
// end the current step
|
||||
scanPackages.end()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ the `@Bean` factory method in favor of any pre-declared constructor on the bean
|
||||
****
|
||||
|
||||
NOTE: We acknowledge that overriding beans in test scenarios is convenient, and there is
|
||||
explicit support for this. Please refer to
|
||||
explicit support for this as of Spring Framework 6.2. Please refer to
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc[this section] for more details.
|
||||
|
||||
|
||||
|
||||
+21
-16
@@ -50,8 +50,9 @@ XML configuration:
|
||||
|
||||
The preceding XML is more succinct. However, typos are discovered at runtime rather than
|
||||
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
|
||||
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
|
||||
completion when you create bean definitions. Such IDE assistance is highly recommended.
|
||||
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
|
||||
that supports automatic property completion when you create bean definitions. Such IDE
|
||||
assistance is highly recommended.
|
||||
|
||||
You can also configure a `java.util.Properties` instance, as follows:
|
||||
|
||||
@@ -84,11 +85,11 @@ element. The following example shows how to use it:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="collaborator" class="..." />
|
||||
<bean id="theTargetBean" class="..."/>
|
||||
|
||||
<bean id="client" class="...">
|
||||
<bean id="theClientBean" class="...">
|
||||
<property name="targetName">
|
||||
<idref bean="collaborator" />
|
||||
<idref bean="theTargetBean"/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
@@ -98,24 +99,28 @@ following snippet:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="collaborator" class="..." />
|
||||
<bean id="theTargetBean" class="..." />
|
||||
|
||||
<bean id="client" class="...">
|
||||
<property name="targetName" value="collaborator" />
|
||||
<bean id="theClientBean" class="...">
|
||||
<property name="targetName" ref="theTargetBean"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The first form is preferable to the second, because using the `idref` tag lets the
|
||||
container validate at deployment time that the referenced, named bean actually exists. In
|
||||
the second variation, no validation is performed on the value that is passed to the
|
||||
`targetName` property of the `client` bean. Typos are therefore only discovered (with most
|
||||
container validate at deployment time that the referenced, named bean actually
|
||||
exists. In the second variation, no validation is performed on the value that is passed
|
||||
to the `targetName` property of the `client` bean. Typos are only discovered (with most
|
||||
likely fatal results) when the `client` bean is actually instantiated. If the `client`
|
||||
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting
|
||||
exception may only be discovered long after the container is deployed.
|
||||
bean is a xref:core/beans/factory-scopes.adoc[prototype] bean, this typo and the resulting exception
|
||||
may only be discovered long after the container is deployed.
|
||||
|
||||
NOTE: A common place (at least in versions earlier than Spring 2.0) where the `<idref/>`
|
||||
element brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors]
|
||||
in a `ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
|
||||
NOTE: The `local` attribute on the `idref` element is no longer supported in the 4.0 beans
|
||||
XSD, since it does not provide value over a regular `bean` reference any more. Change
|
||||
your existing `idref local` references to `idref bean` when upgrading to the 4.0 schema.
|
||||
|
||||
A common place (at least in versions earlier than Spring 2.0) where the `<idref/>` element
|
||||
brings value is in the configuration of xref:core/aop-api/pfb.adoc#aop-pfb-1[AOP interceptors] in a
|
||||
`ProxyFactoryBean` bean definition. Using `<idref/>` elements when you specify the
|
||||
interceptor names prevents you from misspelling an interceptor ID.
|
||||
|
||||
|
||||
|
||||
@@ -67,13 +67,6 @@ interface, clearly indicating the post-processor nature of that bean. Otherwise,
|
||||
Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the
|
||||
initialization of other beans in the context, this early type detection is critical.
|
||||
|
||||
Furthermore, when registering a `BeanPostProcessor` via an `@Bean` factory method,
|
||||
declare the method as `static` and ideally with no dependencies. Doing so avoids eager
|
||||
initialization of the configuration class and other beans, which would make them
|
||||
ineligible for full post-processing (such as auto-proxying). See the
|
||||
"BeanPostProcessor-returning `@Bean` methods" section in the
|
||||
{spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc for details.
|
||||
|
||||
[[beans-factory-programmatically-registering-beanpostprocessors]]
|
||||
.Programmatically registering `BeanPostProcessor` instances
|
||||
NOTE: While the recommended approach for `BeanPostProcessor` registration is through
|
||||
@@ -87,7 +80,7 @@ of execution. Note also that `BeanPostProcessor` instances registered programmat
|
||||
are always processed before those registered through auto-detection, regardless of any
|
||||
explicit ordering.
|
||||
|
||||
.`BeanPostProcessor` instances and early initialization
|
||||
.`BeanPostProcessor` instances and AOP auto-proxying
|
||||
[NOTE]
|
||||
====
|
||||
Classes that implement the `BeanPostProcessor` interface are special and are treated
|
||||
@@ -97,23 +90,17 @@ of the `ApplicationContext`. Next, all `BeanPostProcessor` instances are registe
|
||||
in a sorted fashion and applied to all further beans in the container. Because AOP
|
||||
auto-proxying is implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessor`
|
||||
instances nor the beans they directly reference are eligible for auto-proxying and,
|
||||
thus, do not have aspects woven into them. More generally, any bean that is instantiated
|
||||
during this early phase is not eligible for full post-processing by all
|
||||
`BeanPostProcessor` instances.
|
||||
thus, do not have aspects woven into them.
|
||||
|
||||
For any such bean, you should see a WARN-level log message similar to the following.
|
||||
For any such bean, you should see an informational log message: `Bean someBean is not
|
||||
eligible for getting processed by all BeanPostProcessor interfaces (for example: not
|
||||
eligible for auto-proxying)`.
|
||||
|
||||
[quote]
|
||||
Bean 'someBean' of type [org.example.SomeType] is not eligible for getting processed by
|
||||
all BeanPostProcessors (for example: not eligible for auto-proxying).
|
||||
|
||||
To minimize the number of beans affected, register a `BeanPostProcessor` with a
|
||||
`static` `@Bean` method that has no dependencies (see the note above). If you have
|
||||
beans wired into your `BeanPostProcessor` by using autowiring or `@Resource` (which
|
||||
may fall back to autowiring), Spring might access unexpected beans when searching
|
||||
for type-matching dependency candidates and, therefore, make them ineligible for
|
||||
auto-proxying or other kinds of bean post-processing. For example, if you have a
|
||||
dependency annotated with `@Resource` where the field or setter name does not
|
||||
If you have beans wired into your `BeanPostProcessor` by using autowiring or
|
||||
`@Resource` (which may fall back to autowiring), Spring might access unexpected beans
|
||||
when searching for type-matching dependency candidates and, therefore, make them
|
||||
ineligible for auto-proxying or other kinds of bean post-processing. For example, if you
|
||||
have a dependency annotated with `@Resource` where the field or setter name does not
|
||||
directly correspond to the declared name of a bean and no name attribute is used,
|
||||
Spring accesses other beans for matching them by type.
|
||||
====
|
||||
@@ -148,7 +135,7 @@ Java::
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean);
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -177,48 +164,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can register the `InstantiationTracingBeanPostProcessor` with Java configuration
|
||||
by using a `static` `@Bean` method (recommended to avoid eager initialization of the
|
||||
configuration class and other beans):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public static InstantiationTracingBeanPostProcessor instantiationTracingBeanPostProcessor() {
|
||||
return new InstantiationTracingBeanPostProcessor();
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun instantiationTracingBeanPostProcessor() = InstantiationTracingBeanPostProcessor()
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via the
|
||||
`bean` element with XML configuration:
|
||||
The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -246,9 +192,10 @@ Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via
|
||||
----
|
||||
|
||||
Notice how the `InstantiationTracingBeanPostProcessor` is merely defined. It does not
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as with any
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as you would any
|
||||
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
|
||||
script.)
|
||||
script. The Spring dynamic language support is detailed in the chapter entitled
|
||||
xref:languages/dynamic.adoc[Dynamic Language Support].)
|
||||
|
||||
The following Java application runs the preceding code and configuration:
|
||||
|
||||
@@ -354,23 +301,6 @@ implement the `BeanFactoryPostProcessor` interface. It uses these beans as bean
|
||||
post-processors, at the appropriate time. You can deploy these post-processor beans as
|
||||
you would any other bean.
|
||||
|
||||
When registering a `BeanFactoryPostProcessor` via an `@Bean` factory method in a
|
||||
`@Configuration` class, declare the method as `static` to avoid lifecycle conflicts
|
||||
with annotation processing (such as `@Autowired`, `@Value`, and `@PostConstruct`) in the
|
||||
configuration class. See the "BeanFactoryPostProcessor-returning `@Bean` methods"
|
||||
section in the {spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc
|
||||
for details and an example.
|
||||
|
||||
For any non-static `@Bean` factory method with a `BeanFactoryPostProcessor` return type,
|
||||
you should see an INFO-level log message similar to the following.
|
||||
|
||||
[quote]
|
||||
@Bean method MyConfig.myBfpp is non-static and returns an object assignable to Spring's
|
||||
BeanFactoryPostProcessor interface. This will result in a failure to process annotations
|
||||
such as @Autowired, @Resource, and @PostConstruct within the method's declaring
|
||||
@Configuration class. Add the 'static' modifier to this method to avoid these container
|
||||
lifecycle issues; see @Bean javadoc for complete details.
|
||||
|
||||
NOTE: As with ``BeanPostProcessor``s , you typically do not want to configure
|
||||
``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a
|
||||
`Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all.
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
`@Bean` is a method-level annotation and a direct analog of the XML `<bean/>` element.
|
||||
The annotation supports some of the attributes offered by `<bean/>`, such as:
|
||||
|
||||
* xref:core/beans/definition.adoc#beans-beanname[name]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-initializingbean[init-method]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-disposablebean[destroy-method]
|
||||
* xref:core/beans/dependencies/factory-autowire.adoc[autowiring]
|
||||
* `name`.
|
||||
|
||||
You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
`@Component`-annotated class.
|
||||
@@ -17,10 +17,9 @@ You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
== Declaring a Bean
|
||||
|
||||
To declare a bean, you can annotate a method with the `@Bean` annotation. You use this
|
||||
method to register a bean definition within an `ApplicationContext` of the type specified
|
||||
by the method's return type. By default, the bean name is the same as the method name
|
||||
(unless a different xref:#beans-java-customizing-bean-naming[bean name generator] is
|
||||
configured). The following example shows a `@Bean` method declaration:
|
||||
method to register a bean definition within an `ApplicationContext` of the type
|
||||
specified as the method's return value. By default, the bean name is the same as
|
||||
the method name. The following example shows a `@Bean` method declaration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -127,7 +126,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
However, this limits the visibility for advanced type prediction to the specified
|
||||
However, this limits the visibility for advance type prediction to the specified
|
||||
interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`)
|
||||
known to the container only once the affected singleton bean has been instantiated.
|
||||
Non-lazy singleton beans get instantiated according to their declaration order,
|
||||
@@ -474,15 +473,8 @@ Kotlin::
|
||||
== Customizing Bean Naming
|
||||
|
||||
By default, configuration classes use a `@Bean` method's name as the name of the
|
||||
resulting bean. However, as of Spring Framework 7.0, you can change this default strategy
|
||||
by configuring a custom
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
when bootstrapping the context or configuring component scanning. For example,
|
||||
{spring-framework-api}/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.html[`FullyQualifiedConfigurationBeanNameGenerator`]
|
||||
can be used to generate fully-qualified default bean names for `@Bean` methods without an
|
||||
explicit `name` attribute. For individual `@Bean` methods, the default or
|
||||
generator-derived name can be overridden with the `name` attribute, as the following
|
||||
example shows:
|
||||
resulting bean. This functionality can be overridden, however, with the `name` attribute,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -513,7 +505,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Bean("myThing")` is equivalent to `@Bean(name = "myThing")`.
|
||||
|
||||
[[beans-java-bean-aliasing]]
|
||||
== Bean Aliasing
|
||||
|
||||
+8
-20
@@ -327,8 +327,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
TIP: Note that there is no need to specify `@Autowired` if the target bean defines
|
||||
only one constructor.
|
||||
TIP: Constructor injection in `@Configuration` classes is only supported as of Spring
|
||||
Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
|
||||
bean defines only one constructor.
|
||||
|
||||
[discrete]
|
||||
[[beans-java-injecting-imported-beans-fq]]
|
||||
@@ -338,11 +339,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
|
||||
modularity, but determining exactly where the autowired bean definitions are declared is
|
||||
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
|
||||
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
|
||||
explicit in the code, and this may be just fine. Note that the
|
||||
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
|
||||
showing how everything is wired, which may be all you need. Also, your Java IDE can
|
||||
easily find all declarations and uses of the `AccountRepository` type and quickly show
|
||||
you the location of `@Bean` methods that return that type.
|
||||
explicit in the code, and this may be just fine. Remember that the
|
||||
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
|
||||
can render graphs showing how everything is wired, which may be all you need. Also,
|
||||
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
|
||||
and quickly show you the location of `@Bean` methods that return that type.
|
||||
|
||||
In cases where this ambiguity is not acceptable and you wish to have direct navigation
|
||||
from within your IDE from one `@Configuration` class to another, consider autowiring the
|
||||
@@ -610,19 +611,6 @@ Kotlin::
|
||||
See the {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`]
|
||||
javadoc for more detail.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `@Conditional` annotation declared on an enclosing `@Configuration` class is only
|
||||
applied to the registration of a nested `@Configuration` class if the nested class is
|
||||
reached through the parser's recursion from its enclosing class, or via `@Import`. If a
|
||||
nested class is discovered independently of its enclosing class — for example, via
|
||||
`@ComponentScan` or by directly registering it against the application context — it is
|
||||
processed using only its own `@Conditional` annotations. Thus, if you wish to ensure that
|
||||
the same `@Conditional` annotations apply in such scenarios, you must redeclare the
|
||||
relevant annotations on the nested class, or extract them into a composed annotation
|
||||
which you apply to both the enclosing class and the nested class.
|
||||
====
|
||||
|
||||
|
||||
[[beans-java-combining]]
|
||||
== Combining Java and XML Configuration
|
||||
|
||||
+65
-2
@@ -9,7 +9,28 @@ efficient way.
|
||||
Those bean registrar implementations are typically imported with an `@Import` annotation
|
||||
on `@Configuration` classes.
|
||||
|
||||
include-code::./MyConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@Import(MyBeanRegistrar.class)
|
||||
class MyConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@Import(MyBeanRegistrar::class)
|
||||
class MyConfiguration {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: You can leverage type-level conditional annotations ({spring-framework-api}/context/annotation/Conditional.html[`@Conditional`],
|
||||
but also other variants) to conditionally import the related bean registrars.
|
||||
@@ -19,7 +40,49 @@ The bean registrar implementation uses {spring-framework-api}/beans/factory/Bean
|
||||
and flexible way. For example, it allows custom registration through an `if` expression, a
|
||||
`for` loop, etc.
|
||||
|
||||
include-code::./MyBeanRegistrar[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class);
|
||||
registry.registerBean("bar", Bar.class, spec -> spec
|
||||
.prototype()
|
||||
.lazyInit()
|
||||
.description("Custom description")
|
||||
.supplier(context -> new Bar(context.bean(Foo.class))));
|
||||
if (env.matchesProfiles("baz")) {
|
||||
registry.registerBean(Baz.class, spec -> spec
|
||||
.supplier(context -> new Baz("Hello World!")));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyBeanRegistrar : BeanRegistrarDsl({
|
||||
registerBean<Foo>()
|
||||
registerBean(
|
||||
name = "bar",
|
||||
prototype = true,
|
||||
lazyInit = true,
|
||||
description = "Custom description") {
|
||||
Bar(bean<Foo>())
|
||||
}
|
||||
profile("baz") {
|
||||
registerBean { Baz("Hello World!") }
|
||||
}
|
||||
})
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Bean registrars are supported with xref:core/aot.adoc[Ahead of Time Optimizations],
|
||||
either on the JVM or with GraalVM native images, including when instance suppliers are used.
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
[[beans-standard-annotations]]
|
||||
= Using JSR-330 Standard Annotations
|
||||
= Using JSR 330 Standard Annotations
|
||||
|
||||
Spring offers support for JSR-330 standard _Dependency Injection_ annotations which are
|
||||
available in the `jakarta.inject` package. These annotations may optionally be used as
|
||||
alternatives to Spring annotations.
|
||||
|
||||
To use them, you need to have the relevant jar in your classpath. For example, the
|
||||
`jakarta.inject` artifact is available in the standard Maven repository
|
||||
(`https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/`),
|
||||
Spring offers support for JSR-330 standard annotations (Dependency Injection). Those
|
||||
annotations are scanned in the same way as the Spring annotations. To use them, you need
|
||||
to have the relevant jars in your classpath.
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
If you use Maven, you can add the following dependency to your `pom.xml` file.
|
||||
If you use Maven, the `jakarta.inject` artifact is available in the standard Maven
|
||||
repository (
|
||||
https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/[https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/]).
|
||||
You can add the following dependency to your file pom.xml:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -27,14 +26,13 @@ If you use Maven, you can add the following dependency to your `pom.xml` file.
|
||||
[[beans-inject-named]]
|
||||
== Dependency Injection with `@Inject` and `@Named`
|
||||
|
||||
Instead of using `@Autowired` for dependency injection, you may optionally choose to use
|
||||
`@jakarta.inject.Inject` as follows.
|
||||
Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@@ -56,7 +54,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
@@ -74,19 +72,17 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level, and
|
||||
constructor-argument level.
|
||||
|
||||
Furthermore, as an alternative to Spring's `ObjectProvider` mechanism, you may choose to
|
||||
declare your injection point as a `jakarta.inject.Provider`, allowing for on-demand
|
||||
access to beans of shorter scopes or lazy access to other beans through a
|
||||
`Provider.get()` call. The following example offers a variant of the preceding example.
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level
|
||||
and constructor-argument level. Furthermore, you may declare your injection point as a
|
||||
`Provider`, allowing for on-demand access to beans of shorter scopes or lazy access to
|
||||
other beans through a `Provider.get()` call. The following example offers a variant of the
|
||||
preceding example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
@@ -109,10 +105,9 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Provider
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -128,15 +123,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
If you would like to use a qualified name for the dependency that should be injected, you
|
||||
may choose to use the `@Named` annotation as an alternative to Spring's `@Qualifier`
|
||||
support, as the following example shows.
|
||||
If you would like to use a qualified name for the dependency that should be injected,
|
||||
you should use the `@Named` annotation, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -156,7 +150,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -176,15 +170,12 @@ Kotlin::
|
||||
======
|
||||
|
||||
As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have a
|
||||
`required` attribute. The following examples show how to use `@Inject` with `Optional`,
|
||||
`@Nullable`, and Kotlin's built-in support for nullable types.
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have
|
||||
a `required` attribute. The following pair of examples show how to use `@Inject` and
|
||||
`@Nullable`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -198,11 +189,8 @@ As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -214,10 +202,8 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -228,21 +214,21 @@ Kotlin::
|
||||
|
||||
|
||||
[[beans-named]]
|
||||
== `@Named`: Standard Equivalent to the `@Component` Annotation
|
||||
== `@Named` and `@ManagedBean`: Standard Equivalents to the `@Component` Annotation
|
||||
|
||||
Instead of `@Component` or other Spring stereotype annotations, you may optionally choose
|
||||
to use `@jakarta.inject.Named`, as the following example shows.
|
||||
Instead of `@Component`, you can use `@jakarta.inject.Named` or `jakarta.annotation.ManagedBean`,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Named("movieListener")
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
@@ -258,12 +244,12 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
|
||||
@Named("movieListener")
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -274,15 +260,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
It is very common to use `@Component` or other Spring stereotype annotations without
|
||||
specifying an explicit name for the component, and `@Named` can be used in a similar
|
||||
fashion, as the following example shows.
|
||||
It is very common to use `@Component` without specifying a name for the component.
|
||||
`@Named` can be used in a similar fashion, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -303,7 +288,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -319,14 +304,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
When you use `@Named`, you can use component scanning in the exact same way as when you
|
||||
use Spring annotations, as the following example shows.
|
||||
When you use `@Named` or `@ManagedBean`, you can use component scanning in the
|
||||
exact same way as when you use Spring annotations, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example")
|
||||
@@ -337,7 +322,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"])
|
||||
@@ -347,106 +332,55 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` annotation is not composable. You
|
||||
should use Spring's stereotype model for building custom component annotations.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you work with legacy systems that still use `@javax.inject.Named` or
|
||||
`@javax.annotation.ManagedBean` for components (note the `javax` package namespace), you
|
||||
can explicitly configure component scanning to include those annotation types, as shown
|
||||
in the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = "org.example",
|
||||
includeFilters = @Filter({
|
||||
javax.inject.Named.class,
|
||||
javax.annotation.ManagedBean.class
|
||||
})
|
||||
)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = ["org.example"],
|
||||
includeFilters = [Filter([
|
||||
javax.inject.Named::class,
|
||||
javax.annotation.ManagedBean::class
|
||||
])]
|
||||
)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In addition, if you would like for the `value` attributes in `@javax.inject.Named` and
|
||||
`@javax.annotation.ManagedBean` to be used as component names, you need to override the
|
||||
`isStereotypeWithNameValue(...)` method in `AnnotationBeanNameGenerator` to add explicit
|
||||
support for `javax.annotation.ManagedBean` and `javax.inject.Named` and register your
|
||||
custom `AnnotationBeanNameGenerator` via the `nameGenerator` attribute in
|
||||
`@ComponentScan`.
|
||||
====
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `@ManagedBean`
|
||||
annotations are not composable. You should use Spring's stereotype model for building
|
||||
custom component annotations.
|
||||
|
||||
|
||||
[[beans-standard-annotations-limitations]]
|
||||
== Limitations of JSR-330 Standard Annotations
|
||||
|
||||
When you work with JSR-330 standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows.
|
||||
When you work with standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows:
|
||||
|
||||
[[annotations-comparison]]
|
||||
.Spring component model versus JSR-330 variants
|
||||
.Spring component model elements versus JSR-330 variants
|
||||
|===
|
||||
| Spring | JSR-330 | JSR-330 restrictions / comments
|
||||
| Spring| jakarta.inject.*| jakarta.inject restrictions / comments
|
||||
|
||||
| `@Autowired`
|
||||
| `@Inject`
|
||||
| `@Inject` has no `required` attribute. Can be used with Java's `Optional` instead.
|
||||
| @Autowired
|
||||
| @Inject
|
||||
| `@Inject` has no 'required' attribute. Can be used with Java 8's `Optional` instead.
|
||||
|
||||
| `@Component`
|
||||
| `@Named`
|
||||
| @Component
|
||||
| @Named / @ManagedBean
|
||||
| JSR-330 does not provide a composable model, only a way to identify named components.
|
||||
|
||||
| `@Scope("singleton")`
|
||||
| `@Singleton`
|
||||
| @Scope("singleton")
|
||||
| @Singleton
|
||||
| The JSR-330 default scope is like Spring's `prototype`. However, in order to keep it
|
||||
consistent with Spring's general defaults, a JSR-330 bean declared in the Spring
|
||||
container is a `singleton` by default. In order to use a scope other than `singleton`,
|
||||
you should use Spring's `@Scope` annotation. `jakarta.inject` also provides a
|
||||
`jakarta.inject.Scope` annotation; however, this one is only intended to be used
|
||||
`jakarta.inject.Scope` annotation: however, this one is only intended to be used
|
||||
for creating custom annotations.
|
||||
|
||||
| `@Qualifier`
|
||||
| `@Qualifier` / `@Named`
|
||||
| @Qualifier
|
||||
| @Qualifier / @Named
|
||||
| `jakarta.inject.Qualifier` is just a meta-annotation for building custom qualifiers.
|
||||
Concrete `String` qualifiers (like Spring's `@Qualifier` with a value) can be associated
|
||||
through `jakarta.inject.Named`.
|
||||
|
||||
| `@Value`
|
||||
| @Value
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| `@Lazy`
|
||||
| @Lazy
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| `ObjectFactory`
|
||||
| `Provider`
|
||||
| ObjectFactory
|
||||
| Provider
|
||||
| `jakarta.inject.Provider` is a direct alternative to Spring's `ObjectFactory`,
|
||||
only with a shorter `get()` method name. It can also be used in combination with
|
||||
Spring's `@Autowired` or with non-annotated constructors and setter methods.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
Java NIO provides `ByteBuffer` but many libraries build their own byte buffer API on top,
|
||||
especially for network operations where reusing buffers and/or using direct buffers is
|
||||
beneficial for performance. For example Netty has the `ByteBuf` hierarchy,
|
||||
Jetty uses pooled byte buffers with a callback to be released, and so on.
|
||||
beneficial for performance. For example Netty has the `ByteBuf` hierarchy, Undertow uses
|
||||
XNIO, Jetty uses pooled byte buffers with a callback to be released, and so on.
|
||||
The `spring-core` module provides a set of abstractions to work with various byte buffer
|
||||
APIs as follows:
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
|
||||
Language] but offers additional features, most notably method invocation and basic string
|
||||
templating functionality.
|
||||
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and
|
||||
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
|
||||
Spring community with a single well supported expression language that can be used across
|
||||
all the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements for
|
||||
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
|
||||
is based on a technology-agnostic API that lets other expression language implementations
|
||||
be integrated, should the need arise.
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
|
||||
EL, to name a few -- the Spring Expression Language was created to provide the Spring
|
||||
community with a single well supported expression language that can be used across all
|
||||
the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements
|
||||
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
|
||||
That said, SpEL is based on a technology-agnostic API that lets other expression language
|
||||
implementations be integrated, should the need arise.
|
||||
|
||||
While SpEL serves as the foundation for expression evaluation within the Spring
|
||||
portfolio, it is not directly tied to Spring and can be used independently. To
|
||||
|
||||
@@ -395,16 +395,6 @@ set a JVM system property or Spring property named `spring.context.expression.ma
|
||||
to the maximum expression length needed by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
Similarly, the number of operations performed during the evaluation of a SpEL expression
|
||||
cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If
|
||||
you create a `SpelExpressionParser` programmatically (the recommend approach), you can
|
||||
specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that
|
||||
you provide to the `SpelExpressionParser`. If you are not able to configure an explicit
|
||||
value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system
|
||||
property or Spring property named `spring.expression.maxOperations` to the maximum number
|
||||
of operations required by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
|
||||
[[expressions-spel-compilation]]
|
||||
== SpEL Compilation
|
||||
|
||||
@@ -2,12 +2,4 @@
|
||||
= Language Reference
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Expression Language (SpEL) expressions are composed of a sequence of tokens such
|
||||
as literals, operators, method invocations, and so forth.
|
||||
|
||||
Whitespace can be used freely between tokens to format and improve the readability of
|
||||
expressions. Specifically, the `\s` (space), `\t` (tab), `\r` (carriage return), and `\n`
|
||||
(newline) characters are all valid separators between tokens. However, whitespace is
|
||||
ignored by the expression parser unless it is part of a string literal.
|
||||
|
||||
The following sections describe the features and syntax of SpEL.
|
||||
This section describes how the Spring Expression Language works.
|
||||
|
||||
@@ -53,7 +53,7 @@ Kotlin::
|
||||
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
|
||||
|
||||
// uses CustomValue:::compareTo
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java)
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java);
|
||||
----
|
||||
======
|
||||
|
||||
@@ -167,7 +167,7 @@ Kotlin::
|
||||
[CAUTION]
|
||||
====
|
||||
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>`.
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
|
||||
|
||||
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
|
||||
to `false`.
|
||||
@@ -312,13 +312,13 @@ Kotlin::
|
||||
|
||||
// evaluates to 'a'
|
||||
val ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(Char::class.java)
|
||||
.getValue(Character::class.java);
|
||||
|
||||
// -- Repeat --
|
||||
|
||||
// evaluates to "abcabc"
|
||||
val repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String::class.java)
|
||||
.getValue(String::class.java);
|
||||
----
|
||||
======
|
||||
|
||||
@@ -485,7 +485,7 @@ Kotlin::
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
----
|
||||
======
|
||||
|
||||
@@ -541,7 +541,32 @@ For example, if we want to overload the `ADD` operator to allow two lists to be
|
||||
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
|
||||
follows.
|
||||
|
||||
include-code::./ListConcatenation[]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
pubic class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@Override
|
||||
public boolean overridesOperation(Operation operation, Object left, Object right) {
|
||||
return (operation == Operation.ADD &&
|
||||
left instanceof List && right instanceof List);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object operate(Operation operation, Object left, Object right) {
|
||||
if (operation == Operation.ADD &&
|
||||
left instanceof List list1 && right instanceof List list2) {
|
||||
|
||||
List result = new ArrayList(list1);
|
||||
result.addAll(list2);
|
||||
return result;
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"No overload for operation %s and operands [%s] and [%s]"
|
||||
.formatted(operation, left, right));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If we register `ListConcatenation` as the `OperatorOverloader` in a
|
||||
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
|
||||
@@ -564,8 +589,8 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val context = StandardEvaluationContext()
|
||||
context.operatorOverloader = ListConcatenation()
|
||||
StandardEvaluationContext context = StandardEvaluationContext()
|
||||
context.setOperatorOverloader(ListConcatenation())
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ is applicable for typical implementations of indexed structures.
|
||||
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
|
||||
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
|
||||
to bytecode for read access. Note, however, that the configured read-method must be
|
||||
invocable via a `public` class or `public` interface for compilation to succeed.
|
||||
invokable via a `public` class or `public` interface for compilation to succeed.
|
||||
|
||||
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
|
||||
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
|
||||
|
||||
@@ -6,7 +6,7 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Variable names must begin with a letter (as defined below), an underscore, or a dollar
|
||||
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
|
||||
sign.
|
||||
|
||||
Variable names must be composed of one or more of the following supported types of
|
||||
|
||||
@@ -8,9 +8,9 @@ recommended in order to get familiar with those annotations and semantics.
|
||||
|
||||
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 Java by leveraging nullability checkers such as https://github.com/uber/NullAway[NullAway] or IDEs
|
||||
supporting JSpecify annotations such as IntelliJ IDEA and Eclipse (the latter requiring manual configuration). In Kotlin,
|
||||
JSpecify annotations are automatically translated to {kotlin-docs}/null-safety.html[Kotlin's null safety].
|
||||
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
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
[[resilience]]
|
||||
= Resilience Features
|
||||
|
||||
As of 7.0, the core Spring Framework includes common resilience features, in particular
|
||||
<<resilience-annotations-retryable>> and <<resilience-annotations-concurrencylimit>>
|
||||
annotations for method invocations as well as <<resilience-programmatic-retry,
|
||||
programmatic retry support>>.
|
||||
As of 7.0, the core Spring Framework includes a couple of common resilience features,
|
||||
in particular `@Retryable` and `@ConcurrencyLimit` annotations for method invocations.
|
||||
|
||||
|
||||
[[resilience-annotations-retryable]]
|
||||
== `@Retryable`
|
||||
[[resilience-retryable]]
|
||||
== Using `@Retryable`
|
||||
|
||||
{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`] is an annotation
|
||||
that specifies retry characteristics for an individual method (with the annotation
|
||||
declared at the method level), or for all proxy-invoked methods in a given class hierarchy
|
||||
(with the annotation declared at the type level).
|
||||
`@Retryable` is a common annotation that specifies retry characteristics for an individual
|
||||
method (with the annotation declared at the method level), or for all proxy-invoked
|
||||
methods in a given class hierarchy (with the annotation declared at the type level).
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -23,25 +20,11 @@ public void sendNotification() {
|
||||
}
|
||||
----
|
||||
|
||||
By default, the method invocation will be retried for any exception thrown: with at most
|
||||
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
|
||||
between attempts. If all attempts have failed and the retry policy has been exhausted,
|
||||
the last original exception from the target method will be propagated to the caller.
|
||||
By default, the method invocation will be retried for any exception thrown: with at
|
||||
most 3 retry attempts after an initial failure, and a delay of 1 second between attempts.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `@Retryable` method will be invoked at least once and retried at most `maxRetries`
|
||||
times, where `maxRetries` is the maximum number of retry attempts. Specifically,
|
||||
`total attempts = 1 initial attempt + maxRetries attempts`.
|
||||
|
||||
For example, if `maxRetries` is set to `4`, the `@Retryable` method will be invoked at
|
||||
least once and at most 5 times.
|
||||
====
|
||||
|
||||
This can be specifically adapted for every method if necessary — for example, by narrowing
|
||||
the exceptions to retry via the `includes` and `excludes` attributes. The supplied
|
||||
exception types will be matched against an exception thrown by a failed invocation as well
|
||||
as nested causes.
|
||||
This can be specifically adapted for every method if necessary – for example, by narrowing
|
||||
the exceptions to retry:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -51,70 +34,42 @@ public void sendNotification() {
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: `@Retryable(MessageDeliveryException.class)` is a shortcut for
|
||||
`@Retryable(includes{nbsp}={nbsp}MessageDeliveryException.class)`.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
For advanced use cases, you can specify a custom `MethodRetryPredicate` via the
|
||||
`predicate` attribute in `@Retryable`, and the predicate will be used to determine whether
|
||||
to retry a failed method invocation based on a `Method` and a given `Throwable` – for
|
||||
example, by checking the message of the `Throwable`.
|
||||
|
||||
Custom predicates can be combined with `includes` and `excludes`; however, custom
|
||||
predicates will always be applied after `includes` and `excludes` have been applied.
|
||||
====
|
||||
|
||||
Or for 4 retry attempts and an exponential back-off strategy with a bit of jitter:
|
||||
Or for 5 retry attempts and an exponential back-off strategy with a bit of jitter:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(
|
||||
includes = MessageDeliveryException.class,
|
||||
maxRetries = 4,
|
||||
delay = 100,
|
||||
jitter = 10,
|
||||
multiplier = 2,
|
||||
maxDelay = 1000)
|
||||
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
----
|
||||
|
||||
Last but not least, `@Retryable` also works for reactive methods with a reactive return
|
||||
type, decorating the pipeline with Reactor's retry capabilities:
|
||||
Last but not least, `@Retryable` also works for reactive methods with a reactive
|
||||
return type, decorating the pipeline with Reactor's retry capabilities:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(maxRetries = 4, delay = 100)
|
||||
@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000)
|
||||
public Mono<Void> sendNotification() {
|
||||
return Mono.from(...); // <1>
|
||||
}
|
||||
----
|
||||
<1> This raw `Mono` will get decorated with a retry spec.
|
||||
|
||||
For details on the various characteristics, see the available annotation attributes in
|
||||
{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
|
||||
For details on the various characteristics, see the available annotation attributes
|
||||
in {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`].
|
||||
|
||||
TIP: Several attributes in `@Retryable` have `String` variants that provide property
|
||||
placeholder and SpEL support, as an alternative to the specifically typed annotation
|
||||
attributes used in the above examples.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
During `@Retryable` processing, Spring publishes a `MethodRetryEvent` for every exception
|
||||
coming out of the target method. This can be used to track/log all original exceptions
|
||||
whereas the caller of the `@Retryable` method will only ever see the last exception.
|
||||
====
|
||||
NOTE: There a `String` variants with placeholder support available for several attributes
|
||||
as well, as an alternative to the specifically typed annotation attributes used in the
|
||||
above examples.
|
||||
|
||||
|
||||
[[resilience-annotations-concurrencylimit]]
|
||||
== `@ConcurrencyLimit`
|
||||
[[resilience-concurrency]]
|
||||
== Using `@ConcurrencyLimit`
|
||||
|
||||
{spring-framework-api}/resilience/annotation/ConcurrencyLimit.html[`@ConcurrencyLimit`] is
|
||||
an annotation that specifies a concurrency limit for an individual method (with the
|
||||
annotation declared at the method level), or for all proxy-invoked methods in a given
|
||||
class hierarchy (with the annotation declared at the type level).
|
||||
`@ConcurrencyLimit` is an annotation that specifies a concurrency limit for an individual
|
||||
method (with the annotation declared at the method level), or for all proxy-invoked
|
||||
methods in a given class hierarchy (with the annotation declared at the type level).
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -128,232 +83,33 @@ This is meant to protect the target resource from being accessed from too many t
|
||||
the same time, similar to the effect of a pool size limit for a thread pool or a
|
||||
connection pool that blocks access if its limit is reached.
|
||||
|
||||
You may optionally set the limit to `1`, effectively locking access to the target bean
|
||||
You may optionally set the limit to 1, effectively locking access to the target bean
|
||||
instance:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ConcurrencyLimit(1)
|
||||
@ConcurrencyLimit(1) // <1>
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
----
|
||||
<1> 1 is the default, but specifying it makes the intent clearer.
|
||||
|
||||
Such limiting is particularly useful with Virtual Threads where there is generally no
|
||||
thread pool limit in place. For asynchronous tasks, this can be constrained on
|
||||
Such limiting is particularly useful with Virtual Threads where there is generally
|
||||
no thread pool limit in place. For asynchronous tasks, this can be constrained on
|
||||
{spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`].
|
||||
For synchronous invocations, this annotation provides equivalent behavior through
|
||||
{spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`]
|
||||
which has been available since Spring Framework 1.0 for programmatic use with the AOP
|
||||
framework.
|
||||
|
||||
TIP: `@ConcurrencyLimit` also has a `limitString` attribute that provides property
|
||||
placeholder and SpEL support, as an alternative to the `int` based examples above.
|
||||
|
||||
[[resilience-enable]]
|
||||
== Configuring `@EnableResilientMethods`
|
||||
|
||||
[[resilience-annotations-configuration]]
|
||||
== Enabling Resilient Methods
|
||||
|
||||
Like many of Spring's core annotation-based features, `@Retryable` and `@ConcurrencyLimit`
|
||||
are designed as metadata that you can choose to honor or ignore. The most convenient way
|
||||
to enable processing of the resilience annotations is to declare
|
||||
{spring-framework-api}/resilience/annotation/EnableResilientMethods.html[`@EnableResilientMethods`]
|
||||
on a corresponding `@Configuration` class.
|
||||
|
||||
Alternatively, these annotations can be individually enabled by defining a
|
||||
`RetryAnnotationBeanPostProcessor` or a `ConcurrencyLimitBeanPostProcessor` bean in the
|
||||
context.
|
||||
|
||||
|
||||
[[resilience-programmatic-retry]]
|
||||
== Programmatic Retry Support
|
||||
|
||||
In contrast to <<resilience-annotations-retryable>> which provides a declarative approach
|
||||
for specifying retry semantics for methods within beans registered in the
|
||||
`ApplicationContext`,
|
||||
{spring-framework-api}/core/retry/RetryTemplate.html[`RetryTemplate`] provides a
|
||||
programmatic API for retrying arbitrary blocks of code.
|
||||
|
||||
Specifically, a `RetryTemplate` executes and potentially retries a
|
||||
{spring-framework-api}/core/retry/Retryable.html[`Retryable`] operation based on a
|
||||
configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`].
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(); // <1>
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Implicitly uses `RetryPolicy.withDefaults()`.
|
||||
|
||||
By default, a retryable operation will be retried for any exception thrown: with at most
|
||||
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
|
||||
between attempts.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A retryable operation will be executed at least once and retried at most `maxRetries`
|
||||
times, where `maxRetries` is the maximum number of retry attempts. Specifically,
|
||||
`total attempts = 1 initial attempt + maxRetries attempts`.
|
||||
|
||||
For example, if `maxRetries` is set to `4`, the retryable operation will be invoked at
|
||||
least once and at most 5 times.
|
||||
====
|
||||
|
||||
If you only need to customize the number of retry attempts, you can use the
|
||||
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1>
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Explicitly uses `RetryPolicy.withMaxRetries(4)`.
|
||||
|
||||
If you need to narrow the types of exceptions to retry, that can be achieved via the
|
||||
`includes()` and `excludes()` builder methods. The supplied exception types will be
|
||||
matched against an exception thrown by a failed operation as well as nested causes.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.includes(MessageDeliveryException.class) // <1>
|
||||
.excludes(...) // <2>
|
||||
.build();
|
||||
|
||||
var retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Specify one or more exception types to include.
|
||||
<2> Specify one or more exception types to exclude.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
For advanced use cases, you can specify a custom `Predicate<Throwable>` via the
|
||||
`predicate()` method in the `RetryPolicy.Builder`, and the predicate will be used to
|
||||
determine whether to retry a failed operation based on a given `Throwable` – for example,
|
||||
by checking the message of the `Throwable`.
|
||||
|
||||
Custom predicates can be combined with `includes` and `excludes`; however, custom
|
||||
predicates will always be applied after `includes` and `excludes` have been applied.
|
||||
====
|
||||
|
||||
The following example demonstrates how to configure a `RetryPolicy` with 4 retry attempts
|
||||
and an exponential back-off strategy with a bit of jitter.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.includes(MessageDeliveryException.class)
|
||||
.maxRetries(4)
|
||||
.delay(Duration.ofMillis(100))
|
||||
.jitter(Duration.ofMillis(10))
|
||||
.multiplier(2)
|
||||
.maxDelay(Duration.ofSeconds(1))
|
||||
.build();
|
||||
|
||||
var retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Although the factory methods and builder API for `RetryPolicy` cover most common
|
||||
configuration scenarios, you can implement a custom `RetryPolicy` for complete control
|
||||
over the types of exceptions that should trigger a retry as well as the
|
||||
{spring-framework-api}/util/backoff/BackOff.html[`BackOff`] strategy to use. Note that
|
||||
you can also configure a customized `BackOff` strategy via the `backOff()` method in
|
||||
the `RetryPolicy.Builder`.
|
||||
====
|
||||
|
||||
Note that the examples above apply a pattern similar to `@Retryable` method invocations
|
||||
where the last original exception will be propagated to the caller, using the `invoke`
|
||||
variants on `RetryTemplate` which are available with and without a return value.
|
||||
The callback may throw unchecked exceptions, the last one of which is exposed for
|
||||
direct handling on the caller side:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
var result = retryTemplate.invoke(() -> {
|
||||
jmsClient.destination("notifications").send(...);
|
||||
return "result";
|
||||
});
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
`RetryTemplate` instances are very light and can be created on the fly,
|
||||
potentially with a specific retry policy to use for a given invocation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
new RetryTemplate(RetryPolicy.withMaxRetries(4)).invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
For deeper interaction, you may use RetryTemplate's `execute` method. The caller will
|
||||
have to handle the checked `RetryException` thrown by `RetryTemplate`, exposing the
|
||||
outcome of all attempts:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
var result = retryTemplate.execute(() -> {
|
||||
jmsClient.destination("notifications").send(...);
|
||||
return "result";
|
||||
});
|
||||
}
|
||||
catch (RetryException ex) {
|
||||
// ex.getExceptions() / ex.getLastException() ...
|
||||
}
|
||||
----
|
||||
|
||||
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
|
||||
with a `RetryTemplate` to react to key retry steps (before or after a retry attempt etc.)
|
||||
or simply to every invocation attempt, being able to track all exceptions coming out of
|
||||
the callback and all retry outcomes (exhaustion, interruption, timeout). This is
|
||||
particularly useful when using `invoke` where no retry state other than the last
|
||||
original exception is exposed otherwise:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryListener(new RetryListener() {
|
||||
@Override
|
||||
public void onRetryableExecution(RetryPolicy retryPolicy, Retryable<?> retryable, RetryState retryState) {
|
||||
...
|
||||
}
|
||||
});
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
|
||||
You can also compose multiple listeners via a
|
||||
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
|
||||
Note that like many of Spring's core annotation-based features, `@Retryable` and
|
||||
`@ConcurrencyLimit` are designed as metadata that you can choose to honor or ignore.
|
||||
The most convenient way to enable actual processing of the resilience annotations
|
||||
through AOP interception is to declare `@EnableResilientMethods` on a corresponding
|
||||
configuration class. Alternatively, you may declare `RetryAnnotationBeanPostProcessor`
|
||||
and/or `ConcurrencyLimitBeanPostProcessor` individually.
|
||||
|
||||
@@ -347,10 +347,10 @@ recognized and used as the `PropertyEditor` for `Something`-typed properties.
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com
|
||||
└── example
|
||||
└── things
|
||||
├── *Something*
|
||||
└── *SomethingEditor* // the PropertyEditor for the Something class
|
||||
chank
|
||||
pop
|
||||
Something
|
||||
SomethingEditor // the PropertyEditor for the Something class
|
||||
----
|
||||
|
||||
Note that you can also use the standard `BeanInfo` JavaBeans mechanism here as well
|
||||
@@ -362,10 +362,10 @@ following example uses the `BeanInfo` mechanism to explicitly register one or mo
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com
|
||||
└── example
|
||||
└── things
|
||||
├── *Something*
|
||||
└── *SomethingBeanInfo* // the BeanInfo for the Something class
|
||||
chank
|
||||
pop
|
||||
Something
|
||||
SomethingBeanInfo // the BeanInfo for the Something class
|
||||
----
|
||||
|
||||
The following Java source code for the referenced `SomethingBeanInfo` class
|
||||
|
||||
@@ -676,7 +676,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
|
||||
.paramSource(new Actor("Leonor", "Watling"))
|
||||
.paramSource(new Actor("Leonor", "Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ configuration options. You should instantiate the `SimpleJdbcInsert` in the data
|
||||
layer's initialization method. For this example, the initializing method is the
|
||||
`setDataSource` method. You do not need to subclass the `SimpleJdbcInsert` class. Instead,
|
||||
you can create a new instance and set the table name by using the `withTableName` method.
|
||||
Configuration methods for this class follow the `fluent` style that returns the instance
|
||||
Configuration methods for this class follow the `fluid` style that returns the instance
|
||||
of the `SimpleJdbcInsert`, which lets you chain all configuration methods. The following
|
||||
example uses only one configuration method (we show examples of multiple methods later):
|
||||
|
||||
@@ -349,11 +349,11 @@ parameters return the data read from the table.
|
||||
|
||||
You can declare `SimpleJdbcCall` in a manner similar to declaring `SimpleJdbcInsert`. You
|
||||
should instantiate and configure the class in the initialization method of your data-access
|
||||
layer. In contrast to the `StoredProcedure` class, you do not need to create a subclass,
|
||||
and you do not need to declare parameters that can be looked up in the database metadata.
|
||||
The following `SimpleJdbcCall` configuration example uses the preceding stored procedure.
|
||||
The only configuration option (other than the `DataSource`) is the name of the stored
|
||||
procedure.
|
||||
layer. Compared to the `StoredProcedure` class, you need not create a subclass
|
||||
and you need not to declare parameters that can be looked up in the database metadata.
|
||||
The following example of a `SimpleJdbcCall` configuration uses the preceding stored
|
||||
procedure (the only configuration option, in addition to the `DataSource`, is the name
|
||||
of the stored procedure):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -10,11 +10,11 @@ cover the other ORM technologies and show brief examples.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 7.0, Spring requires Hibernate ORM 7.x for Spring's
|
||||
`HibernateJpaVendorAdapter`.
|
||||
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.
|
||||
|
||||
The `org.springframework.orm.jpa.hibernate` package supersedes the former `orm.hibernate5`:
|
||||
now for use with Hibernate ORM 7.x, tightly integrated with `HibernateJpaVendorAdapter`
|
||||
now for use with Hibernate ORM 7.0, tightly integrated with `HibernateJpaVendorAdapter`
|
||||
as well as supporting Hibernate's native `SessionFactory.getCurrentSession()` style.
|
||||
====
|
||||
|
||||
|
||||
+42
-13
@@ -8,7 +8,7 @@ danger of undue coupling, because code that is meant to be used transactionally
|
||||
almost always deployed that way anyway.
|
||||
|
||||
NOTE: The standard `jakarta.transaction.Transactional` annotation is also supported as
|
||||
a drop-in replacement for Spring's own annotation. Please refer to the JTA documentation
|
||||
a drop-in replacement to Spring's own annotation. Please refer to the JTA documentation
|
||||
for more details.
|
||||
|
||||
The ease-of-use afforded by the use of the `@Transactional` annotation is best
|
||||
@@ -89,16 +89,47 @@ annotation in a `@Configuration` class. See the
|
||||
{spring-framework-api}/transaction/annotation/EnableTransactionManagement.html[javadoc]
|
||||
for full details.
|
||||
|
||||
The following example shows the configuration needed to enable annotation-driven transaction management:
|
||||
In XML configuration, the `<tx:annotation-driven/>` tag provides similar convenience:
|
||||
|
||||
include-code::./AppConfig[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- from the file 'context.xml' -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
TIP: In programmatic configuration, the `@EnableTransactionManagement` annotation uses any
|
||||
`TransactionManager` bean in the context. In XML configuration, you can omit
|
||||
the `transaction-manager` attribute in the `<tx:annotation-driven/>` tag if the bean
|
||||
name of the `TransactionManager` that you want to wire in has the name `transactionManager`.
|
||||
If the `TransactionManager` bean has any other name, you have to use the
|
||||
`transaction-manager` attribute explicitly, as in the preceding example.
|
||||
<!-- this is the service object that we want to make transactional -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- enable the configuration of transactional behavior based on annotations -->
|
||||
<!-- a TransactionManager is still required -->
|
||||
<tx:annotation-driven transaction-manager="txManager"/> <1>
|
||||
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<!-- (this dependency is defined somewhere else) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
<1> The line that makes the bean instance transactional.
|
||||
|
||||
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>`
|
||||
tag if the bean name of the `TransactionManager` that you want to wire in has the name
|
||||
`transactionManager`. If the `TransactionManager` bean that you want to dependency-inject
|
||||
has any other name, you have to use the `transaction-manager` attribute, as in the
|
||||
preceding example.
|
||||
|
||||
Reactive transactional methods use reactive return types in contrast to imperative
|
||||
programming arrangements as the following listing shows:
|
||||
@@ -203,10 +234,8 @@ on an interface, a class definition, or a method on a class. However, the mere p
|
||||
of the `@Transactional` annotation is not enough to activate the transactional behavior.
|
||||
The `@Transactional` annotation is merely metadata that can be consumed by corresponding
|
||||
runtime infrastructure which uses that metadata to configure the appropriate beans with
|
||||
transactional behavior. In the preceding examples that use programmatic configuration,
|
||||
the `@EnableTransactionManagement` annotation switches on actual transaction management
|
||||
at runtime. Whereas, in the preceding example that uses XML configuration, the
|
||||
`<tx:annotation-driven/>` element switches on actual transaction management at runtime.
|
||||
transactional behavior. In the preceding example, the `<tx:annotation-driven/>` element
|
||||
switches on actual transaction management at runtime.
|
||||
|
||||
TIP: The Spring team recommends that you annotate methods of concrete classes with the
|
||||
`@Transactional` annotation, rather than relying on annotated methods in interfaces,
|
||||
|
||||
+5
-7
@@ -86,13 +86,11 @@ rollback rules may be configured via the `rollbackFor`/`noRollbackFor` and
|
||||
`rollbackForClassName`/`noRollbackForClassName` attributes, which allow rules to be
|
||||
defined based on exception types or patterns, respectively.
|
||||
|
||||
When a rollback rule is defined with an exception type – for example, via `rollbackFor` –
|
||||
that type will be used to match against the type of a thrown exception. Specifically,
|
||||
given a configured exception type `C`, a thrown exception of type `T` will be considered
|
||||
a match against `C` if `T` is equal to `C` or a subclass of `C`. This provides type
|
||||
safety and avoids any unintentional matches that may occur when using a pattern. For
|
||||
example, a value of `jakarta.servlet.ServletException.class` will only match thrown
|
||||
exceptions of type `jakarta.servlet.ServletException` and its subclasses.
|
||||
When a rollback rule is defined with an exception type, that type will be used to match
|
||||
against the type of a thrown exception and its super types, providing type safety and
|
||||
avoiding any unintentional matches that may occur when using a pattern. For example, a
|
||||
value of `jakarta.servlet.ServletException.class` will only match thrown exceptions of
|
||||
type `jakarta.servlet.ServletException` and its subclasses.
|
||||
|
||||
When a rollback rule is defined with an exception pattern, the pattern can be a fully
|
||||
qualified class name or a substring of a fully qualified class name for an exception type
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ where all the underlying resources have to participate in the service-level tran
|
||||
|
||||
NOTE: By default, a participating transaction joins the characteristics of the outer scope,
|
||||
silently ignoring the local isolation level, timeout value, or read-only flag (if any).
|
||||
Consider switching the `validateExistingTransaction` flag to `true` on your transaction
|
||||
Consider switching the `validateExistingTransactions` flag to `true` on your transaction
|
||||
manager if you want isolation level declarations to be rejected when participating in
|
||||
an existing transaction with a different isolation level. This non-lenient mode also
|
||||
rejects read-only mismatches (that is, an inner read-write transaction that tries to participate
|
||||
|
||||
@@ -3,22 +3,21 @@
|
||||
:page-aliases: integration/class-data-sharing.adoc
|
||||
:page-aliases: integration/cds.adoc
|
||||
|
||||
The ahead-of-time cache is a JVM feature introduced in Java 24 via
|
||||
The ahead-of-time cache is a JVM feature introduced in Java 24 via the
|
||||
https://openjdk.org/jeps/483[JEP 483] that can help reduce the startup time and memory
|
||||
footprint of Java applications. AOT cache is a natural evolution of
|
||||
https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[Class Data Sharing (CDS)].
|
||||
footprint of Java applications. AOT cache is a natural evolution of https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[Class Data Sharing (CDS)].
|
||||
Spring Framework supports both CDS and AOT cache, and it is recommended that you use the
|
||||
latter if available in the JVM version you are using (Java 24+).
|
||||
later if available in the JVM version your are using (Java 24+).
|
||||
|
||||
To use this feature, an AOT cache should be created for the particular classpath of the
|
||||
application. It is possible to create this cache on the deployed instance, or during a
|
||||
training run performed for example when packaging the application thanks to a hook-point
|
||||
training run performed for example when packaging the application thanks to an hook-point
|
||||
provided by the Spring Framework to ease such use case. Once the cache is available, users
|
||||
should opt in to use it via a JVM flag.
|
||||
|
||||
NOTE: If you are using Spring Boot, it is highly recommended to leverage its
|
||||
{spring-boot-docs-ref}/packaging/efficient.html#packaging.efficient.unpacking[executable JAR unpacking support]
|
||||
which is designed to fulfill the class loading requirements of both the AOT cache and CDS.
|
||||
which is designed to fulfill the class loading requirements of both AOT cache and CDS.
|
||||
|
||||
== Creating the cache
|
||||
|
||||
@@ -30,22 +29,14 @@ invoked; but the lifecycle has not started, and the `ContextRefreshedEvent` has
|
||||
been published.
|
||||
|
||||
To create the cache during the training run, it is possible to specify the `-Dspring.context.exit=onRefresh`
|
||||
JVM flag to start and then exit your Spring application once the
|
||||
JVM flag to start then exit your Spring application once the
|
||||
`ApplicationContext` has refreshed:
|
||||
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
|
||||
AOT cache (Java 25+)::
|
||||
+
|
||||
[source,bash,subs="verbatim,quotes"]
|
||||
----
|
||||
java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar ...
|
||||
----
|
||||
|
||||
AOT cache (Java 24)::
|
||||
AOT cache::
|
||||
+
|
||||
[source,bash,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -64,12 +55,6 @@ java -XX:ArchiveClassesAtExit=app.jsa -Dspring.context.exit=onRefresh ...
|
||||
======
|
||||
--
|
||||
|
||||
NOTE: With Java 25+, AOT cache stores, among other things, the
|
||||
https://openjdk.org/jeps/515[method profiling information]. Therefore, to benefit of this capability,
|
||||
it is recommended to create an AOT cache for an application that experienced a portion of a
|
||||
production-like workflow instead of using the `-Dspring.context.exit=onRefresh` flag which designed to
|
||||
optimize only the startup of your application.
|
||||
|
||||
== Using the cache
|
||||
|
||||
Once the cache file has been created, you can use it to start your application faster:
|
||||
@@ -97,7 +82,7 @@ java -XX:SharedArchiveFile=app.jsa ...
|
||||
|
||||
Pay attention to the logs and the startup time to check if the AOT cache is used successfully.
|
||||
To figure out how effective the cache is, you can enable class loading logs by adding
|
||||
an extra attribute: `-Xlog:class+load:file=aot-cache.log`. This creates an `aot-cache.log` with
|
||||
an extra attribute: `-Xlog:class+load:file=aot-cache.log`. This creates a `aot-cache.log` with
|
||||
every attempt to load a class and its source. Classes that are loaded from the cache should have
|
||||
a "shared objects file" source, as shown in the following example:
|
||||
|
||||
@@ -110,11 +95,11 @@ a "shared objects file" source, as shown in the following example:
|
||||
[0.151s][info][class,load] org.springframework.context.MessageSource source: shared objects file
|
||||
----
|
||||
|
||||
If the AOT cache cannot be enabled or if you have a large number of classes that are not loaded from
|
||||
If the AOT cache can't be enabled or if you have a large number of classes that are not loaded from
|
||||
the cache, make sure that the following conditions are fulfilled when creating and using the cache:
|
||||
|
||||
- The very same JVM must be used.
|
||||
- The classpath must be specified as a JAR or a list of JARs, and avoid the usage of directories and `*` wildcard characters.
|
||||
- The timestamps of the JARs must be preserved.
|
||||
- When using the cache, the classpath must be the same as the one used to create it, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but will not be cached).
|
||||
- When using the cache, the classpath must be the same than the one used to create it, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but won't be cached).
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
|
||||
|
||||
|
||||
|
||||
[[appendix.xsd-schemas]]
|
||||
== XML Schemas
|
||||
|
||||
This part of the appendix lists XML schemas related to integration technologies.
|
||||
|
||||
|
||||
|
||||
[[appendix.xsd-schemas-jee]]
|
||||
=== The `jee` Schema
|
||||
|
||||
@@ -167,7 +172,7 @@ different properties with `jee`:
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -179,7 +184,7 @@ without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -195,7 +200,7 @@ with `jee`:
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -210,7 +215,7 @@ and a number of properties without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -229,7 +234,7 @@ and a number of properties with `jee`:
|
||||
|
||||
The `<jee:remote-slsb/>` element configures a reference to a `remote` EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -246,7 +251,7 @@ without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -308,7 +313,7 @@ xref:integration/jmx/naming.adoc#jmx-context-mbeanexport[Configuring Annotation-
|
||||
=== The `cache` Schema
|
||||
|
||||
You can use the `cache` elements to enable support for Spring's `@CacheEvict`, `@CachePut`,
|
||||
and `@Caching` annotations. The `cache` schema also supports declarative XML-based caching. See
|
||||
and `@Caching` annotations. It it also supports declarative XML-based caching. See
|
||||
xref:integration/cache/annotations.adoc#cache-annotation-enable[Enabling Caching Annotations] and
|
||||
xref:integration/cache/declarative-xml.adoc[Declarative XML-based Caching] for details.
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ xref:integration/cache/store-configuration.adoc#cache-store-configuration-jsr107
|
||||
[[cache-store-configuration-caffeine]]
|
||||
== Caffeine Cache
|
||||
|
||||
Caffeine is a rewrite of Guava's cache, and its implementation is located in the
|
||||
Caffeine is a Java 8 rewrite of Guava's cache, and its implementation is located in the
|
||||
`org.springframework.cache.caffeine` package and provides access to several features
|
||||
of Caffeine.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ behind the scenes for each annotated method, by using a `JmsListenerContainerFac
|
||||
Such a container is not registered against the application context but can be easily
|
||||
located for management purposes by using the `JmsListenerEndpointRegistry` bean.
|
||||
|
||||
TIP: `@JmsListener` is a repeatable annotation, so you can associate
|
||||
TIP: `@JmsListener` is a repeatable annotation on Java 8, so you can associate
|
||||
several JMS destinations with the same method by adding additional `@JmsListener`
|
||||
declarations to it.
|
||||
|
||||
|
||||
@@ -9,7 +9,39 @@ that takes no destination argument uses the default destination.
|
||||
The following example uses the `MessageCreator` callback to create a text message from the
|
||||
supplied `Session` object:
|
||||
|
||||
include-code::./JmsQueueSender[]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.JMSException;
|
||||
import jakarta.jms.Message;
|
||||
import jakarta.jms.Queue;
|
||||
import jakarta.jms.Session;
|
||||
|
||||
import org.springframework.jms.core.MessageCreator;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
|
||||
public class JmsQueueSender {
|
||||
|
||||
private JmsTemplate jmsTemplate;
|
||||
private Queue queue;
|
||||
|
||||
public void setConnectionFactory(ConnectionFactory cf) {
|
||||
this.jmsTemplate = new JmsTemplate(cf);
|
||||
}
|
||||
|
||||
public void setQueue(Queue queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
public void simpleSend() {
|
||||
this.jmsTemplate.send(this.queue, new MessageCreator() {
|
||||
public Message createMessage(Session session) throws JMSException {
|
||||
return session.createTextMessage("hello queue world");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In the preceding example, the `JmsTemplate` is constructed by passing a reference to a
|
||||
`ConnectionFactory`. As an alternative, a zero-argument constructor and
|
||||
@@ -52,7 +84,21 @@ gives you access to the message after it has been converted but before it is sen
|
||||
following example shows how to modify a message header and a property after a
|
||||
`java.util.Map` is converted to a message:
|
||||
|
||||
include-code::./JmsSenderWithConversion[]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public void sendWithConversion() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("Name", "Mark");
|
||||
map.put("Age", new Integer(47));
|
||||
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
|
||||
public Message postProcessMessage(Message message) throws JMSException {
|
||||
message.setIntProperty("AccountID", 1234);
|
||||
message.setJMSCorrelationID("123-00001");
|
||||
return message;
|
||||
}
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
This results in a message of the following form:
|
||||
|
||||
@@ -80,6 +126,32 @@ to `jakarta.jms.TextMessage`, `jakarta.jms.BytesMessage`, etc. For a contract su
|
||||
generic message payloads, use `org.springframework.messaging.converter.MessageConverter`
|
||||
with `JmsMessagingTemplate` or preferably `JmsClient` as your central delegate instead.
|
||||
|
||||
|
||||
[[jms-sending-jmsclient]]
|
||||
== Sending a Message with `JmsClient`
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Reusable handle, typically created through JmsClient.create(ConnectionFactory)
|
||||
// For custom conversion, use JmsClient.create(ConnectionFactory, MessageConverter)
|
||||
private JmsClient jmsClient;
|
||||
|
||||
public void sendWithConversion() {
|
||||
this.jmsClient.destination("myQueue")
|
||||
.withTimeToLive(1000)
|
||||
.send("myPayload"); // optionally with a headers Map next to the payload
|
||||
}
|
||||
|
||||
public void sendCustomMessage() {
|
||||
Message<?> message =
|
||||
MessageBuilder.withPayload("myPayload").build(); // optionally with headers
|
||||
this.jmsClient.destination("myQueue")
|
||||
.withTimeToLive(1000)
|
||||
.send(message);
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[jms-sending-callbacks]]
|
||||
== Using `SessionCallback` and `ProducerCallback` on `JmsTemplate`
|
||||
|
||||
@@ -88,21 +160,3 @@ want to perform multiple operations on a JMS `Session` or `MessageProducer`. The
|
||||
`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` /
|
||||
`MessageProducer` pair, respectively. The `execute()` methods on `JmsTemplate` run
|
||||
these callback methods.
|
||||
|
||||
|
||||
[[jms-sending-jmsclient]]
|
||||
== Sending a Message with `JmsClient`
|
||||
|
||||
include-code::./JmsClientSample[]
|
||||
|
||||
|
||||
[[jms-sending-postprocessor]]
|
||||
== Post-processing outgoing messages
|
||||
|
||||
Applications often need to intercept messages before they are sent out, for example to add message properties to all outgoing messages.
|
||||
The `org.springframework.messaging.core.MessagePostProcessor` based on the spring-messaging `Message` can do that,
|
||||
when configured on the `JmsClient`. It will be used for all outgoing messages sent with the `send` and `sendAndReceive` methods.
|
||||
|
||||
Here is an example of an interceptor adding a "tenantId" property to all outgoing messages.
|
||||
|
||||
include-code::./JmsClientWithPostProcessor[]
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
[[observability]]
|
||||
= Observability Support
|
||||
|
||||
With https://docs.micrometer.io/micrometer/reference/concepts.html[Micrometer], developers can instrument libraries and applications for metrics (timers, gauges, counters)
|
||||
that collect statistics about their runtime behavior. Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
https://docs.micrometer.io/tracing/reference/[Micrometer can also produce traces], giving you a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Micrometer defines an {micrometer-docs}/observation.html[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Each observation will produce:
|
||||
|
||||
* https://docs.micrometer.io/micrometer/reference/observation/components.html#micrometer-observation-default-meter-handler[several metrics - a timer, a long task timer and many counters]
|
||||
* a https://docs.micrometer.io/tracing/reference/glossary.html[span for the current trace]
|
||||
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||
You can learn more about {spring-boot-docs-ref}/actuator/observability.html[configuring the observability infrastructure in Spring Boot].
|
||||
@@ -194,13 +189,13 @@ This observation uses the `io.micrometer.jakarta9.instrument.jms.DefaultJmsProce
|
||||
[[observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
|
||||
HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications,
|
||||
or `"http.server.request.duration"` if using the OpenTelemetry convention.
|
||||
HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
|
||||
[[observability.http-server.servlet]]
|
||||
=== Servlet applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
||||
It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the Servlet filter.
|
||||
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and xref:web/webmvc/mvc-ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
|
||||
@@ -212,11 +207,6 @@ NOTE: Because the instrumentation is done at the Servlet Filter level, the obser
|
||||
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
|
||||
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside the scope of this project.
|
||||
|
||||
[[observability.http-server.servlet.default]]
|
||||
==== Default Semantic Convention
|
||||
|
||||
It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
@@ -238,16 +228,6 @@ By default, the following `KeyValues` are created:
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
[[observability.http-server.servlet.otel]]
|
||||
==== OpenTelemetry Semantic Convention
|
||||
|
||||
An OpenTelemetry variant is available with `org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention`, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This variant complies with the https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/http/http-metrics.md[OpenTelemetry Semantic Conventions for HTTP Metrics (v1.36.0)]
|
||||
and the https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/http/http-spans.md[OpenTelemetry Semantic Conventions for HTTP Spans (v1.36.0)].
|
||||
|
||||
|
||||
[[observability.http-server.reactive]]
|
||||
=== Reactive applications
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ The Spring Framework provides the following choices for making calls to REST end
|
||||
|
||||
* 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, now deprecated in favor of `RestClient`
|
||||
* xref:integration/rest-clients.adoc#rest-http-service-client[HTTP Service Clients] -- annotated interface backed by generated proxy
|
||||
* 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]]
|
||||
@@ -402,27 +402,6 @@ To serialize only a subset of the object properties, you can specify a {baeldung
|
||||
.toBodilessEntity();
|
||||
----
|
||||
|
||||
==== URL encoded Forms
|
||||
|
||||
URL encoded forms, using the `"application/x-www-form-urlencoded"` media type, are useful for sending String key/values over the wire.
|
||||
This is supported by the `FormHttpMessageConverter`, if the application uses a `MultiValueMap<String, String>` as source instance
|
||||
or a target type.
|
||||
|
||||
For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
||||
form.add("project", "Spring Framework");
|
||||
form.add("module", "spring-web");
|
||||
ResponseEntity<Void> response = this.restClient.post()
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body(form)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
----
|
||||
|
||||
|
||||
==== Multipart
|
||||
|
||||
To send multipart data, you need to provide a `MultiValueMap<String, Object>` whose values may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for part content with headers.
|
||||
@@ -440,70 +419,18 @@ For example:
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
|
||||
|
||||
ResponseEntity<Void> response = this.restClient.post()
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA)
|
||||
.body(parts)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
// send using RestClient.post or RestTemplate.postForEntity
|
||||
----
|
||||
|
||||
In most cases, you do not have to specify the `Content-Type` for each part.
|
||||
The content type is determined automatically based on the `HttpMessageConverter` chosen to serialize it or, in the case of a `Resource`, based on the file extension.
|
||||
If necessary, you can explicitly provide the `MediaType` with an `HttpEntity` wrapper.
|
||||
|
||||
The `Content-Type` is set to `multipart/form-data` by the `MultipartHttpMessageConverter`.
|
||||
As seen in the previous section, `MultiValueMap` types can also be used for URL encoded forms.
|
||||
It is preferable to explicitly set the media type in the `Content-Type` or `Accept` HTTP request headers to ensure that the expected
|
||||
message converter is used.
|
||||
|
||||
`RestClient` can also receive multipart responses.
|
||||
To decode a multipart response body, use a `ParameterizedTypeReference<MultiValueMap<String, Part>>`.
|
||||
The decoded map contains `Part` instances where `FormFieldPart` represents form field values
|
||||
and `FilePart` represents file parts with a `filename()` and a `transferTo()` method.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
MultiValueMap<String, Part> result = this.restClient.get()
|
||||
.uri("https://example.com/upload")
|
||||
.accept(MediaType.MULTIPART_FORM_DATA)
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<>() {});
|
||||
|
||||
Part field = result.getFirst("fieldPart");
|
||||
if (field instanceof FormFieldPart formField) {
|
||||
String fieldValue = formField.value();
|
||||
}
|
||||
Part file = result.getFirst("filePart");
|
||||
if (file instanceof FilePart filePart) {
|
||||
filePart.transferTo(Path.of("/tmp/" + filePart.filename()));
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
val result = this.restClient.get()
|
||||
.uri("https://example.com/upload")
|
||||
.accept(MediaType.MULTIPART_FORM_DATA)
|
||||
.retrieve()
|
||||
.body(object : ParameterizedTypeReference<MultiValueMap<String, Part>>() {})
|
||||
|
||||
val field = result?.getFirst("fieldPart")
|
||||
if (field is FormFieldPart) {
|
||||
val fieldValue = field.value()
|
||||
}
|
||||
val file = result?.getFirst("filePart")
|
||||
if (file is FilePart) {
|
||||
file.transferTo(Path.of("/tmp/" + file.filename()))
|
||||
}
|
||||
----
|
||||
======
|
||||
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set to `multipart/form-data` by the `FormHttpMessageConverter`.
|
||||
If the `MultiValueMap` has `String` values, the `Content-Type` defaults to `application/x-www-form-urlencoded`.
|
||||
If necessary the `Content-Type` may also be set explicitly.
|
||||
|
||||
[[rest-request-factories]]
|
||||
=== Client Request Factories
|
||||
@@ -539,7 +466,7 @@ synchronous, asynchronous, and streaming scenarios.
|
||||
* Non-blocking I/O
|
||||
* Reactive Streams back pressure
|
||||
* High concurrency with fewer hardware resources
|
||||
* Functional-style, fluent API that takes advantage of lambda expressions
|
||||
* Functional-style, fluent API that takes advantage of Java 8 lambdas
|
||||
* Synchronous and asynchronous interactions
|
||||
* Streaming up to or streaming down from a server
|
||||
|
||||
@@ -552,8 +479,7 @@ See xref:web/webflux-webclient.adoc[WebClient] for more details.
|
||||
The `RestTemplate` provides a high-level API over HTTP client libraries in the form of a classic Spring Template class.
|
||||
It exposes the following groups of overloaded methods:
|
||||
|
||||
WARNING: As of Spring Framework 7.0, `RestTemplate` is deprecated in favor of `RestClient` and will be removed in a future version,
|
||||
please use the xref:integration/rest-clients.adoc#migrating-to-restclient["Migrating to RestClient"] guide.
|
||||
NOTE: The xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] offers a more modern API for synchronous HTTP access.
|
||||
For asynchronous and streaming scenarios, consider the reactive xref:web/webflux-webclient.adoc[WebClient].
|
||||
|
||||
[[rest-overview-of-resttemplate-methods-tbl]]
|
||||
@@ -621,26 +547,10 @@ See the xref:integration/observability.adoc#http-client.resttemplate[RestTemplat
|
||||
Objects passed into and returned from `RestTemplate` methods are converted to and from HTTP messages
|
||||
with the help of an `HttpMessageConverter`, see <<rest-message-conversion>>.
|
||||
|
||||
[[migrating-to-restclient]]
|
||||
=== Migrating to `RestClient`
|
||||
|
||||
Applications can adopt `RestClient` in a gradual fashion, first focusing on API usage and then on infrastructure setup.
|
||||
You can consider the following steps:
|
||||
|
||||
1. Create one or more `RestClient` from existing `RestTemplate` instances, like: `RestClient restClient = RestClient.create(restTemplate)`.
|
||||
Gradually replace `RestTemplate` usage in your application, component by component, by focusing first on issuing requests.
|
||||
See the table below for API equivalents.
|
||||
2. Once all client requests go through `RestClient` instances, you can now work on replicating your existing
|
||||
`RestTemplate` instance creations by using `RestClient.Builder`. Because `RestTemplate` and `RestClient`
|
||||
share the same infrastructure, you can reuse custom `ClientHttpRequestFactory` or `ClientHttpRequestInterceptor`
|
||||
in your setup. See xref:integration/rest-clients.adoc#rest-restclient[the `RestClient` builder API].
|
||||
|
||||
If no other library is available on the classpath, `RestClient` will choose the `JdkClientHttpRequestFactory`
|
||||
powered by the modern JDK `HttpClient`, whereas `RestTemplate` would pick the `SimpleClientHttpRequestFactory` that
|
||||
uses `HttpURLConnection`. This can explain subtle behavior difference at runtime at the HTTP level.
|
||||
|
||||
=== Migrating from `RestTemplate` to `RestClient`
|
||||
|
||||
The following table shows `RestClient` equivalents for `RestTemplate` methods.
|
||||
It can be used to migrate from the latter to the former.
|
||||
|
||||
.RestClient equivalents for RestTemplate methods
|
||||
[cols="1,1", options="header"]
|
||||
@@ -944,14 +854,9 @@ The following table shows `RestClient` equivalents for `RestTemplate` methods.
|
||||
|
||||
|===
|
||||
|
||||
`RestClient` and `RestTemplate` instances share the same behavior when it comes to throwing exceptions
|
||||
(with the `RestClientException` type being at the top of the hierarchy).
|
||||
When `RestTemplate` consistently throws `HttpClientErrorException` for "4xx" response statues,
|
||||
`RestClient` allows for more flexibility with custom xref:integration/rest-clients.adoc#rest-http-service-client-exceptions["status handlers"].
|
||||
|
||||
|
||||
[[rest-http-service-client]]
|
||||
== HTTP Service Clients
|
||||
[[rest-http-interface]]
|
||||
== HTTP Interface Clients
|
||||
|
||||
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
|
||||
@@ -1023,14 +928,7 @@ Now, you're ready to create client proxies:
|
||||
// Use service methods for remote calls...
|
||||
----
|
||||
|
||||
HTTP service clients is a powerful and expressive choice for remote access over HTTP.
|
||||
It allows one team to own the knowledge of how a REST API works, what parts are relevant
|
||||
to a client application, what input and output types to create, what endpoint method
|
||||
signatures are needed, what Javadoc to have, and so on. The resulting Java API guides and
|
||||
is ready to use.
|
||||
|
||||
|
||||
[[rest-http-service-client-method-parameters]]
|
||||
[[rest-http-interface-method-parameters]]
|
||||
=== Method Parameters
|
||||
|
||||
`@HttpExchange` methods support flexible method signatures with the following inputs:
|
||||
@@ -1102,13 +1000,13 @@ parameter annotation) is set to `false`, or the parameter is marked optional as
|
||||
`StreamingHttpOutputMessage.Body` that allows sending the request body by writing to an
|
||||
`OutputStream`.
|
||||
|
||||
[[rest-http-service-client.custom-resolver]]
|
||||
[[rest-http-interface.custom-resolver]]
|
||||
=== Custom Arguments
|
||||
|
||||
You can configure a custom `HttpServiceArgumentResolver`. The example interface below
|
||||
uses a custom `Search` method parameter type:
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=httpserviceclient,indent=0]
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
|
||||
|
||||
A custom argument resolver could be implemented like this:
|
||||
|
||||
@@ -1123,7 +1021,7 @@ the use of more fine-grained method parameters for individual parts of the reque
|
||||
|
||||
|
||||
|
||||
[[rest-http-service-client-return-values]]
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
The supported return values depend on the underlying client.
|
||||
@@ -1199,7 +1097,7 @@ underlying HTTP client, which operates at a lower level and provides more contro
|
||||
`InputStream` or `ResponseEntity<InputStream>` that provides access to the raw response
|
||||
body content.
|
||||
|
||||
[[rest-http-service-client-exceptions]]
|
||||
[[rest-http-interface-exceptions]]
|
||||
=== Error Handling
|
||||
|
||||
To customize error handling for HTTP Service client proxies, you can configure the
|
||||
@@ -1236,7 +1134,7 @@ documentation for each client, as well as the Javadoc of `defaultStatusHandler`
|
||||
|
||||
|
||||
|
||||
[[rest-http-service-client-adapter-decorator]]
|
||||
[[rest-http-interface-adapter-decorator]]
|
||||
=== Decorating the Adapter
|
||||
|
||||
`HttpExchangeAdapter` and `ReactorHttpExchangeAdapter` are contracts that decouple HTTP
|
||||
@@ -1252,7 +1150,7 @@ built-in decorators to suppress 404 exceptions and return a `ResponseEntity` wit
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// For RestClient
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restClientAdapter)
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restCqlientAdapter)
|
||||
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
|
||||
.build();
|
||||
|
||||
@@ -1264,8 +1162,8 @@ built-in decorators to suppress 404 exceptions and return a `ResponseEntity` wit
|
||||
|
||||
|
||||
|
||||
[[rest-http-service-client-group-config]]
|
||||
=== HTTP Service Groups
|
||||
[[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
|
||||
|
||||
@@ -533,7 +533,8 @@ that returns a value:
|
||||
----
|
||||
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return
|
||||
type but also `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
type but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of
|
||||
Spring 4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
the asynchronous task and for immediate composition with further processing steps.
|
||||
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as `@PostConstruct`.
|
||||
|
||||
@@ -0,0 +1,831 @@
|
||||
[[dynamic-language]]
|
||||
= Dynamic Language Support
|
||||
|
||||
Spring provides comprehensive support for using classes and objects that have been
|
||||
defined by using a dynamic language (such as Groovy) with Spring. This support lets
|
||||
you write any number of classes in a supported dynamic language and have the Spring
|
||||
container transparently instantiate, configure, and dependency inject the resulting
|
||||
objects.
|
||||
|
||||
Spring's scripting support primarily targets Groovy and BeanShell. Beyond those
|
||||
specifically supported languages, the JSR-223 scripting mechanism is supported
|
||||
for integration with any JSR-223 capable language provider (as of Spring 4.2),
|
||||
for example, JRuby.
|
||||
|
||||
You can find fully working examples of where this dynamic language support can be
|
||||
immediately useful in xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios].
|
||||
|
||||
|
||||
[[dynamic-language-a-first-example]]
|
||||
== A First Example
|
||||
|
||||
The bulk of this chapter is concerned with describing the dynamic language support in
|
||||
detail. Before diving into all of the ins and outs of the dynamic language support,
|
||||
we look at a quick example of a bean defined in a dynamic language. The dynamic
|
||||
language for this first bean is Groovy. (The basis of this example was taken from the
|
||||
Spring test suite. If you want to see equivalent examples in any of the other
|
||||
supported languages, take a look at the source code).
|
||||
|
||||
The next example shows the `Messenger` interface, which the Groovy bean is going to
|
||||
implement. Note that this interface is defined in plain Java. Dependent objects that
|
||||
are injected with a reference to the `Messenger` do not know that the underlying
|
||||
implementation is a Groovy script. The following listing shows the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public interface Messenger {
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
----
|
||||
|
||||
The following example defines a class that has a dependency on the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public class DefaultBookingService implements BookingService {
|
||||
|
||||
private Messenger messenger;
|
||||
|
||||
public void setMessenger(Messenger messenger) {
|
||||
this.messenger = messenger;
|
||||
}
|
||||
|
||||
public void processBooking() {
|
||||
// use the injected Messenger object...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following example implements the `Messenger` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
----
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// Import the Messenger interface (written in Java) that is to be implemented
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// Define the implementation in Groovy in file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
String message
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
To use the custom dynamic language tags to define dynamic-language-backed beans, you
|
||||
need to have the XML Schema preamble at the top of your Spring XML configuration file.
|
||||
You also need to use a Spring `ApplicationContext` implementation as your IoC
|
||||
container. Using the dynamic-language-backed beans with a plain `BeanFactory`
|
||||
implementation is supported, but you have to manage the plumbing of the Spring internals
|
||||
to do so.
|
||||
|
||||
For more information on schema-based configuration, see xref:languages/dynamic.adoc#xsd-schemas-lang[XML Schema-based Configuration]
|
||||
.
|
||||
====
|
||||
|
||||
Finally, the following example shows the bean definitions that effect the injection of the
|
||||
Groovy-defined `Messenger` implementation into an instance of the
|
||||
`DefaultBookingService` class:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
||||
|
||||
<!-- this is the bean definition for the Groovy-backed Messenger implementation -->
|
||||
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy">
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
</lang:groovy>
|
||||
|
||||
<!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger -->
|
||||
<bean id="bookingService" class="x.y.DefaultBookingService">
|
||||
<property name="messenger" ref="messenger" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The `bookingService` bean (a `DefaultBookingService`) can now use its private `messenger`
|
||||
member variable as normal, because the `Messenger` instance that was injected into it is
|
||||
a `Messenger` instance. There is nothing special going on here -- just plain Java and
|
||||
plain Groovy.
|
||||
|
||||
Hopefully, the preceding XML snippet is self-explanatory, but do not worry unduly if it is not.
|
||||
Keep reading for the in-depth detail on the whys and wherefores of the preceding configuration.
|
||||
|
||||
|
||||
[[dynamic-language-beans]]
|
||||
== Defining Beans that Are Backed by Dynamic Languages
|
||||
|
||||
This section describes exactly how you define Spring-managed beans in any of the
|
||||
supported dynamic languages.
|
||||
|
||||
Note that this chapter does not attempt to explain the syntax and idioms of the supported
|
||||
dynamic languages. For example, if you want to use Groovy to write certain of the classes
|
||||
in your application, we assume that you already know Groovy. If you need further details
|
||||
about the dynamic languages themselves, see
|
||||
xref:languages/dynamic.adoc#dynamic-language-resources[Further Resources] at the end of
|
||||
this chapter.
|
||||
|
||||
[[dynamic-language-beans-concepts]]
|
||||
=== Common Concepts
|
||||
|
||||
The steps involved in using dynamic-language-backed beans are as follows:
|
||||
|
||||
. Write the test for the dynamic language source code (naturally).
|
||||
. Then write the dynamic language source code itself.
|
||||
. Define your dynamic-language-backed beans by using the appropriate `<lang:language/>`
|
||||
element in the XML configuration (you can define such beans programmatically by
|
||||
using the Spring API, although you will have to consult the source code for
|
||||
directions on how to do this, as this chapter does not cover this type of advanced configuration).
|
||||
Note that this is an iterative step. You need at least one bean definition for each dynamic
|
||||
language source file (although multiple bean definitions can reference the same source file).
|
||||
|
||||
The first two steps (testing and writing your dynamic language source files) are beyond
|
||||
the scope of this chapter. See the language specification and reference manual
|
||||
for your chosen dynamic language and crack on with developing your dynamic language
|
||||
source files. You first want to read the rest of this chapter, though, as
|
||||
Spring's dynamic language support does make some (small) assumptions about the contents
|
||||
of your dynamic language source files.
|
||||
|
||||
[[dynamic-language-beans-concepts-xml-language-element]]
|
||||
==== The <lang:language/> element
|
||||
|
||||
The final step in the list in the xref:languages/dynamic.adoc#dynamic-language-beans-concepts[preceding section]
|
||||
involves defining dynamic-language-backed bean definitions, one for each bean that you
|
||||
want to configure (this is no different from normal JavaBean configuration). However,
|
||||
instead of specifying the fully qualified class name of the class that is to be
|
||||
instantiated and configured by the container, you can use the `<lang:language/>`
|
||||
element to define the dynamic language-backed bean.
|
||||
|
||||
Each of the supported languages has a corresponding `<lang:language/>` element:
|
||||
|
||||
* `<lang:groovy/>` (Groovy)
|
||||
* `<lang:bsh/>` (BeanShell)
|
||||
* `<lang:std/>` (JSR-223, for example, with JRuby)
|
||||
|
||||
The exact attributes and child elements that are available for configuration depends on
|
||||
exactly which language the bean has been defined in (the language-specific sections
|
||||
later in this chapter detail this).
|
||||
|
||||
[[dynamic-language-refreshable-beans]]
|
||||
==== Refreshable Beans
|
||||
|
||||
One of the (and perhaps the single) most compelling value adds of the dynamic language
|
||||
support in Spring is the "`refreshable bean`" feature.
|
||||
|
||||
A refreshable bean is a dynamic-language-backed bean. With a small amount of
|
||||
configuration, a dynamic-language-backed bean can monitor changes in its underlying
|
||||
source file resource and then reload itself when the dynamic language source file is
|
||||
changed (for example, when you edit and save changes to the file on the file system).
|
||||
|
||||
This lets you deploy any number of dynamic language source files as part of an
|
||||
application, configure the Spring container to create beans backed by dynamic
|
||||
language source files (using the mechanisms described in this chapter), and (later,
|
||||
as requirements change or some other external factor comes into play) edit a dynamic
|
||||
language source file and have any change they make be reflected in the bean that is
|
||||
backed by the changed dynamic language source file. There is no need to shut down a
|
||||
running application (or redeploy in the case of a web application). The
|
||||
dynamic-language-backed bean so amended picks up the new state and logic from the
|
||||
changed dynamic language source file.
|
||||
|
||||
NOTE: This feature is off by default.
|
||||
|
||||
Now we can take a look at an example to see how easy it is to start using refreshable
|
||||
beans. To turn on the refreshable beans feature, you have to specify exactly one
|
||||
additional attribute on the `<lang:language/>` element of your bean definition. So,
|
||||
if we stick with xref:languages/dynamic.adoc#dynamic-language-a-first-example[the example] from earlier in
|
||||
this chapter, the following example shows what we would change in the Spring XML
|
||||
configuration to effect refreshable beans:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<!-- this bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute -->
|
||||
<lang:groovy id="messenger"
|
||||
refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks -->
|
||||
script-source="classpath:Messenger.groovy">
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
</lang:groovy>
|
||||
|
||||
<bean id="bookingService" class="x.y.DefaultBookingService">
|
||||
<property name="messenger" ref="messenger" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
That really is all you have to do. The `refresh-check-delay` attribute defined on the
|
||||
`messenger` bean definition is the number of milliseconds after which the bean is
|
||||
refreshed with any changes made to the underlying dynamic language source file.
|
||||
You can turn off the refresh behavior by assigning a negative value to the
|
||||
`refresh-check-delay` attribute. Remember that, by default, the refresh behavior is
|
||||
disabled. If you do not want the refresh behavior, do not define the attribute.
|
||||
|
||||
If we then run the following application, we can exercise the refreshable feature.
|
||||
(Please excuse the "`jumping-through-hoops-to-pause-the-execution`" shenanigans
|
||||
in this next slice of code.) The `System.in.read()` call is only there so that the
|
||||
execution of the program pauses while you (the developer in this scenario) go off
|
||||
and edit the underlying dynamic language source file so that the refresh triggers
|
||||
on the dynamic-language-backed bean when the program resumes execution.
|
||||
|
||||
The following listing shows this sample application:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.scripting.Messenger;
|
||||
|
||||
public final class Boot {
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
||||
Messenger messenger = (Messenger) ctx.getBean("messenger");
|
||||
System.out.println(messenger.getMessage());
|
||||
// pause execution while I go off and make changes to the source file...
|
||||
System.in.read();
|
||||
System.out.println(messenger.getMessage());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Assume then, for the purposes of this example, that all calls to the `getMessage()`
|
||||
method of `Messenger` implementations have to be changed such that the message is
|
||||
surrounded by quotation marks. The following listing shows the changes that you
|
||||
(the developer) should make to the `Messenger.groovy` source file when the
|
||||
execution of the program is paused:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting
|
||||
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
private String message = "Bingo"
|
||||
|
||||
public String getMessage() {
|
||||
// change the implementation to surround the message in quotes
|
||||
return "'" + this.message + "'"
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
When the program runs, the output before the input pause will be `I Can Do The Frug`.
|
||||
After the change to the source file is made and saved and the program resumes execution,
|
||||
the result of calling the `getMessage()` method on the dynamic-language-backed
|
||||
`Messenger` implementation is `'I Can Do The Frug'` (notice the inclusion of the
|
||||
additional quotation marks).
|
||||
|
||||
Changes to a script do not trigger a refresh if the changes occur within the window of
|
||||
the `refresh-check-delay` value. Changes to the script are not actually picked up until
|
||||
a method is called on the dynamic-language-backed bean. It is only when a method is
|
||||
called on a dynamic-language-backed bean that it checks to see if its underlying script
|
||||
source has changed. Any exceptions that relate to refreshing the script (such as
|
||||
encountering a compilation error or finding that the script file has been deleted)
|
||||
results in a fatal exception being propagated to the calling code.
|
||||
|
||||
The refreshable bean behavior described earlier does not apply to dynamic language
|
||||
source files defined with the `<lang:inline-script/>` element notation (see
|
||||
xref:languages/dynamic.adoc#dynamic-language-beans-inline[Inline Dynamic Language Source Files]).
|
||||
Additionally, it applies only to beans where changes to the underlying source file can
|
||||
actually be detected (for example, by code that checks the last modified date of a
|
||||
dynamic language source file that exists on the file system).
|
||||
|
||||
[[dynamic-language-beans-inline]]
|
||||
==== Inline Dynamic Language Source Files
|
||||
|
||||
The dynamic language support can also cater to dynamic language source files that are
|
||||
embedded directly in Spring bean definitions. More specifically, the
|
||||
`<lang:inline-script/>` element lets you define dynamic language source immediately
|
||||
inside a Spring configuration file. An example might clarify how the inline script
|
||||
feature works:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:groovy id="messenger">
|
||||
<lang:inline-script>
|
||||
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
class GroovyMessenger implements Messenger {
|
||||
String message
|
||||
}
|
||||
|
||||
</lang:inline-script>
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
</lang:groovy>
|
||||
----
|
||||
|
||||
If we put to one side the issues surrounding whether it is good practice to define
|
||||
dynamic language source inside a Spring configuration file, the `<lang:inline-script/>`
|
||||
element can be useful in some scenarios. For instance, we might want to quickly add a
|
||||
Spring `Validator` implementation to a Spring MVC `Controller`. This is but a moment's
|
||||
work using inline source. (See
|
||||
xref:languages/dynamic.adoc#dynamic-language-scenarios-validators[Scripted Validators]
|
||||
for such an example.)
|
||||
|
||||
[[dynamic-language-beans-ctor-injection]]
|
||||
==== Understanding Constructor Injection in the Context of Dynamic-language-backed Beans
|
||||
|
||||
There is one very important thing to be aware of with regard to Spring's dynamic
|
||||
language support. Namely, you can not (currently) supply constructor arguments
|
||||
to dynamic-language-backed beans (and, hence, constructor-injection is not available for
|
||||
dynamic-language-backed beans). In the interests of making this special handling of
|
||||
constructors and properties 100% clear, the following mixture of code and configuration
|
||||
does not work:
|
||||
|
||||
.An approach that cannot work
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// from the file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
GroovyMessenger() {}
|
||||
|
||||
// this constructor is not available for Constructor Injection
|
||||
GroovyMessenger(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
String message
|
||||
|
||||
String anotherMessage
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:groovy id="badMessenger"
|
||||
script-source="classpath:Messenger.groovy">
|
||||
<!-- this next constructor argument will not be injected into the GroovyMessenger -->
|
||||
<!-- in fact, this isn't even allowed according to the schema -->
|
||||
<constructor-arg value="This will not work" />
|
||||
|
||||
<!-- only property values are injected into the dynamic-language-backed object -->
|
||||
<lang:property name="anotherMessage" value="Passed straight through to the dynamic-language-backed object" />
|
||||
|
||||
</lang>
|
||||
----
|
||||
|
||||
In practice this limitation is not as significant as it first appears, since setter
|
||||
injection is the injection style favored by the overwhelming majority of developers
|
||||
(we leave the discussion as to whether that is a good thing to another day).
|
||||
|
||||
[[dynamic-language-beans-groovy]]
|
||||
=== Groovy Beans
|
||||
|
||||
This section describes how to use beans defined in Groovy in Spring.
|
||||
|
||||
The Groovy homepage includes the following description:
|
||||
|
||||
"`Groovy is an agile dynamic language for the Java 2 Platform that has many of the
|
||||
features that people like so much in languages like Python, Ruby and Smalltalk, making
|
||||
them available to Java developers using a Java-like syntax.`"
|
||||
|
||||
If you have read this chapter straight from the top, you have already
|
||||
xref:languages/dynamic.adoc#dynamic-language-a-first-example[seen an example] of a Groovy-dynamic-language-backed
|
||||
bean. Now consider another example (again using an example from the Spring test suite):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public interface Calculator {
|
||||
|
||||
int add(int x, int y);
|
||||
}
|
||||
----
|
||||
|
||||
The following example implements the `Calculator` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// from the file 'calculator.groovy'
|
||||
class GroovyCalculator implements Calculator {
|
||||
|
||||
int add(int x, int y) {
|
||||
x + y
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following bean definition uses the calculator defined in Groovy:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- from the file 'beans.xml' -->
|
||||
<beans>
|
||||
<lang:groovy id="calculator" script-source="classpath:calculator.groovy"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
Finally, the following small application exercises the preceding configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
||||
Calculator calc = ctx.getBean("calculator", Calculator.class);
|
||||
System.out.println(calc.add(2, 8));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The resulting output from running the above program is (unsurprisingly) `10`.
|
||||
(For more interesting examples, see the dynamic language showcase project for a more
|
||||
complex example or see the examples xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios] later in this chapter).
|
||||
|
||||
You must not define more than one class per Groovy source file. While this is perfectly
|
||||
legal in Groovy, it is (arguably) a bad practice. In the interests of a consistent
|
||||
approach, you should (in the opinion of the Spring team) respect the standard Java
|
||||
conventions of one (public) class per source file.
|
||||
|
||||
[[dynamic-language-beans-groovy-customizer]]
|
||||
==== Customizing Groovy Objects by Using a Callback
|
||||
|
||||
The `GroovyObjectCustomizer` interface is a callback that lets you hook additional
|
||||
creation logic into the process of creating a Groovy-backed bean. For example,
|
||||
implementations of this interface could invoke any required initialization methods,
|
||||
set some default property values, or specify a custom `MetaClass`. The following listing
|
||||
shows the `GroovyObjectCustomizer` interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface GroovyObjectCustomizer {
|
||||
|
||||
void customize(GroovyObject goo);
|
||||
}
|
||||
----
|
||||
|
||||
The Spring Framework instantiates an instance of your Groovy-backed bean and then
|
||||
passes the created `GroovyObject` to the specified `GroovyObjectCustomizer` (if one
|
||||
has been defined). You can do whatever you like with the supplied `GroovyObject`
|
||||
reference. We expect that most people want to set a custom `MetaClass` with this
|
||||
callback, and the following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer {
|
||||
|
||||
public void customize(GroovyObject goo) {
|
||||
DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) {
|
||||
|
||||
public Object invokeMethod(Object object, String methodName, Object[] arguments) {
|
||||
System.out.println("Invoking '" + methodName + "'.");
|
||||
return super.invokeMethod(object, methodName, arguments);
|
||||
}
|
||||
};
|
||||
metaClass.initialize();
|
||||
goo.setMetaClass(metaClass);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
A full discussion of meta-programming in Groovy is beyond the scope of the Spring
|
||||
reference manual. See the relevant section of the Groovy reference manual or do a
|
||||
search online. Plenty of articles address this topic. Actually, making use of a
|
||||
`GroovyObjectCustomizer` is easy if you use the Spring namespace support, as the
|
||||
following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- define the GroovyObjectCustomizer just like any other bean -->
|
||||
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
|
||||
|
||||
<!-- ... and plug it into the desired Groovy bean via the 'customizer-ref' attribute -->
|
||||
<lang:groovy id="calculator"
|
||||
script-source="classpath:org/springframework/scripting/groovy/Calculator.groovy"
|
||||
customizer-ref="tracingCustomizer"/>
|
||||
----
|
||||
|
||||
If you do not use the Spring namespace support, you can still use the
|
||||
`GroovyObjectCustomizer` functionality, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="calculator" class="org.springframework.scripting.groovy.GroovyScriptFactory">
|
||||
<constructor-arg value="classpath:org/springframework/scripting/groovy/Calculator.groovy"/>
|
||||
<!-- define the GroovyObjectCustomizer (as an inner bean) -->
|
||||
<constructor-arg>
|
||||
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
|
||||
----
|
||||
|
||||
NOTE: You may also specify a Groovy `CompilationCustomizer` (such as an `ImportCustomizer`)
|
||||
or even a full Groovy `CompilerConfiguration` object in the same place as Spring's
|
||||
`GroovyObjectCustomizer`. Furthermore, you may set a common `GroovyClassLoader` with custom
|
||||
configuration for your beans at the `ConfigurableApplicationContext.setClassLoader` level;
|
||||
this also leads to shared `GroovyClassLoader` usage and is therefore recommendable in case of
|
||||
a large number of scripted beans (avoiding an isolated `GroovyClassLoader` instance per bean).
|
||||
|
||||
[[dynamic-language-beans-bsh]]
|
||||
=== BeanShell Beans
|
||||
|
||||
This section describes how to use BeanShell beans in Spring.
|
||||
|
||||
The https://beanshell.github.io/intro.html[BeanShell homepage] includes the following
|
||||
description:
|
||||
|
||||
----
|
||||
BeanShell is a small, free, embeddable Java source interpreter with dynamic language
|
||||
features, written in Java. BeanShell dynamically runs standard Java syntax and
|
||||
extends it with common scripting conveniences such as loose types, commands, and method
|
||||
closures like those in Perl and JavaScript.
|
||||
----
|
||||
|
||||
In contrast to Groovy, BeanShell-backed bean definitions require some (small) additional
|
||||
configuration. The implementation of the BeanShell dynamic language support in Spring is
|
||||
interesting, because Spring creates a JDK dynamic proxy that implements all of the
|
||||
interfaces that are specified in the `script-interfaces` attribute value of the
|
||||
`<lang:bsh>` element (this is why you must supply at least one interface in the value
|
||||
of the attribute, and, consequently, program to interfaces when you use BeanShell-backed
|
||||
beans). This means that every method call on a BeanShell-backed object goes through the
|
||||
JDK dynamic proxy invocation mechanism.
|
||||
|
||||
Now we can show a fully working example of using a BeanShell-based bean that implements
|
||||
the `Messenger` interface that was defined earlier in this chapter. We again show the
|
||||
definition of the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
public interface Messenger {
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the BeanShell "`implementation`" (we use the term loosely here)
|
||||
of the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String message;
|
||||
|
||||
String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
void setMessage(String aMessage) {
|
||||
message = aMessage;
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the Spring XML that defines an "`instance`" of the above
|
||||
"`class`" (again, we use these terms very loosely here):
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh"
|
||||
script-interfaces="org.springframework.scripting.Messenger">
|
||||
|
||||
<lang:property name="message" value="Hello World!" />
|
||||
</lang:bsh>
|
||||
----
|
||||
|
||||
See xref:languages/dynamic.adoc#dynamic-language-scenarios[Scenarios] for some scenarios
|
||||
where you might want to use BeanShell-based beans.
|
||||
|
||||
|
||||
[[dynamic-language-scenarios]]
|
||||
== Scenarios
|
||||
|
||||
The possible scenarios where defining Spring managed beans in a scripting language would
|
||||
be beneficial are many and varied. This section describes two possible use cases for the
|
||||
dynamic language support in Spring.
|
||||
|
||||
[[dynamic-language-scenarios-controllers]]
|
||||
=== Scripted Spring MVC Controllers
|
||||
|
||||
One group of classes that can benefit from using dynamic-language-backed beans is that
|
||||
of Spring MVC controllers. In pure Spring MVC applications, the navigational flow
|
||||
through a web application is, to a large extent, determined by code encapsulated within
|
||||
your Spring MVC controllers. As the navigational flow and other presentation layer logic
|
||||
of a web application needs to be updated to respond to support issues or changing
|
||||
business requirements, it may well be easier to effect any such required changes by
|
||||
editing one or more dynamic language source files and seeing those changes being
|
||||
immediately reflected in the state of a running application.
|
||||
|
||||
Remember that, in the lightweight architectural model espoused by projects such as
|
||||
Spring, you typically aim to have a really thin presentation layer, with all
|
||||
the meaty business logic of an application being contained in the domain and service
|
||||
layer classes. Developing Spring MVC controllers as dynamic-language-backed beans lets
|
||||
you change presentation layer logic by editing and saving text files. Any
|
||||
changes to such dynamic language source files is (depending on the configuration)
|
||||
automatically reflected in the beans that are backed by dynamic language source files.
|
||||
|
||||
NOTE: To effect this automatic "`pickup`" of any changes to dynamic-language-backed
|
||||
beans, you have to enable the "`refreshable beans`" functionality. See
|
||||
xref:languages/dynamic.adoc#dynamic-language-refreshable-beans[Refreshable Beans] for a full treatment of this feature.
|
||||
|
||||
The following example shows an `org.springframework.web.servlet.mvc.Controller` implemented
|
||||
by using the Groovy dynamic language:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.showcase.fortune.web
|
||||
|
||||
import org.springframework.showcase.fortune.service.FortuneService
|
||||
import org.springframework.showcase.fortune.domain.Fortune
|
||||
import org.springframework.web.servlet.ModelAndView
|
||||
import org.springframework.web.servlet.mvc.Controller
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
|
||||
// from the file '/WEB-INF/groovy/FortuneController.groovy'
|
||||
class FortuneController implements Controller {
|
||||
|
||||
@Property FortuneService fortuneService
|
||||
|
||||
ModelAndView handleRequest(HttpServletRequest request,
|
||||
HttpServletResponse httpServletResponse) {
|
||||
return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<lang:groovy id="fortune"
|
||||
refresh-check-delay="3000"
|
||||
script-source="/WEB-INF/groovy/FortuneController.groovy">
|
||||
<lang:property name="fortuneService" ref="fortuneService"/>
|
||||
</lang:groovy>
|
||||
----
|
||||
|
||||
[[dynamic-language-scenarios-validators]]
|
||||
=== Scripted Validators
|
||||
|
||||
Another area of application development with Spring that may benefit from the
|
||||
flexibility afforded by dynamic-language-backed beans is that of validation. It can
|
||||
be easier to express complex validation logic by using a loosely typed dynamic language
|
||||
(that may also have support for inline regular expressions) as opposed to regular Java.
|
||||
|
||||
Again, developing validators as dynamic-language-backed beans lets you change
|
||||
validation logic by editing and saving a simple text file. Any such changes is
|
||||
(depending on the configuration) automatically reflected in the execution of a
|
||||
running application and would not require the restart of an application.
|
||||
|
||||
NOTE: To effect the automatic "`pickup`" of any changes to dynamic-language-backed
|
||||
beans, you have to enable the 'refreshable beans' feature. See
|
||||
xref:languages/dynamic.adoc#dynamic-language-refreshable-beans[Refreshable Beans]
|
||||
for a full and detailed treatment of this feature.
|
||||
|
||||
The following example shows a Spring `org.springframework.validation.Validator`
|
||||
implemented by using the Groovy dynamic language (see
|
||||
xref:core/validation/validator.adoc[Validation using Spring’s Validator interface]
|
||||
for a discussion of the `Validator` interface):
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.validation.Validator
|
||||
import org.springframework.validation.Errors
|
||||
import org.springframework.beans.TestBean
|
||||
|
||||
class TestBeanValidator implements Validator {
|
||||
|
||||
boolean supports(Class clazz) {
|
||||
return TestBean.class.isAssignableFrom(clazz)
|
||||
}
|
||||
|
||||
void validate(Object bean, Errors errors) {
|
||||
if(bean.name?.trim()?.size() > 0) {
|
||||
return
|
||||
}
|
||||
errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[dynamic-language-final-notes]]
|
||||
== Additional Details
|
||||
|
||||
This last section contains some additional details related to the dynamic language support.
|
||||
|
||||
[[dynamic-language-final-notes-aop]]
|
||||
=== AOP -- Advising Scripted Beans
|
||||
|
||||
You can use the Spring AOP framework to advise scripted beans. The Spring AOP
|
||||
framework actually is unaware that a bean that is being advised might be a scripted
|
||||
bean, so all of the AOP use cases and functionality that you use (or aim to use)
|
||||
work with scripted beans. When you advise scripted beans, you cannot use class-based
|
||||
proxies. You must use xref:core/aop/proxying.adoc[interface-based proxies].
|
||||
|
||||
You are not limited to advising scripted beans. You can also write aspects themselves
|
||||
in a supported dynamic language and use such beans to advise other Spring beans.
|
||||
This really would be an advanced use of the dynamic language support though.
|
||||
|
||||
[[dynamic-language-final-notes-scopes]]
|
||||
=== Scoping
|
||||
|
||||
In case it is not immediately obvious, scripted beans can be scoped in the same way as
|
||||
any other bean. The `scope` attribute on the various `<lang:language/>` elements lets
|
||||
you control the scope of the underlying scripted bean, as it does with a regular
|
||||
bean. (The default scope is xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton],
|
||||
as it is with "`regular`" beans.)
|
||||
|
||||
The following example uses the `scope` attribute to define a Groovy bean scoped as
|
||||
a xref:core/beans/factory-scopes.adoc#beans-factory-scopes-prototype[prototype]:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
||||
|
||||
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy" scope="prototype">
|
||||
<lang:property name="message" value="I Can Do The RoboCop" />
|
||||
</lang:groovy>
|
||||
|
||||
<bean id="bookingService" class="x.y.DefaultBookingService">
|
||||
<property name="messenger" ref="messenger" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
See xref:core/beans/factory-scopes.adoc[Bean Scopes] in
|
||||
xref:web/webmvc-view/mvc-xslt.adoc#mvc-view-xslt-beandefs[The IoC Container]
|
||||
for a full discussion of the scoping support in the Spring Framework.
|
||||
|
||||
[[xsd-schemas-lang]]
|
||||
=== The `lang` XML schema
|
||||
|
||||
The `lang` elements in Spring XML configuration deal with exposing objects that have been
|
||||
written in a dynamic language (such as Groovy or BeanShell) as beans in the Spring container.
|
||||
|
||||
These elements (and the dynamic language support) are comprehensively covered in
|
||||
xref:languages/dynamic.adoc[Dynamic Language Support]. See that section
|
||||
for full details on this support and the `lang` elements.
|
||||
|
||||
To use the elements in the `lang` schema, you need to have the following preamble at the
|
||||
top of your Spring XML configuration file. The text in the following snippet references
|
||||
the correct schema so that the tags in the `lang` namespace are available to you:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[dynamic-language-resources]]
|
||||
== Further Resources
|
||||
|
||||
The following links go to further resources about the various dynamic languages referenced
|
||||
in this chapter:
|
||||
|
||||
* The https://www.groovy-lang.org/[Groovy] homepage
|
||||
* The https://beanshell.github.io/intro.html[BeanShell] homepage
|
||||
* The https://www.jruby.org[JRuby] homepage
|
||||
@@ -6,37 +6,9 @@ Groovy is a powerful, optionally typed, and dynamic language, with static-typing
|
||||
compilation capabilities. It offers a concise syntax and integrates smoothly with any
|
||||
existing Java application.
|
||||
|
||||
[[beans-factory-groovy]]
|
||||
== The Groovy Bean Definition DSL
|
||||
|
||||
The Spring Framework provides a dedicated `ApplicationContext` that supports a Groovy-based
|
||||
Bean Definition DSL, as known from the Grails framework.
|
||||
|
||||
Typically, such configuration live in a ".groovy" file with the structure shown in the
|
||||
following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
beans {
|
||||
dataSource(BasicDataSource) {
|
||||
driverClassName = "org.hsqldb.jdbcDriver"
|
||||
url = "jdbc:hsqldb:mem:grailsDB"
|
||||
username = "sa"
|
||||
password = ""
|
||||
settings = [mynew:"setting"]
|
||||
}
|
||||
sessionFactory(SessionFactory) {
|
||||
dataSource = dataSource
|
||||
}
|
||||
myService(MyService) {
|
||||
nestedBean = { AnotherBean bean ->
|
||||
dataSource = dataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This configuration style is largely equivalent to XML bean definitions and even
|
||||
supports Spring's XML configuration namespaces. It also allows for importing XML
|
||||
bean definition files through an `importBeans` directive.
|
||||
Bean Definition DSL. For more details, see
|
||||
xref:core/beans/basics.adoc#beans-factory-groovy[The Groovy Bean Definition DSL].
|
||||
|
||||
Further support for Groovy, including beans written in Groovy, refreshable script beans,
|
||||
and more is available in xref:languages/dynamic.adoc[Dynamic Language Support].
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
[[kotlin-bean-registration-dsl]]
|
||||
= Bean Registration DSL
|
||||
[[kotlin-bean-definition-dsl]]
|
||||
= Bean Definition DSL
|
||||
|
||||
See xref:core/beans/java/programmatic-bean-registration.adoc[Programmatic Bean Registration].
|
||||
@@ -3,16 +3,14 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
The Spring Framework supports various Kotlin constructs, such as instantiating Kotlin classes
|
||||
through primary constructors, data binding for immutable classes, and optional parameters
|
||||
with default values for functions.
|
||||
through primary constructors, immutable classes data binding, and function optional parameters
|
||||
with default values.
|
||||
|
||||
Kotlin parameter names are recognized through a dedicated `KotlinReflectionParameterNameDiscoverer`,
|
||||
which allows finding interface method parameter names without requiring the Java `-parameters`
|
||||
compiler flag to be enabled during compilation.
|
||||
|
||||
TIP: For completeness, we nevertheless recommend running the Kotlin compiler with its
|
||||
`-java-parameters` flag for standard Java parameter exposure.
|
||||
which allows finding interface method parameter names without requiring the Java 8 `-parameters`
|
||||
compiler flag to be enabled during compilation. (For completeness, we nevertheless recommend
|
||||
running the Kotlin compiler with its `-java-parameters` flag for standard Java parameter exposure.)
|
||||
|
||||
You can declare configuration classes as
|
||||
{kotlin-docs}/nested-classes.html[top level or nested but not inner],
|
||||
since the latter requires a reference to the outer class.
|
||||
since the later requires a reference to the outer class.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[[coroutines]]
|
||||
= Coroutines
|
||||
|
||||
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are instances of
|
||||
suspendable computations allowing to write non-blocking code in an imperative way. On language side,
|
||||
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are Kotlin
|
||||
lightweight threads allowing to write non-blocking code in an imperative way. On language side,
|
||||
suspending functions provides an abstraction for asynchronous operations while on library side
|
||||
{kotlin-github-org}/kotlinx.coroutines[kotlinx.coroutines] provides functions like
|
||||
{kotlin-coroutines-api}/kotlinx-coroutines-core/kotlinx.coroutines/async.html[`async { }`]
|
||||
@@ -250,29 +250,3 @@ For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[coroutines.propagation]]
|
||||
== Context Propagation
|
||||
|
||||
Spring applications are xref:integration/observability.adoc[instrumented with Micrometer for Observability support].
|
||||
For tracing support, the current observation is propagated through a `ThreadLocal` for blocking code,
|
||||
or the Reactor `Context` for reactive pipelines. But the current observation also needs to be made available
|
||||
in the execution context of a suspended function. Without that, the current "traceId" will not be automatically
|
||||
prepended to logged statements from coroutines.
|
||||
|
||||
The {spring-framework-api-kdoc}/spring-core/org.springframework.core/-propagation-context-element/index.html[`PropagationContextElement`] operator generally ensures that the
|
||||
{micrometer-context-propagation-docs}/[Micrometer Context Propagation library] works with Kotlin Coroutines.
|
||||
|
||||
It requires the `io.micrometer:context-propagation` dependency and optionally the
|
||||
`org.jetbrains.kotlinx:kotlinx-coroutines-reactor` one. Automatic context propagation via
|
||||
`CoroutinesUtils#invokeSuspendingFunction` (used by Spring to adapt Coroutines to Reactor `Flux` or `Mono`) can be
|
||||
enabled by invoking `Hooks.enableAutomaticContextPropagation()`.
|
||||
|
||||
Applications can also use `PropagationContextElement` explicitly to augment the `CoroutineContext`
|
||||
with the context propagation mechanism:
|
||||
|
||||
include-code::./ContextPropagationSample[tag=context,indent=0]
|
||||
|
||||
Here, assuming that Micrometer Tracing is configured, the resulting logging statement will show the current "traceId"
|
||||
and unlock better observability for your application.
|
||||
|
||||
|
||||
@@ -319,17 +319,9 @@ progresses.
|
||||
== Testing
|
||||
|
||||
This section addresses testing with the combination of Kotlin and Spring Framework.
|
||||
The recommended testing framework is https://junit.org/[JUnit] along with
|
||||
The recommended testing framework is https://junit.org/junit5/[JUnit] along with
|
||||
https://mockk.io/[Mockk] for mocking.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
|
||||
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
|
||||
in this section.
|
||||
====
|
||||
|
||||
NOTE: If you are using Spring Boot, see
|
||||
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
|
||||
|
||||
@@ -360,6 +352,7 @@ file with a `spring.test.constructor.autowire.mode = all` property.
|
||||
[[per_class-lifecycle]]
|
||||
=== `PER_CLASS` Lifecycle
|
||||
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
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.
|
||||
|
||||
@@ -73,7 +73,7 @@ As of Spring Framework 6.0, Spring has been upgraded to the Jakarta EE 9 level
|
||||
traditional `javax` packages. With EE 9 as the minimum and EE 10 supported already,
|
||||
Spring is prepared to provide out-of-the-box support for the further evolution of
|
||||
the Jakarta EE APIs. Spring Framework 6.0 is fully compatible with Tomcat 10.1,
|
||||
Jetty 11 as web servers, and also with Hibernate ORM 6.1.
|
||||
Jetty 11 and Undertow 2.3 as web servers, and also with Hibernate ORM 6.1.
|
||||
|
||||
Over time, the role of Java/Jakarta EE in application development has evolved. In the
|
||||
early days of J2EE and Spring, applications were created to be deployed to an application
|
||||
|
||||
@@ -237,8 +237,8 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RSocketStrategies strategies = RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.build();
|
||||
|
||||
RSocketRequester requester = RSocketRequester.builder()
|
||||
@@ -251,8 +251,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val strategies = RSocketStrategies.builder()
|
||||
.encoders { it.add(JacksonCborEncoder()) }
|
||||
.decoders { it.add(JacksonCborDecoder()) }
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.build()
|
||||
|
||||
val requester = RSocketRequester.builder()
|
||||
@@ -681,8 +681,8 @@ Java::
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.routeMatcher(new PathPatternRouteMatcher())
|
||||
.build();
|
||||
}
|
||||
@@ -703,8 +703,8 @@ Kotlin::
|
||||
|
||||
@Bean
|
||||
fun rsocketStrategies() = RSocketStrategies.builder()
|
||||
.encoders { it.add(JacksonCborEncoder()) }
|
||||
.decoders { it.add(JacksonCborDecoder()) }
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.routeMatcher(PathPatternRouteMatcher())
|
||||
.build()
|
||||
}
|
||||
|
||||
+2
-53
@@ -3,9 +3,8 @@
|
||||
|
||||
The following annotations are supported when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and the JUnit Jupiter testing framework:
|
||||
and JUnit Jupiter (that is, the programming model in JUnit):
|
||||
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
|
||||
* 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`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
|
||||
@@ -15,56 +14,6 @@ and the JUnit Jupiter testing framework:
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
|
||||
|
||||
[[integration-testing-annotations-springextensionconfig]]
|
||||
== `@SpringExtensionConfig`
|
||||
|
||||
`@SpringExtensionConfig` is a type-level annotation that can be used to configure the
|
||||
behavior of the `SpringExtension`.
|
||||
|
||||
As of Spring Framework 7.0, the `SpringExtension` is configured to use a test-method
|
||||
scoped `ExtensionContext`, which enables consistent dependency injection into fields and
|
||||
constructors from the `ApplicationContext` for the current test method in a `@Nested`
|
||||
test class hierarchy. However, if a third-party `TestExecutionListener` is not compatible
|
||||
with the semantics associated with a test-method scoped extension context — or if a
|
||||
developer wishes to switch to test-class scoped semantics — the `SpringExtension` can be
|
||||
configured to use a test-class scoped `ExtensionContext` by annotating a top-level test
|
||||
class with `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
|
||||
|
||||
Alternatively, you can change the global default by setting the
|
||||
`spring.test.extension.context.scope` property to `test_class`. The property is resolved
|
||||
first via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism
|
||||
which also supports JVM system properties — for example,
|
||||
`-Dspring.test.extension.context.scope=test_class`. If the Spring property has not been
|
||||
set, the `SpringExtension` will attempt to resolve the property as a
|
||||
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter]
|
||||
as a fallback mechanism. If the property has not been set via either of those mechanisms,
|
||||
the `SpringExtension` will use a test-method scoped extension context by default. Note,
|
||||
however, that a `@SpringExtensionConfig` declaration always takes precedence over this
|
||||
property.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If a test class uses JUnit Jupiter's `@TestInstance(Lifecycle.PER_CLASS)` semantics, the
|
||||
`SpringExtension` will always use a test-class scoped `ExtensionContext`, and
|
||||
configuration via `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)` or
|
||||
the `spring.test.extension.context.scope` property will have no effect for that test
|
||||
class.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This annotation is currently only applicable to `@Nested` test class hierarchies and
|
||||
should be applied to the top-level enclosing class of a `@Nested` test class hierarchy.
|
||||
Consequently, there is no need to declare this annotation on a test class that does not
|
||||
contain `@Nested` test classes.
|
||||
|
||||
In addition,
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
|
||||
does not apply to this annotation. `@SpringExtensionConfig` will always be detected
|
||||
within a `@Nested` test class hierarchy, effectively disregarding any
|
||||
`@NestedTestConfiguration(OVERRIDE)` declarations.
|
||||
====
|
||||
|
||||
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
|
||||
== `@SpringJUnitConfig`
|
||||
|
||||
@@ -238,7 +187,7 @@ default mode may be set via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
The default mode may also be configured as a
|
||||
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter].
|
||||
https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
|
||||
|
||||
If the `spring.test.constructor.autowire.mode` property is not set, test class
|
||||
constructors will not be automatically autowired.
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
= Meta-Annotation Support for Testing
|
||||
|
||||
You can use most test-related annotations as
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to
|
||||
create custom composed annotations and reduce configuration duplication across a test
|
||||
suite.
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to create custom composed
|
||||
annotations and reduce configuration duplication across a test suite.
|
||||
|
||||
For example, you can use each of the following as a meta-annotation in conjunction with
|
||||
the xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
You can use each of the following as a meta-annotation in conjunction with the
|
||||
xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
|
||||
* `@BootstrapWith`
|
||||
* `@ContextConfiguration`
|
||||
@@ -38,7 +37,111 @@ the xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
* `@EnabledIf` _(only supported on JUnit Jupiter)_
|
||||
* `@DisabledIf` _(only supported on JUnit Jupiter)_
|
||||
|
||||
Consider the following test classes that use the `SpringExtension` with JUnit Jupiter:
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public class UserRepositoryTests { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
----
|
||||
======
|
||||
|
||||
If we discover that we are repeating the preceding configuration across our JUnit 4-based
|
||||
test suite, we can reduce the duplication by introducing a custom composed annotation
|
||||
that centralizes the common test configuration for Spring, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public @interface TransactionalDevTestConfig { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
annotation class TransactionalDevTestConfig { }
|
||||
----
|
||||
======
|
||||
|
||||
Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the
|
||||
configuration of individual JUnit 4 based test classes, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@TransactionalDevTestConfig
|
||||
public class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@TransactionalDevTestConfig
|
||||
public class UserRepositoryTests { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@TransactionalDevTestConfig
|
||||
class OrderRepositoryTests
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@TransactionalDevTestConfig
|
||||
class UserRepositoryTests
|
||||
----
|
||||
======
|
||||
|
||||
If we write tests that use JUnit Jupiter, we can reduce code duplication even further,
|
||||
since annotations in JUnit Jupiter can also be used as meta-annotations. Consider the
|
||||
following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -47,13 +150,13 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
@@ -64,22 +167,23 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
----
|
||||
======
|
||||
|
||||
If we discover that we are repeating the preceding configuration across our test suite,
|
||||
we can reduce the duplication by introducing a custom composed annotation that
|
||||
centralizes the common test configuration for Spring and JUnit Jupiter, as follows:
|
||||
If we discover that we are repeating the preceding configuration across our JUnit
|
||||
Jupiter-based test suite, we can reduce the duplication by introducing a custom composed
|
||||
annotation that centralizes the common test configuration for Spring and JUnit Jupiter,
|
||||
as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -90,7 +194,7 @@ Java::
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public @interface TransactionalDevTestConfig { }
|
||||
@@ -103,7 +207,7 @@ Kotlin::
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
annotation class TransactionalDevTestConfig { }
|
||||
|
||||
-7
@@ -72,13 +72,6 @@ bean definition profiles programmatically by implementing a custom
|
||||
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc#testcontext-ctx-management-env-profiles-ActiveProfilesResolver[`ActiveProfilesResolver`]
|
||||
and registering it by using the `resolver` attribute of `@ActiveProfiles`.
|
||||
|
||||
NOTE: When `@ActiveProfiles` is declared on a test class, the `spring.profiles.active`
|
||||
property (whether configured as a JVM system property or environment variable) is not
|
||||
taken into account by the TestContext Framework when determining active profiles. If
|
||||
you need to allow `spring.profiles.active` to override the profiles configured via
|
||||
`@ActiveProfiles`, you can implement a custom `ActiveProfilesResolver` as described in
|
||||
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles].
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles],
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration], and the
|
||||
{spring-framework-api}/test/context/ActiveProfiles.html[`@ActiveProfiles`] javadoc for
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@
|
||||
`@AfterTransaction` indicates that the annotated `void` method should be run after a
|
||||
transaction is ended, for test methods that have been configured to run within a
|
||||
transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` methods
|
||||
are not required to be `public` and may be declared on interface default methods.
|
||||
are not required to be `public` and may be declared on Java 8-based interface default
|
||||
methods.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@
|
||||
`@BeforeTransaction` indicates that the annotated `void` method should be run before a
|
||||
transaction is started, for test methods that have been configured to run within a
|
||||
transaction by using Spring's `@Transactional` annotation. `@BeforeTransaction` methods
|
||||
are not required to be `public` and may be declared on interface default methods.
|
||||
are not required to be `public` and may be declared on Java 8-based interface default
|
||||
methods.
|
||||
|
||||
The following example shows how to use the `@BeforeTransaction` annotation:
|
||||
|
||||
|
||||
+10
-326
@@ -12,20 +12,18 @@ The annotations can be applied in the following ways.
|
||||
* On a non-static field in a test class or any of its superclasses.
|
||||
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
|
||||
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
|
||||
* On a parameter in the constructor for a test class.
|
||||
* At the type level on a test class or any superclass or implemented interface in the
|
||||
type hierarchy above the test class.
|
||||
* At the type level on an enclosing class for a `@Nested` test class or on any class or
|
||||
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
|
||||
class.
|
||||
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field or constructor parameter,
|
||||
the bean to mock or spy is inferred from the type of the annotated field or parameter. If
|
||||
multiple candidates exist in the `ApplicationContext`, a `@Qualifier` annotation can be
|
||||
declared on the field or parameter to help disambiguate. In the absence of a `@Qualifier`
|
||||
annotation, the name of the annotated field or parameter will be used as a _fallback
|
||||
qualifier_. Alternatively, you can explicitly specify a bean name to mock or spy by
|
||||
setting the `value` or `name` attribute in the annotation.
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
|
||||
is inferred from the type of the annotated field. If multiple candidates exist in the
|
||||
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
|
||||
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
|
||||
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
|
||||
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
|
||||
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
|
||||
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation –
|
||||
@@ -79,35 +77,14 @@ exactly one candidate bean exists.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
As stated in the documentation for Mockito, there are times when using `Mockito.when()` is
|
||||
inappropriate for stubbing a spy – for example, if calling a real method on a spy results
|
||||
in undesired side effects.
|
||||
|
||||
To avoid such undesired side effects, consider using
|
||||
`Mockito.doReturn(...).when(spy)...`, `Mockito.doThrow(...).when(spy)...`,
|
||||
`Mockito.doNothing().when(spy)...`, and similar methods.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
When using `@MockitoBean` to mock a non-singleton bean, the non-singleton bean will be
|
||||
replaced with a singleton mock, and the corresponding bean definition will be converted
|
||||
to a `singleton`. Consequently, if you mock a `prototype` or scoped bean, the mock will
|
||||
be treated as a `singleton`.
|
||||
|
||||
Similarly, when using `@MockitoSpyBean` to create a spy for a non-singleton bean, the
|
||||
corresponding bean definition will be converted to a `singleton`. Consequently, if you
|
||||
create a spy for a `prototype` or scoped bean, the spy will be treated as a `singleton`.
|
||||
Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
|
||||
will result in an exception.
|
||||
|
||||
When using `@MockitoBean` to mock a bean created by a `FactoryBean`, the `FactoryBean`
|
||||
will be replaced with a singleton mock of the type of object created by the `FactoryBean`.
|
||||
|
||||
Similarly, when using `@MockitoSpyBean` to create a spy for a `FactoryBean`, a spy will
|
||||
be created for the object created by the `FactoryBean`, not for the `FactoryBean` itself.
|
||||
|
||||
Furthermore, `@MockitoSpyBean` cannot be used to spy on a scoped proxy — for example, a
|
||||
bean annotated with `@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)`. Any attempt to do
|
||||
so will fail with an exception.
|
||||
When using `@MockitoSpyBean` to create a spy for a `FactoryBean`, a spy will be created
|
||||
for the object created by the `FactoryBean`, not for the `FactoryBean` itself.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
@@ -142,21 +119,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoBean // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock.
|
||||
======
|
||||
|
||||
In the example above, we are creating a mock for `CustomService`. If more than one bean
|
||||
@@ -185,98 +147,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoBean("service") // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoBean` on a constructor parameter for a
|
||||
by-type lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoBean CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
|
||||
the constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoBean val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
|
||||
the constructor.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoBean` on a constructor parameter for a
|
||||
by-name lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoBean("service") CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock and inject it into the
|
||||
constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoBean("service") val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock and inject it into the
|
||||
constructor.
|
||||
======
|
||||
|
||||
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
|
||||
@@ -296,19 +166,6 @@ Java::
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` mocks by-type.
|
||||
<2> Register `PrintingService` mock by-name.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MockitoBean(types = [OrderService::class, UserService::class]) // <1>
|
||||
@MockitoBean(name = "ps1", types = [PrintingService::class]) // <2>
|
||||
annotation class SharedMocks
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` mocks by-type.
|
||||
<2> Register `PrintingService` mock by-name.
|
||||
======
|
||||
|
||||
The following demonstrates how `@SharedMocks` can be used on a test class.
|
||||
@@ -339,34 +196,6 @@ Java::
|
||||
----
|
||||
<1> Register common mocks via the custom `@SharedMocks` annotation.
|
||||
<2> Optionally inject mocks to _stub_ or _verify_ them.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@SharedMocks // <1>
|
||||
class BeanOverrideTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var orderService: OrderService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var userService: UserService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var ps1: PrintingService // <2>
|
||||
|
||||
// Inject other components that rely on the mocks.
|
||||
|
||||
@Test
|
||||
fun testThatDependsOnMocks() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Register common mocks via the custom `@SharedMocks` annotation.
|
||||
<2> Optionally inject mocks to _stub_ or _verify_ them.
|
||||
======
|
||||
|
||||
TIP: The mocks can also be injected into `@Configuration` classes or other test-related
|
||||
@@ -396,21 +225,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoSpyBean // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy.
|
||||
======
|
||||
|
||||
In the example above, we are wrapping the bean with type `CustomService`. If more than
|
||||
@@ -436,95 +250,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoSpyBean("service") // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
|
||||
a by-type lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoSpyBean CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
|
||||
constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoSpyBean val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
|
||||
constructor.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
|
||||
a by-name lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoSpyBean("service") CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoSpyBean("service") val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
|
||||
======
|
||||
|
||||
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
|
||||
@@ -544,19 +269,6 @@ Java::
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` spies by-type.
|
||||
<2> Register `PrintingService` spy by-name.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MockitoSpyBean(types = [OrderService::class, UserService::class]) // <1>
|
||||
@MockitoSpyBean(name = "ps1", types = [PrintingService::class]) // <2>
|
||||
annotation class SharedSpies
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` spies by-type.
|
||||
<2> Register `PrintingService` spy by-name.
|
||||
======
|
||||
|
||||
The following demonstrates how `@SharedSpies` can be used on a test class.
|
||||
@@ -587,34 +299,6 @@ Java::
|
||||
----
|
||||
<1> Register common spies via the custom `@SharedSpies` annotation.
|
||||
<2> Optionally inject spies to _stub_ or _verify_ them.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@SharedSpies // <1>
|
||||
class BeanOverrideTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var orderService: OrderService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var userService: UserService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var ps1: PrintingService // <2>
|
||||
|
||||
// Inject other components that rely on the spies.
|
||||
|
||||
@Test
|
||||
fun testThatDependsOnMocks() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Register common spies via the custom `@SharedSpies` annotation.
|
||||
<2> Optionally inject spies to _stub_ or _verify_ them.
|
||||
======
|
||||
|
||||
TIP: The spies can also be injected into `@Configuration` classes or other test-related
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. You can
|
||||
use `@SqlGroup` natively to declare several nested `@Sql` annotations, or you can use it
|
||||
in conjunction with Java's support for repeatable annotations, where `@Sql` can be
|
||||
in conjunction with Java 8's support for repeatable annotations, where `@Sql` can be
|
||||
declared several times on the same class or method, implicitly generating this container
|
||||
annotation. The following example shows how to declare an SQL group:
|
||||
|
||||
|
||||
+6
-52
@@ -73,27 +73,6 @@ Java::
|
||||
----
|
||||
<1> Mark a field for overriding the bean with type `CustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@TestBean // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// test case body...
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun customService(): CustomService { // <2>
|
||||
return MyFakeCustomService()
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Mark a field for overriding the bean with type `CustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
======
|
||||
|
||||
In the example above, we are overriding the bean with type `CustomService`. If more than
|
||||
@@ -123,28 +102,6 @@ Java::
|
||||
<1> Mark a field for overriding the bean with name `service`, and specify that the
|
||||
factory method is named `createCustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@TestBean(name = "service", methodName = "createCustomService") // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// test case body...
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createCustomService(): CustomService { // <2>
|
||||
return MyFakeCustomService()
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Mark a field for overriding the bean with name `service`, and specify that the
|
||||
factory method is named `createCustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
======
|
||||
|
||||
[TIP]
|
||||
@@ -159,15 +116,12 @@ fully-qualified method name following the syntax `<fully-qualified class name>#<
|
||||
– for example, `methodName = "org.example.TestUtils#createCustomService"`.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
[TIP]
|
||||
====
|
||||
When overriding a non-singleton bean, the non-singleton bean will be replaced with a
|
||||
singleton bean corresponding to the value returned from the `@TestBean` factory method,
|
||||
and the corresponding bean definition will be converted to a `singleton`. Consequently,
|
||||
if `@TestBean` is used to override a `prototype` or scoped bean, the overridden bean will
|
||||
be treated as a `singleton`.
|
||||
Only _singleton_ beans can be overridden. Any attempt to override a non-singleton bean
|
||||
will result in an exception.
|
||||
|
||||
Similarly, when overriding a bean created by a `FactoryBean`, the `FactoryBean` will be
|
||||
replaced with a singleton bean corresponding to the value returned from the `@TestBean`
|
||||
factory method.
|
||||
When overriding a bean created by a `FactoryBean`, the `FactoryBean` will be replaced
|
||||
with a singleton bean corresponding to the value returned from the `@TestBean` factory
|
||||
method.
|
||||
====
|
||||
|
||||
@@ -9,6 +9,7 @@ and can be used anywhere in the Spring Framework.
|
||||
* `@Qualifier`
|
||||
* `@Value`
|
||||
* `@Resource` (jakarta.annotation) if JSR-250 is present
|
||||
* `@ManagedBean` (jakarta.annotation) if JSR-250 is present
|
||||
* `@Inject` (jakarta.inject) if JSR-330 is present
|
||||
* `@Named` (jakarta.inject) if JSR-330 is present
|
||||
* `@PersistenceContext` (jakarta.persistence) if JPA is present
|
||||
|
||||
@@ -5,25 +5,24 @@ It is important to be able to perform some integration testing without requiring
|
||||
deployment to your application server or connecting to other enterprise infrastructure.
|
||||
Doing so lets you test things such as:
|
||||
|
||||
* The correct wiring of your Spring components.
|
||||
* Data access using JDBC or an ORM tool.
|
||||
** This can include such things as the correctness of SQL statements, Hibernate queries,
|
||||
JPA entity mappings, and so forth.
|
||||
* The correct wiring of your Spring IoC container contexts.
|
||||
* Data access using JDBC or an ORM tool. This can include such things as the correctness
|
||||
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
|
||||
|
||||
The Spring Framework provides first-class support for integration testing in the
|
||||
`spring-test` module. The name of the actual JAR file might include the release version,
|
||||
depending on where you get it from (see the
|
||||
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
|
||||
for details). This library includes the `org.springframework.test` package, which
|
||||
`spring-test` module. The name of the actual JAR file might include the release version
|
||||
and might also be in the long `org.springframework.test` form, depending on where you get
|
||||
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
|
||||
for an explanation). This library includes the `org.springframework.test` package, which
|
||||
contains valuable classes for integration testing with a Spring container. This testing
|
||||
does not rely on an application server or other deployment environment. Such tests are
|
||||
slower to run than unit tests but much faster than the equivalent Selenium tests or
|
||||
remote tests that rely on deployment to an application server.
|
||||
|
||||
Unit and integration testing support is provided in the form of the annotation-driven
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
|
||||
framework is agnostic of the actual testing framework in use, which allows
|
||||
instrumentation of tests in various environments, including JUnit, TestNG, and others.
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
|
||||
agnostic of the actual testing framework in use, which allows instrumentation of tests
|
||||
in various environments, including JUnit, TestNG, and others.
|
||||
|
||||
The following section provides an overview of the high-level goals of Spring's
|
||||
integration support, and the rest of this chapter then focuses on dedicated topics:
|
||||
|
||||
@@ -184,10 +184,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
standaloneSetup(SimpleController())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
|
||||
.build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
mockMvc = standaloneSetup(PersonController()).addFilters<StandaloneMockMvcBuilder>(CharacterEncodingFilter()).build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -159,18 +159,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyWebTests {
|
||||
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockMvc = standaloneSetup(AccountController())
|
||||
.defaultRequest<StandaloneMockMvcBuilder>(get("/")
|
||||
.contextPath("/app").servletPath("/main")
|
||||
.accept(MediaType.APPLICATION_JSON)).build()
|
||||
}
|
||||
}
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -25,13 +25,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// static import of MockMvcBuilders.standaloneSetup
|
||||
|
||||
val mockMvc = standaloneSetup(MusicController())
|
||||
.defaultRequest<StandaloneMockMvcBuilder>(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
|
||||
.build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
@@ -59,13 +53,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// static import of SharedHttpSessionConfigurer.sharedHttpSession
|
||||
|
||||
val mockMvc = MockMvcBuilders.standaloneSetup(TestController())
|
||||
.apply<StandaloneMockMvcBuilder>(sharedHttpSession())
|
||||
.build()
|
||||
|
||||
// Use mockMvc to perform requests...
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user